mirror of
https://github.com/esphome/esphome.git
synced 2025-07-28 22:26:36 +00:00
commit
aaaf9b2b62
4
.github/actions/build-image/action.yaml
vendored
4
.github/actions/build-image/action.yaml
vendored
@ -47,7 +47,7 @@ runs:
|
|||||||
|
|
||||||
- name: Build and push to ghcr by digest
|
- name: Build and push to ghcr by digest
|
||||||
id: build-ghcr
|
id: build-ghcr
|
||||||
uses: docker/build-push-action@v6.16.0
|
uses: docker/build-push-action@v6.17.0
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILD_SUMMARY: false
|
DOCKER_BUILD_SUMMARY: false
|
||||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||||
@ -73,7 +73,7 @@ runs:
|
|||||||
|
|
||||||
- name: Build and push to dockerhub by digest
|
- name: Build and push to dockerhub by digest
|
||||||
id: build-dockerhub
|
id: build-dockerhub
|
||||||
uses: docker/build-push-action@v6.16.0
|
uses: docker/build-push-action@v6.17.0
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILD_SUMMARY: false
|
DOCKER_BUILD_SUMMARY: false
|
||||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||||
|
@ -169,7 +169,7 @@ esphome/components/gp2y1010au0f/* @zry98
|
|||||||
esphome/components/gp8403/* @jesserockz
|
esphome/components/gp8403/* @jesserockz
|
||||||
esphome/components/gpio/* @esphome/core
|
esphome/components/gpio/* @esphome/core
|
||||||
esphome/components/gpio/one_wire/* @ssieb
|
esphome/components/gpio/one_wire/* @ssieb
|
||||||
esphome/components/gps/* @coogle
|
esphome/components/gps/* @coogle @ximex
|
||||||
esphome/components/graph/* @synco
|
esphome/components/graph/* @synco
|
||||||
esphome/components/graphical_display_menu/* @MrMDavidson
|
esphome/components/graphical_display_menu/* @MrMDavidson
|
||||||
esphome/components/gree/* @orestismers
|
esphome/components/gree/* @orestismers
|
||||||
|
@ -43,7 +43,7 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.core import CORE, EsphomeError, coroutine
|
from esphome.core import CORE, EsphomeError, coroutine
|
||||||
from esphome.helpers import get_bool_env, indent, is_ip_address
|
from esphome.helpers import get_bool_env, indent, is_ip_address
|
||||||
from esphome.log import Fore, color, setup_log
|
from esphome.log import AnsiFore, color, setup_log
|
||||||
from esphome.util import (
|
from esphome.util import (
|
||||||
get_serial_ports,
|
get_serial_ports,
|
||||||
list_yaml_files,
|
list_yaml_files,
|
||||||
@ -83,7 +83,7 @@ def choose_prompt(options, purpose: str = None):
|
|||||||
raise ValueError
|
raise ValueError
|
||||||
break
|
break
|
||||||
except ValueError:
|
except ValueError:
|
||||||
safe_print(color(Fore.RED, f"Invalid option: '{opt}'"))
|
safe_print(color(AnsiFore.RED, f"Invalid option: '{opt}'"))
|
||||||
return options[opt - 1][1]
|
return options[opt - 1][1]
|
||||||
|
|
||||||
|
|
||||||
@ -596,30 +596,30 @@ def command_update_all(args):
|
|||||||
click.echo(f"{half_line}{middle_text}{half_line}")
|
click.echo(f"{half_line}{middle_text}{half_line}")
|
||||||
|
|
||||||
for f in files:
|
for f in files:
|
||||||
print(f"Updating {color(Fore.CYAN, f)}")
|
print(f"Updating {color(AnsiFore.CYAN, f)}")
|
||||||
print("-" * twidth)
|
print("-" * twidth)
|
||||||
print()
|
print()
|
||||||
rc = run_external_process(
|
rc = run_external_process(
|
||||||
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
|
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
|
||||||
)
|
)
|
||||||
if rc == 0:
|
if rc == 0:
|
||||||
print_bar(f"[{color(Fore.BOLD_GREEN, 'SUCCESS')}] {f}")
|
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {f}")
|
||||||
success[f] = True
|
success[f] = True
|
||||||
else:
|
else:
|
||||||
print_bar(f"[{color(Fore.BOLD_RED, 'ERROR')}] {f}")
|
print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {f}")
|
||||||
success[f] = False
|
success[f] = False
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print()
|
print()
|
||||||
print()
|
print()
|
||||||
|
|
||||||
print_bar(f"[{color(Fore.BOLD_WHITE, 'SUMMARY')}]")
|
print_bar(f"[{color(AnsiFore.BOLD_WHITE, 'SUMMARY')}]")
|
||||||
failed = 0
|
failed = 0
|
||||||
for f in files:
|
for f in files:
|
||||||
if success[f]:
|
if success[f]:
|
||||||
print(f" - {f}: {color(Fore.GREEN, 'SUCCESS')}")
|
print(f" - {f}: {color(AnsiFore.GREEN, 'SUCCESS')}")
|
||||||
else:
|
else:
|
||||||
print(f" - {f}: {color(Fore.BOLD_RED, 'FAILED')}")
|
print(f" - {f}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
|
||||||
failed += 1
|
failed += 1
|
||||||
return failed
|
return failed
|
||||||
|
|
||||||
@ -645,7 +645,7 @@ def command_rename(args, config):
|
|||||||
if c not in ALLOWED_NAME_CHARS:
|
if c not in ALLOWED_NAME_CHARS:
|
||||||
print(
|
print(
|
||||||
color(
|
color(
|
||||||
Fore.BOLD_RED,
|
AnsiFore.BOLD_RED,
|
||||||
f"'{c}' is an invalid character for names. Valid characters are: "
|
f"'{c}' is an invalid character for names. Valid characters are: "
|
||||||
f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)",
|
f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)",
|
||||||
)
|
)
|
||||||
@ -658,7 +658,9 @@ def command_rename(args, config):
|
|||||||
yaml = yaml_util.load_yaml(CORE.config_path)
|
yaml = yaml_util.load_yaml(CORE.config_path)
|
||||||
if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
|
if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
|
||||||
print(
|
print(
|
||||||
color(Fore.BOLD_RED, "Complex YAML files cannot be automatically renamed.")
|
color(
|
||||||
|
AnsiFore.BOLD_RED, "Complex YAML files cannot be automatically renamed."
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return 1
|
return 1
|
||||||
old_name = yaml[CONF_ESPHOME][CONF_NAME]
|
old_name = yaml[CONF_ESPHOME][CONF_NAME]
|
||||||
@ -681,7 +683,7 @@ def command_rename(args, config):
|
|||||||
)
|
)
|
||||||
> 1
|
> 1
|
||||||
):
|
):
|
||||||
print(color(Fore.BOLD_RED, "Too many matches in YAML to safely rename"))
|
print(color(AnsiFore.BOLD_RED, "Too many matches in YAML to safely rename"))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
new_raw = re.sub(
|
new_raw = re.sub(
|
||||||
@ -693,7 +695,7 @@ def command_rename(args, config):
|
|||||||
|
|
||||||
new_path = os.path.join(CORE.config_dir, args.name + ".yaml")
|
new_path = os.path.join(CORE.config_dir, args.name + ".yaml")
|
||||||
print(
|
print(
|
||||||
f"Updating {color(Fore.CYAN, CORE.config_path)} to {color(Fore.CYAN, new_path)}"
|
f"Updating {color(AnsiFore.CYAN, CORE.config_path)} to {color(AnsiFore.CYAN, new_path)}"
|
||||||
)
|
)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
@ -702,7 +704,7 @@ def command_rename(args, config):
|
|||||||
|
|
||||||
rc = run_external_process("esphome", "config", new_path)
|
rc = run_external_process("esphome", "config", new_path)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
print(color(Fore.BOLD_RED, "Rename failed. Reverting changes."))
|
print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
|
||||||
os.remove(new_path)
|
os.remove(new_path)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
@ -728,7 +730,7 @@ def command_rename(args, config):
|
|||||||
if CORE.config_path != new_path:
|
if CORE.config_path != new_path:
|
||||||
os.remove(CORE.config_path)
|
os.remove(CORE.config_path)
|
||||||
|
|
||||||
print(color(Fore.BOLD_GREEN, "SUCCESS"))
|
print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
|
||||||
print()
|
print()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/version.h"
|
#include "esphome/core/version.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
#ifdef USE_DEEP_SLEEP
|
#ifdef USE_DEEP_SLEEP
|
||||||
#include "esphome/components/deep_sleep/deep_sleep_component.h"
|
#include "esphome/components/deep_sleep/deep_sleep_component.h"
|
||||||
@ -146,7 +147,7 @@ void APIConnection::loop() {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
this->last_traffic_ = millis();
|
this->last_traffic_ = App.get_loop_component_start_time();
|
||||||
// read a packet
|
// read a packet
|
||||||
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
|
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
|
||||||
if (this->remove_)
|
if (this->remove_)
|
||||||
@ -163,7 +164,7 @@ void APIConnection::loop() {
|
|||||||
static uint32_t keepalive = 60000;
|
static uint32_t keepalive = 60000;
|
||||||
static uint8_t max_ping_retries = 60;
|
static uint8_t max_ping_retries = 60;
|
||||||
static uint16_t ping_retry_interval = 1000;
|
static uint16_t ping_retry_interval = 1000;
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
if (this->sent_ping_) {
|
if (this->sent_ping_) {
|
||||||
// Disconnect if not responded within 2.5*keepalive
|
// Disconnect if not responded within 2.5*keepalive
|
||||||
if (now - this->last_traffic_ > (keepalive * 5) / 2) {
|
if (now - this->last_traffic_ > (keepalive * 5) / 2) {
|
||||||
@ -1961,7 +1962,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
APIError err = this->helper_->write_packet(message_type, buffer.get_buffer()->data(), buffer.get_buffer()->size());
|
APIError err = this->helper_->write_protobuf_packet(message_type, buffer);
|
||||||
if (err == APIError::WOULD_BLOCK)
|
if (err == APIError::WOULD_BLOCK)
|
||||||
return false;
|
return false;
|
||||||
if (err != APIError::OK) {
|
if (err != APIError::OK) {
|
||||||
|
@ -315,7 +315,14 @@ class APIConnection : public APIServerConnection {
|
|||||||
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
|
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
|
||||||
// FIXME: ensure no recursive writes can happen
|
// FIXME: ensure no recursive writes can happen
|
||||||
this->proto_write_buffer_.clear();
|
this->proto_write_buffer_.clear();
|
||||||
this->proto_write_buffer_.reserve(reserve_size);
|
// Get header padding size - used for both reserve and insert
|
||||||
|
uint8_t header_padding = this->helper_->frame_header_padding();
|
||||||
|
// Reserve space for header padding + message + footer
|
||||||
|
// - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
|
||||||
|
// - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
|
||||||
|
this->proto_write_buffer_.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
|
||||||
|
// Insert header padding bytes so message encoding starts at the correct position
|
||||||
|
this->proto_write_buffer_.insert(this->proto_write_buffer_.begin(), header_padding, 0);
|
||||||
return {&this->proto_write_buffer_};
|
return {&this->proto_write_buffer_};
|
||||||
}
|
}
|
||||||
bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
|
bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
|
||||||
|
@ -493,9 +493,12 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &rea
|
|||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> data;
|
||||||
data.resize(reason.length() + 1);
|
data.resize(reason.length() + 1);
|
||||||
data[0] = 0x01; // failure
|
data[0] = 0x01; // failure
|
||||||
for (size_t i = 0; i < reason.length(); i++) {
|
|
||||||
data[i + 1] = (uint8_t) reason[i];
|
// Copy error message in bulk
|
||||||
|
if (!reason.empty()) {
|
||||||
|
std::memcpy(data.data() + 1, reason.c_str(), reason.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
// temporarily remove failed state
|
// temporarily remove failed state
|
||||||
auto orig_state = state_;
|
auto orig_state = state_;
|
||||||
state_ = State::EXPLICIT_REJECT;
|
state_ = State::EXPLICIT_REJECT;
|
||||||
@ -557,7 +560,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
bool APINoiseFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
bool APINoiseFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
||||||
APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
|
APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
|
||||||
int err;
|
int err;
|
||||||
APIError aerr;
|
APIError aerr;
|
||||||
aerr = state_action_();
|
aerr = state_action_();
|
||||||
@ -569,31 +572,36 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload
|
|||||||
return APIError::WOULD_BLOCK;
|
return APIError::WOULD_BLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||||
|
// Message data starts after padding
|
||||||
|
size_t payload_len = raw_buffer->size() - frame_header_padding_;
|
||||||
size_t padding = 0;
|
size_t padding = 0;
|
||||||
size_t msg_len = 4 + payload_len + padding;
|
size_t msg_len = 4 + payload_len + padding;
|
||||||
size_t frame_len = 3 + msg_len + noise_cipherstate_get_mac_length(send_cipher_);
|
|
||||||
auto tmpbuf = std::unique_ptr<uint8_t[]>{new (std::nothrow) uint8_t[frame_len]};
|
|
||||||
if (tmpbuf == nullptr) {
|
|
||||||
HELPER_LOG("Could not allocate for writing packet");
|
|
||||||
return APIError::OUT_OF_MEMORY;
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpbuf[0] = 0x01; // indicator
|
// We need to resize to include MAC space, but we already reserved it in create_buffer
|
||||||
// tmpbuf[1], tmpbuf[2] to be set later
|
raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
|
||||||
|
|
||||||
|
// Write the noise header in the padded area
|
||||||
|
// Buffer layout:
|
||||||
|
// [0] - 0x01 indicator byte
|
||||||
|
// [1-2] - Size of encrypted payload (filled after encryption)
|
||||||
|
// [3-4] - Message type (encrypted)
|
||||||
|
// [5-6] - Payload length (encrypted)
|
||||||
|
// [7...] - Actual payload data (encrypted)
|
||||||
|
uint8_t *buf_start = raw_buffer->data();
|
||||||
|
buf_start[0] = 0x01; // indicator
|
||||||
|
// buf_start[1], buf_start[2] to be set later after encryption
|
||||||
const uint8_t msg_offset = 3;
|
const uint8_t msg_offset = 3;
|
||||||
const uint8_t payload_offset = msg_offset + 4;
|
buf_start[msg_offset + 0] = (uint8_t) (type >> 8); // type high byte
|
||||||
tmpbuf[msg_offset + 0] = (uint8_t) (type >> 8); // type
|
buf_start[msg_offset + 1] = (uint8_t) type; // type low byte
|
||||||
tmpbuf[msg_offset + 1] = (uint8_t) type;
|
buf_start[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len high byte
|
||||||
tmpbuf[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len
|
buf_start[msg_offset + 3] = (uint8_t) payload_len; // data_len low byte
|
||||||
tmpbuf[msg_offset + 3] = (uint8_t) payload_len;
|
// payload data is already in the buffer starting at position 7
|
||||||
// copy data
|
|
||||||
std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]);
|
|
||||||
// fill padding with zeros
|
|
||||||
std::fill(&tmpbuf[payload_offset + payload_len], &tmpbuf[frame_len], 0);
|
|
||||||
|
|
||||||
NoiseBuffer mbuf;
|
NoiseBuffer mbuf;
|
||||||
noise_buffer_init(mbuf);
|
noise_buffer_init(mbuf);
|
||||||
noise_buffer_set_inout(mbuf, &tmpbuf[msg_offset], msg_len, frame_len - msg_offset);
|
// The capacity parameter should be msg_len + frame_footer_size_ (MAC length) to allow space for encryption
|
||||||
|
noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_);
|
||||||
err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
state_ = State::FAILED;
|
state_ = State::FAILED;
|
||||||
@ -602,11 +610,13 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t total_len = 3 + mbuf.size;
|
size_t total_len = 3 + mbuf.size;
|
||||||
tmpbuf[1] = (uint8_t) (mbuf.size >> 8);
|
buf_start[1] = (uint8_t) (mbuf.size >> 8);
|
||||||
tmpbuf[2] = (uint8_t) mbuf.size;
|
buf_start[2] = (uint8_t) mbuf.size;
|
||||||
|
|
||||||
struct iovec iov;
|
struct iovec iov;
|
||||||
iov.iov_base = &tmpbuf[0];
|
// Point iov_base to the beginning of the buffer (no unused padding in Noise)
|
||||||
|
// We send the entire frame: indicator + size + encrypted(type + data_len + payload + MAC)
|
||||||
|
iov.iov_base = buf_start;
|
||||||
iov.iov_len = total_len;
|
iov.iov_len = total_len;
|
||||||
|
|
||||||
// write raw to not have two packets sent if NAGLE disabled
|
// write raw to not have two packets sent if NAGLE disabled
|
||||||
@ -718,6 +728,8 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
|
|||||||
return APIError::HANDSHAKESTATE_SPLIT_FAILED;
|
return APIError::HANDSHAKESTATE_SPLIT_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_);
|
||||||
|
|
||||||
HELPER_LOG("Handshake complete!");
|
HELPER_LOG("Handshake complete!");
|
||||||
noise_handshakestate_free(handshake_);
|
noise_handshakestate_free(handshake_);
|
||||||
handshake_ = nullptr;
|
handshake_ = nullptr;
|
||||||
@ -830,6 +842,10 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
|||||||
// read header
|
// read header
|
||||||
while (!rx_header_parsed_) {
|
while (!rx_header_parsed_) {
|
||||||
uint8_t data;
|
uint8_t data;
|
||||||
|
// Reading one byte at a time is fastest in practice for ESP32 when
|
||||||
|
// there is no data on the wire (which is the common case).
|
||||||
|
// This results in faster failure detection compared to
|
||||||
|
// attempting to read multiple bytes at once.
|
||||||
ssize_t received = socket_->read(&data, 1);
|
ssize_t received = socket_->read(&data, 1);
|
||||||
if (received == -1) {
|
if (received == -1) {
|
||||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||||
@ -843,27 +859,60 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
|||||||
HELPER_LOG("Connection closed");
|
HELPER_LOG("Connection closed");
|
||||||
return APIError::CONNECTION_CLOSED;
|
return APIError::CONNECTION_CLOSED;
|
||||||
}
|
}
|
||||||
rx_header_buf_.push_back(data);
|
|
||||||
|
|
||||||
// try parse header
|
// Successfully read a byte
|
||||||
if (rx_header_buf_[0] != 0x00) {
|
|
||||||
state_ = State::FAILED;
|
// Process byte according to current buffer position
|
||||||
HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
|
if (rx_header_buf_pos_ == 0) { // Case 1: First byte (indicator byte)
|
||||||
return APIError::BAD_INDICATOR;
|
if (data != 0x00) {
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("Bad indicator byte %u", data);
|
||||||
|
return APIError::BAD_INDICATOR;
|
||||||
|
}
|
||||||
|
// We don't store the indicator byte, just increment position
|
||||||
|
rx_header_buf_pos_ = 1; // Set to 1 directly
|
||||||
|
continue; // Need more bytes before we can parse
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t i = 1;
|
// Check buffer overflow before storing
|
||||||
|
if (rx_header_buf_pos_ == 5) { // Case 2: Buffer would overflow (5 bytes is max allowed)
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("Header buffer overflow");
|
||||||
|
return APIError::BAD_DATA_PACKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store byte in buffer (adjust index to account for skipped indicator byte)
|
||||||
|
rx_header_buf_[rx_header_buf_pos_ - 1] = data;
|
||||||
|
|
||||||
|
// Increment position after storing
|
||||||
|
rx_header_buf_pos_++;
|
||||||
|
|
||||||
|
// Case 3: If we only have one varint byte, we need more
|
||||||
|
if (rx_header_buf_pos_ == 2) { // Have read indicator + 1 byte
|
||||||
|
continue; // Need more bytes before we can parse
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, we have at least 3 bytes total:
|
||||||
|
// - Validated indicator byte (0x00) but not stored
|
||||||
|
// - At least 2 bytes in the buffer for the varints
|
||||||
|
// Buffer layout:
|
||||||
|
// First 1-3 bytes: Message size varint (variable length)
|
||||||
|
// - 2 bytes would only allow up to 16383, which is less than noise's 65535
|
||||||
|
// - 3 bytes allows up to 2097151, ensuring we support at least as much as noise
|
||||||
|
// Remaining 1-2 bytes: Message type varint (variable length)
|
||||||
|
// We now attempt to parse both varints. If either is incomplete,
|
||||||
|
// we'll continue reading more bytes.
|
||||||
|
|
||||||
uint32_t consumed = 0;
|
uint32_t consumed = 0;
|
||||||
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
|
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[0], rx_header_buf_pos_ - 1, &consumed);
|
||||||
if (!msg_size_varint.has_value()) {
|
if (!msg_size_varint.has_value()) {
|
||||||
// not enough data there yet
|
// not enough data there yet
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
i += consumed;
|
|
||||||
rx_header_parsed_len_ = msg_size_varint->as_uint32();
|
rx_header_parsed_len_ = msg_size_varint->as_uint32();
|
||||||
|
|
||||||
auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
|
auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[consumed], rx_header_buf_pos_ - 1 - consumed, &consumed);
|
||||||
if (!msg_type_varint.has_value()) {
|
if (!msg_type_varint.has_value()) {
|
||||||
// not enough data there yet
|
// not enough data there yet
|
||||||
continue;
|
continue;
|
||||||
@ -909,7 +958,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
|||||||
// consume msg
|
// consume msg
|
||||||
rx_buf_ = {};
|
rx_buf_ = {};
|
||||||
rx_buf_len_ = 0;
|
rx_buf_len_ = 0;
|
||||||
rx_header_buf_.clear();
|
rx_header_buf_pos_ = 0;
|
||||||
rx_header_parsed_ = false;
|
rx_header_parsed_ = false;
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
@ -953,28 +1002,66 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
||||||
APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
|
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
|
||||||
if (state_ != State::DATA) {
|
if (state_ != State::DATA) {
|
||||||
return APIError::BAD_STATE;
|
return APIError::BAD_STATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> header;
|
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||||
header.reserve(1 + api::ProtoSize::varint(static_cast<uint32_t>(payload_len)) +
|
// Message data starts after padding (frame_header_padding_ = 6)
|
||||||
api::ProtoSize::varint(static_cast<uint32_t>(type)));
|
size_t payload_len = raw_buffer->size() - frame_header_padding_;
|
||||||
header.push_back(0x00);
|
|
||||||
ProtoVarInt(payload_len).encode(header);
|
|
||||||
ProtoVarInt(type).encode(header);
|
|
||||||
|
|
||||||
struct iovec iov[2];
|
// Calculate varint sizes for header components
|
||||||
iov[0].iov_base = &header[0];
|
size_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len));
|
||||||
iov[0].iov_len = header.size();
|
size_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
|
||||||
if (payload_len == 0) {
|
size_t total_header_len = 1 + size_varint_len + type_varint_len;
|
||||||
return write_raw_(iov, 1);
|
|
||||||
|
if (total_header_len > frame_header_padding_) {
|
||||||
|
// Header is too large to fit in the padding
|
||||||
|
return APIError::BAD_ARG;
|
||||||
}
|
}
|
||||||
iov[1].iov_base = const_cast<uint8_t *>(payload);
|
|
||||||
iov[1].iov_len = payload_len;
|
|
||||||
|
|
||||||
return write_raw_(iov, 2);
|
// Calculate where to start writing the header
|
||||||
|
// The header starts at the latest possible position to minimize unused padding
|
||||||
|
//
|
||||||
|
// Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3
|
||||||
|
// [0-2] - Unused padding
|
||||||
|
// [3] - 0x00 indicator byte
|
||||||
|
// [4] - Payload size varint (1 byte, for sizes 0-127)
|
||||||
|
// [5] - Message type varint (1 byte, for types 0-127)
|
||||||
|
// [6...] - Actual payload data
|
||||||
|
//
|
||||||
|
// Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2
|
||||||
|
// [0-1] - Unused padding
|
||||||
|
// [2] - 0x00 indicator byte
|
||||||
|
// [3-4] - Payload size varint (2 bytes, for sizes 128-16383)
|
||||||
|
// [5] - Message type varint (1 byte, for types 0-127)
|
||||||
|
// [6...] - Actual payload data
|
||||||
|
//
|
||||||
|
// Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
|
||||||
|
// [0] - 0x00 indicator byte
|
||||||
|
// [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151)
|
||||||
|
// [4-5] - Message type varint (2 bytes, for types 128-32767)
|
||||||
|
// [6...] - Actual payload data
|
||||||
|
uint8_t *buf_start = raw_buffer->data();
|
||||||
|
size_t header_offset = frame_header_padding_ - total_header_len;
|
||||||
|
|
||||||
|
// Write the plaintext header
|
||||||
|
buf_start[header_offset] = 0x00; // indicator
|
||||||
|
|
||||||
|
// Encode size varint directly into buffer
|
||||||
|
ProtoVarInt(payload_len).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
|
||||||
|
|
||||||
|
// Encode type varint directly into buffer
|
||||||
|
ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
|
||||||
|
|
||||||
|
struct iovec iov;
|
||||||
|
// Point iov_base to the beginning of our header (skip unused padding)
|
||||||
|
// This ensures we only send the actual header and payload, not the empty padding bytes
|
||||||
|
iov.iov_base = buf_start + header_offset;
|
||||||
|
iov.iov_len = total_header_len + payload_len;
|
||||||
|
|
||||||
|
return write_raw_(&iov, 1);
|
||||||
}
|
}
|
||||||
APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
|
APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
|
||||||
// try send from tx_buf
|
// try send from tx_buf
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
|
class ProtoWriteBuffer;
|
||||||
|
|
||||||
struct ReadPacketBuffer {
|
struct ReadPacketBuffer {
|
||||||
std::vector<uint8_t> container;
|
std::vector<uint8_t> container;
|
||||||
uint16_t type;
|
uint16_t type;
|
||||||
@ -65,32 +67,46 @@ class APIFrameHelper {
|
|||||||
virtual APIError loop() = 0;
|
virtual APIError loop() = 0;
|
||||||
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
||||||
virtual bool can_write_without_blocking() = 0;
|
virtual bool can_write_without_blocking() = 0;
|
||||||
virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0;
|
virtual APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) = 0;
|
||||||
virtual std::string getpeername() = 0;
|
virtual std::string getpeername() = 0;
|
||||||
virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0;
|
virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0;
|
||||||
virtual APIError close() = 0;
|
virtual APIError close() = 0;
|
||||||
virtual APIError shutdown(int how) = 0;
|
virtual APIError shutdown(int how) = 0;
|
||||||
// Give this helper a name for logging
|
// Give this helper a name for logging
|
||||||
virtual void set_log_info(std::string info) = 0;
|
virtual void set_log_info(std::string info) = 0;
|
||||||
|
// Get the frame header padding required by this protocol
|
||||||
|
virtual uint8_t frame_header_padding() = 0;
|
||||||
|
// Get the frame footer size required by this protocol
|
||||||
|
virtual uint8_t frame_footer_size() = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Common implementation for writing raw data to socket
|
// Common implementation for writing raw data to socket
|
||||||
template<typename StateEnum>
|
template<typename StateEnum>
|
||||||
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
|
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
|
||||||
const std::string &info, StateEnum &state, StateEnum failed_state);
|
const std::string &info, StateEnum &state, StateEnum failed_state);
|
||||||
|
|
||||||
|
uint8_t frame_header_padding_{0};
|
||||||
|
uint8_t frame_footer_size_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
class APINoiseFrameHelper : public APIFrameHelper {
|
class APINoiseFrameHelper : public APIFrameHelper {
|
||||||
public:
|
public:
|
||||||
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
|
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
|
||||||
: socket_(std::move(socket)), ctx_(std::move(std::move(ctx))) {}
|
: socket_(std::move(socket)), ctx_(std::move(ctx)) {
|
||||||
|
// Noise header structure:
|
||||||
|
// Pos 0: indicator (0x01)
|
||||||
|
// Pos 1-2: encrypted payload size (16-bit big-endian)
|
||||||
|
// Pos 3-6: encrypted type (16-bit) + data_len (16-bit)
|
||||||
|
// Pos 7+: actual payload data
|
||||||
|
frame_header_padding_ = 7;
|
||||||
|
}
|
||||||
~APINoiseFrameHelper() override;
|
~APINoiseFrameHelper() override;
|
||||||
APIError init() override;
|
APIError init() override;
|
||||||
APIError loop() override;
|
APIError loop() override;
|
||||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||||
bool can_write_without_blocking() override;
|
bool can_write_without_blocking() override;
|
||||||
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
|
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
|
||||||
std::string getpeername() override { return this->socket_->getpeername(); }
|
std::string getpeername() override { return this->socket_->getpeername(); }
|
||||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
|
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
|
||||||
return this->socket_->getpeername(addr, addrlen);
|
return this->socket_->getpeername(addr, addrlen);
|
||||||
@ -99,6 +115,10 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
|||||||
APIError shutdown(int how) override;
|
APIError shutdown(int how) override;
|
||||||
// Give this helper a name for logging
|
// Give this helper a name for logging
|
||||||
void set_log_info(std::string info) override { info_ = std::move(info); }
|
void set_log_info(std::string info) override { info_ = std::move(info); }
|
||||||
|
// Get the frame header padding required by this protocol
|
||||||
|
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||||
|
// Get the frame footer size required by this protocol
|
||||||
|
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
struct ParsedFrame {
|
struct ParsedFrame {
|
||||||
@ -119,6 +139,9 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
|||||||
std::unique_ptr<socket::Socket> socket_;
|
std::unique_ptr<socket::Socket> socket_;
|
||||||
|
|
||||||
std::string info_;
|
std::string info_;
|
||||||
|
// Fixed-size header buffer for noise protocol:
|
||||||
|
// 1 byte for indicator + 2 bytes for message size (16-bit value, not varint)
|
||||||
|
// Note: Maximum message size is 65535, with a limit of 128 bytes during handshake phase
|
||||||
uint8_t rx_header_buf_[3];
|
uint8_t rx_header_buf_[3];
|
||||||
size_t rx_header_buf_len_ = 0;
|
size_t rx_header_buf_len_ = 0;
|
||||||
std::vector<uint8_t> rx_buf_;
|
std::vector<uint8_t> rx_buf_;
|
||||||
@ -149,13 +172,20 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
|||||||
#ifdef USE_API_PLAINTEXT
|
#ifdef USE_API_PLAINTEXT
|
||||||
class APIPlaintextFrameHelper : public APIFrameHelper {
|
class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||||
public:
|
public:
|
||||||
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {}
|
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {
|
||||||
|
// Plaintext header structure (worst case):
|
||||||
|
// Pos 0: indicator (0x00)
|
||||||
|
// Pos 1-3: payload size varint (up to 3 bytes)
|
||||||
|
// Pos 4-5: message type varint (up to 2 bytes)
|
||||||
|
// Pos 6+: actual payload data
|
||||||
|
frame_header_padding_ = 6;
|
||||||
|
}
|
||||||
~APIPlaintextFrameHelper() override = default;
|
~APIPlaintextFrameHelper() override = default;
|
||||||
APIError init() override;
|
APIError init() override;
|
||||||
APIError loop() override;
|
APIError loop() override;
|
||||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||||
bool can_write_without_blocking() override;
|
bool can_write_without_blocking() override;
|
||||||
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
|
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
|
||||||
std::string getpeername() override { return this->socket_->getpeername(); }
|
std::string getpeername() override { return this->socket_->getpeername(); }
|
||||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
|
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
|
||||||
return this->socket_->getpeername(addr, addrlen);
|
return this->socket_->getpeername(addr, addrlen);
|
||||||
@ -164,6 +194,10 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
|||||||
APIError shutdown(int how) override;
|
APIError shutdown(int how) override;
|
||||||
// Give this helper a name for logging
|
// Give this helper a name for logging
|
||||||
void set_log_info(std::string info) override { info_ = std::move(info); }
|
void set_log_info(std::string info) override { info_ = std::move(info); }
|
||||||
|
// Get the frame header padding required by this protocol
|
||||||
|
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||||
|
// Get the frame footer size required by this protocol
|
||||||
|
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
struct ParsedFrame {
|
struct ParsedFrame {
|
||||||
@ -179,7 +213,16 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
|||||||
std::unique_ptr<socket::Socket> socket_;
|
std::unique_ptr<socket::Socket> socket_;
|
||||||
|
|
||||||
std::string info_;
|
std::string info_;
|
||||||
std::vector<uint8_t> rx_header_buf_;
|
// Fixed-size header buffer for plaintext protocol:
|
||||||
|
// We only need space for the two varints since we validate the indicator byte separately.
|
||||||
|
// To match noise protocol's maximum message size (65535), we need:
|
||||||
|
// 3 bytes for message size varint (supports up to 2097151) + 2 bytes for message type varint
|
||||||
|
//
|
||||||
|
// While varints could theoretically be up to 10 bytes each for 64-bit values,
|
||||||
|
// attempting to process messages with headers that large would likely crash the
|
||||||
|
// ESP32 due to memory constraints.
|
||||||
|
uint8_t rx_header_buf_[5]; // 5 bytes for varints (3 for size + 2 for type)
|
||||||
|
uint8_t rx_header_buf_pos_ = 0;
|
||||||
bool rx_header_parsed_ = false;
|
bool rx_header_parsed_ = false;
|
||||||
uint32_t rx_header_parsed_type_ = 0;
|
uint32_t rx_header_parsed_type_ = 0;
|
||||||
uint32_t rx_header_parsed_len_ = 0;
|
uint32_t rx_header_parsed_len_ = 0;
|
||||||
|
@ -20,16 +20,26 @@ class ProtoVarInt {
|
|||||||
explicit ProtoVarInt(uint64_t value) : value_(value) {}
|
explicit ProtoVarInt(uint64_t value) : value_(value) {}
|
||||||
|
|
||||||
static optional<ProtoVarInt> parse(const uint8_t *buffer, uint32_t len, uint32_t *consumed) {
|
static optional<ProtoVarInt> parse(const uint8_t *buffer, uint32_t len, uint32_t *consumed) {
|
||||||
if (consumed != nullptr)
|
if (len == 0) {
|
||||||
*consumed = 0;
|
if (consumed != nullptr)
|
||||||
|
*consumed = 0;
|
||||||
if (len == 0)
|
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t result = 0;
|
// Most common case: single-byte varint (values 0-127)
|
||||||
uint8_t bitpos = 0;
|
if ((buffer[0] & 0x80) == 0) {
|
||||||
|
if (consumed != nullptr)
|
||||||
|
*consumed = 1;
|
||||||
|
return ProtoVarInt(buffer[0]);
|
||||||
|
}
|
||||||
|
|
||||||
for (uint32_t i = 0; i < len; i++) {
|
// General case for multi-byte varints
|
||||||
|
// Since we know buffer[0]'s high bit is set, initialize with its value
|
||||||
|
uint64_t result = buffer[0] & 0x7F;
|
||||||
|
uint8_t bitpos = 7;
|
||||||
|
|
||||||
|
// Start from the second byte since we've already processed the first
|
||||||
|
for (uint32_t i = 1; i < len; i++) {
|
||||||
uint8_t val = buffer[i];
|
uint8_t val = buffer[i];
|
||||||
result |= uint64_t(val & 0x7F) << uint64_t(bitpos);
|
result |= uint64_t(val & 0x7F) << uint64_t(bitpos);
|
||||||
bitpos += 7;
|
bitpos += 7;
|
||||||
@ -40,7 +50,9 @@ class ProtoVarInt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
if (consumed != nullptr)
|
||||||
|
*consumed = 0;
|
||||||
|
return {}; // Incomplete or invalid varint
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t as_uint32() const { return this->value_; }
|
uint32_t as_uint32() const { return this->value_; }
|
||||||
@ -71,6 +83,34 @@ class ProtoVarInt {
|
|||||||
return static_cast<int64_t>(this->value_ >> 1);
|
return static_cast<int64_t>(this->value_ >> 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Encode the varint value to a pre-allocated buffer without bounds checking.
|
||||||
|
*
|
||||||
|
* @param buffer The pre-allocated buffer to write the encoded varint to
|
||||||
|
* @param len The size of the buffer in bytes
|
||||||
|
*
|
||||||
|
* @note The caller is responsible for ensuring the buffer is large enough
|
||||||
|
* to hold the encoded value. Use ProtoSize::varint() to calculate
|
||||||
|
* the exact size needed before calling this method.
|
||||||
|
* @note No bounds checking is performed for performance reasons.
|
||||||
|
*/
|
||||||
|
void encode_to_buffer_unchecked(uint8_t *buffer, size_t len) {
|
||||||
|
uint64_t val = this->value_;
|
||||||
|
if (val <= 0x7F) {
|
||||||
|
buffer[0] = val;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t i = 0;
|
||||||
|
while (val && i < len) {
|
||||||
|
uint8_t temp = val & 0x7F;
|
||||||
|
val >>= 7;
|
||||||
|
if (val) {
|
||||||
|
buffer[i++] = temp | 0x80;
|
||||||
|
} else {
|
||||||
|
buffer[i++] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
void encode(std::vector<uint8_t> &out) {
|
void encode(std::vector<uint8_t> &out) {
|
||||||
uint64_t val = this->value_;
|
uint64_t val = this->value_;
|
||||||
if (val <= 0x7F) {
|
if (val <= 0x7F) {
|
||||||
|
@ -7,7 +7,7 @@ CODEOWNERS = ["@bazuchan"]
|
|||||||
ballu_ns = cg.esphome_ns.namespace("ballu")
|
ballu_ns = cg.esphome_ns.namespace("ballu")
|
||||||
BalluClimate = ballu_ns.class_("BalluClimate", climate_ir.ClimateIR)
|
BalluClimate = ballu_ns.class_("BalluClimate", climate_ir.ClimateIR)
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(BalluClimate)
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(BalluClimate)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include "bedjet_hub.h"
|
#include "bedjet_hub.h"
|
||||||
#include "bedjet_child.h"
|
#include "bedjet_child.h"
|
||||||
#include "bedjet_const.h"
|
#include "bedjet_const.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -15,17 +15,21 @@ void BinarySensor::publish_state(bool state) {
|
|||||||
if (!this->publish_dedup_.next(state))
|
if (!this->publish_dedup_.next(state))
|
||||||
return;
|
return;
|
||||||
if (this->filter_list_ == nullptr) {
|
if (this->filter_list_ == nullptr) {
|
||||||
this->send_state_internal(state);
|
this->send_state_internal(state, false);
|
||||||
} else {
|
} else {
|
||||||
this->filter_list_->input(state);
|
this->filter_list_->input(state, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void BinarySensor::publish_initial_state(bool state) {
|
void BinarySensor::publish_initial_state(bool state) {
|
||||||
this->has_state_ = false;
|
if (!this->publish_dedup_.next(state))
|
||||||
this->publish_state(state);
|
return;
|
||||||
|
if (this->filter_list_ == nullptr) {
|
||||||
|
this->send_state_internal(state, true);
|
||||||
|
} else {
|
||||||
|
this->filter_list_->input(state, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void BinarySensor::send_state_internal(bool state) {
|
void BinarySensor::send_state_internal(bool state, bool is_initial) {
|
||||||
bool is_initial = !this->has_state_;
|
|
||||||
if (is_initial) {
|
if (is_initial) {
|
||||||
ESP_LOGD(TAG, "'%s': Sending initial state %s", this->get_name().c_str(), ONOFF(state));
|
ESP_LOGD(TAG, "'%s': Sending initial state %s", this->get_name().c_str(), ONOFF(state));
|
||||||
} else {
|
} else {
|
||||||
|
@ -67,7 +67,7 @@ class BinarySensor : public EntityBase, public EntityBase_DeviceClass {
|
|||||||
|
|
||||||
// ========== INTERNAL METHODS ==========
|
// ========== INTERNAL METHODS ==========
|
||||||
// (In most use cases you won't need these)
|
// (In most use cases you won't need these)
|
||||||
void send_state_internal(bool state);
|
void send_state_internal(bool state, bool is_initial);
|
||||||
|
|
||||||
/// Return whether this binary sensor has outputted a state.
|
/// Return whether this binary sensor has outputted a state.
|
||||||
virtual bool has_state() const;
|
virtual bool has_state() const;
|
||||||
|
@ -9,37 +9,37 @@ namespace binary_sensor {
|
|||||||
|
|
||||||
static const char *const TAG = "sensor.filter";
|
static const char *const TAG = "sensor.filter";
|
||||||
|
|
||||||
void Filter::output(bool value) {
|
void Filter::output(bool value, bool is_initial) {
|
||||||
if (!this->dedup_.next(value))
|
if (!this->dedup_.next(value))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (this->next_ == nullptr) {
|
if (this->next_ == nullptr) {
|
||||||
this->parent_->send_state_internal(value);
|
this->parent_->send_state_internal(value, is_initial);
|
||||||
} else {
|
} else {
|
||||||
this->next_->input(value);
|
this->next_->input(value, is_initial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Filter::input(bool value) {
|
void Filter::input(bool value, bool is_initial) {
|
||||||
auto b = this->new_value(value);
|
auto b = this->new_value(value, is_initial);
|
||||||
if (b.has_value()) {
|
if (b.has_value()) {
|
||||||
this->output(*b);
|
this->output(*b, is_initial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<bool> DelayedOnOffFilter::new_value(bool value) {
|
optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) {
|
||||||
if (value) {
|
if (value) {
|
||||||
this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); });
|
this->set_timeout("ON_OFF", this->on_delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
|
||||||
} else {
|
} else {
|
||||||
this->set_timeout("ON_OFF", this->off_delay_.value(), [this]() { this->output(false); });
|
this->set_timeout("ON_OFF", this->off_delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
optional<bool> DelayedOnFilter::new_value(bool value) {
|
optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
|
||||||
if (value) {
|
if (value) {
|
||||||
this->set_timeout("ON", this->delay_.value(), [this]() { this->output(true); });
|
this->set_timeout("ON", this->delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
|
||||||
return {};
|
return {};
|
||||||
} else {
|
} else {
|
||||||
this->cancel_timeout("ON");
|
this->cancel_timeout("ON");
|
||||||
@ -49,9 +49,9 @@ optional<bool> DelayedOnFilter::new_value(bool value) {
|
|||||||
|
|
||||||
float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
optional<bool> DelayedOffFilter::new_value(bool value) {
|
optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
this->set_timeout("OFF", this->delay_.value(), [this]() { this->output(false); });
|
this->set_timeout("OFF", this->delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
|
||||||
return {};
|
return {};
|
||||||
} else {
|
} else {
|
||||||
this->cancel_timeout("OFF");
|
this->cancel_timeout("OFF");
|
||||||
@ -61,11 +61,11 @@ optional<bool> DelayedOffFilter::new_value(bool value) {
|
|||||||
|
|
||||||
float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
optional<bool> InvertFilter::new_value(bool value) { return !value; }
|
optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; }
|
||||||
|
|
||||||
AutorepeatFilter::AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings) : timings_(std::move(timings)) {}
|
AutorepeatFilter::AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings) : timings_(std::move(timings)) {}
|
||||||
|
|
||||||
optional<bool> AutorepeatFilter::new_value(bool value) {
|
optional<bool> AutorepeatFilter::new_value(bool value, bool is_initial) {
|
||||||
if (value) {
|
if (value) {
|
||||||
// Ignore if already running
|
// Ignore if already running
|
||||||
if (this->active_timing_ != 0)
|
if (this->active_timing_ != 0)
|
||||||
@ -101,7 +101,7 @@ void AutorepeatFilter::next_timing_() {
|
|||||||
|
|
||||||
void AutorepeatFilter::next_value_(bool val) {
|
void AutorepeatFilter::next_value_(bool val) {
|
||||||
const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
|
const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
|
||||||
this->output(val);
|
this->output(val, false); // This is at least the second one so not initial
|
||||||
this->set_timeout("ON_OFF", val ? timing.time_on : timing.time_off, [this, val]() { this->next_value_(!val); });
|
this->set_timeout("ON_OFF", val ? timing.time_on : timing.time_off, [this, val]() { this->next_value_(!val); });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,18 +109,18 @@ float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARD
|
|||||||
|
|
||||||
LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move(f)) {}
|
LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move(f)) {}
|
||||||
|
|
||||||
optional<bool> LambdaFilter::new_value(bool value) { return this->f_(value); }
|
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }
|
||||||
|
|
||||||
optional<bool> SettleFilter::new_value(bool value) {
|
optional<bool> SettleFilter::new_value(bool value, bool is_initial) {
|
||||||
if (!this->steady_) {
|
if (!this->steady_) {
|
||||||
this->set_timeout("SETTLE", this->delay_.value(), [this, value]() {
|
this->set_timeout("SETTLE", this->delay_.value(), [this, value, is_initial]() {
|
||||||
this->steady_ = true;
|
this->steady_ = true;
|
||||||
this->output(value);
|
this->output(value, is_initial);
|
||||||
});
|
});
|
||||||
return {};
|
return {};
|
||||||
} else {
|
} else {
|
||||||
this->steady_ = false;
|
this->steady_ = false;
|
||||||
this->output(value);
|
this->output(value, is_initial);
|
||||||
this->set_timeout("SETTLE", this->delay_.value(), [this]() { this->steady_ = true; });
|
this->set_timeout("SETTLE", this->delay_.value(), [this]() { this->steady_ = true; });
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,11 @@ class BinarySensor;
|
|||||||
|
|
||||||
class Filter {
|
class Filter {
|
||||||
public:
|
public:
|
||||||
virtual optional<bool> new_value(bool value) = 0;
|
virtual optional<bool> new_value(bool value, bool is_initial) = 0;
|
||||||
|
|
||||||
void input(bool value);
|
void input(bool value, bool is_initial);
|
||||||
|
|
||||||
void output(bool value);
|
void output(bool value, bool is_initial);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend BinarySensor;
|
friend BinarySensor;
|
||||||
@ -30,7 +30,7 @@ class Filter {
|
|||||||
|
|
||||||
class DelayedOnOffFilter : public Filter, public Component {
|
class DelayedOnOffFilter : public Filter, public Component {
|
||||||
public:
|
public:
|
||||||
optional<bool> new_value(bool value) override;
|
optional<bool> new_value(bool value, bool is_initial) override;
|
||||||
|
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ class DelayedOnOffFilter : public Filter, public Component {
|
|||||||
|
|
||||||
class DelayedOnFilter : public Filter, public Component {
|
class DelayedOnFilter : public Filter, public Component {
|
||||||
public:
|
public:
|
||||||
optional<bool> new_value(bool value) override;
|
optional<bool> new_value(bool value, bool is_initial) override;
|
||||||
|
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ class DelayedOnFilter : public Filter, public Component {
|
|||||||
|
|
||||||
class DelayedOffFilter : public Filter, public Component {
|
class DelayedOffFilter : public Filter, public Component {
|
||||||
public:
|
public:
|
||||||
optional<bool> new_value(bool value) override;
|
optional<bool> new_value(bool value, bool is_initial) override;
|
||||||
|
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ class DelayedOffFilter : public Filter, public Component {
|
|||||||
|
|
||||||
class InvertFilter : public Filter {
|
class InvertFilter : public Filter {
|
||||||
public:
|
public:
|
||||||
optional<bool> new_value(bool value) override;
|
optional<bool> new_value(bool value, bool is_initial) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AutorepeatFilterTiming {
|
struct AutorepeatFilterTiming {
|
||||||
@ -86,7 +86,7 @@ class AutorepeatFilter : public Filter, public Component {
|
|||||||
public:
|
public:
|
||||||
explicit AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings);
|
explicit AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings);
|
||||||
|
|
||||||
optional<bool> new_value(bool value) override;
|
optional<bool> new_value(bool value, bool is_initial) override;
|
||||||
|
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ class LambdaFilter : public Filter {
|
|||||||
public:
|
public:
|
||||||
explicit LambdaFilter(std::function<optional<bool>(bool)> f);
|
explicit LambdaFilter(std::function<optional<bool>(bool)> f);
|
||||||
|
|
||||||
optional<bool> new_value(bool value) override;
|
optional<bool> new_value(bool value, bool is_initial) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::function<optional<bool>(bool)> f_;
|
std::function<optional<bool>(bool)> f_;
|
||||||
@ -110,7 +110,7 @@ class LambdaFilter : public Filter {
|
|||||||
|
|
||||||
class SettleFilter : public Filter, public Component {
|
class SettleFilter : public Filter, public Component {
|
||||||
public:
|
public:
|
||||||
optional<bool> new_value(bool value) override;
|
optional<bool> new_value(bool value, bool is_initial) override;
|
||||||
|
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/macros.h"
|
#include "esphome/core/macros.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
@ -177,7 +178,7 @@ void BluetoothProxy::loop() {
|
|||||||
// Flush any pending BLE advertisements that have been accumulated but not yet sent
|
// Flush any pending BLE advertisements that have been accumulated but not yet sent
|
||||||
if (this->raw_advertisements_) {
|
if (this->raw_advertisements_) {
|
||||||
static uint32_t last_flush_time = 0;
|
static uint32_t last_flush_time = 0;
|
||||||
uint32_t now = millis();
|
uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
// Flush accumulated advertisements every 100ms
|
// Flush accumulated advertisements every 100ms
|
||||||
if (now - last_flush_time >= 100) {
|
if (now - last_flush_time >= 100) {
|
||||||
|
@ -32,14 +32,14 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(CCS811Component),
|
cv.GenerateID(): cv.declare_id(CCS811Component),
|
||||||
cv.Required(CONF_ECO2): sensor.sensor_schema(
|
cv.Optional(CONF_ECO2): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||||
icon=ICON_MOLECULE_CO2,
|
icon=ICON_MOLECULE_CO2,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Required(CONF_TVOC): sensor.sensor_schema(
|
cv.Optional(CONF_TVOC): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_PARTS_PER_BILLION,
|
unit_of_measurement=UNIT_PARTS_PER_BILLION,
|
||||||
icon=ICON_RADIATOR,
|
icon=ICON_RADIATOR,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
@ -64,10 +64,13 @@ async def to_code(config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
sens = await sensor.new_sensor(config[CONF_ECO2])
|
if eco2_config := config.get(CONF_ECO2):
|
||||||
cg.add(var.set_co2(sens))
|
sens = await sensor.new_sensor(eco2_config)
|
||||||
sens = await sensor.new_sensor(config[CONF_TVOC])
|
cg.add(var.set_co2(sens))
|
||||||
cg.add(var.set_tvoc(sens))
|
|
||||||
|
if tvoc_config := config.get(CONF_TVOC):
|
||||||
|
sens = await sensor.new_sensor(tvoc_config)
|
||||||
|
cg.add(var.set_tvoc(sens))
|
||||||
|
|
||||||
if version_config := config.get(CONF_VERSION):
|
if version_config := config.get(CONF_VERSION):
|
||||||
sens = await text_sensor.new_text_sensor(version_config)
|
sens = await text_sensor.new_text_sensor(version_config)
|
||||||
|
@ -40,7 +40,7 @@ def climate_ir_schema(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def climare_ir_with_receiver_schema(
|
def climate_ir_with_receiver_schema(
|
||||||
class_: MockObjClass,
|
class_: MockObjClass,
|
||||||
) -> cv.Schema:
|
) -> cv.Schema:
|
||||||
return climate_ir_schema(class_).extend(
|
return climate_ir_schema(class_).extend(
|
||||||
@ -59,7 +59,7 @@ def deprecated_schema_constant(config):
|
|||||||
type = str(id.type).split("::", maxsplit=1)[0]
|
type = str(id.type).split("::", maxsplit=1)[0]
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Using `climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA` is deprecated and will be removed in ESPHome 2025.11.0. "
|
"Using `climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA` is deprecated and will be removed in ESPHome 2025.11.0. "
|
||||||
"Please use `climate_ir.climare_ir_with_receiver_schema(...)` instead. "
|
"Please use `climate_ir.climate_ir_with_receiver_schema(...)` instead. "
|
||||||
"If you are seeing this, report an issue to the external_component author and ask them to update it. "
|
"If you are seeing this, report an issue to the external_component author and ask them to update it. "
|
||||||
"https://developers.esphome.io/blog/2025/05/14/_schema-deprecations/. "
|
"https://developers.esphome.io/blog/2025/05/14/_schema-deprecations/. "
|
||||||
"Component using this schema: %s",
|
"Component using this schema: %s",
|
||||||
@ -68,7 +68,7 @@ def deprecated_schema_constant(config):
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
CLIMATE_IR_WITH_RECEIVER_SCHEMA = climare_ir_with_receiver_schema(ClimateIR)
|
CLIMATE_IR_WITH_RECEIVER_SCHEMA = climate_ir_with_receiver_schema(ClimateIR)
|
||||||
CLIMATE_IR_WITH_RECEIVER_SCHEMA.add_extra(deprecated_schema_constant)
|
CLIMATE_IR_WITH_RECEIVER_SCHEMA.add_extra(deprecated_schema_constant)
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ CONF_BIT_HIGH = "bit_high"
|
|||||||
CONF_BIT_ONE_LOW = "bit_one_low"
|
CONF_BIT_ONE_LOW = "bit_one_low"
|
||||||
CONF_BIT_ZERO_LOW = "bit_zero_low"
|
CONF_BIT_ZERO_LOW = "bit_zero_low"
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(LgIrClimate).extend(
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(LgIrClimate).extend(
|
||||||
{
|
{
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_HEADER_HIGH, default="8000us"
|
CONF_HEADER_HIGH, default="8000us"
|
||||||
|
@ -7,7 +7,7 @@ CODEOWNERS = ["@glmnet"]
|
|||||||
coolix_ns = cg.esphome_ns.namespace("coolix")
|
coolix_ns = cg.esphome_ns.namespace("coolix")
|
||||||
CoolixClimate = coolix_ns.class_("CoolixClimate", climate_ir.ClimateIR)
|
CoolixClimate = coolix_ns.class_("CoolixClimate", climate_ir.ClimateIR)
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(CoolixClimate)
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(CoolixClimate)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "cse7766.h"
|
#include "cse7766.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace cse7766 {
|
namespace cse7766 {
|
||||||
@ -7,7 +8,7 @@ namespace cse7766 {
|
|||||||
static const char *const TAG = "cse7766";
|
static const char *const TAG = "cse7766";
|
||||||
|
|
||||||
void CSE7766Component::loop() {
|
void CSE7766Component::loop() {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
if (now - this->last_transmission_ >= 500) {
|
if (now - this->last_transmission_ >= 500) {
|
||||||
// last transmission too long ago. Reset RX index.
|
// last transmission too long ago. Reset RX index.
|
||||||
this->raw_data_index_ = 0;
|
this->raw_data_index_ = 0;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "current_based_cover.h"
|
#include "current_based_cover.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@ -60,7 +61,7 @@ void CurrentBasedCover::loop() {
|
|||||||
if (this->current_operation == COVER_OPERATION_IDLE)
|
if (this->current_operation == COVER_OPERATION_IDLE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
if (this->current_operation == COVER_OPERATION_OPENING) {
|
if (this->current_operation == COVER_OPERATION_OPENING) {
|
||||||
if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction
|
if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction
|
||||||
|
@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
|||||||
daikin_ns = cg.esphome_ns.namespace("daikin")
|
daikin_ns = cg.esphome_ns.namespace("daikin")
|
||||||
DaikinClimate = daikin_ns.class_("DaikinClimate", climate_ir.ClimateIR)
|
DaikinClimate = daikin_ns.class_("DaikinClimate", climate_ir.ClimateIR)
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinClimate)
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinClimate)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
|||||||
daikin_arc_ns = cg.esphome_ns.namespace("daikin_arc")
|
daikin_arc_ns = cg.esphome_ns.namespace("daikin_arc")
|
||||||
DaikinArcClimate = daikin_arc_ns.class_("DaikinArcClimate", climate_ir.ClimateIR)
|
DaikinArcClimate = daikin_arc_ns.class_("DaikinArcClimate", climate_ir.ClimateIR)
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinArcClimate)
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinArcClimate)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
@ -9,7 +9,7 @@ daikin_brc_ns = cg.esphome_ns.namespace("daikin_brc")
|
|||||||
DaikinBrcClimate = daikin_brc_ns.class_("DaikinBrcClimate", climate_ir.ClimateIR)
|
DaikinBrcClimate = daikin_brc_ns.class_("DaikinBrcClimate", climate_ir.ClimateIR)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinBrcClimate).extend(
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinBrcClimate).extend(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "daly_bms.h"
|
#include "daly_bms.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace daly_bms {
|
namespace daly_bms {
|
||||||
@ -32,7 +33,7 @@ void DalyBmsComponent::update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DalyBmsComponent::loop() {
|
void DalyBmsComponent::loop() {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
if (this->receiving_ && (now - this->last_transmission_ >= 200)) {
|
if (this->receiving_ && (now - this->last_transmission_ >= 200)) {
|
||||||
// last transmission too long ago. Reset RX index.
|
// last transmission too long ago. Reset RX index.
|
||||||
ESP_LOGW(TAG, "Last transmission too long ago. Reset RX index.");
|
ESP_LOGW(TAG, "Last transmission too long ago. Reset RX index.");
|
||||||
|
@ -70,7 +70,7 @@ void DebugComponent::loop() {
|
|||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
// calculate loop time - from last call to this one
|
// calculate loop time - from last call to this one
|
||||||
if (this->loop_time_sensor_ != nullptr) {
|
if (this->loop_time_sensor_ != nullptr) {
|
||||||
uint32_t now = millis();
|
uint32_t now = App.get_loop_component_start_time();
|
||||||
uint32_t loop_time = now - this->last_loop_timetag_;
|
uint32_t loop_time = now - this->last_loop_timetag_;
|
||||||
this->max_loop_time_ = std::max(this->max_loop_time_, loop_time);
|
this->max_loop_time_ = std::max(this->max_loop_time_, loop_time);
|
||||||
this->last_loop_timetag_ = now;
|
this->last_loop_timetag_ = now;
|
||||||
|
@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
|||||||
delonghi_ns = cg.esphome_ns.namespace("delonghi")
|
delonghi_ns = cg.esphome_ns.namespace("delonghi")
|
||||||
DelonghiClimate = delonghi_ns.class_("DelonghiClimate", climate_ir.ClimateIR)
|
DelonghiClimate = delonghi_ns.class_("DelonghiClimate", climate_ir.ClimateIR)
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DelonghiClimate)
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DelonghiClimate)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
@ -27,14 +27,14 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(DPS310Component),
|
cv.GenerateID(): cv.declare_id(DPS310Component),
|
||||||
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_CELSIUS,
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
icon=ICON_THERMOMETER,
|
icon=ICON_THERMOMETER,
|
||||||
accuracy_decimals=1,
|
accuracy_decimals=1,
|
||||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Required(CONF_PRESSURE): sensor.sensor_schema(
|
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||||
icon=ICON_GAUGE,
|
icon=ICON_GAUGE,
|
||||||
accuracy_decimals=1,
|
accuracy_decimals=1,
|
||||||
@ -53,10 +53,10 @@ async def to_code(config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
if CONF_TEMPERATURE in config:
|
if temperature := config.get(CONF_TEMPERATURE):
|
||||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
sens = await sensor.new_sensor(temperature)
|
||||||
cg.add(var.set_temperature_sensor(sens))
|
cg.add(var.set_temperature_sensor(sens))
|
||||||
|
|
||||||
if CONF_PRESSURE in config:
|
if pressure := config.get(CONF_PRESSURE):
|
||||||
sens = await sensor.new_sensor(config[CONF_PRESSURE])
|
sens = await sensor.new_sensor(pressure)
|
||||||
cg.add(var.set_pressure_sensor(sens))
|
cg.add(var.set_pressure_sensor(sens))
|
||||||
|
@ -26,19 +26,19 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(EE895Component),
|
cv.GenerateID(): cv.declare_id(EE895Component),
|
||||||
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_CELSIUS,
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
accuracy_decimals=1,
|
accuracy_decimals=1,
|
||||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Required(CONF_CO2): sensor.sensor_schema(
|
cv.Optional(CONF_CO2): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||||
icon=ICON_MOLECULE_CO2,
|
icon=ICON_MOLECULE_CO2,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Required(CONF_PRESSURE): sensor.sensor_schema(
|
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||||
accuracy_decimals=1,
|
accuracy_decimals=1,
|
||||||
device_class=DEVICE_CLASS_PRESSURE,
|
device_class=DEVICE_CLASS_PRESSURE,
|
||||||
@ -56,14 +56,14 @@ async def to_code(config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
if CONF_TEMPERATURE in config:
|
if temperature := config.get(CONF_TEMPERATURE):
|
||||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
sens = await sensor.new_sensor(temperature)
|
||||||
cg.add(var.set_temperature_sensor(sens))
|
cg.add(var.set_temperature_sensor(sens))
|
||||||
|
|
||||||
if CONF_CO2 in config:
|
if co2 := config.get(CONF_CO2):
|
||||||
sens = await sensor.new_sensor(config[CONF_CO2])
|
sens = await sensor.new_sensor(co2)
|
||||||
cg.add(var.set_co2_sensor(sens))
|
cg.add(var.set_co2_sensor(sens))
|
||||||
|
|
||||||
if CONF_PRESSURE in config:
|
if pressure := config.get(CONF_PRESSURE):
|
||||||
sens = await sensor.new_sensor(config[CONF_PRESSURE])
|
sens = await sensor.new_sensor(pressure)
|
||||||
cg.add(var.set_pressure_sensor(sens))
|
cg.add(var.set_pressure_sensor(sens))
|
||||||
|
@ -7,7 +7,7 @@ AUTO_LOAD = ["climate_ir"]
|
|||||||
emmeti_ns = cg.esphome_ns.namespace("emmeti")
|
emmeti_ns = cg.esphome_ns.namespace("emmeti")
|
||||||
EmmetiClimate = emmeti_ns.class_("EmmetiClimate", climate_ir.ClimateIR)
|
EmmetiClimate = emmeti_ns.class_("EmmetiClimate", climate_ir.ClimateIR)
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(EmmetiClimate)
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(EmmetiClimate)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "endstop_cover.h"
|
#include "endstop_cover.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace endstop {
|
namespace endstop {
|
||||||
@ -65,7 +66,7 @@ void EndstopCover::loop() {
|
|||||||
if (this->current_operation == COVER_OPERATION_IDLE)
|
if (this->current_operation == COVER_OPERATION_IDLE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
if (this->current_operation == COVER_OPERATION_OPENING && this->is_open_()) {
|
if (this->current_operation == COVER_OPERATION_OPENING && this->is_open_()) {
|
||||||
float dur = (now - this->start_dir_time_) / 1e3f;
|
float dur = (now - this->start_dir_time_) / 1e3f;
|
||||||
|
@ -28,21 +28,21 @@ UNIT_INDEX = "index"
|
|||||||
|
|
||||||
CONFIG_SCHEMA_BASE = cv.Schema(
|
CONFIG_SCHEMA_BASE = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_ECO2): sensor.sensor_schema(
|
cv.Optional(CONF_ECO2): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||||
icon=ICON_MOLECULE_CO2,
|
icon=ICON_MOLECULE_CO2,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Required(CONF_TVOC): sensor.sensor_schema(
|
cv.Optional(CONF_TVOC): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_PARTS_PER_BILLION,
|
unit_of_measurement=UNIT_PARTS_PER_BILLION,
|
||||||
icon=ICON_RADIATOR,
|
icon=ICON_RADIATOR,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Required(CONF_AQI): sensor.sensor_schema(
|
cv.Optional(CONF_AQI): sensor.sensor_schema(
|
||||||
icon=ICON_CHEMICAL_WEAPON,
|
icon=ICON_CHEMICAL_WEAPON,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
device_class=DEVICE_CLASS_AQI,
|
device_class=DEVICE_CLASS_AQI,
|
||||||
@ -62,12 +62,15 @@ async def to_code_base(config):
|
|||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
sens = await sensor.new_sensor(config[CONF_ECO2])
|
if eco2_config := config.get(CONF_ECO2):
|
||||||
cg.add(var.set_co2(sens))
|
sens = await sensor.new_sensor(eco2_config)
|
||||||
sens = await sensor.new_sensor(config[CONF_TVOC])
|
cg.add(var.set_co2(sens))
|
||||||
cg.add(var.set_tvoc(sens))
|
if tvoc_config := config.get(CONF_TVOC):
|
||||||
sens = await sensor.new_sensor(config[CONF_AQI])
|
sens = await sensor.new_sensor(tvoc_config)
|
||||||
cg.add(var.set_aqi(sens))
|
cg.add(var.set_tvoc(sens))
|
||||||
|
if aqi_config := config.get(CONF_AQI):
|
||||||
|
sens = await sensor.new_sensor(aqi_config)
|
||||||
|
cg.add(var.set_aqi(sens))
|
||||||
|
|
||||||
if compensation_config := config.get(CONF_COMPENSATION):
|
if compensation_config := config.get(CONF_COMPENSATION):
|
||||||
sens = await cg.get_variable(compensation_config[CONF_TEMPERATURE])
|
sens = await cg.get_variable(compensation_config[CONF_TEMPERATURE])
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "ble_uuid.h"
|
#include "ble_uuid.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace esp32_ble {
|
namespace esp32_ble {
|
||||||
@ -143,7 +144,7 @@ void BLEAdvertising::loop() {
|
|||||||
if (this->raw_advertisements_callbacks_.empty()) {
|
if (this->raw_advertisements_callbacks_.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
if (now - this->last_advertisement_time_ > this->advertising_cycle_time_) {
|
if (now - this->last_advertisement_time_ > this->advertising_cycle_time_) {
|
||||||
this->stop();
|
this->stop();
|
||||||
this->current_adv_index_ += 1;
|
this->current_adv_index_ += 1;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include "esp32_camera.h"
|
#include "esp32_camera.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
|
|
||||||
@ -162,7 +163,7 @@ void ESP32Camera::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// request idle image every idle_update_interval
|
// request idle image every idle_update_interval
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) {
|
if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) {
|
||||||
this->last_idle_request_ = now;
|
this->last_idle_request_ = now;
|
||||||
this->request_image(IDLE);
|
this->request_image(IDLE);
|
||||||
|
@ -106,7 +106,7 @@ class CameraImageReader {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* ---------------- ESP32Camera class ---------------- */
|
/* ---------------- ESP32Camera class ---------------- */
|
||||||
class ESP32Camera : public Component, public EntityBase {
|
class ESP32Camera : public EntityBase, public Component {
|
||||||
public:
|
public:
|
||||||
ESP32Camera();
|
ESP32Camera();
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ void ESP32ImprovComponent::loop() {
|
|||||||
|
|
||||||
if (!this->incoming_data_.empty())
|
if (!this->incoming_data_.empty())
|
||||||
this->process_incoming_data_();
|
this->process_incoming_data_();
|
||||||
uint32_t now = millis();
|
uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
switch (this->state_) {
|
switch (this->state_) {
|
||||||
case improv::STATE_STOPPED:
|
case improv::STATE_STOPPED:
|
||||||
|
@ -288,7 +288,7 @@ uint32_t ESP32TouchComponent::component_touch_pad_read(touch_pad_t tp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ESP32TouchComponent::loop() {
|
void ESP32TouchComponent::loop() {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250;
|
bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250;
|
||||||
for (auto *child : this->children_) {
|
for (auto *child : this->children_) {
|
||||||
child->value_ = this->component_touch_pad_read(child->get_touch_pad());
|
child->value_ = this->component_touch_pad_read(child->get_touch_pad());
|
||||||
|
@ -240,7 +240,7 @@ void EthernetComponent::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EthernetComponent::loop() {
|
void EthernetComponent::loop() {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
switch (this->state_) {
|
switch (this->state_) {
|
||||||
case EthernetComponentState::STOPPED:
|
case EthernetComponentState::STOPPED:
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "feedback_cover.h"
|
#include "feedback_cover.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace feedback {
|
namespace feedback {
|
||||||
@ -220,7 +221,7 @@ void FeedbackCover::set_open_obstacle_sensor(binary_sensor::BinarySensor *open_o
|
|||||||
void FeedbackCover::loop() {
|
void FeedbackCover::loop() {
|
||||||
if (this->current_operation == COVER_OPERATION_IDLE)
|
if (this->current_operation == COVER_OPERATION_IDLE)
|
||||||
return;
|
return;
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
// Recompute position every loop cycle
|
// Recompute position every loop cycle
|
||||||
this->recompute_position_();
|
this->recompute_position_();
|
||||||
|
@ -8,7 +8,7 @@ FujitsuGeneralClimate = fujitsu_general_ns.class_(
|
|||||||
"FujitsuGeneralClimate", climate_ir.ClimateIR
|
"FujitsuGeneralClimate", climate_ir.ClimateIR
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(FujitsuGeneralClimate)
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(FujitsuGeneralClimate)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
#include "gcja5.h"
|
#include "gcja5.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@ -16,7 +17,7 @@ static const char *const TAG = "gcja5";
|
|||||||
void GCJA5Component::setup() { ESP_LOGCONFIG(TAG, "Setting up gcja5..."); }
|
void GCJA5Component::setup() { ESP_LOGCONFIG(TAG, "Setting up gcja5..."); }
|
||||||
|
|
||||||
void GCJA5Component::loop() {
|
void GCJA5Component::loop() {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
if (now - this->last_transmission_ >= 500) {
|
if (now - this->last_transmission_ >= 500) {
|
||||||
// last transmission too long ago. Reset RX index.
|
// last transmission too long ago. Reset RX index.
|
||||||
this->rx_message_.clear();
|
this->rx_message_.clear();
|
||||||
|
@ -9,23 +9,32 @@ from esphome.const import (
|
|||||||
CONF_LONGITUDE,
|
CONF_LONGITUDE,
|
||||||
CONF_SATELLITES,
|
CONF_SATELLITES,
|
||||||
CONF_SPEED,
|
CONF_SPEED,
|
||||||
|
DEVICE_CLASS_SPEED,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
UNIT_DEGREES,
|
UNIT_DEGREES,
|
||||||
UNIT_KILOMETER_PER_HOUR,
|
UNIT_KILOMETER_PER_HOUR,
|
||||||
UNIT_METER,
|
UNIT_METER,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CONF_GPS_ID = "gps_id"
|
||||||
|
CONF_HDOP = "hdop"
|
||||||
|
|
||||||
|
ICON_ALTIMETER = "mdi:altimeter"
|
||||||
|
ICON_COMPASS = "mdi:compass"
|
||||||
|
ICON_LATITUDE = "mdi:latitude"
|
||||||
|
ICON_LONGITUDE = "mdi:longitude"
|
||||||
|
ICON_SATELLITE = "mdi:satellite-variant"
|
||||||
|
ICON_SPEEDOMETER = "mdi:speedometer"
|
||||||
|
|
||||||
DEPENDENCIES = ["uart"]
|
DEPENDENCIES = ["uart"]
|
||||||
AUTO_LOAD = ["sensor"]
|
AUTO_LOAD = ["sensor"]
|
||||||
|
|
||||||
CODEOWNERS = ["@coogle"]
|
CODEOWNERS = ["@coogle", "@ximex"]
|
||||||
|
|
||||||
gps_ns = cg.esphome_ns.namespace("gps")
|
gps_ns = cg.esphome_ns.namespace("gps")
|
||||||
GPS = gps_ns.class_("GPS", cg.Component, uart.UARTDevice)
|
GPS = gps_ns.class_("GPS", cg.Component, uart.UARTDevice)
|
||||||
GPSListener = gps_ns.class_("GPSListener")
|
GPSListener = gps_ns.class_("GPSListener")
|
||||||
|
|
||||||
CONF_GPS_ID = "gps_id"
|
|
||||||
CONF_HDOP = "hdop"
|
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
@ -33,25 +42,37 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.GenerateID(): cv.declare_id(GPS),
|
cv.GenerateID(): cv.declare_id(GPS),
|
||||||
cv.Optional(CONF_LATITUDE): sensor.sensor_schema(
|
cv.Optional(CONF_LATITUDE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_DEGREES,
|
unit_of_measurement=UNIT_DEGREES,
|
||||||
|
icon=ICON_LATITUDE,
|
||||||
accuracy_decimals=6,
|
accuracy_decimals=6,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_LONGITUDE): sensor.sensor_schema(
|
cv.Optional(CONF_LONGITUDE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_DEGREES,
|
unit_of_measurement=UNIT_DEGREES,
|
||||||
|
icon=ICON_LONGITUDE,
|
||||||
accuracy_decimals=6,
|
accuracy_decimals=6,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_SPEED): sensor.sensor_schema(
|
cv.Optional(CONF_SPEED): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_KILOMETER_PER_HOUR,
|
unit_of_measurement=UNIT_KILOMETER_PER_HOUR,
|
||||||
|
icon=ICON_SPEEDOMETER,
|
||||||
accuracy_decimals=3,
|
accuracy_decimals=3,
|
||||||
|
device_class=DEVICE_CLASS_SPEED,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_COURSE): sensor.sensor_schema(
|
cv.Optional(CONF_COURSE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_DEGREES,
|
unit_of_measurement=UNIT_DEGREES,
|
||||||
|
icon=ICON_COMPASS,
|
||||||
accuracy_decimals=2,
|
accuracy_decimals=2,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_ALTITUDE): sensor.sensor_schema(
|
cv.Optional(CONF_ALTITUDE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_METER,
|
unit_of_measurement=UNIT_METER,
|
||||||
|
icon=ICON_ALTIMETER,
|
||||||
accuracy_decimals=2,
|
accuracy_decimals=2,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_SATELLITES): sensor.sensor_schema(
|
cv.Optional(CONF_SATELLITES): sensor.sensor_schema(
|
||||||
|
icon=ICON_SATELLITE,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
@ -73,28 +94,28 @@ async def to_code(config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await uart.register_uart_device(var, config)
|
await uart.register_uart_device(var, config)
|
||||||
|
|
||||||
if CONF_LATITUDE in config:
|
if latitude_config := config.get(CONF_LATITUDE):
|
||||||
sens = await sensor.new_sensor(config[CONF_LATITUDE])
|
sens = await sensor.new_sensor(latitude_config)
|
||||||
cg.add(var.set_latitude_sensor(sens))
|
cg.add(var.set_latitude_sensor(sens))
|
||||||
|
|
||||||
if CONF_LONGITUDE in config:
|
if longitude_config := config.get(CONF_LONGITUDE):
|
||||||
sens = await sensor.new_sensor(config[CONF_LONGITUDE])
|
sens = await sensor.new_sensor(longitude_config)
|
||||||
cg.add(var.set_longitude_sensor(sens))
|
cg.add(var.set_longitude_sensor(sens))
|
||||||
|
|
||||||
if CONF_SPEED in config:
|
if speed_config := config.get(CONF_SPEED):
|
||||||
sens = await sensor.new_sensor(config[CONF_SPEED])
|
sens = await sensor.new_sensor(speed_config)
|
||||||
cg.add(var.set_speed_sensor(sens))
|
cg.add(var.set_speed_sensor(sens))
|
||||||
|
|
||||||
if CONF_COURSE in config:
|
if course_config := config.get(CONF_COURSE):
|
||||||
sens = await sensor.new_sensor(config[CONF_COURSE])
|
sens = await sensor.new_sensor(course_config)
|
||||||
cg.add(var.set_course_sensor(sens))
|
cg.add(var.set_course_sensor(sens))
|
||||||
|
|
||||||
if CONF_ALTITUDE in config:
|
if altitude_config := config.get(CONF_ALTITUDE):
|
||||||
sens = await sensor.new_sensor(config[CONF_ALTITUDE])
|
sens = await sensor.new_sensor(altitude_config)
|
||||||
cg.add(var.set_altitude_sensor(sens))
|
cg.add(var.set_altitude_sensor(sens))
|
||||||
|
|
||||||
if CONF_SATELLITES in config:
|
if satellites_config := config.get(CONF_SATELLITES):
|
||||||
sens = await sensor.new_sensor(config[CONF_SATELLITES])
|
sens = await sensor.new_sensor(satellites_config)
|
||||||
cg.add(var.set_satellites_sensor(sens))
|
cg.add(var.set_satellites_sensor(sens))
|
||||||
|
|
||||||
if hdop_config := config.get(CONF_HDOP):
|
if hdop_config := config.get(CONF_HDOP):
|
||||||
@ -102,4 +123,4 @@ async def to_code(config):
|
|||||||
cg.add(var.set_hdop_sensor(sens))
|
cg.add(var.set_hdop_sensor(sens))
|
||||||
|
|
||||||
# https://platformio.org/lib/show/1655/TinyGPSPlus
|
# https://platformio.org/lib/show/1655/TinyGPSPlus
|
||||||
cg.add_library("mikalhart/TinyGPSPlus", "1.0.2")
|
cg.add_library("mikalhart/TinyGPSPlus", "1.1.0")
|
||||||
|
@ -10,6 +10,17 @@ static const char *const TAG = "gps";
|
|||||||
|
|
||||||
TinyGPSPlus &GPSListener::get_tiny_gps() { return this->parent_->get_tiny_gps(); }
|
TinyGPSPlus &GPSListener::get_tiny_gps() { return this->parent_->get_tiny_gps(); }
|
||||||
|
|
||||||
|
void GPS::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "GPS:");
|
||||||
|
LOG_SENSOR(" ", "Latitude", this->latitude_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Longitude", this->longitude_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Speed", this->speed_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Course", this->course_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Altitude", this->altitude_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Satellites", this->satellites_sensor_);
|
||||||
|
LOG_SENSOR(" ", "HDOP", this->hdop_sensor_);
|
||||||
|
}
|
||||||
|
|
||||||
void GPS::update() {
|
void GPS::update() {
|
||||||
if (this->latitude_sensor_ != nullptr)
|
if (this->latitude_sensor_ != nullptr)
|
||||||
this->latitude_sensor_->publish_state(this->latitude_);
|
this->latitude_sensor_->publish_state(this->latitude_);
|
||||||
@ -34,40 +45,45 @@ void GPS::update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GPS::loop() {
|
void GPS::loop() {
|
||||||
while (this->available() && !this->has_time_) {
|
while (this->available() > 0 && !this->has_time_) {
|
||||||
if (this->tiny_gps_.encode(this->read())) {
|
if (this->tiny_gps_.encode(this->read())) {
|
||||||
if (tiny_gps_.location.isUpdated()) {
|
if (this->tiny_gps_.location.isUpdated()) {
|
||||||
this->latitude_ = tiny_gps_.location.lat();
|
this->latitude_ = this->tiny_gps_.location.lat();
|
||||||
this->longitude_ = tiny_gps_.location.lng();
|
this->longitude_ = this->tiny_gps_.location.lng();
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Location:");
|
ESP_LOGD(TAG, "Location:");
|
||||||
ESP_LOGD(TAG, " Lat: %f", this->latitude_);
|
ESP_LOGD(TAG, " Lat: %.6f °", this->latitude_);
|
||||||
ESP_LOGD(TAG, " Lon: %f", this->longitude_);
|
ESP_LOGD(TAG, " Lon: %.6f °", this->longitude_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tiny_gps_.speed.isUpdated()) {
|
if (this->tiny_gps_.speed.isUpdated()) {
|
||||||
this->speed_ = tiny_gps_.speed.kmph();
|
this->speed_ = this->tiny_gps_.speed.kmph();
|
||||||
ESP_LOGD(TAG, "Speed: %.3f km/h", this->speed_);
|
ESP_LOGD(TAG, "Speed: %.3f km/h", this->speed_);
|
||||||
}
|
}
|
||||||
if (tiny_gps_.course.isUpdated()) {
|
|
||||||
this->course_ = tiny_gps_.course.deg();
|
if (this->tiny_gps_.course.isUpdated()) {
|
||||||
|
this->course_ = this->tiny_gps_.course.deg();
|
||||||
ESP_LOGD(TAG, "Course: %.2f °", this->course_);
|
ESP_LOGD(TAG, "Course: %.2f °", this->course_);
|
||||||
}
|
}
|
||||||
if (tiny_gps_.altitude.isUpdated()) {
|
|
||||||
this->altitude_ = tiny_gps_.altitude.meters();
|
if (this->tiny_gps_.altitude.isUpdated()) {
|
||||||
|
this->altitude_ = this->tiny_gps_.altitude.meters();
|
||||||
ESP_LOGD(TAG, "Altitude: %.2f m", this->altitude_);
|
ESP_LOGD(TAG, "Altitude: %.2f m", this->altitude_);
|
||||||
}
|
}
|
||||||
if (tiny_gps_.satellites.isUpdated()) {
|
|
||||||
this->satellites_ = tiny_gps_.satellites.value();
|
if (this->tiny_gps_.satellites.isUpdated()) {
|
||||||
|
this->satellites_ = this->tiny_gps_.satellites.value();
|
||||||
ESP_LOGD(TAG, "Satellites: %d", this->satellites_);
|
ESP_LOGD(TAG, "Satellites: %d", this->satellites_);
|
||||||
}
|
}
|
||||||
if (tiny_gps_.hdop.isUpdated()) {
|
|
||||||
this->hdop_ = tiny_gps_.hdop.hdop();
|
if (this->tiny_gps_.hdop.isUpdated()) {
|
||||||
|
this->hdop_ = this->tiny_gps_.hdop.hdop();
|
||||||
ESP_LOGD(TAG, "HDOP: %.3f", this->hdop_);
|
ESP_LOGD(TAG, "HDOP: %.3f", this->hdop_);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto *listener : this->listeners_)
|
for (auto *listener : this->listeners_) {
|
||||||
listener->on_update(this->tiny_gps_);
|
listener->on_update(this->tiny_gps_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/uart/uart.h"
|
#include "esphome/components/uart/uart.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include <TinyGPS++.h>
|
#include <TinyGPSPlus.h>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -27,13 +27,13 @@ class GPSListener {
|
|||||||
|
|
||||||
class GPS : public PollingComponent, public uart::UARTDevice {
|
class GPS : public PollingComponent, public uart::UARTDevice {
|
||||||
public:
|
public:
|
||||||
void set_latitude_sensor(sensor::Sensor *latitude_sensor) { latitude_sensor_ = latitude_sensor; }
|
void set_latitude_sensor(sensor::Sensor *latitude_sensor) { this->latitude_sensor_ = latitude_sensor; }
|
||||||
void set_longitude_sensor(sensor::Sensor *longitude_sensor) { longitude_sensor_ = longitude_sensor; }
|
void set_longitude_sensor(sensor::Sensor *longitude_sensor) { this->longitude_sensor_ = longitude_sensor; }
|
||||||
void set_speed_sensor(sensor::Sensor *speed_sensor) { speed_sensor_ = speed_sensor; }
|
void set_speed_sensor(sensor::Sensor *speed_sensor) { this->speed_sensor_ = speed_sensor; }
|
||||||
void set_course_sensor(sensor::Sensor *course_sensor) { course_sensor_ = course_sensor; }
|
void set_course_sensor(sensor::Sensor *course_sensor) { this->course_sensor_ = course_sensor; }
|
||||||
void set_altitude_sensor(sensor::Sensor *altitude_sensor) { altitude_sensor_ = altitude_sensor; }
|
void set_altitude_sensor(sensor::Sensor *altitude_sensor) { this->altitude_sensor_ = altitude_sensor; }
|
||||||
void set_satellites_sensor(sensor::Sensor *satellites_sensor) { satellites_sensor_ = satellites_sensor; }
|
void set_satellites_sensor(sensor::Sensor *satellites_sensor) { this->satellites_sensor_ = satellites_sensor; }
|
||||||
void set_hdop_sensor(sensor::Sensor *hdop_sensor) { hdop_sensor_ = hdop_sensor; }
|
void set_hdop_sensor(sensor::Sensor *hdop_sensor) { this->hdop_sensor_ = hdop_sensor; }
|
||||||
|
|
||||||
void register_listener(GPSListener *listener) {
|
void register_listener(GPSListener *listener) {
|
||||||
listener->parent_ = this;
|
listener->parent_ = this;
|
||||||
@ -41,19 +41,20 @@ class GPS : public PollingComponent, public uart::UARTDevice {
|
|||||||
}
|
}
|
||||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
void update() override;
|
void update() override;
|
||||||
|
|
||||||
TinyGPSPlus &get_tiny_gps() { return this->tiny_gps_; }
|
TinyGPSPlus &get_tiny_gps() { return this->tiny_gps_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
float latitude_ = NAN;
|
float latitude_{NAN};
|
||||||
float longitude_ = NAN;
|
float longitude_{NAN};
|
||||||
float speed_ = NAN;
|
float speed_{NAN};
|
||||||
float course_ = NAN;
|
float course_{NAN};
|
||||||
float altitude_ = NAN;
|
float altitude_{NAN};
|
||||||
int satellites_ = 0;
|
uint16_t satellites_{0};
|
||||||
double hdop_ = NAN;
|
float hdop_{NAN};
|
||||||
|
|
||||||
sensor::Sensor *latitude_sensor_{nullptr};
|
sensor::Sensor *latitude_sensor_{nullptr};
|
||||||
sensor::Sensor *longitude_sensor_{nullptr};
|
sensor::Sensor *longitude_sensor_{nullptr};
|
||||||
|
@ -21,7 +21,7 @@ MODELS = {
|
|||||||
"yag": Model.GREE_YAG,
|
"yag": Model.GREE_YAG,
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(GreeClimate).extend(
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(GreeClimate).extend(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_MODEL): cv.enum(MODELS),
|
cv.Required(CONF_MODEL): cv.enum(MODELS),
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "growatt_solar.h"
|
#include "growatt_solar.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace growatt_solar {
|
namespace growatt_solar {
|
||||||
@ -18,7 +19,7 @@ void GrowattSolar::loop() {
|
|||||||
|
|
||||||
void GrowattSolar::update() {
|
void GrowattSolar::update() {
|
||||||
// If our last send has had no reply yet, and it wasn't that long ago, do nothing.
|
// If our last send has had no reply yet, and it wasn't that long ago, do nothing.
|
||||||
uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
if (now - this->last_send_ < this->get_update_interval() / 2) {
|
if (now - this->last_send_ < this->get_update_interval() / 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ VERTICAL_DIRECTIONS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
climate_ir.climare_ir_with_receiver_schema(HeatpumpIRClimate).extend(
|
climate_ir.climate_ir_with_receiver_schema(HeatpumpIRClimate).extend(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_PROTOCOL): cv.enum(PROTOCOLS),
|
cv.Required(CONF_PROTOCOL): cv.enum(PROTOCOLS),
|
||||||
cv.Required(CONF_HORIZONTAL_DEFAULT): cv.enum(HORIZONTAL_DIRECTIONS),
|
cv.Required(CONF_HORIZONTAL_DEFAULT): cv.enum(HORIZONTAL_DIRECTIONS),
|
||||||
|
@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
|||||||
hitachi_ac344_ns = cg.esphome_ns.namespace("hitachi_ac344")
|
hitachi_ac344_ns = cg.esphome_ns.namespace("hitachi_ac344")
|
||||||
HitachiClimate = hitachi_ac344_ns.class_("HitachiClimate", climate_ir.ClimateIR)
|
HitachiClimate = hitachi_ac344_ns.class_("HitachiClimate", climate_ir.ClimateIR)
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(HitachiClimate)
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(HitachiClimate)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
|||||||
hitachi_ac424_ns = cg.esphome_ns.namespace("hitachi_ac424")
|
hitachi_ac424_ns = cg.esphome_ns.namespace("hitachi_ac424")
|
||||||
HitachiClimate = hitachi_ac424_ns.class_("HitachiClimate", climate_ir.ClimateIR)
|
HitachiClimate = hitachi_ac424_ns.class_("HitachiClimate", climate_ir.ClimateIR)
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(HitachiClimate)
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(HitachiClimate)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
@ -25,13 +25,13 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(HTE501Component),
|
cv.GenerateID(): cv.declare_id(HTE501Component),
|
||||||
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_CELSIUS,
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
accuracy_decimals=1,
|
accuracy_decimals=1,
|
||||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Required(CONF_HUMIDITY): sensor.sensor_schema(
|
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_PERCENT,
|
unit_of_measurement=UNIT_PERCENT,
|
||||||
accuracy_decimals=1,
|
accuracy_decimals=1,
|
||||||
device_class=DEVICE_CLASS_HUMIDITY,
|
device_class=DEVICE_CLASS_HUMIDITY,
|
||||||
@ -49,10 +49,10 @@ async def to_code(config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
if CONF_TEMPERATURE in config:
|
if temperature := config.get(CONF_TEMPERATURE):
|
||||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
sens = await sensor.new_sensor(temperature)
|
||||||
cg.add(var.set_temperature_sensor(sens))
|
cg.add(var.set_temperature_sensor(sens))
|
||||||
|
|
||||||
if CONF_HUMIDITY in config:
|
if humidity := config.get(CONF_HUMIDITY):
|
||||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
sens = await sensor.new_sensor(humidity)
|
||||||
cg.add(var.set_humidity_sensor(sens))
|
cg.add(var.set_humidity_sensor(sens))
|
||||||
|
@ -23,13 +23,13 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(HYT271Component),
|
cv.GenerateID(): cv.declare_id(HYT271Component),
|
||||||
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_CELSIUS,
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
accuracy_decimals=1,
|
accuracy_decimals=1,
|
||||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Required(CONF_HUMIDITY): sensor.sensor_schema(
|
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_PERCENT,
|
unit_of_measurement=UNIT_PERCENT,
|
||||||
accuracy_decimals=1,
|
accuracy_decimals=1,
|
||||||
device_class=DEVICE_CLASS_HUMIDITY,
|
device_class=DEVICE_CLASS_HUMIDITY,
|
||||||
@ -47,10 +47,10 @@ async def to_code(config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
if CONF_TEMPERATURE in config:
|
if temperature := config.get(CONF_TEMPERATURE):
|
||||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
sens = await sensor.new_sensor(temperature)
|
||||||
cg.add(var.set_temperature(sens))
|
cg.add(var.set_temperature(sens))
|
||||||
|
|
||||||
if CONF_HUMIDITY in config:
|
if humidity := config.get(CONF_HUMIDITY):
|
||||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
sens = await sensor.new_sensor(humidity)
|
||||||
cg.add(var.set_humidity(sens))
|
cg.add(var.set_humidity(sens))
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "kuntze.h"
|
#include "kuntze.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace kuntze {
|
namespace kuntze {
|
||||||
@ -60,7 +61,7 @@ void Kuntze::on_modbus_data(const std::vector<uint8_t> &data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Kuntze::loop() {
|
void Kuntze::loop() {
|
||||||
uint32_t now = millis();
|
uint32_t now = App.get_loop_component_start_time();
|
||||||
// timeout after 15 seconds
|
// timeout after 15 seconds
|
||||||
if (this->waiting_ && (now - this->last_send_ > 15000)) {
|
if (this->waiting_ && (now - this->last_send_ > 15000)) {
|
||||||
ESP_LOGW(TAG, "timed out waiting for response");
|
ESP_LOGW(TAG, "timed out waiting for response");
|
||||||
|
@ -254,6 +254,7 @@ async def to_code(config):
|
|||||||
config[CONF_TX_BUFFER_SIZE],
|
config[CONF_TX_BUFFER_SIZE],
|
||||||
)
|
)
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
|
cg.add(log.create_pthread_key())
|
||||||
task_log_buffer_size = config[CONF_TASK_LOG_BUFFER_SIZE]
|
task_log_buffer_size = config[CONF_TASK_LOG_BUFFER_SIZE]
|
||||||
if task_log_buffer_size > 0:
|
if task_log_buffer_size > 0:
|
||||||
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
|
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
|
||||||
|
@ -14,25 +14,47 @@ namespace logger {
|
|||||||
static const char *const TAG = "logger";
|
static const char *const TAG = "logger";
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
// Implementation for ESP32 (multi-core with atomic support)
|
// Implementation for ESP32 (multi-task platform with task-specific tracking)
|
||||||
// Main thread: synchronous logging with direct buffer access
|
// Main task always uses direct buffer access for console output and callbacks
|
||||||
// Other threads: console output with stack buffer, callbacks via async buffer
|
//
|
||||||
|
// For non-main tasks:
|
||||||
|
// - WITH task log buffer: Prefer sending to ring buffer for async processing
|
||||||
|
// - Avoids allocating stack memory for console output in normal operation
|
||||||
|
// - Prevents console corruption from concurrent writes by multiple tasks
|
||||||
|
// - Messages are serialized through main loop for proper console output
|
||||||
|
// - Fallback to emergency console logging only if ring buffer is full
|
||||||
|
// - WITHOUT task log buffer: Only emergency console output, no callbacks
|
||||||
void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT
|
void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT
|
||||||
if (level > this->level_for(tag) || recursion_guard_.load(std::memory_order_relaxed))
|
if (level > this->level_for(tag))
|
||||||
return;
|
return;
|
||||||
recursion_guard_.store(true, std::memory_order_relaxed);
|
|
||||||
|
|
||||||
TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
|
TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
|
||||||
|
bool is_main_task = (current_task == main_task_);
|
||||||
|
|
||||||
// For main task: call log_message_to_buffer_and_send_ which does console and callback logging
|
// Check and set recursion guard - uses pthread TLS for per-task state
|
||||||
if (current_task == main_task_) {
|
if (this->check_and_set_task_log_recursion_(is_main_task)) {
|
||||||
|
return; // Recursion detected
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main task uses the shared buffer for efficiency
|
||||||
|
if (is_main_task) {
|
||||||
this->log_message_to_buffer_and_send_(level, tag, line, format, args);
|
this->log_message_to_buffer_and_send_(level, tag, line, format, args);
|
||||||
recursion_guard_.store(false, std::memory_order_release);
|
this->reset_task_log_recursion_(is_main_task);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For non-main tasks: use stack-allocated buffer only for console output
|
bool message_sent = false;
|
||||||
if (this->baud_rate_ > 0) { // If logging is enabled, write to console
|
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
|
// For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered
|
||||||
|
message_sent = this->log_buffer_->send_message_thread_safe(static_cast<uint8_t>(level), tag,
|
||||||
|
static_cast<uint16_t>(line), current_task, format, args);
|
||||||
|
#endif // USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
|
|
||||||
|
// Emergency console logging for non-main tasks when ring buffer is full or disabled
|
||||||
|
// This is a fallback mechanism to ensure critical log messages are visible
|
||||||
|
// Note: This may cause interleaved/corrupted console output if multiple tasks
|
||||||
|
// log simultaneously, but it's better than losing important messages entirely
|
||||||
|
if (!message_sent && this->baud_rate_ > 0) { // If logging is enabled, write to console
|
||||||
// Maximum size for console log messages (includes null terminator)
|
// Maximum size for console log messages (includes null terminator)
|
||||||
static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144;
|
static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144;
|
||||||
char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety
|
char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety
|
||||||
@ -42,32 +64,21 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *
|
|||||||
this->write_msg_(console_buffer);
|
this->write_msg_(console_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
// Reset the recursion guard for this task
|
||||||
// For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered
|
this->reset_task_log_recursion_(is_main_task);
|
||||||
if (this->log_callback_.size() > 0) {
|
|
||||||
// This will be processed in the main loop
|
|
||||||
this->log_buffer_->send_message_thread_safe(static_cast<uint8_t>(level), tag, static_cast<uint16_t>(line),
|
|
||||||
current_task, format, args);
|
|
||||||
}
|
|
||||||
#endif // USE_ESPHOME_TASK_LOG_BUFFER
|
|
||||||
|
|
||||||
recursion_guard_.store(false, std::memory_order_release);
|
|
||||||
}
|
}
|
||||||
#endif // USE_ESP32
|
#else
|
||||||
|
// Implementation for all other platforms
|
||||||
#ifndef USE_ESP32
|
|
||||||
// Implementation for platforms that do not support atomic operations
|
|
||||||
// or have to consider logging in other tasks
|
|
||||||
void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT
|
void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT
|
||||||
if (level > this->level_for(tag) || recursion_guard_)
|
if (level > this->level_for(tag) || global_recursion_guard_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
recursion_guard_ = true;
|
global_recursion_guard_ = true;
|
||||||
|
|
||||||
// Format and send to both console and callbacks
|
// Format and send to both console and callbacks
|
||||||
this->log_message_to_buffer_and_send_(level, tag, line, format, args);
|
this->log_message_to_buffer_and_send_(level, tag, line, format, args);
|
||||||
|
|
||||||
recursion_guard_ = false;
|
global_recursion_guard_ = false;
|
||||||
}
|
}
|
||||||
#endif // !USE_ESP32
|
#endif // !USE_ESP32
|
||||||
|
|
||||||
@ -76,10 +87,10 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *
|
|||||||
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
|
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
|
||||||
void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format,
|
void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format,
|
||||||
va_list args) { // NOLINT
|
va_list args) { // NOLINT
|
||||||
if (level > this->level_for(tag) || recursion_guard_)
|
if (level > this->level_for(tag) || global_recursion_guard_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
recursion_guard_ = true;
|
global_recursion_guard_ = true;
|
||||||
this->tx_buffer_at_ = 0;
|
this->tx_buffer_at_ = 0;
|
||||||
|
|
||||||
// Copy format string from progmem
|
// Copy format string from progmem
|
||||||
@ -91,7 +102,7 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr
|
|||||||
|
|
||||||
// Buffer full from copying format
|
// Buffer full from copying format
|
||||||
if (this->tx_buffer_at_ >= this->tx_buffer_size_) {
|
if (this->tx_buffer_at_ >= this->tx_buffer_size_) {
|
||||||
recursion_guard_ = false; // Make sure to reset the recursion guard before returning
|
global_recursion_guard_ = false; // Make sure to reset the recursion guard before returning
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +118,7 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr
|
|||||||
}
|
}
|
||||||
this->call_log_callbacks_(level, tag, this->tx_buffer_ + msg_start);
|
this->call_log_callbacks_(level, tag, this->tx_buffer_ + msg_start);
|
||||||
|
|
||||||
recursion_guard_ = false;
|
global_recursion_guard_ = false;
|
||||||
}
|
}
|
||||||
#endif // USE_STORE_LOG_STR_IN_FLASH
|
#endif // USE_STORE_LOG_STR_IN_FLASH
|
||||||
|
|
||||||
@ -179,7 +190,17 @@ void Logger::loop() {
|
|||||||
this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
|
this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
|
||||||
this->tx_buffer_[this->tx_buffer_at_] = '\0';
|
this->tx_buffer_[this->tx_buffer_at_] = '\0';
|
||||||
this->call_log_callbacks_(message->level, message->tag, this->tx_buffer_);
|
this->call_log_callbacks_(message->level, message->tag, this->tx_buffer_);
|
||||||
|
// At this point all the data we need from message has been transferred to the tx_buffer
|
||||||
|
// so we can release the message to allow other tasks to use it as soon as possible.
|
||||||
this->log_buffer_->release_message_main_loop(received_token);
|
this->log_buffer_->release_message_main_loop(received_token);
|
||||||
|
|
||||||
|
// Write to console from the main loop to prevent corruption from concurrent writes
|
||||||
|
// This ensures all log messages appear on the console in a clean, serialized manner
|
||||||
|
// Note: Messages may appear slightly out of order due to async processing, but
|
||||||
|
// this is preferred over corrupted/interleaved console output
|
||||||
|
if (this->baud_rate_ > 0) {
|
||||||
|
this->write_msg_(this->tx_buffer_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <map>
|
#include <map>
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#include <atomic>
|
#include <pthread.h>
|
||||||
#endif
|
#endif
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
@ -84,6 +84,23 @@ enum UARTSelection {
|
|||||||
};
|
};
|
||||||
#endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY
|
#endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Logger component for all ESPHome logging.
|
||||||
|
*
|
||||||
|
* This class implements a multi-platform logging system with protection against recursion.
|
||||||
|
*
|
||||||
|
* Recursion Protection Strategy:
|
||||||
|
* - On ESP32: Uses task-specific recursion guards
|
||||||
|
* * Main task: Uses a dedicated boolean member variable for efficiency
|
||||||
|
* * Other tasks: Uses pthread TLS with a dynamically allocated key for task-specific state
|
||||||
|
* - On other platforms: Uses a simple global recursion guard
|
||||||
|
*
|
||||||
|
* We use pthread TLS via pthread_key_create to create a unique key for storing
|
||||||
|
* task-specific recursion state, which:
|
||||||
|
* 1. Efficiently handles multiple tasks without locks or mutexes
|
||||||
|
* 2. Works with ESP-IDF's pthread implementation that uses a linked list for TLS variables
|
||||||
|
* 3. Avoids the limitations of the fixed FreeRTOS task local storage slots
|
||||||
|
*/
|
||||||
class Logger : public Component {
|
class Logger : public Component {
|
||||||
public:
|
public:
|
||||||
explicit Logger(uint32_t baud_rate, size_t tx_buffer_size);
|
explicit Logger(uint32_t baud_rate, size_t tx_buffer_size);
|
||||||
@ -102,6 +119,9 @@ class Logger : public Component {
|
|||||||
#ifdef USE_ESP_IDF
|
#ifdef USE_ESP_IDF
|
||||||
uart_port_t get_uart_num() const { return uart_num_; }
|
uart_port_t get_uart_num() const { return uart_num_; }
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); }
|
||||||
|
#endif
|
||||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
|
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
|
||||||
void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
|
void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
|
||||||
/// Get the UART used by the logger.
|
/// Get the UART used by the logger.
|
||||||
@ -222,18 +242,22 @@ class Logger : public Component {
|
|||||||
std::map<std::string, int> log_levels_{};
|
std::map<std::string, int> log_levels_{};
|
||||||
CallbackManager<void(int, const char *, const char *)> log_callback_{};
|
CallbackManager<void(int, const char *, const char *)> log_callback_{};
|
||||||
int current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
|
int current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
|
||||||
#ifdef USE_ESP32
|
|
||||||
std::atomic<bool> recursion_guard_{false};
|
|
||||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer
|
std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
// Task-specific recursion guards:
|
||||||
|
// - Main task uses a dedicated member variable for efficiency
|
||||||
|
// - Other tasks use pthread TLS with a dynamically created key via pthread_key_create
|
||||||
|
bool main_task_recursion_guard_{false};
|
||||||
|
pthread_key_t log_recursion_key_;
|
||||||
#else
|
#else
|
||||||
bool recursion_guard_{false};
|
bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
|
||||||
#endif
|
#endif
|
||||||
void *main_task_ = nullptr;
|
|
||||||
CallbackManager<void(int)> level_callback_{};
|
CallbackManager<void(int)> level_callback_{};
|
||||||
|
|
||||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
|
void *main_task_ = nullptr; // Only used for thread name identification
|
||||||
const char *HOT get_thread_name_() {
|
const char *HOT get_thread_name_() {
|
||||||
TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
|
TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
|
||||||
if (current_task == main_task_) {
|
if (current_task == main_task_) {
|
||||||
@ -248,6 +272,32 @@ class Logger : public Component {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
inline bool HOT check_and_set_task_log_recursion_(bool is_main_task) {
|
||||||
|
if (is_main_task) {
|
||||||
|
const bool was_recursive = main_task_recursion_guard_;
|
||||||
|
main_task_recursion_guard_ = true;
|
||||||
|
return was_recursive;
|
||||||
|
}
|
||||||
|
|
||||||
|
intptr_t current = (intptr_t) pthread_getspecific(log_recursion_key_);
|
||||||
|
if (current != 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
pthread_setspecific(log_recursion_key_, (void *) 1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void HOT reset_task_log_recursion_(bool is_main_task) {
|
||||||
|
if (is_main_task) {
|
||||||
|
main_task_recursion_guard_ = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_setspecific(log_recursion_key_, (void *) 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
inline void HOT write_header_to_buffer_(int level, const char *tag, int line, const char *thread_name, char *buffer,
|
inline void HOT write_header_to_buffer_(int level, const char *tag, int line, const char *thread_name, char *buffer,
|
||||||
int *buffer_at, int buffer_size) {
|
int *buffer_at, int buffer_size) {
|
||||||
// Format header
|
// Format header
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "matrix_keypad.h"
|
#include "matrix_keypad.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace matrix_keypad {
|
namespace matrix_keypad {
|
||||||
@ -28,7 +29,7 @@ void MatrixKeypad::setup() {
|
|||||||
void MatrixKeypad::loop() {
|
void MatrixKeypad::loop() {
|
||||||
static uint32_t active_start = 0;
|
static uint32_t active_start = 0;
|
||||||
static int active_key = -1;
|
static int active_key = -1;
|
||||||
uint32_t now = millis();
|
uint32_t now = App.get_loop_component_start_time();
|
||||||
int key = -1;
|
int key = -1;
|
||||||
bool error = false;
|
bool error = false;
|
||||||
int pos = 0, row, col;
|
int pos = 0, row, col;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
#include "max7219font.h"
|
#include "max7219font.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@ -63,7 +64,7 @@ void MAX7219Component::dump_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MAX7219Component::loop() {
|
void MAX7219Component::loop() {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
const uint32_t millis_since_last_scroll = now - this->last_scroll_;
|
const uint32_t millis_since_last_scroll = now - this->last_scroll_;
|
||||||
const size_t first_line_size = this->max_displaybuffer_[0].size();
|
const size_t first_line_size = this->max_displaybuffer_[0].size();
|
||||||
// check if the buffer has shrunk past the current position since last update
|
// check if the buffer has shrunk past the current position since last update
|
||||||
|
@ -32,7 +32,7 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(MHZ19Component),
|
cv.GenerateID(): cv.declare_id(MHZ19Component),
|
||||||
cv.Required(CONF_CO2): sensor.sensor_schema(
|
cv.Optional(CONF_CO2): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||||
icon=ICON_MOLECULE_CO2,
|
icon=ICON_MOLECULE_CO2,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
@ -61,16 +61,20 @@ async def to_code(config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await uart.register_uart_device(var, config)
|
await uart.register_uart_device(var, config)
|
||||||
|
|
||||||
if CONF_CO2 in config:
|
if co2 := config.get(CONF_CO2):
|
||||||
sens = await sensor.new_sensor(config[CONF_CO2])
|
sens = await sensor.new_sensor(co2)
|
||||||
cg.add(var.set_co2_sensor(sens))
|
cg.add(var.set_co2_sensor(sens))
|
||||||
|
|
||||||
if CONF_TEMPERATURE in config:
|
if temperature := config.get(CONF_TEMPERATURE):
|
||||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
sens = await sensor.new_sensor(temperature)
|
||||||
cg.add(var.set_temperature_sensor(sens))
|
cg.add(var.set_temperature_sensor(sens))
|
||||||
|
|
||||||
if CONF_AUTOMATIC_BASELINE_CALIBRATION in config:
|
if (
|
||||||
cg.add(var.set_abc_enabled(config[CONF_AUTOMATIC_BASELINE_CALIBRATION]))
|
automatic_baseline_calibration := config.get(
|
||||||
|
CONF_AUTOMATIC_BASELINE_CALIBRATION
|
||||||
|
)
|
||||||
|
) is not None:
|
||||||
|
cg.add(var.set_abc_enabled(automatic_baseline_calibration))
|
||||||
|
|
||||||
cg.add(var.set_warmup_seconds(config[CONF_WARMUP_TIME]))
|
cg.add(var.set_warmup_seconds(config[CONF_WARMUP_TIME]))
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ midea_ir_ns = cg.esphome_ns.namespace("midea_ir")
|
|||||||
MideaIR = midea_ir_ns.class_("MideaIR", climate_ir.ClimateIR)
|
MideaIR = midea_ir_ns.class_("MideaIR", climate_ir.ClimateIR)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(MideaIR).extend(
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(MideaIR).extend(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ VERTICAL_DIRECTIONS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(MitsubishiClimate).extend(
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(MitsubishiClimate).extend(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_SET_FAN_MODE, default="3levels"): cv.enum(SETFANMODE),
|
cv.Optional(CONF_SET_FAN_MODE, default="3levels"): cv.enum(SETFANMODE),
|
||||||
cv.Optional(CONF_SUPPORTS_DRY, default=False): cv.boolean,
|
cv.Optional(CONF_SUPPORTS_DRY, default=False): cv.boolean,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "modbus.h"
|
#include "modbus.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace modbus {
|
namespace modbus {
|
||||||
@ -13,7 +14,7 @@ void Modbus::setup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Modbus::loop() {
|
void Modbus::loop() {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
while (this->available()) {
|
while (this->available()) {
|
||||||
uint8_t byte;
|
uint8_t byte;
|
||||||
|
@ -345,7 +345,7 @@ void MQTTClientComponent::loop() {
|
|||||||
this->disconnect_reason_.reset();
|
this->disconnect_reason_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
switch (this->state_) {
|
switch (this->state_) {
|
||||||
case MQTT_CLIENT_DISABLED:
|
case MQTT_CLIENT_DISABLED:
|
||||||
|
@ -24,13 +24,13 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(MS5611Component),
|
cv.GenerateID(): cv.declare_id(MS5611Component),
|
||||||
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_CELSIUS,
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
accuracy_decimals=1,
|
accuracy_decimals=1,
|
||||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Required(CONF_PRESSURE): sensor.sensor_schema(
|
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||||
icon=ICON_GAUGE,
|
icon=ICON_GAUGE,
|
||||||
accuracy_decimals=1,
|
accuracy_decimals=1,
|
||||||
@ -49,10 +49,10 @@ async def to_code(config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
if CONF_TEMPERATURE in config:
|
if temperature := config.get(CONF_TEMPERATURE):
|
||||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
sens = await sensor.new_sensor(temperature)
|
||||||
cg.add(var.set_temperature_sensor(sens))
|
cg.add(var.set_temperature_sensor(sens))
|
||||||
|
|
||||||
if CONF_PRESSURE in config:
|
if pressure := config.get(CONF_PRESSURE):
|
||||||
sens = await sensor.new_sensor(config[CONF_PRESSURE])
|
sens = await sensor.new_sensor(pressure)
|
||||||
cg.add(var.set_pressure_sensor(sens))
|
cg.add(var.set_pressure_sensor(sens))
|
||||||
|
@ -29,19 +29,19 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(MS8607Component),
|
cv.GenerateID(): cv.declare_id(MS8607Component),
|
||||||
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_CELSIUS,
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
accuracy_decimals=2, # Resolution: 0.01
|
accuracy_decimals=2, # Resolution: 0.01
|
||||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Required(CONF_PRESSURE): sensor.sensor_schema(
|
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||||
accuracy_decimals=2, # Resolution: 0.016
|
accuracy_decimals=2, # Resolution: 0.016
|
||||||
device_class=DEVICE_CLASS_PRESSURE,
|
device_class=DEVICE_CLASS_PRESSURE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Required(CONF_HUMIDITY): sensor.sensor_schema(
|
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_PERCENT,
|
unit_of_measurement=UNIT_PERCENT,
|
||||||
accuracy_decimals=2, # Resolution: 0.04
|
accuracy_decimals=2, # Resolution: 0.04
|
||||||
device_class=DEVICE_CLASS_HUMIDITY,
|
device_class=DEVICE_CLASS_HUMIDITY,
|
||||||
|
@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
|||||||
noblex_ns = cg.esphome_ns.namespace("noblex")
|
noblex_ns = cg.esphome_ns.namespace("noblex")
|
||||||
NoblexClimate = noblex_ns.class_("NoblexClimate", climate_ir.ClimateIR)
|
NoblexClimate = noblex_ns.class_("NoblexClimate", climate_ir.ClimateIR)
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(NoblexClimate)
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(NoblexClimate)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "pmsx003.h"
|
#include "pmsx003.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace pmsx003 {
|
namespace pmsx003 {
|
||||||
@ -42,7 +43,7 @@ void PMSX003Component::dump_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PMSX003Component::loop() {
|
void PMSX003Component::loop() {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
// If we update less often than it takes the device to stabilise, spin the fan down
|
// If we update less often than it takes the device to stabilise, spin the fan down
|
||||||
// rather than running it constantly. It does take some time to stabilise, so we
|
// rather than running it constantly. It does take some time to stabilise, so we
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "pzem004t.h"
|
#include "pzem004t.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@ -16,7 +17,7 @@ void PZEM004T::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PZEM004T::loop() {
|
void PZEM004T::loop() {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
if (now - this->last_read_ > 500 && this->available() < 7) {
|
if (now - this->last_read_ > 500 && this->available() < 7) {
|
||||||
while (this->available())
|
while (this->available())
|
||||||
this->read();
|
this->read();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "rf_bridge.h"
|
#include "rf_bridge.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@ -128,7 +129,7 @@ void RFBridgeComponent::write_byte_str_(const std::string &codes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RFBridgeComponent::loop() {
|
void RFBridgeComponent::loop() {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
if (now - this->last_bridge_byte_ > 50) {
|
if (now - this->last_bridge_byte_ > 50) {
|
||||||
this->rx_buffer_.clear();
|
this->rx_buffer_.clear();
|
||||||
this->last_bridge_byte_ = now;
|
this->last_bridge_byte_ = now;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "sds011.h"
|
#include "sds011.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace sds011 {
|
namespace sds011 {
|
||||||
@ -75,7 +76,7 @@ void SDS011Component::dump_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SDS011Component::loop() {
|
void SDS011Component::loop() {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
if ((now - this->last_transmission_ >= 500) && this->data_index_) {
|
if ((now - this->last_transmission_ >= 500) && this->data_index_) {
|
||||||
// last transmission too long ago. Reset RX index.
|
// last transmission too long ago. Reset RX index.
|
||||||
ESP_LOGV(TAG, "Last transmission too long ago. Reset RX index.");
|
ESP_LOGV(TAG, "Last transmission too long ago. Reset RX index.");
|
||||||
|
@ -25,6 +25,10 @@ static const uint16_t SEN5X_CMD_TEMPERATURE_COMPENSATION = 0x60B2;
|
|||||||
static const uint16_t SEN5X_CMD_VOC_ALGORITHM_STATE = 0x6181;
|
static const uint16_t SEN5X_CMD_VOC_ALGORITHM_STATE = 0x6181;
|
||||||
static const uint16_t SEN5X_CMD_VOC_ALGORITHM_TUNING = 0x60D0;
|
static const uint16_t SEN5X_CMD_VOC_ALGORITHM_TUNING = 0x60D0;
|
||||||
|
|
||||||
|
static const int8_t SEN5X_INDEX_SCALE_FACTOR = 10; // used for VOC and NOx index values
|
||||||
|
static const int8_t SEN5X_MIN_INDEX_VALUE = 1 * SEN5X_INDEX_SCALE_FACTOR; // must be adjusted by the scale factor
|
||||||
|
static const int16_t SEN5X_MAX_INDEX_VALUE = 500 * SEN5X_INDEX_SCALE_FACTOR; // must be adjusted by the scale factor
|
||||||
|
|
||||||
void SEN5XComponent::setup() {
|
void SEN5XComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up sen5x...");
|
ESP_LOGCONFIG(TAG, "Setting up sen5x...");
|
||||||
|
|
||||||
@ -88,8 +92,9 @@ void SEN5XComponent::setup() {
|
|||||||
product_name_.push_back(current_char);
|
product_name_.push_back(current_char);
|
||||||
// second char
|
// second char
|
||||||
current_char = *current_int & 0xFF;
|
current_char = *current_int & 0xFF;
|
||||||
if (current_char)
|
if (current_char) {
|
||||||
product_name_.push_back(current_char);
|
product_name_.push_back(current_char);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
current_int++;
|
current_int++;
|
||||||
} while (current_char && --max);
|
} while (current_char && --max);
|
||||||
@ -271,10 +276,10 @@ void SEN5XComponent::dump_config() {
|
|||||||
ESP_LOGCONFIG(TAG, " Low RH/T acceleration mode");
|
ESP_LOGCONFIG(TAG, " Low RH/T acceleration mode");
|
||||||
break;
|
break;
|
||||||
case MEDIUM_ACCELERATION:
|
case MEDIUM_ACCELERATION:
|
||||||
ESP_LOGCONFIG(TAG, " Medium RH/T accelertion mode");
|
ESP_LOGCONFIG(TAG, " Medium RH/T acceleration mode");
|
||||||
break;
|
break;
|
||||||
case HIGH_ACCELERATION:
|
case HIGH_ACCELERATION:
|
||||||
ESP_LOGCONFIG(TAG, " High RH/T accelertion mode");
|
ESP_LOGCONFIG(TAG, " High RH/T acceleration mode");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -337,47 +342,61 @@ void SEN5XComponent::update() {
|
|||||||
ESP_LOGD(TAG, "read data error (%d)", this->last_error_);
|
ESP_LOGD(TAG, "read data error (%d)", this->last_error_);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
float pm_1_0 = measurements[0] / 10.0;
|
|
||||||
if (measurements[0] == 0xFFFF)
|
|
||||||
pm_1_0 = NAN;
|
|
||||||
float pm_2_5 = measurements[1] / 10.0;
|
|
||||||
if (measurements[1] == 0xFFFF)
|
|
||||||
pm_2_5 = NAN;
|
|
||||||
float pm_4_0 = measurements[2] / 10.0;
|
|
||||||
if (measurements[2] == 0xFFFF)
|
|
||||||
pm_4_0 = NAN;
|
|
||||||
float pm_10_0 = measurements[3] / 10.0;
|
|
||||||
if (measurements[3] == 0xFFFF)
|
|
||||||
pm_10_0 = NAN;
|
|
||||||
float humidity = measurements[4] / 100.0;
|
|
||||||
if (measurements[4] == 0xFFFF)
|
|
||||||
humidity = NAN;
|
|
||||||
float temperature = (int16_t) measurements[5] / 200.0;
|
|
||||||
if (measurements[5] == 0xFFFF)
|
|
||||||
temperature = NAN;
|
|
||||||
float voc = measurements[6] / 10.0;
|
|
||||||
if (measurements[6] == 0xFFFF)
|
|
||||||
voc = NAN;
|
|
||||||
float nox = measurements[7] / 10.0;
|
|
||||||
if (measurements[7] == 0xFFFF)
|
|
||||||
nox = NAN;
|
|
||||||
|
|
||||||
if (this->pm_1_0_sensor_ != nullptr)
|
ESP_LOGVV(TAG, "pm_1_0 = 0x%.4x", measurements[0]);
|
||||||
|
float pm_1_0 = measurements[0] == UINT16_MAX ? NAN : measurements[0] / 10.0f;
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "pm_2_5 = 0x%.4x", measurements[1]);
|
||||||
|
float pm_2_5 = measurements[1] == UINT16_MAX ? NAN : measurements[1] / 10.0f;
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "pm_4_0 = 0x%.4x", measurements[2]);
|
||||||
|
float pm_4_0 = measurements[2] == UINT16_MAX ? NAN : measurements[2] / 10.0f;
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "pm_10_0 = 0x%.4x", measurements[3]);
|
||||||
|
float pm_10_0 = measurements[3] == UINT16_MAX ? NAN : measurements[3] / 10.0f;
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "humidity = 0x%.4x", measurements[4]);
|
||||||
|
float humidity = measurements[4] == INT16_MAX ? NAN : static_cast<int16_t>(measurements[4]) / 100.0f;
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "temperature = 0x%.4x", measurements[5]);
|
||||||
|
float temperature = measurements[5] == INT16_MAX ? NAN : static_cast<int16_t>(measurements[5]) / 200.0f;
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "voc = 0x%.4x", measurements[6]);
|
||||||
|
int16_t voc_idx = static_cast<int16_t>(measurements[6]);
|
||||||
|
float voc = (voc_idx < SEN5X_MIN_INDEX_VALUE || voc_idx > SEN5X_MAX_INDEX_VALUE)
|
||||||
|
? NAN
|
||||||
|
: static_cast<float>(voc_idx) / 10.0f;
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "nox = 0x%.4x", measurements[7]);
|
||||||
|
int16_t nox_idx = static_cast<int16_t>(measurements[7]);
|
||||||
|
float nox = (nox_idx < SEN5X_MIN_INDEX_VALUE || nox_idx > SEN5X_MAX_INDEX_VALUE)
|
||||||
|
? NAN
|
||||||
|
: static_cast<float>(nox_idx) / 10.0f;
|
||||||
|
|
||||||
|
if (this->pm_1_0_sensor_ != nullptr) {
|
||||||
this->pm_1_0_sensor_->publish_state(pm_1_0);
|
this->pm_1_0_sensor_->publish_state(pm_1_0);
|
||||||
if (this->pm_2_5_sensor_ != nullptr)
|
}
|
||||||
|
if (this->pm_2_5_sensor_ != nullptr) {
|
||||||
this->pm_2_5_sensor_->publish_state(pm_2_5);
|
this->pm_2_5_sensor_->publish_state(pm_2_5);
|
||||||
if (this->pm_4_0_sensor_ != nullptr)
|
}
|
||||||
|
if (this->pm_4_0_sensor_ != nullptr) {
|
||||||
this->pm_4_0_sensor_->publish_state(pm_4_0);
|
this->pm_4_0_sensor_->publish_state(pm_4_0);
|
||||||
if (this->pm_10_0_sensor_ != nullptr)
|
}
|
||||||
|
if (this->pm_10_0_sensor_ != nullptr) {
|
||||||
this->pm_10_0_sensor_->publish_state(pm_10_0);
|
this->pm_10_0_sensor_->publish_state(pm_10_0);
|
||||||
if (this->temperature_sensor_ != nullptr)
|
}
|
||||||
|
if (this->temperature_sensor_ != nullptr) {
|
||||||
this->temperature_sensor_->publish_state(temperature);
|
this->temperature_sensor_->publish_state(temperature);
|
||||||
if (this->humidity_sensor_ != nullptr)
|
}
|
||||||
|
if (this->humidity_sensor_ != nullptr) {
|
||||||
this->humidity_sensor_->publish_state(humidity);
|
this->humidity_sensor_->publish_state(humidity);
|
||||||
if (this->voc_sensor_ != nullptr)
|
}
|
||||||
|
if (this->voc_sensor_ != nullptr) {
|
||||||
this->voc_sensor_->publish_state(voc);
|
this->voc_sensor_->publish_state(voc);
|
||||||
if (this->nox_sensor_ != nullptr)
|
}
|
||||||
|
if (this->nox_sensor_ != nullptr) {
|
||||||
this->nox_sensor_->publish_state(nox);
|
this->nox_sensor_->publish_state(nox);
|
||||||
|
}
|
||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(SenseAirComponent),
|
cv.GenerateID(): cv.declare_id(SenseAirComponent),
|
||||||
cv.Required(CONF_CO2): sensor.sensor_schema(
|
cv.Optional(CONF_CO2): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||||
icon=ICON_MOLECULE_CO2,
|
icon=ICON_MOLECULE_CO2,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
@ -57,8 +57,8 @@ async def to_code(config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await uart.register_uart_device(var, config)
|
await uart.register_uart_device(var, config)
|
||||||
|
|
||||||
if CONF_CO2 in config:
|
if co2 := config.get(CONF_CO2):
|
||||||
sens = await sensor.new_sensor(config[CONF_CO2])
|
sens = await sensor.new_sensor(co2)
|
||||||
cg.add(var.set_co2_sensor(sens))
|
cg.add(var.set_co2_sensor(sens))
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,14 +37,14 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(SGP30Component),
|
cv.GenerateID(): cv.declare_id(SGP30Component),
|
||||||
cv.Required(CONF_ECO2): sensor.sensor_schema(
|
cv.Optional(CONF_ECO2): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||||
icon=ICON_MOLECULE_CO2,
|
icon=ICON_MOLECULE_CO2,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Required(CONF_TVOC): sensor.sensor_schema(
|
cv.Optional(CONF_TVOC): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_PARTS_PER_BILLION,
|
unit_of_measurement=UNIT_PARTS_PER_BILLION,
|
||||||
icon=ICON_RADIATOR,
|
icon=ICON_RADIATOR,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
@ -86,32 +86,30 @@ async def to_code(config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
if CONF_ECO2 in config:
|
if eco2_config := config.get(CONF_ECO2):
|
||||||
sens = await sensor.new_sensor(config[CONF_ECO2])
|
sens = await sensor.new_sensor(eco2_config)
|
||||||
cg.add(var.set_eco2_sensor(sens))
|
cg.add(var.set_eco2_sensor(sens))
|
||||||
|
|
||||||
if CONF_TVOC in config:
|
if tvoc_config := config.get(CONF_TVOC):
|
||||||
sens = await sensor.new_sensor(config[CONF_TVOC])
|
sens = await sensor.new_sensor(tvoc_config)
|
||||||
cg.add(var.set_tvoc_sensor(sens))
|
cg.add(var.set_tvoc_sensor(sens))
|
||||||
|
|
||||||
if CONF_ECO2_BASELINE in config:
|
if eco2_baseline_config := config.get(CONF_ECO2_BASELINE):
|
||||||
sens = await sensor.new_sensor(config[CONF_ECO2_BASELINE])
|
sens = await sensor.new_sensor(eco2_baseline_config)
|
||||||
cg.add(var.set_eco2_baseline_sensor(sens))
|
cg.add(var.set_eco2_baseline_sensor(sens))
|
||||||
|
|
||||||
if CONF_TVOC_BASELINE in config:
|
if tvoc_baseline_config := config.get(CONF_TVOC_BASELINE):
|
||||||
sens = await sensor.new_sensor(config[CONF_TVOC_BASELINE])
|
sens = await sensor.new_sensor(tvoc_baseline_config)
|
||||||
cg.add(var.set_tvoc_baseline_sensor(sens))
|
cg.add(var.set_tvoc_baseline_sensor(sens))
|
||||||
|
|
||||||
if CONF_STORE_BASELINE in config:
|
if (store_baseline := config.get(CONF_STORE_BASELINE)) is not None:
|
||||||
cg.add(var.set_store_baseline(config[CONF_STORE_BASELINE]))
|
cg.add(var.set_store_baseline(store_baseline))
|
||||||
|
|
||||||
if CONF_BASELINE in config:
|
if baseline_config := config.get(CONF_BASELINE):
|
||||||
baseline_config = config[CONF_BASELINE]
|
|
||||||
cg.add(var.set_eco2_baseline(baseline_config[CONF_ECO2_BASELINE]))
|
cg.add(var.set_eco2_baseline(baseline_config[CONF_ECO2_BASELINE]))
|
||||||
cg.add(var.set_tvoc_baseline(baseline_config[CONF_TVOC_BASELINE]))
|
cg.add(var.set_tvoc_baseline(baseline_config[CONF_TVOC_BASELINE]))
|
||||||
|
|
||||||
if CONF_COMPENSATION in config:
|
if compensation_config := config.get(CONF_COMPENSATION):
|
||||||
compensation_config = config[CONF_COMPENSATION]
|
|
||||||
sens = await cg.get_variable(compensation_config[CONF_HUMIDITY_SOURCE])
|
sens = await cg.get_variable(compensation_config[CONF_HUMIDITY_SOURCE])
|
||||||
cg.add(var.set_humidity_sensor(sens))
|
cg.add(var.set_humidity_sensor(sens))
|
||||||
sens = await cg.get_variable(compensation_config[CONF_TEMPERATURE_SOURCE])
|
sens = await cg.get_variable(compensation_config[CONF_TEMPERATURE_SOURCE])
|
||||||
|
@ -26,13 +26,13 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(SHTCXComponent),
|
cv.GenerateID(): cv.declare_id(SHTCXComponent),
|
||||||
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_CELSIUS,
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
accuracy_decimals=1,
|
accuracy_decimals=1,
|
||||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Required(CONF_HUMIDITY): sensor.sensor_schema(
|
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_PERCENT,
|
unit_of_measurement=UNIT_PERCENT,
|
||||||
accuracy_decimals=1,
|
accuracy_decimals=1,
|
||||||
device_class=DEVICE_CLASS_HUMIDITY,
|
device_class=DEVICE_CLASS_HUMIDITY,
|
||||||
@ -50,10 +50,10 @@ async def to_code(config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
if CONF_TEMPERATURE in config:
|
if temperature := config.get(CONF_TEMPERATURE):
|
||||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
sens = await sensor.new_sensor(temperature)
|
||||||
cg.add(var.set_temperature_sensor(sens))
|
cg.add(var.set_temperature_sensor(sens))
|
||||||
|
|
||||||
if CONF_HUMIDITY in config:
|
if humidity := config.get(CONF_HUMIDITY):
|
||||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
sens = await sensor.new_sensor(humidity)
|
||||||
cg.add(var.set_humidity_sensor(sens))
|
cg.add(var.set_humidity_sensor(sens))
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "slow_pwm_output.h"
|
#include "slow_pwm_output.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace slow_pwm {
|
namespace slow_pwm {
|
||||||
@ -39,7 +40,7 @@ void SlowPWMOutput::set_output_state_(bool new_state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SlowPWMOutput::loop() {
|
void SlowPWMOutput::loop() {
|
||||||
uint32_t now = millis();
|
uint32_t now = App.get_loop_component_start_time();
|
||||||
float scaled_state = this->state_ * this->period_;
|
float scaled_state = this->state_ * this->period_;
|
||||||
|
|
||||||
if (now - this->period_start_time_ >= this->period_) {
|
if (now - this->period_start_time_ >= this->period_) {
|
||||||
|
@ -20,7 +20,7 @@ SprinklerSwitch::SprinklerSwitch(switch_::Switch *off_switch, switch_::Switch *o
|
|||||||
bool SprinklerSwitch::is_latching_valve() { return (this->off_switch_ != nullptr) && (this->on_switch_ != nullptr); }
|
bool SprinklerSwitch::is_latching_valve() { return (this->off_switch_ != nullptr) && (this->on_switch_ != nullptr); }
|
||||||
|
|
||||||
void SprinklerSwitch::loop() {
|
void SprinklerSwitch::loop() {
|
||||||
if ((this->pinned_millis_) && (millis() > this->pinned_millis_ + this->pulse_duration_)) {
|
if ((this->pinned_millis_) && (App.get_loop_component_start_time() > this->pinned_millis_ + this->pulse_duration_)) {
|
||||||
this->pinned_millis_ = 0; // reset tracker
|
this->pinned_millis_ = 0; // reset tracker
|
||||||
if (this->off_switch_->state) {
|
if (this->off_switch_->state) {
|
||||||
this->off_switch_->turn_off();
|
this->off_switch_->turn_off();
|
||||||
@ -148,22 +148,23 @@ SprinklerValveOperator::SprinklerValveOperator(SprinklerValve *valve, Sprinkler
|
|||||||
: controller_(controller), valve_(valve) {}
|
: controller_(controller), valve_(valve) {}
|
||||||
|
|
||||||
void SprinklerValveOperator::loop() {
|
void SprinklerValveOperator::loop() {
|
||||||
if (millis() >= this->start_millis_) { // dummy check
|
uint32_t now = App.get_loop_component_start_time();
|
||||||
|
if (now >= this->start_millis_) { // dummy check
|
||||||
switch (this->state_) {
|
switch (this->state_) {
|
||||||
case STARTING:
|
case STARTING:
|
||||||
if (millis() > (this->start_millis_ + this->start_delay_)) {
|
if (now > (this->start_millis_ + this->start_delay_)) {
|
||||||
this->run_(); // start_delay_ has been exceeded, so ensure both valves are on and update the state
|
this->run_(); // start_delay_ has been exceeded, so ensure both valves are on and update the state
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ACTIVE:
|
case ACTIVE:
|
||||||
if (millis() > (this->start_millis_ + this->start_delay_ + this->run_duration_)) {
|
if (now > (this->start_millis_ + this->start_delay_ + this->run_duration_)) {
|
||||||
this->stop(); // start_delay_ + run_duration_ has been exceeded, start shutting down
|
this->stop(); // start_delay_ + run_duration_ has been exceeded, start shutting down
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STOPPING:
|
case STOPPING:
|
||||||
if (millis() > (this->stop_millis_ + this->stop_delay_)) {
|
if (now > (this->stop_millis_ + this->stop_delay_)) {
|
||||||
this->kill_(); // stop_delay_has been exceeded, ensure all valves are off
|
this->kill_(); // stop_delay_has been exceeded, ensure all valves are off
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -19,7 +19,7 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(T6615Component),
|
cv.GenerateID(): cv.declare_id(T6615Component),
|
||||||
cv.Required(CONF_CO2): sensor.sensor_schema(
|
cv.Optional(CONF_CO2): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
||||||
@ -41,6 +41,6 @@ async def to_code(config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await uart.register_uart_device(var, config)
|
await uart.register_uart_device(var, config)
|
||||||
|
|
||||||
if CONF_CO2 in config:
|
if co2 := config.get(CONF_CO2):
|
||||||
sens = await sensor.new_sensor(config[CONF_CO2])
|
sens = await sensor.new_sensor(co2)
|
||||||
cg.add(var.set_co2_sensor(sens))
|
cg.add(var.set_co2_sensor(sens))
|
||||||
|
@ -63,7 +63,8 @@ void T6615Component::loop() {
|
|||||||
case T6615Command::GET_PPM: {
|
case T6615Command::GET_PPM: {
|
||||||
const uint16_t ppm = encode_uint16(response_buffer[3], response_buffer[4]);
|
const uint16_t ppm = encode_uint16(response_buffer[3], response_buffer[4]);
|
||||||
ESP_LOGD(TAG, "T6615 Received CO₂=%uppm", ppm);
|
ESP_LOGD(TAG, "T6615 Received CO₂=%uppm", ppm);
|
||||||
this->co2_sensor_->publish_state(ppm);
|
if (this->co2_sensor_ != nullptr)
|
||||||
|
this->co2_sensor_->publish_state(ppm);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -7,7 +7,7 @@ CODEOWNERS = ["@glmnet"]
|
|||||||
tcl112_ns = cg.esphome_ns.namespace("tcl112")
|
tcl112_ns = cg.esphome_ns.namespace("tcl112")
|
||||||
Tcl112Climate = tcl112_ns.class_("Tcl112Climate", climate_ir.ClimateIR)
|
Tcl112Climate = tcl112_ns.class_("Tcl112Climate", climate_ir.ClimateIR)
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(Tcl112Climate)
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(Tcl112Climate)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "time_based_cover.h"
|
#include "time_based_cover.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace time_based {
|
namespace time_based {
|
||||||
@ -26,7 +27,7 @@ void TimeBasedCover::loop() {
|
|||||||
if (this->current_operation == COVER_OPERATION_IDLE)
|
if (this->current_operation == COVER_OPERATION_IDLE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
// Recompute position every loop cycle
|
// Recompute position every loop cycle
|
||||||
this->recompute_position_();
|
this->recompute_position_();
|
||||||
|
@ -16,7 +16,7 @@ MODELS = {
|
|||||||
"RAC-PT1411HWRU-F": Model.MODEL_RAC_PT1411HWRU_F,
|
"RAC-PT1411HWRU-F": Model.MODEL_RAC_PT1411HWRU_F,
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(ToshibaClimate).extend(
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(ToshibaClimate).extend(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_MODEL, default="generic"): cv.enum(MODELS, upper=True),
|
cv.Optional(CONF_MODEL, default="generic"): cv.enum(MODELS, upper=True),
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "uart_switch.h"
|
#include "uart_switch.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace uart {
|
namespace uart {
|
||||||
@ -8,7 +9,7 @@ static const char *const TAG = "uart.switch";
|
|||||||
|
|
||||||
void UARTSwitch::loop() {
|
void UARTSwitch::loop() {
|
||||||
if (this->send_every_) {
|
if (this->send_every_) {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
if (now - this->last_transmission_ > this->send_every_) {
|
if (now - this->last_transmission_ > this->send_every_) {
|
||||||
this->write_command_(this->state);
|
this->write_command_(this->state);
|
||||||
this->last_transmission_ = now;
|
this->last_transmission_ = now;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "uponor_smatrix_climate.h"
|
#include "uponor_smatrix_climate.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace uponor_smatrix {
|
namespace uponor_smatrix {
|
||||||
@ -13,7 +14,7 @@ void UponorSmatrixClimate::dump_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UponorSmatrixClimate::loop() {
|
void UponorSmatrixClimate::loop() {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
// Publish state after all update packets are processed
|
// Publish state after all update packets are processed
|
||||||
if (this->last_data_ != 0 && (now - this->last_data_ > 100) && this->target_temperature_raw_ != 0) {
|
if (this->last_data_ != 0 && (now - this->last_data_ > 100) && this->target_temperature_raw_ != 0) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "uponor_smatrix.h"
|
#include "uponor_smatrix.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace uponor_smatrix {
|
namespace uponor_smatrix {
|
||||||
@ -35,7 +36,7 @@ void UponorSmatrixComponent::dump_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UponorSmatrixComponent::loop() {
|
void UponorSmatrixComponent::loop() {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
// Discard stale data
|
// Discard stale data
|
||||||
if (!this->rx_buffer_.empty() && (now - this->last_rx_ > 50)) {
|
if (!this->rx_buffer_.empty() && (now - this->last_rx_ > 50)) {
|
||||||
|
@ -15,7 +15,7 @@ MODELS = {
|
|||||||
"DG11J1-91": Model.MODEL_DG11J1_91,
|
"DG11J1-91": Model.MODEL_DG11J1_91,
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(WhirlpoolClimate).extend(
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(WhirlpoolClimate).extend(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_MODEL, default="DG11J1-3A"): cv.enum(MODELS, upper=True),
|
cv.Optional(CONF_MODEL, default="DG11J1-3A"): cv.enum(MODELS, upper=True),
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ whynter_ns = cg.esphome_ns.namespace("whynter")
|
|||||||
Whynter = whynter_ns.class_("Whynter", climate_ir.ClimateIR)
|
Whynter = whynter_ns.class_("Whynter", climate_ir.ClimateIR)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(Whynter).extend(
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(Whynter).extend(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ CODEOWNERS = ["@cfeenstra1024"]
|
|||||||
zhlt01_ns = cg.esphome_ns.namespace("zhlt01")
|
zhlt01_ns = cg.esphome_ns.namespace("zhlt01")
|
||||||
ZHLT01Climate = zhlt01_ns.class_("ZHLT01Climate", climate_ir.ClimateIR)
|
ZHLT01Climate = zhlt01_ns.class_("ZHLT01Climate", climate_ir.ClimateIR)
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(ZHLT01Climate)
|
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(ZHLT01Climate)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
@ -28,7 +28,7 @@ import esphome.core.config as core_config
|
|||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
from esphome.helpers import indent
|
from esphome.helpers import indent
|
||||||
from esphome.loader import ComponentManifest, get_component, get_platform
|
from esphome.loader import ComponentManifest, get_component, get_platform
|
||||||
from esphome.log import Fore, color
|
from esphome.log import AnsiFore, color
|
||||||
from esphome.types import ConfigFragmentType, ConfigType
|
from esphome.types import ConfigFragmentType, ConfigType
|
||||||
from esphome.util import OrderedDict, safe_print
|
from esphome.util import OrderedDict, safe_print
|
||||||
from esphome.voluptuous_schema import ExtraKeysInvalid
|
from esphome.voluptuous_schema import ExtraKeysInvalid
|
||||||
@ -959,7 +959,7 @@ def line_info(config, path, highlight=True):
|
|||||||
if obj:
|
if obj:
|
||||||
mark = obj.start_mark
|
mark = obj.start_mark
|
||||||
source = f"[source {mark.document}:{mark.line + 1}]"
|
source = f"[source {mark.document}:{mark.line + 1}]"
|
||||||
return color(Fore.CYAN, source)
|
return color(AnsiFore.CYAN, source)
|
||||||
return "None"
|
return "None"
|
||||||
|
|
||||||
|
|
||||||
@ -983,7 +983,7 @@ def dump_dict(
|
|||||||
if at_root:
|
if at_root:
|
||||||
error = config.get_error_for_path(path)
|
error = config.get_error_for_path(path)
|
||||||
if error is not None:
|
if error is not None:
|
||||||
ret += f"\n{color(Fore.BOLD_RED, _format_vol_invalid(error, config))}\n"
|
ret += f"\n{color(AnsiFore.BOLD_RED, _format_vol_invalid(error, config))}\n"
|
||||||
|
|
||||||
if isinstance(conf, (list, tuple)):
|
if isinstance(conf, (list, tuple)):
|
||||||
multiline = True
|
multiline = True
|
||||||
@ -995,11 +995,11 @@ def dump_dict(
|
|||||||
path_ = path + [i]
|
path_ = path + [i]
|
||||||
error = config.get_error_for_path(path_)
|
error = config.get_error_for_path(path_)
|
||||||
if error is not None:
|
if error is not None:
|
||||||
ret += f"\n{color(Fore.BOLD_RED, _format_vol_invalid(error, config))}\n"
|
ret += f"\n{color(AnsiFore.BOLD_RED, _format_vol_invalid(error, config))}\n"
|
||||||
|
|
||||||
sep = "- "
|
sep = "- "
|
||||||
if config.is_in_error_path(path_):
|
if config.is_in_error_path(path_):
|
||||||
sep = color(Fore.RED, sep)
|
sep = color(AnsiFore.RED, sep)
|
||||||
msg, _ = dump_dict(config, path_, at_root=False)
|
msg, _ = dump_dict(config, path_, at_root=False)
|
||||||
msg = indent(msg)
|
msg = indent(msg)
|
||||||
inf = line_info(config, path_, highlight=config.is_in_error_path(path_))
|
inf = line_info(config, path_, highlight=config.is_in_error_path(path_))
|
||||||
@ -1018,11 +1018,11 @@ def dump_dict(
|
|||||||
path_ = path + [k]
|
path_ = path + [k]
|
||||||
error = config.get_error_for_path(path_)
|
error = config.get_error_for_path(path_)
|
||||||
if error is not None:
|
if error is not None:
|
||||||
ret += f"\n{color(Fore.BOLD_RED, _format_vol_invalid(error, config))}\n"
|
ret += f"\n{color(AnsiFore.BOLD_RED, _format_vol_invalid(error, config))}\n"
|
||||||
|
|
||||||
st = f"{k}: "
|
st = f"{k}: "
|
||||||
if config.is_in_error_path(path_):
|
if config.is_in_error_path(path_):
|
||||||
st = color(Fore.RED, st)
|
st = color(AnsiFore.RED, st)
|
||||||
msg, m = dump_dict(config, path_, at_root=False)
|
msg, m = dump_dict(config, path_, at_root=False)
|
||||||
|
|
||||||
inf = line_info(config, path_, highlight=config.is_in_error_path(path_))
|
inf = line_info(config, path_, highlight=config.is_in_error_path(path_))
|
||||||
@ -1044,7 +1044,7 @@ def dump_dict(
|
|||||||
if len(conf) > 80:
|
if len(conf) > 80:
|
||||||
conf = f"|-\n{indent(conf)}"
|
conf = f"|-\n{indent(conf)}"
|
||||||
error = config.get_error_for_path(path)
|
error = config.get_error_for_path(path)
|
||||||
col = Fore.BOLD_RED if error else Fore.KEEP
|
col = AnsiFore.BOLD_RED if error else AnsiFore.KEEP
|
||||||
ret += color(col, str(conf))
|
ret += color(col, str(conf))
|
||||||
elif isinstance(conf, core.Lambda):
|
elif isinstance(conf, core.Lambda):
|
||||||
if is_secret(conf):
|
if is_secret(conf):
|
||||||
@ -1052,13 +1052,13 @@ def dump_dict(
|
|||||||
|
|
||||||
conf = f"!lambda |-\n{indent(str(conf.value))}"
|
conf = f"!lambda |-\n{indent(str(conf.value))}"
|
||||||
error = config.get_error_for_path(path)
|
error = config.get_error_for_path(path)
|
||||||
col = Fore.BOLD_RED if error else Fore.KEEP
|
col = AnsiFore.BOLD_RED if error else AnsiFore.KEEP
|
||||||
ret += color(col, conf)
|
ret += color(col, conf)
|
||||||
elif conf is None:
|
elif conf is None:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
error = config.get_error_for_path(path)
|
error = config.get_error_for_path(path)
|
||||||
col = Fore.BOLD_RED if error else Fore.KEEP
|
col = AnsiFore.BOLD_RED if error else AnsiFore.KEEP
|
||||||
ret += color(col, str(conf))
|
ret += color(col, str(conf))
|
||||||
multiline = "\n" in ret
|
multiline = "\n" in ret
|
||||||
|
|
||||||
@ -1100,13 +1100,13 @@ def read_config(command_line_substitutions):
|
|||||||
if not CORE.verbose:
|
if not CORE.verbose:
|
||||||
res = strip_default_ids(res)
|
res = strip_default_ids(res)
|
||||||
|
|
||||||
safe_print(color(Fore.BOLD_RED, "Failed config"))
|
safe_print(color(AnsiFore.BOLD_RED, "Failed config"))
|
||||||
safe_print("")
|
safe_print("")
|
||||||
for path, domain in res.output_paths:
|
for path, domain in res.output_paths:
|
||||||
if not res.is_in_error_path(path):
|
if not res.is_in_error_path(path):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
errstr = color(Fore.BOLD_RED, f"{domain}:")
|
errstr = color(AnsiFore.BOLD_RED, f"{domain}:")
|
||||||
errline = line_info(res, path)
|
errline = line_info(res, path)
|
||||||
if errline:
|
if errline:
|
||||||
errstr += f" {errline}"
|
errstr += f" {errline}"
|
||||||
@ -1121,7 +1121,7 @@ def read_config(command_line_substitutions):
|
|||||||
safe_print(indent("\n".join(split_dump[:i])))
|
safe_print(indent("\n".join(split_dump[:i])))
|
||||||
|
|
||||||
for err in res.errors:
|
for err in res.errors:
|
||||||
safe_print(color(Fore.BOLD_RED, err.msg))
|
safe_print(color(AnsiFore.BOLD_RED, err.msg))
|
||||||
safe_print("")
|
safe_print("")
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Constants used by esphome."""
|
"""Constants used by esphome."""
|
||||||
|
|
||||||
__version__ = "2025.5.0b2"
|
__version__ = "2025.5.0b3"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
|
@ -67,22 +67,32 @@ void Application::loop() {
|
|||||||
uint32_t new_app_state = 0;
|
uint32_t new_app_state = 0;
|
||||||
|
|
||||||
this->scheduler.call();
|
this->scheduler.call();
|
||||||
this->feed_wdt();
|
|
||||||
|
// Get the initial loop time at the start
|
||||||
|
uint32_t last_op_end_time = millis();
|
||||||
|
|
||||||
|
// Feed WDT with time
|
||||||
|
this->feed_wdt(last_op_end_time);
|
||||||
|
|
||||||
for (Component *component : this->looping_components_) {
|
for (Component *component : this->looping_components_) {
|
||||||
|
// Update the cached time before each component runs
|
||||||
|
this->loop_component_start_time_ = last_op_end_time;
|
||||||
|
|
||||||
{
|
{
|
||||||
this->set_current_component(component);
|
this->set_current_component(component);
|
||||||
WarnIfComponentBlockingGuard guard{component};
|
WarnIfComponentBlockingGuard guard{component, last_op_end_time};
|
||||||
component->call();
|
component->call();
|
||||||
|
// Use the finish method to get the current time as the end time
|
||||||
|
last_op_end_time = guard.finish();
|
||||||
}
|
}
|
||||||
new_app_state |= component->get_component_state();
|
new_app_state |= component->get_component_state();
|
||||||
this->app_state_ |= new_app_state;
|
this->app_state_ |= new_app_state;
|
||||||
this->feed_wdt();
|
this->feed_wdt(last_op_end_time);
|
||||||
}
|
}
|
||||||
this->app_state_ = new_app_state;
|
this->app_state_ = new_app_state;
|
||||||
|
|
||||||
const uint32_t now = millis();
|
// Use the last component's end time instead of calling millis() again
|
||||||
|
auto elapsed = last_op_end_time - this->last_loop_;
|
||||||
auto elapsed = now - this->last_loop_;
|
|
||||||
if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) {
|
if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) {
|
||||||
yield();
|
yield();
|
||||||
} else {
|
} else {
|
||||||
@ -94,7 +104,7 @@ void Application::loop() {
|
|||||||
delay_time = std::min(next_schedule, delay_time);
|
delay_time = std::min(next_schedule, delay_time);
|
||||||
delay(delay_time);
|
delay(delay_time);
|
||||||
}
|
}
|
||||||
this->last_loop_ = now;
|
this->last_loop_ = last_op_end_time;
|
||||||
|
|
||||||
if (this->dump_config_at_ < this->components_.size()) {
|
if (this->dump_config_at_ < this->components_.size()) {
|
||||||
if (this->dump_config_at_ == 0) {
|
if (this->dump_config_at_ == 0) {
|
||||||
@ -109,10 +119,12 @@ void Application::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IRAM_ATTR HOT Application::feed_wdt() {
|
void IRAM_ATTR HOT Application::feed_wdt(uint32_t time) {
|
||||||
static uint32_t last_feed = 0;
|
static uint32_t last_feed = 0;
|
||||||
uint32_t now = micros();
|
// Use provided time if available, otherwise get current time
|
||||||
if (now - last_feed > 3000) {
|
uint32_t now = time ? time : millis();
|
||||||
|
// Compare in milliseconds (3ms threshold)
|
||||||
|
if (now - last_feed > 3) {
|
||||||
arch_feed_wdt();
|
arch_feed_wdt();
|
||||||
last_feed = now;
|
last_feed = now;
|
||||||
#ifdef USE_STATUS_LED
|
#ifdef USE_STATUS_LED
|
||||||
|
@ -217,6 +217,9 @@ class Application {
|
|||||||
|
|
||||||
std::string get_compilation_time() const { return this->compilation_time_; }
|
std::string get_compilation_time() const { return this->compilation_time_; }
|
||||||
|
|
||||||
|
/// Get the cached time in milliseconds from when the current component started its loop execution
|
||||||
|
inline uint32_t IRAM_ATTR HOT get_loop_component_start_time() const { return this->loop_component_start_time_; }
|
||||||
|
|
||||||
/** Set the target interval with which to run the loop() calls.
|
/** Set the target interval with which to run the loop() calls.
|
||||||
* If the loop() method takes longer than the target interval, ESPHome won't
|
* If the loop() method takes longer than the target interval, ESPHome won't
|
||||||
* sleep in loop(), but if the time spent in loop() is small than the target, ESPHome
|
* sleep in loop(), but if the time spent in loop() is small than the target, ESPHome
|
||||||
@ -236,7 +239,7 @@ class Application {
|
|||||||
|
|
||||||
void schedule_dump_config() { this->dump_config_at_ = 0; }
|
void schedule_dump_config() { this->dump_config_at_ = 0; }
|
||||||
|
|
||||||
void feed_wdt();
|
void feed_wdt(uint32_t time = 0);
|
||||||
|
|
||||||
void reboot();
|
void reboot();
|
||||||
|
|
||||||
@ -551,6 +554,7 @@ class Application {
|
|||||||
size_t dump_config_at_{SIZE_MAX};
|
size_t dump_config_at_{SIZE_MAX};
|
||||||
uint32_t app_state_{0};
|
uint32_t app_state_{0};
|
||||||
Component *current_component_{nullptr};
|
Component *current_component_{nullptr};
|
||||||
|
uint32_t loop_component_start_time_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Global storage of Application pointer - only one Application can exist.
|
/// Global storage of Application pointer - only one Application can exist.
|
||||||
|
@ -240,10 +240,12 @@ void PollingComponent::stop_poller() {
|
|||||||
uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; }
|
uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; }
|
||||||
void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
|
void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
|
||||||
|
|
||||||
WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component)
|
WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component, uint32_t start_time)
|
||||||
: started_(millis()), component_(component) {}
|
: started_(start_time), component_(component) {}
|
||||||
WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {
|
uint32_t WarnIfComponentBlockingGuard::finish() {
|
||||||
uint32_t blocking_time = millis() - this->started_;
|
uint32_t curr_time = millis();
|
||||||
|
|
||||||
|
uint32_t blocking_time = curr_time - this->started_;
|
||||||
bool should_warn;
|
bool should_warn;
|
||||||
if (this->component_ != nullptr) {
|
if (this->component_ != nullptr) {
|
||||||
should_warn = this->component_->should_warn_of_blocking(blocking_time);
|
should_warn = this->component_->should_warn_of_blocking(blocking_time);
|
||||||
@ -254,8 +256,11 @@ WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {
|
|||||||
const char *src = component_ == nullptr ? "<null>" : component_->get_component_source();
|
const char *src = component_ == nullptr ? "<null>" : component_->get_component_source();
|
||||||
ESP_LOGW(TAG, "Component %s took a long time for an operation (%" PRIu32 " ms).", src, blocking_time);
|
ESP_LOGW(TAG, "Component %s took a long time for an operation (%" PRIu32 " ms).", src, blocking_time);
|
||||||
ESP_LOGW(TAG, "Components should block for at most 30 ms.");
|
ESP_LOGW(TAG, "Components should block for at most 30 ms.");
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return curr_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {}
|
||||||
|
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@ -339,7 +339,11 @@ class PollingComponent : public Component {
|
|||||||
|
|
||||||
class WarnIfComponentBlockingGuard {
|
class WarnIfComponentBlockingGuard {
|
||||||
public:
|
public:
|
||||||
WarnIfComponentBlockingGuard(Component *component);
|
WarnIfComponentBlockingGuard(Component *component, uint32_t start_time);
|
||||||
|
|
||||||
|
// Finish the timing operation and return the current time
|
||||||
|
uint32_t finish();
|
||||||
|
|
||||||
~WarnIfComponentBlockingGuard();
|
~WarnIfComponentBlockingGuard();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -229,8 +229,11 @@ void HOT Scheduler::call() {
|
|||||||
// - timeouts/intervals get added, potentially invalidating vector pointers
|
// - timeouts/intervals get added, potentially invalidating vector pointers
|
||||||
// - timeouts/intervals get cancelled
|
// - timeouts/intervals get cancelled
|
||||||
{
|
{
|
||||||
WarnIfComponentBlockingGuard guard{item->component};
|
uint32_t now_ms = millis();
|
||||||
|
WarnIfComponentBlockingGuard guard{item->component, now_ms};
|
||||||
item->callback();
|
item->callback();
|
||||||
|
// Call finish to ensure blocking time is properly calculated and reported
|
||||||
|
guard.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
|
from enum import Enum
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
|
|
||||||
class AnsiFore:
|
class AnsiFore(Enum):
|
||||||
KEEP = ""
|
KEEP = ""
|
||||||
BLACK = "\033[30m"
|
BLACK = "\033[30m"
|
||||||
RED = "\033[31m"
|
RED = "\033[31m"
|
||||||
@ -26,7 +27,7 @@ class AnsiFore:
|
|||||||
BOLD_RESET = "\033[1;39m"
|
BOLD_RESET = "\033[1;39m"
|
||||||
|
|
||||||
|
|
||||||
class AnsiStyle:
|
class AnsiStyle(Enum):
|
||||||
BRIGHT = "\033[1m"
|
BRIGHT = "\033[1m"
|
||||||
BOLD = "\033[1m"
|
BOLD = "\033[1m"
|
||||||
DIM = "\033[2m"
|
DIM = "\033[2m"
|
||||||
@ -35,16 +36,10 @@ class AnsiStyle:
|
|||||||
RESET_ALL = "\033[0m"
|
RESET_ALL = "\033[0m"
|
||||||
|
|
||||||
|
|
||||||
Fore = AnsiFore()
|
def color(col: AnsiFore, msg: str, reset: bool = True) -> str:
|
||||||
Style = AnsiStyle()
|
s = col.value + msg
|
||||||
|
|
||||||
|
|
||||||
def color(col: str, msg: str, reset: bool = True) -> bool:
|
|
||||||
if col and not col.startswith("\033["):
|
|
||||||
raise ValueError("Color must be value from esphome.log.Fore")
|
|
||||||
s = str(col) + msg
|
|
||||||
if reset and col:
|
if reset and col:
|
||||||
s += str(Style.RESET_ALL)
|
s += AnsiStyle.RESET_ALL.value
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
@ -54,20 +49,21 @@ class ESPHomeLogFormatter(logging.Formatter):
|
|||||||
fmt += "%(levelname)s %(message)s"
|
fmt += "%(levelname)s %(message)s"
|
||||||
super().__init__(fmt=fmt, style="%")
|
super().__init__(fmt=fmt, style="%")
|
||||||
|
|
||||||
def format(self, record):
|
# @override
|
||||||
|
def format(self, record: logging.LogRecord) -> str:
|
||||||
formatted = super().format(record)
|
formatted = super().format(record)
|
||||||
prefix = {
|
prefix = {
|
||||||
"DEBUG": Fore.CYAN,
|
"DEBUG": AnsiFore.CYAN.value,
|
||||||
"INFO": Fore.GREEN,
|
"INFO": AnsiFore.GREEN.value,
|
||||||
"WARNING": Fore.YELLOW,
|
"WARNING": AnsiFore.YELLOW.value,
|
||||||
"ERROR": Fore.RED,
|
"ERROR": AnsiFore.RED.value,
|
||||||
"CRITICAL": Fore.RED,
|
"CRITICAL": AnsiFore.RED.value,
|
||||||
}.get(record.levelname, "")
|
}.get(record.levelname, "")
|
||||||
return f"{prefix}{formatted}{Style.RESET_ALL}"
|
return f"{prefix}{formatted}{AnsiStyle.RESET_ALL.value}"
|
||||||
|
|
||||||
|
|
||||||
def setup_log(
|
def setup_log(
|
||||||
log_level=logging.INFO,
|
log_level: int = logging.INFO,
|
||||||
include_timestamp: bool = False,
|
include_timestamp: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
import colorama
|
import colorama
|
||||||
|
@ -28,7 +28,7 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.core import CORE, EsphomeError
|
from esphome.core import CORE, EsphomeError
|
||||||
from esphome.helpers import get_int_env, get_str_env
|
from esphome.helpers import get_int_env, get_str_env
|
||||||
from esphome.log import Fore, color
|
from esphome.log import AnsiFore, color
|
||||||
from esphome.util import safe_print
|
from esphome.util import safe_print
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -291,7 +291,7 @@ def get_fingerprint(config):
|
|||||||
|
|
||||||
sha1 = hashlib.sha1(cert_der).hexdigest()
|
sha1 = hashlib.sha1(cert_der).hexdigest()
|
||||||
|
|
||||||
safe_print(f"SHA1 Fingerprint: {color(Fore.CYAN, sha1)}")
|
safe_print(f"SHA1 Fingerprint: {color(AnsiFore.CYAN, sha1)}")
|
||||||
safe_print(
|
safe_print(
|
||||||
f"Copy the string above into mqtt.ssl_fingerprints section of {CORE.config_path}"
|
f"Copy the string above into mqtt.ssl_fingerprints section of {CORE.config_path}"
|
||||||
)
|
)
|
||||||
|
@ -15,7 +15,9 @@ class ExtraKeysInvalid(vol.Invalid):
|
|||||||
def ensure_multiple_invalid(err):
|
def ensure_multiple_invalid(err):
|
||||||
if isinstance(err, vol.MultipleInvalid):
|
if isinstance(err, vol.MultipleInvalid):
|
||||||
return err
|
return err
|
||||||
return vol.MultipleInvalid(err)
|
if isinstance(err, list):
|
||||||
|
return vol.MultipleInvalid(err)
|
||||||
|
return vol.MultipleInvalid([err])
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=protected-access, unidiomatic-typecheck
|
# pylint: disable=protected-access, unidiomatic-typecheck
|
||||||
|
@ -9,7 +9,7 @@ import esphome.config_validation as cv
|
|||||||
from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD
|
from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
from esphome.helpers import get_bool_env, write_file
|
from esphome.helpers import get_bool_env, write_file
|
||||||
from esphome.log import Fore, color
|
from esphome.log import AnsiFore, color
|
||||||
from esphome.storage_json import StorageJSON, ext_storage_path
|
from esphome.storage_json import StorageJSON, ext_storage_path
|
||||||
from esphome.util import safe_input, safe_print
|
from esphome.util import safe_input, safe_print
|
||||||
|
|
||||||
@ -219,7 +219,7 @@ def wizard_write(path, **kwargs):
|
|||||||
elif board in rtl87xx_boards.BOARDS:
|
elif board in rtl87xx_boards.BOARDS:
|
||||||
platform = "RTL87XX"
|
platform = "RTL87XX"
|
||||||
else:
|
else:
|
||||||
safe_print(color(Fore.RED, f'The board "{board}" is unknown.'))
|
safe_print(color(AnsiFore.RED, f'The board "{board}" is unknown.'))
|
||||||
return False
|
return False
|
||||||
kwargs["platform"] = platform
|
kwargs["platform"] = platform
|
||||||
hardware = kwargs["platform"]
|
hardware = kwargs["platform"]
|
||||||
@ -274,12 +274,12 @@ def wizard(path):
|
|||||||
|
|
||||||
if not path.endswith(".yaml") and not path.endswith(".yml"):
|
if not path.endswith(".yaml") and not path.endswith(".yml"):
|
||||||
safe_print(
|
safe_print(
|
||||||
f"Please make your configuration file {color(Fore.CYAN, path)} have the extension .yaml or .yml"
|
f"Please make your configuration file {color(AnsiFore.CYAN, path)} have the extension .yaml or .yml"
|
||||||
)
|
)
|
||||||
return 1
|
return 1
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
safe_print(
|
safe_print(
|
||||||
f"Uh oh, it seems like {color(Fore.CYAN, path)} already exists, please delete that file first or chose another configuration file."
|
f"Uh oh, it seems like {color(AnsiFore.CYAN, path)} already exists, please delete that file first or chose another configuration file."
|
||||||
)
|
)
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
@ -298,17 +298,19 @@ def wizard(path):
|
|||||||
sleep(3.0)
|
sleep(3.0)
|
||||||
safe_print()
|
safe_print()
|
||||||
safe_print_step(1, CORE_BIG)
|
safe_print_step(1, CORE_BIG)
|
||||||
safe_print(f"First up, please choose a {color(Fore.GREEN, 'name')} for your node.")
|
safe_print(
|
||||||
|
f"First up, please choose a {color(AnsiFore.GREEN, 'name')} for your node."
|
||||||
|
)
|
||||||
safe_print(
|
safe_print(
|
||||||
"It should be a unique name that can be used to identify the device later."
|
"It should be a unique name that can be used to identify the device later."
|
||||||
)
|
)
|
||||||
sleep(1)
|
sleep(1)
|
||||||
safe_print(
|
safe_print(
|
||||||
f"For example, I like calling the node in my living room {color(Fore.BOLD_WHITE, 'livingroom')}."
|
f"For example, I like calling the node in my living room {color(AnsiFore.BOLD_WHITE, 'livingroom')}."
|
||||||
)
|
)
|
||||||
safe_print()
|
safe_print()
|
||||||
sleep(1)
|
sleep(1)
|
||||||
name = safe_input(color(Fore.BOLD_WHITE, "(name): "))
|
name = safe_input(color(AnsiFore.BOLD_WHITE, "(name): "))
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@ -317,7 +319,7 @@ def wizard(path):
|
|||||||
except vol.Invalid:
|
except vol.Invalid:
|
||||||
safe_print(
|
safe_print(
|
||||||
color(
|
color(
|
||||||
Fore.RED,
|
AnsiFore.RED,
|
||||||
f'Oh noes, "{name}" isn\'t a valid name. Names can only '
|
f'Oh noes, "{name}" isn\'t a valid name. Names can only '
|
||||||
f"include numbers, lower-case letters and hyphens. ",
|
f"include numbers, lower-case letters and hyphens. ",
|
||||||
)
|
)
|
||||||
@ -325,11 +327,13 @@ def wizard(path):
|
|||||||
name = strip_accents(name).lower().replace(" ", "-")
|
name = strip_accents(name).lower().replace(" ", "-")
|
||||||
name = strip_accents(name).lower().replace("_", "-")
|
name = strip_accents(name).lower().replace("_", "-")
|
||||||
name = "".join(c for c in name if c in ALLOWED_NAME_CHARS)
|
name = "".join(c for c in name if c in ALLOWED_NAME_CHARS)
|
||||||
safe_print(f'Shall I use "{color(Fore.CYAN, name)}" as the name instead?')
|
safe_print(
|
||||||
|
f'Shall I use "{color(AnsiFore.CYAN, name)}" as the name instead?'
|
||||||
|
)
|
||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
name = default_input("(name [{}]): ", name)
|
name = default_input("(name [{}]): ", name)
|
||||||
|
|
||||||
safe_print(f'Great! Your node is now called "{color(Fore.CYAN, name)}".')
|
safe_print(f'Great! Your node is now called "{color(AnsiFore.CYAN, name)}".')
|
||||||
sleep(1)
|
sleep(1)
|
||||||
safe_print_step(2, ESP_BIG)
|
safe_print_step(2, ESP_BIG)
|
||||||
safe_print(
|
safe_print(
|
||||||
@ -346,7 +350,7 @@ def wizard(path):
|
|||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
safe_print()
|
safe_print()
|
||||||
platform = safe_input(
|
platform = safe_input(
|
||||||
color(Fore.BOLD_WHITE, f"({'/'.join(wizard_platforms)}): ")
|
color(AnsiFore.BOLD_WHITE, f"({'/'.join(wizard_platforms)}): ")
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
platform = vol.All(vol.Upper, vol.Any(*wizard_platforms))(platform.upper())
|
platform = vol.All(vol.Upper, vol.Any(*wizard_platforms))(platform.upper())
|
||||||
@ -355,7 +359,9 @@ def wizard(path):
|
|||||||
safe_print(
|
safe_print(
|
||||||
f'Unfortunately, I can\'t find an espressif microcontroller called "{platform}". Please try again.'
|
f'Unfortunately, I can\'t find an espressif microcontroller called "{platform}". Please try again.'
|
||||||
)
|
)
|
||||||
safe_print(f"Thanks! You've chosen {color(Fore.CYAN, platform)} as your platform.")
|
safe_print(
|
||||||
|
f"Thanks! You've chosen {color(AnsiFore.CYAN, platform)} as your platform."
|
||||||
|
)
|
||||||
safe_print()
|
safe_print()
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
@ -376,27 +382,29 @@ def wizard(path):
|
|||||||
else:
|
else:
|
||||||
raise NotImplementedError("Unknown platform!")
|
raise NotImplementedError("Unknown platform!")
|
||||||
|
|
||||||
safe_print(f"Next, I need to know what {color(Fore.GREEN, 'board')} you're using.")
|
safe_print(
|
||||||
|
f"Next, I need to know what {color(AnsiFore.GREEN, 'board')} you're using."
|
||||||
|
)
|
||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
safe_print(f"Please go to {color(Fore.GREEN, board_link)} and choose a board.")
|
safe_print(f"Please go to {color(AnsiFore.GREEN, board_link)} and choose a board.")
|
||||||
if platform == "ESP32":
|
if platform == "ESP32":
|
||||||
safe_print(f"(Type {color(Fore.GREEN, 'esp01_1m')} for Sonoff devices)")
|
safe_print(f"(Type {color(AnsiFore.GREEN, 'esp01_1m')} for Sonoff devices)")
|
||||||
safe_print()
|
safe_print()
|
||||||
# Don't sleep because user needs to copy link
|
# Don't sleep because user needs to copy link
|
||||||
if platform == "ESP32":
|
if platform == "ESP32":
|
||||||
safe_print(f'For example "{color(Fore.BOLD_WHITE, "nodemcu-32s")}".')
|
safe_print(f'For example "{color(AnsiFore.BOLD_WHITE, "nodemcu-32s")}".')
|
||||||
boards_list = esp32_boards.BOARDS.items()
|
boards_list = esp32_boards.BOARDS.items()
|
||||||
elif platform == "ESP8266":
|
elif platform == "ESP8266":
|
||||||
safe_print(f'For example "{color(Fore.BOLD_WHITE, "nodemcuv2")}".')
|
safe_print(f'For example "{color(AnsiFore.BOLD_WHITE, "nodemcuv2")}".')
|
||||||
boards_list = esp8266_boards.BOARDS.items()
|
boards_list = esp8266_boards.BOARDS.items()
|
||||||
elif platform == "BK72XX":
|
elif platform == "BK72XX":
|
||||||
safe_print(f'For example "{color(Fore.BOLD_WHITE, "cb2s")}".')
|
safe_print(f'For example "{color(AnsiFore.BOLD_WHITE, "cb2s")}".')
|
||||||
boards_list = bk72xx_boards.BOARDS.items()
|
boards_list = bk72xx_boards.BOARDS.items()
|
||||||
elif platform == "RTL87XX":
|
elif platform == "RTL87XX":
|
||||||
safe_print(f'For example "{color(Fore.BOLD_WHITE, "wr3")}".')
|
safe_print(f'For example "{color(AnsiFore.BOLD_WHITE, "wr3")}".')
|
||||||
boards_list = rtl87xx_boards.BOARDS.items()
|
boards_list = rtl87xx_boards.BOARDS.items()
|
||||||
elif platform == "RP2040":
|
elif platform == "RP2040":
|
||||||
safe_print(f'For example "{color(Fore.BOLD_WHITE, "rpipicow")}".')
|
safe_print(f'For example "{color(AnsiFore.BOLD_WHITE, "rpipicow")}".')
|
||||||
boards_list = rp2040_boards.BOARDS.items()
|
boards_list = rp2040_boards.BOARDS.items()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -409,19 +417,21 @@ def wizard(path):
|
|||||||
boards.append(board_id)
|
boards.append(board_id)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
board = safe_input(color(Fore.BOLD_WHITE, "(board): "))
|
board = safe_input(color(AnsiFore.BOLD_WHITE, "(board): "))
|
||||||
try:
|
try:
|
||||||
board = vol.All(vol.Lower, vol.Any(*boards))(board)
|
board = vol.All(vol.Lower, vol.Any(*boards))(board)
|
||||||
break
|
break
|
||||||
except vol.Invalid:
|
except vol.Invalid:
|
||||||
safe_print(
|
safe_print(
|
||||||
color(Fore.RED, f'Sorry, I don\'t think the board "{board}" exists.')
|
color(
|
||||||
|
AnsiFore.RED, f'Sorry, I don\'t think the board "{board}" exists.'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
safe_print()
|
safe_print()
|
||||||
sleep(0.25)
|
sleep(0.25)
|
||||||
safe_print()
|
safe_print()
|
||||||
|
|
||||||
safe_print(f"Way to go! You've chosen {color(Fore.CYAN, board)} as your board.")
|
safe_print(f"Way to go! You've chosen {color(AnsiFore.CYAN, board)} as your board.")
|
||||||
safe_print()
|
safe_print()
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
@ -432,19 +442,19 @@ def wizard(path):
|
|||||||
safe_print()
|
safe_print()
|
||||||
sleep(1)
|
sleep(1)
|
||||||
safe_print(
|
safe_print(
|
||||||
f"First, what's the {color(Fore.GREEN, 'SSID')} (the name) of the WiFi network {name} should connect to?"
|
f"First, what's the {color(AnsiFore.GREEN, 'SSID')} (the name) of the WiFi network {name} should connect to?"
|
||||||
)
|
)
|
||||||
sleep(1.5)
|
sleep(1.5)
|
||||||
safe_print(f'For example "{color(Fore.BOLD_WHITE, "Abraham Linksys")}".')
|
safe_print(f'For example "{color(AnsiFore.BOLD_WHITE, "Abraham Linksys")}".')
|
||||||
while True:
|
while True:
|
||||||
ssid = safe_input(color(Fore.BOLD_WHITE, "(ssid): "))
|
ssid = safe_input(color(AnsiFore.BOLD_WHITE, "(ssid): "))
|
||||||
try:
|
try:
|
||||||
ssid = cv.ssid(ssid)
|
ssid = cv.ssid(ssid)
|
||||||
break
|
break
|
||||||
except vol.Invalid:
|
except vol.Invalid:
|
||||||
safe_print(
|
safe_print(
|
||||||
color(
|
color(
|
||||||
Fore.RED,
|
AnsiFore.RED,
|
||||||
f'Unfortunately, "{ssid}" doesn\'t seem to be a valid SSID. Please try again.',
|
f'Unfortunately, "{ssid}" doesn\'t seem to be a valid SSID. Please try again.',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -452,18 +462,18 @@ def wizard(path):
|
|||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
safe_print(
|
safe_print(
|
||||||
f'Thank you very much! You\'ve just chosen "{color(Fore.CYAN, ssid)}" as your SSID.'
|
f'Thank you very much! You\'ve just chosen "{color(AnsiFore.CYAN, ssid)}" as your SSID.'
|
||||||
)
|
)
|
||||||
safe_print()
|
safe_print()
|
||||||
sleep(0.75)
|
sleep(0.75)
|
||||||
|
|
||||||
safe_print(
|
safe_print(
|
||||||
f"Now please state the {color(Fore.GREEN, 'password')} of the WiFi network so that I can connect to it (Leave empty for no password)"
|
f"Now please state the {color(AnsiFore.GREEN, 'password')} of the WiFi network so that I can connect to it (Leave empty for no password)"
|
||||||
)
|
)
|
||||||
safe_print()
|
safe_print()
|
||||||
safe_print(f'For example "{color(Fore.BOLD_WHITE, "PASSWORD42")}"')
|
safe_print(f'For example "{color(AnsiFore.BOLD_WHITE, "PASSWORD42")}"')
|
||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
psk = safe_input(color(Fore.BOLD_WHITE, "(PSK): "))
|
psk = safe_input(color(AnsiFore.BOLD_WHITE, "(PSK): "))
|
||||||
safe_print(
|
safe_print(
|
||||||
"Perfect! WiFi is now set up (you can create static IPs and so on later)."
|
"Perfect! WiFi is now set up (you can create static IPs and so on later)."
|
||||||
)
|
)
|
||||||
@ -475,12 +485,12 @@ def wizard(path):
|
|||||||
"(over the air) and integrates into Home Assistant with a native API."
|
"(over the air) and integrates into Home Assistant with a native API."
|
||||||
)
|
)
|
||||||
safe_print(
|
safe_print(
|
||||||
f"This can be insecure if you do not trust the WiFi network. Do you want to set a {color(Fore.GREEN, 'password')} for connecting to this ESP?"
|
f"This can be insecure if you do not trust the WiFi network. Do you want to set a {color(AnsiFore.GREEN, 'password')} for connecting to this ESP?"
|
||||||
)
|
)
|
||||||
safe_print()
|
safe_print()
|
||||||
sleep(0.25)
|
sleep(0.25)
|
||||||
safe_print("Press ENTER for no password")
|
safe_print("Press ENTER for no password")
|
||||||
password = safe_input(color(Fore.BOLD_WHITE, "(password): "))
|
password = safe_input(color(AnsiFore.BOLD_WHITE, "(password): "))
|
||||||
else:
|
else:
|
||||||
ssid, password, psk = "", "", ""
|
ssid, password, psk = "", "", ""
|
||||||
|
|
||||||
@ -497,8 +507,8 @@ def wizard(path):
|
|||||||
|
|
||||||
safe_print()
|
safe_print()
|
||||||
safe_print(
|
safe_print(
|
||||||
color(Fore.CYAN, "DONE! I've now written a new configuration file to ")
|
color(AnsiFore.CYAN, "DONE! I've now written a new configuration file to ")
|
||||||
+ color(Fore.BOLD_CYAN, path)
|
+ color(AnsiFore.BOLD_CYAN, path)
|
||||||
)
|
)
|
||||||
safe_print()
|
safe_print()
|
||||||
safe_print("Next steps:")
|
safe_print("Next steps:")
|
||||||
|
@ -64,7 +64,7 @@ lib_deps =
|
|||||||
heman/AsyncMqttClient-esphome@1.0.0 ; mqtt
|
heman/AsyncMqttClient-esphome@1.0.0 ; mqtt
|
||||||
esphome/ESPAsyncWebServer-esphome@3.3.0 ; web_server_base
|
esphome/ESPAsyncWebServer-esphome@3.3.0 ; web_server_base
|
||||||
fastled/FastLED@3.9.16 ; fastled_base
|
fastled/FastLED@3.9.16 ; fastled_base
|
||||||
mikalhart/TinyGPSPlus@1.0.2 ; gps
|
mikalhart/TinyGPSPlus@1.1.0 ; gps
|
||||||
freekode/TM1651@1.0.1 ; tm1651
|
freekode/TM1651@1.0.1 ; tm1651
|
||||||
glmnet/Dsmr@0.7 ; dsmr
|
glmnet/Dsmr@0.7 ; dsmr
|
||||||
rweather/Crypto@0.4.0 ; dsmr
|
rweather/Crypto@0.4.0 ; dsmr
|
||||||
|
@ -6,6 +6,20 @@ uart:
|
|||||||
parity: EVEN
|
parity: EVEN
|
||||||
|
|
||||||
gps:
|
gps:
|
||||||
|
latitude:
|
||||||
|
name: "Latitude"
|
||||||
|
longitude:
|
||||||
|
name: "Longitude"
|
||||||
|
altitude:
|
||||||
|
name: "Altitude"
|
||||||
|
speed:
|
||||||
|
name: "Speed"
|
||||||
|
course:
|
||||||
|
name: "Course"
|
||||||
|
satellites:
|
||||||
|
name: "Satellites"
|
||||||
|
hdop:
|
||||||
|
name: "HDOP"
|
||||||
|
|
||||||
time:
|
time:
|
||||||
- platform: gps
|
- platform: gps
|
||||||
|
Loading…
x
Reference in New Issue
Block a user