Compare commits

..

3 Commits

Author SHA1 Message Date
J. Nick Koston
d0cc602979 [time] Use lazy callback for time sync to save 8 bytes 2026-01-30 16:02:20 -06:00
J0k3r2k1
5e3561d60b [mipi_spi] Fix log_pin() FlashStringHelper compatibility (#13624)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-30 14:33:45 -06:00
Thomas Rupprecht
ca9ed369f9 [pmsx003] support device-types PMS1003, PMS3003, PMS9003M (#13640) 2026-01-30 14:59:47 -05:00
12 changed files with 208 additions and 139 deletions

View File

@@ -196,14 +196,11 @@ bool Esp32HostedUpdate::fetch_manifest_() {
int read_or_error = container->read(buf, sizeof(buf));
App.feed_wdt();
yield();
auto result =
http_request::http_read_loop_result(read_or_error, last_data_time, read_timeout, container->is_read_complete());
auto result = http_request::http_read_loop_result(read_or_error, last_data_time, read_timeout);
if (result == http_request::HttpReadLoopResult::RETRY)
continue;
// Note: COMPLETE is currently unreachable since the loop condition checks bytes_read < content_length,
// but this is defensive code in case chunked transfer encoding support is added in the future.
if (result != http_request::HttpReadLoopResult::DATA)
break; // COMPLETE, ERROR, or TIMEOUT
break; // ERROR or TIMEOUT
json_str.append(reinterpret_cast<char *>(buf), read_or_error);
}
container->end();
@@ -324,14 +321,9 @@ bool Esp32HostedUpdate::stream_firmware_to_coprocessor_() {
App.feed_wdt();
yield();
auto result =
http_request::http_read_loop_result(read_or_error, last_data_time, read_timeout, container->is_read_complete());
auto result = http_request::http_read_loop_result(read_or_error, last_data_time, read_timeout);
if (result == http_request::HttpReadLoopResult::RETRY)
continue;
// Note: COMPLETE is currently unreachable since the loop condition checks bytes_read < content_length,
// but this is defensive code in case chunked transfer encoding support is added in the future.
if (result == http_request::HttpReadLoopResult::COMPLETE)
break;
if (result != http_request::HttpReadLoopResult::DATA) {
if (result == http_request::HttpReadLoopResult::TIMEOUT) {
ESP_LOGE(TAG, "Timeout reading firmware data");

View File

@@ -26,7 +26,6 @@ struct Header {
enum HttpStatus {
HTTP_STATUS_OK = 200,
HTTP_STATUS_NO_CONTENT = 204,
HTTP_STATUS_RESET_CONTENT = 205,
HTTP_STATUS_PARTIAL_CONTENT = 206,
/* 3xx - Redirection */
@@ -127,21 +126,19 @@ struct HttpReadResult {
/// Result of processing a non-blocking read with timeout (for manual loops)
enum class HttpReadLoopResult : uint8_t {
DATA, ///< Data was read, process it
COMPLETE, ///< All content has been read, caller should exit loop
RETRY, ///< No data yet, already delayed, caller should continue loop
ERROR, ///< Read error, caller should exit loop
TIMEOUT, ///< Timeout waiting for data, caller should exit loop
DATA, ///< Data was read, process it
RETRY, ///< No data yet, already delayed, caller should continue loop
ERROR, ///< Read error, caller should exit loop
TIMEOUT, ///< Timeout waiting for data, caller should exit loop
};
/// Process a read result with timeout tracking and delay handling
/// @param bytes_read_or_error Return value from read() - positive for bytes read, negative for error
/// @param last_data_time Time of last successful read, updated when data received
/// @param timeout_ms Maximum time to wait for data
/// @param is_read_complete Whether all expected content has been read (from HttpContainer::is_read_complete())
/// @return How the caller should proceed - see HttpReadLoopResult enum
inline HttpReadLoopResult http_read_loop_result(int bytes_read_or_error, uint32_t &last_data_time, uint32_t timeout_ms,
bool is_read_complete) {
/// @return DATA if data received, RETRY if should continue loop, ERROR/TIMEOUT if should exit
inline HttpReadLoopResult http_read_loop_result(int bytes_read_or_error, uint32_t &last_data_time,
uint32_t timeout_ms) {
if (bytes_read_or_error > 0) {
last_data_time = millis();
return HttpReadLoopResult::DATA;
@@ -149,10 +146,7 @@ inline HttpReadLoopResult http_read_loop_result(int bytes_read_or_error, uint32_
if (bytes_read_or_error < 0) {
return HttpReadLoopResult::ERROR;
}
// bytes_read_or_error == 0: either "no data yet" or "all content read"
if (is_read_complete) {
return HttpReadLoopResult::COMPLETE;
}
// bytes_read_or_error == 0: no data available yet
if (millis() - last_data_time >= timeout_ms) {
return HttpReadLoopResult::TIMEOUT;
}
@@ -165,9 +159,9 @@ class HttpRequestComponent;
class HttpContainer : public Parented<HttpRequestComponent> {
public:
virtual ~HttpContainer() = default;
size_t content_length{0};
int status_code{-1}; ///< -1 indicates no response received yet
uint32_t duration_ms{0};
size_t content_length;
int status_code;
uint32_t duration_ms;
/**
* @brief Read data from the HTTP response body.
@@ -200,24 +194,9 @@ class HttpContainer : public Parented<HttpRequestComponent> {
virtual void end() = 0;
void set_secure(bool secure) { this->secure_ = secure; }
void set_chunked(bool chunked) { this->is_chunked_ = chunked; }
size_t get_bytes_read() const { return this->bytes_read_; }
/// Check if all expected content has been read
/// For chunked responses, returns false (completion detected via read() returning error/EOF)
bool is_read_complete() const {
// Per RFC 9112, these responses have no body:
// - 1xx (Informational), 204 No Content, 205 Reset Content, 304 Not Modified
if ((this->status_code >= 100 && this->status_code < 200) || this->status_code == HTTP_STATUS_NO_CONTENT ||
this->status_code == HTTP_STATUS_RESET_CONTENT || this->status_code == HTTP_STATUS_NOT_MODIFIED) {
return true;
}
// For non-chunked responses, complete when bytes_read >= content_length
// This handles both Content-Length: 0 and Content-Length: N cases
return !this->is_chunked_ && this->bytes_read_ >= this->content_length;
}
/**
* @brief Get response headers.
*
@@ -230,7 +209,6 @@ class HttpContainer : public Parented<HttpRequestComponent> {
protected:
size_t bytes_read_{0};
bool secure_{false};
bool is_chunked_{false}; ///< True if response uses chunked transfer encoding
std::map<std::string, std::list<std::string>> response_headers_{};
};
@@ -241,7 +219,7 @@ class HttpContainer : public Parented<HttpRequestComponent> {
/// @param total_size Total bytes to read
/// @param chunk_size Maximum bytes per read call
/// @param timeout_ms Read timeout in milliseconds
/// @return HttpReadResult with status and error_code on failure; use container->get_bytes_read() for total bytes read
/// @return HttpReadResult with status and error_code on failure
inline HttpReadResult http_read_fully(HttpContainer *container, uint8_t *buffer, size_t total_size, size_t chunk_size,
uint32_t timeout_ms) {
size_t read_index = 0;
@@ -253,11 +231,9 @@ inline HttpReadResult http_read_fully(HttpContainer *container, uint8_t *buffer,
App.feed_wdt();
yield();
auto result = http_read_loop_result(read_bytes_or_error, last_data_time, timeout_ms, container->is_read_complete());
auto result = http_read_loop_result(read_bytes_or_error, last_data_time, timeout_ms);
if (result == HttpReadLoopResult::RETRY)
continue;
if (result == HttpReadLoopResult::COMPLETE)
break; // Server sent less data than requested, but transfer is complete
if (result == HttpReadLoopResult::ERROR)
return {HttpReadStatus::ERROR, read_bytes_or_error};
if (result == HttpReadLoopResult::TIMEOUT)
@@ -417,12 +393,11 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
int read_or_error = container->read(buf + read_index, std::min<size_t>(max_length - read_index, 512));
App.feed_wdt();
yield();
auto result =
http_read_loop_result(read_or_error, last_data_time, read_timeout, container->is_read_complete());
auto result = http_read_loop_result(read_or_error, last_data_time, read_timeout);
if (result == HttpReadLoopResult::RETRY)
continue;
if (result != HttpReadLoopResult::DATA)
break; // COMPLETE, ERROR, or TIMEOUT
break; // ERROR or TIMEOUT
read_index += read_or_error;
}
response_body.reserve(read_index);

View File

@@ -135,23 +135,9 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
// When cast to size_t, -1 becomes SIZE_MAX (4294967295 on 32-bit).
// The read() method handles this: bytes_read_ can never reach SIZE_MAX, so the
// early return check (bytes_read_ >= content_length) will never trigger.
//
// TODO: Chunked transfer encoding is NOT properly supported on Arduino.
// The implementation in #7884 was incomplete - it only works correctly on ESP-IDF where
// esp_http_client_read() decodes chunks internally. On Arduino, using getStreamPtr()
// returns raw TCP data with chunk framing (e.g., "12a\r\n{json}\r\n0\r\n\r\n") instead
// of decoded content. This wasn't noticed because requests would complete and payloads
// were only examined on IDF. The long transfer times were also masked by the misleading
// "HTTP on Arduino version >= 3.1 is **very** slow" warning above. This causes two issues:
// 1. Response body is corrupted - contains chunk size headers mixed with data
// 2. Cannot detect end of transfer - connection stays open (keep-alive), causing timeout
// The proper fix would be to use getString() for chunked responses, which decodes chunks
// internally, but this buffers the entire response in memory.
int content_length = container->client_.getSize();
ESP_LOGD(TAG, "Content-Length: %d", content_length);
container->content_length = (size_t) content_length;
// -1 (SIZE_MAX when cast to size_t) means chunked transfer encoding
container->set_chunked(content_length == -1);
container->duration_ms = millis() - start;
return container;
@@ -192,9 +178,9 @@ int HttpContainerArduino::read(uint8_t *buf, size_t max_len) {
if (bufsize == 0) {
this->duration_ms += (millis() - start);
// Check if we've read all expected content (non-chunked only)
// For chunked encoding (content_length == SIZE_MAX), is_read_complete() returns false
if (this->is_read_complete()) {
// Check if we've read all expected content (only valid when content_length is known and not SIZE_MAX)
// For chunked encoding (content_length == SIZE_MAX), we can't use this check
if (this->content_length > 0 && this->bytes_read_ >= this->content_length) {
return 0; // All content read successfully
}
// No data available - check if connection is still open

View File

@@ -160,7 +160,6 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
// esp_http_client_fetch_headers() returns 0 for chunked transfer encoding (no Content-Length header).
// The read() method handles content_length == 0 specially to support chunked responses.
container->content_length = esp_http_client_fetch_headers(client);
container->set_chunked(esp_http_client_is_chunked_response(client));
container->feed_wdt();
container->status_code = esp_http_client_get_status_code(client);
container->feed_wdt();
@@ -196,7 +195,6 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
container->feed_wdt();
container->content_length = esp_http_client_fetch_headers(client);
container->set_chunked(esp_http_client_is_chunked_response(client));
container->feed_wdt();
container->status_code = esp_http_client_get_status_code(client);
container->feed_wdt();
@@ -241,9 +239,10 @@ int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
const uint32_t start = millis();
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
// Check if we've already read all expected content (non-chunked only)
// For chunked responses (content_length == 0), esp_http_client_read() handles EOF
if (this->is_read_complete()) {
// Check if we've already read all expected content
// Skip this check when content_length is 0 (chunked transfer encoding or unknown length)
// For chunked responses, esp_http_client_read() will return 0 when all data is received
if (this->content_length > 0 && this->bytes_read_ >= this->content_length) {
return 0; // All content read successfully
}

View File

@@ -130,13 +130,9 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
App.feed_wdt();
yield();
auto result = http_read_loop_result(bufsize_or_error, last_data_time, read_timeout, container->is_read_complete());
auto result = http_read_loop_result(bufsize_or_error, last_data_time, read_timeout);
if (result == HttpReadLoopResult::RETRY)
continue;
// Note: COMPLETE is currently unreachable since the loop condition checks bytes_read < content_length,
// but this is defensive code in case chunked transfer encoding support is added for OTA in the future.
if (result == HttpReadLoopResult::COMPLETE)
break;
if (result != HttpReadLoopResult::DATA) {
if (result == HttpReadLoopResult::TIMEOUT) {
ESP_LOGE(TAG, "Timeout reading data");

View File

@@ -1,6 +1,39 @@
#include "mipi_spi.h"
#include "esphome/core/log.h"
namespace esphome {
namespace mipi_spi {} // namespace mipi_spi
} // namespace esphome
namespace esphome::mipi_spi {
void internal_dump_config(const char *model, int width, int height, int offset_width, int offset_height, uint8_t madctl,
bool invert_colors, int display_bits, bool is_big_endian, const optional<uint8_t> &brightness,
GPIOPin *cs, GPIOPin *reset, GPIOPin *dc, int spi_mode, uint32_t data_rate, int bus_width) {
ESP_LOGCONFIG(TAG,
"MIPI_SPI Display\n"
" Model: %s\n"
" Width: %d\n"
" Height: %d\n"
" Swap X/Y: %s\n"
" Mirror X: %s\n"
" Mirror Y: %s\n"
" Invert colors: %s\n"
" Color order: %s\n"
" Display pixels: %d bits\n"
" Endianness: %s\n"
" SPI Mode: %d\n"
" SPI Data rate: %uMHz\n"
" SPI Bus width: %d",
model, width, height, YESNO(madctl & MADCTL_MV), YESNO(madctl & (MADCTL_MX | MADCTL_XFLIP)),
YESNO(madctl & (MADCTL_MY | MADCTL_YFLIP)), YESNO(invert_colors), (madctl & MADCTL_BGR) ? "BGR" : "RGB",
display_bits, is_big_endian ? "Big" : "Little", spi_mode, static_cast<unsigned>(data_rate / 1000000),
bus_width);
LOG_PIN(" CS Pin: ", cs);
LOG_PIN(" Reset Pin: ", reset);
LOG_PIN(" DC Pin: ", dc);
if (offset_width != 0)
ESP_LOGCONFIG(TAG, " Offset width: %d", offset_width);
if (offset_height != 0)
ESP_LOGCONFIG(TAG, " Offset height: %d", offset_height);
if (brightness.has_value())
ESP_LOGCONFIG(TAG, " Brightness: %u", brightness.value());
}
} // namespace esphome::mipi_spi

View File

@@ -63,6 +63,11 @@ enum BusType {
BUS_TYPE_SINGLE_16 = 16, // Single bit bus, but 16 bits per transfer
};
// Helper function for dump_config - defined in mipi_spi.cpp to allow use of LOG_PIN macro
void internal_dump_config(const char *model, int width, int height, int offset_width, int offset_height, uint8_t madctl,
bool invert_colors, int display_bits, bool is_big_endian, const optional<uint8_t> &brightness,
GPIOPin *cs, GPIOPin *reset, GPIOPin *dc, int spi_mode, uint32_t data_rate, int bus_width);
/**
* Base class for MIPI SPI displays.
* All the methods are defined here in the header file, as it is not possible to define templated methods in a cpp file.
@@ -201,37 +206,9 @@ class MipiSpi : public display::Display,
}
void dump_config() override {
esph_log_config(TAG,
"MIPI_SPI Display\n"
" Model: %s\n"
" Width: %u\n"
" Height: %u",
this->model_, WIDTH, HEIGHT);
if constexpr (OFFSET_WIDTH != 0)
esph_log_config(TAG, " Offset width: %u", OFFSET_WIDTH);
if constexpr (OFFSET_HEIGHT != 0)
esph_log_config(TAG, " Offset height: %u", OFFSET_HEIGHT);
esph_log_config(TAG,
" Swap X/Y: %s\n"
" Mirror X: %s\n"
" Mirror Y: %s\n"
" Invert colors: %s\n"
" Color order: %s\n"
" Display pixels: %d bits\n"
" Endianness: %s\n",
YESNO(this->madctl_ & MADCTL_MV), YESNO(this->madctl_ & (MADCTL_MX | MADCTL_XFLIP)),
YESNO(this->madctl_ & (MADCTL_MY | MADCTL_YFLIP)), YESNO(this->invert_colors_),
this->madctl_ & MADCTL_BGR ? "BGR" : "RGB", DISPLAYPIXEL * 8, IS_BIG_ENDIAN ? "Big" : "Little");
if (this->brightness_.has_value())
esph_log_config(TAG, " Brightness: %u", this->brightness_.value());
log_pin(TAG, " CS Pin: ", this->cs_);
log_pin(TAG, " Reset Pin: ", this->reset_pin_);
log_pin(TAG, " DC Pin: ", this->dc_pin_);
esph_log_config(TAG,
" SPI Mode: %d\n"
" SPI Data rate: %dMHz\n"
" SPI Bus width: %d",
this->mode_, static_cast<unsigned>(this->data_rate_ / 1000000), BUS_TYPE);
internal_dump_config(this->model_, WIDTH, HEIGHT, OFFSET_WIDTH, OFFSET_HEIGHT, this->madctl_, this->invert_colors_,
DISPLAYPIXEL * 8, IS_BIG_ENDIAN, this->brightness_, this->cs_, this->reset_pin_, this->dc_pin_,
this->mode_, this->data_rate_, BUS_TYPE);
}
protected:

View File

@@ -181,17 +181,20 @@ optional<bool> PMSX003Component::check_byte_() {
bool PMSX003Component::check_payload_length_(uint16_t payload_length) {
// https://avaldebe.github.io/PyPMS/sensors/Plantower/
switch (this->type_) {
case Type::PMSX003:
// The expected payload length is typically 28 bytes.
// However, a 20-byte payload check was already present in the code.
// No official documentation was found confirming this.
// Retaining this check to avoid breaking existing behavior.
case Type::PMS1003:
return payload_length == 28; // 2*13+2
case Type::PMS3003: // Data 7/8/9 not set/reserved
return payload_length == 20; // 2*9+2
case Type::PMSX003: // Data 13 not set/reserved
// Deprecated: Length 20 is for PMS3003 backwards compatibility
return payload_length == 28 || payload_length == 20; // 2*13+2
case Type::PMS5003S:
case Type::PMS5003T:
return payload_length == 28; // 2*13+2 (Data 13 not set/reserved)
case Type::PMS5003ST:
return payload_length == 36; // 2*17+2 (Data 16 not set/reserved)
case Type::PMS5003T: // Data 13 not set/reserved
return payload_length == 28; // 2*13+2
case Type::PMS5003ST: // Data 16 not set/reserved
return payload_length == 36; // 2*17+2
case Type::PMS9003M:
return payload_length == 28; // 2*13+2
}
return false;
}
@@ -314,9 +317,10 @@ void PMSX003Component::parse_data_() {
}
// Firmware Version and Error Code
if (this->type_ == Type::PMS5003ST) {
const uint8_t firmware_version = this->data_[36];
const uint8_t error_code = this->data_[37];
if (this->type_ == Type::PMS1003 || this->type_ == Type::PMS5003ST || this->type_ == Type::PMS9003M) {
const uint8_t firmware_error_code_offset = (this->type_ == Type::PMS5003ST) ? 36 : 28;
const uint8_t firmware_version = this->data_[firmware_error_code_offset];
const uint8_t error_code = this->data_[firmware_error_code_offset + 1];
ESP_LOGD(TAG, "Got Firmware Version: 0x%02X, Error Code: 0x%02X", firmware_version, error_code);
}

View File

@@ -8,10 +8,13 @@
namespace esphome::pmsx003 {
enum class Type : uint8_t {
PMSX003 = 0,
PMS1003 = 0,
PMS3003,
PMSX003, // PMS5003, PMS6003, PMS7003, PMSA003 (NOT PMSA003I - see `pmsa003i` component)
PMS5003S,
PMS5003T,
PMS5003ST,
PMS9003M,
};
enum class Command : uint8_t {

View File

@@ -40,33 +40,127 @@ pmsx003_ns = cg.esphome_ns.namespace("pmsx003")
PMSX003Component = pmsx003_ns.class_("PMSX003Component", uart.UARTDevice, cg.Component)
PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor)
TYPE_PMSX003 = "PMSX003"
TYPE_PMS1003 = "PMS1003"
TYPE_PMS3003 = "PMS3003"
TYPE_PMSX003 = "PMSX003" # PMS5003, PMS6003, PMS7003, PMSA003 (NOT PMSA003I - see `pmsa003i` component)
TYPE_PMS5003S = "PMS5003S"
TYPE_PMS5003T = "PMS5003T"
TYPE_PMS5003ST = "PMS5003ST"
TYPE_PMS9003M = "PMS9003M"
Type = pmsx003_ns.enum("Type", is_class=True)
PMSX003_TYPES = {
TYPE_PMS1003: Type.PMS1003,
TYPE_PMS3003: Type.PMS3003,
TYPE_PMSX003: Type.PMSX003,
TYPE_PMS5003S: Type.PMS5003S,
TYPE_PMS5003T: Type.PMS5003T,
TYPE_PMS5003ST: Type.PMS5003ST,
TYPE_PMS9003M: Type.PMS9003M,
}
SENSORS_TO_TYPE = {
CONF_PM_1_0_STD: [TYPE_PMSX003, TYPE_PMS5003S, TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_PM_2_5_STD: [TYPE_PMSX003, TYPE_PMS5003S, TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_PM_10_0_STD: [TYPE_PMSX003, TYPE_PMS5003S, TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_PM_1_0: [TYPE_PMSX003, TYPE_PMS5003S, TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003S, TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003S, TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_PM_0_3UM: [TYPE_PMSX003, TYPE_PMS5003S, TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_PM_0_5UM: [TYPE_PMSX003, TYPE_PMS5003S, TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_PM_1_0UM: [TYPE_PMSX003, TYPE_PMS5003S, TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_PM_2_5UM: [TYPE_PMSX003, TYPE_PMS5003S, TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_PM_5_0UM: [TYPE_PMSX003, TYPE_PMS5003S, TYPE_PMS5003ST],
CONF_PM_10_0UM: [TYPE_PMSX003, TYPE_PMS5003S, TYPE_PMS5003ST],
CONF_PM_1_0_STD: [
TYPE_PMS1003,
TYPE_PMS3003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_2_5_STD: [
TYPE_PMS1003,
TYPE_PMS3003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_10_0_STD: [
TYPE_PMS1003,
TYPE_PMS3003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_1_0: [
TYPE_PMS1003,
TYPE_PMS3003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_2_5: [
TYPE_PMS1003,
TYPE_PMS3003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_10_0: [
TYPE_PMS1003,
TYPE_PMS3003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_0_3UM: [
TYPE_PMS1003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_0_5UM: [
TYPE_PMS1003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_1_0UM: [
TYPE_PMS1003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_2_5UM: [
TYPE_PMS1003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003T,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_5_0UM: [
TYPE_PMS1003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_PM_10_0UM: [
TYPE_PMS1003,
TYPE_PMSX003,
TYPE_PMS5003S,
TYPE_PMS5003ST,
TYPE_PMS9003M,
],
CONF_FORMALDEHYDE: [TYPE_PMS5003S, TYPE_PMS5003ST],
CONF_TEMPERATURE: [TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_HUMIDITY: [TYPE_PMS5003T, TYPE_PMS5003ST],

View File

@@ -62,7 +62,7 @@ class RealTimeClock : public PollingComponent {
void apply_timezone_();
#endif
CallbackManager<void()> time_sync_callback_;
LazyCallbackManager<void()> time_sync_callback_;
};
template<typename... Ts> class TimeHasTimeCondition : public Condition<Ts...> {

View File

@@ -0,0 +1,10 @@
substitutions:
dc_pin: GPIO15
cs_pin: GPIO5
enable_pin: GPIO4
reset_pin: GPIO16
packages:
spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml
<<: !include common.yaml