mirror of
https://github.com/esphome/esphome.git
synced 2026-02-01 09:17:34 -07:00
Compare commits
29 Commits
esp32_ard_
...
progmem_st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2f5dbc77b | ||
|
|
8506b9b330 | ||
|
|
f78b6dd8c3 | ||
|
|
bd29e870ce | ||
|
|
da2b8aecf1 | ||
|
|
d00bf3f49d | ||
|
|
534584ab31 | ||
|
|
c69e6e4363 | ||
|
|
1f4be6512f | ||
|
|
dcc8f50750 | ||
|
|
b298837276 | ||
|
|
726c5daa74 | ||
|
|
8b3f020dba | ||
|
|
49b652ed89 | ||
|
|
a60dea1d83 | ||
|
|
8556ae7209 | ||
|
|
69be581346 | ||
|
|
e23d295e8b | ||
|
|
3b5c4c2416 | ||
|
|
2d1fbe0736 | ||
|
|
1b6ae4348b | ||
|
|
67febb13c0 | ||
|
|
e46de0c40a | ||
|
|
5e3561d60b | ||
|
|
ca9ed369f9 | ||
|
|
4e96b20b46 | ||
|
|
a1a60c44da | ||
|
|
898c8a5836 | ||
|
|
20edd11ca7 |
@@ -1 +1 @@
|
||||
cf3d341206b4184ec8b7fe85141aef4fe4696aa720c3f8a06d4e57930574bdab
|
||||
069fa9526c52f7c580a9ec17c7678d12f142221387e9b561c18f95394d4629a3
|
||||
|
||||
@@ -22,17 +22,11 @@ const LogString *cover_command_to_str(float pos) {
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
// Cover operation strings indexed by CoverOperation enum (0-2): IDLE, OPENING, CLOSING, plus UNKNOWN
|
||||
PROGMEM_STRING_TABLE(CoverOperationStrings, "IDLE", "OPENING", "CLOSING", "UNKNOWN");
|
||||
|
||||
const LogString *cover_operation_to_str(CoverOperation op) {
|
||||
switch (op) {
|
||||
case COVER_OPERATION_IDLE:
|
||||
return LOG_STR("IDLE");
|
||||
case COVER_OPERATION_OPENING:
|
||||
return LOG_STR("OPENING");
|
||||
case COVER_OPERATION_CLOSING:
|
||||
return LOG_STR("CLOSING");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
return CoverOperationStrings::get_log_str(static_cast<uint8_t>(op), CoverOperationStrings::LAST_INDEX);
|
||||
}
|
||||
|
||||
Cover::Cover() : position{COVER_OPEN} {}
|
||||
|
||||
@@ -9,32 +9,19 @@ namespace esphome::light {
|
||||
|
||||
// See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema
|
||||
|
||||
// Get JSON string for color mode.
|
||||
// ColorMode enum values are sparse bitmasks (0, 1, 3, 7, 11, 19, 35, 39, 47, 51) which would
|
||||
// generate a large jump table. Converting to bit index (0-9) allows a compact switch.
|
||||
// Color mode JSON strings - packed into flash with compile-time generated offsets.
|
||||
// Indexed by ColorModeBitPolicy bit index (1-9), so index 0 maps to bit 1 ("onoff").
|
||||
PROGMEM_STRING_TABLE(ColorModeStrings, "onoff", "brightness", "white", "color_temp", "cwww", "rgb", "rgbw", "rgbct",
|
||||
"rgbww");
|
||||
|
||||
// Get JSON string for color mode. Returns nullptr for UNKNOWN (bit 0).
|
||||
// Returns ProgmemStr so ArduinoJson knows to handle PROGMEM strings on ESP8266.
|
||||
static ProgmemStr get_color_mode_json_str(ColorMode mode) {
|
||||
switch (ColorModeBitPolicy::to_bit(mode)) {
|
||||
case 1:
|
||||
return ESPHOME_F("onoff");
|
||||
case 2:
|
||||
return ESPHOME_F("brightness");
|
||||
case 3:
|
||||
return ESPHOME_F("white");
|
||||
case 4:
|
||||
return ESPHOME_F("color_temp");
|
||||
case 5:
|
||||
return ESPHOME_F("cwww");
|
||||
case 6:
|
||||
return ESPHOME_F("rgb");
|
||||
case 7:
|
||||
return ESPHOME_F("rgbw");
|
||||
case 8:
|
||||
return ESPHOME_F("rgbct");
|
||||
case 9:
|
||||
return ESPHOME_F("rgbww");
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
unsigned bit = ColorModeBitPolicy::to_bit(mode);
|
||||
if (bit == 0)
|
||||
return nullptr;
|
||||
// bit is 1-9 for valid modes, so bit-1 is always valid (0-8). LAST_INDEX fallback never used.
|
||||
return ColorModeStrings::get_progmem_str(bit - 1, ColorModeStrings::LAST_INDEX);
|
||||
}
|
||||
|
||||
void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
@@ -291,34 +292,20 @@ UARTSelection Logger::get_uart() const { return this->uart_; }
|
||||
|
||||
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
|
||||
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
// ESP8266: PSTR() cannot be used in array initializers, so we need to declare
|
||||
// each string separately as a global constant first
|
||||
static const char LOG_LEVEL_NONE[] PROGMEM = "NONE";
|
||||
static const char LOG_LEVEL_ERROR[] PROGMEM = "ERROR";
|
||||
static const char LOG_LEVEL_WARN[] PROGMEM = "WARN";
|
||||
static const char LOG_LEVEL_INFO[] PROGMEM = "INFO";
|
||||
static const char LOG_LEVEL_CONFIG[] PROGMEM = "CONFIG";
|
||||
static const char LOG_LEVEL_DEBUG[] PROGMEM = "DEBUG";
|
||||
static const char LOG_LEVEL_VERBOSE[] PROGMEM = "VERBOSE";
|
||||
static const char LOG_LEVEL_VERY_VERBOSE[] PROGMEM = "VERY_VERBOSE";
|
||||
// Log level strings - packed into flash on ESP8266, indexed by log level (0-7)
|
||||
PROGMEM_STRING_TABLE(LogLevelStrings, "NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE");
|
||||
|
||||
static const LogString *const LOG_LEVELS[] = {
|
||||
reinterpret_cast<const LogString *>(LOG_LEVEL_NONE), reinterpret_cast<const LogString *>(LOG_LEVEL_ERROR),
|
||||
reinterpret_cast<const LogString *>(LOG_LEVEL_WARN), reinterpret_cast<const LogString *>(LOG_LEVEL_INFO),
|
||||
reinterpret_cast<const LogString *>(LOG_LEVEL_CONFIG), reinterpret_cast<const LogString *>(LOG_LEVEL_DEBUG),
|
||||
reinterpret_cast<const LogString *>(LOG_LEVEL_VERBOSE), reinterpret_cast<const LogString *>(LOG_LEVEL_VERY_VERBOSE),
|
||||
};
|
||||
#else
|
||||
static const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
|
||||
#endif
|
||||
static const LogString *get_log_level_str(uint8_t level) {
|
||||
return LogLevelStrings::get_log_str(level, LogLevelStrings::LAST_INDEX);
|
||||
}
|
||||
|
||||
void Logger::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Logger:\n"
|
||||
" Max Level: %s\n"
|
||||
" Initial Level: %s",
|
||||
LOG_STR_ARG(LOG_LEVELS[ESPHOME_LOG_LEVEL]), LOG_STR_ARG(LOG_LEVELS[this->current_level_]));
|
||||
LOG_STR_ARG(get_log_level_str(ESPHOME_LOG_LEVEL)),
|
||||
LOG_STR_ARG(get_log_level_str(this->current_level_)));
|
||||
#ifndef USE_HOST
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Log Baud Rate: %" PRIu32 "\n"
|
||||
@@ -337,7 +324,7 @@ void Logger::dump_config() {
|
||||
|
||||
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
|
||||
for (auto &it : this->log_levels_) {
|
||||
ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.first, LOG_STR_ARG(LOG_LEVELS[it.second]));
|
||||
ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.first, LOG_STR_ARG(get_log_level_str(it.second)));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -345,7 +332,8 @@ void Logger::dump_config() {
|
||||
void Logger::set_log_level(uint8_t level) {
|
||||
if (level > ESPHOME_LOG_LEVEL) {
|
||||
level = ESPHOME_LOG_LEVEL;
|
||||
ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s", LOG_STR_ARG(LOG_LEVELS[ESPHOME_LOG_LEVEL]));
|
||||
ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s",
|
||||
LOG_STR_ARG(get_log_level_str(ESPHOME_LOG_LEVEL)));
|
||||
}
|
||||
this->current_level_ = level;
|
||||
#ifdef USE_LOGGER_LEVEL_LISTENERS
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -643,10 +643,34 @@ static bool topic_match(const char *message, const char *subscription) {
|
||||
}
|
||||
|
||||
void MQTTClientComponent::on_message(const std::string &topic, const std::string &payload) {
|
||||
for (auto &subscription : this->subscriptions_) {
|
||||
if (topic_match(topic.c_str(), subscription.topic.c_str()))
|
||||
subscription.callback(topic, payload);
|
||||
}
|
||||
#ifdef USE_ESP8266
|
||||
// IMPORTANT: This defer is REQUIRED to prevent stack overflow crashes on ESP8266.
|
||||
//
|
||||
// On ESP8266, this callback is invoked directly from the lwIP/AsyncTCP network stack
|
||||
// which runs in the "sys" context with a very limited stack (~4KB). By the time we
|
||||
// reach this function, the stack is already partially consumed by the network
|
||||
// processing chain: tcp_input -> AsyncClient::_recv -> AsyncMqttClient::_onMessage -> here.
|
||||
//
|
||||
// MQTT subscription callbacks can trigger arbitrary user actions (automations, HTTP
|
||||
// requests, sensor updates, etc.) which may have deep call stacks of their own.
|
||||
// For example, an HTTP request action requires: DNS lookup -> TCP connect -> TLS
|
||||
// handshake (if HTTPS) -> request formatting. This easily overflows the remaining
|
||||
// system stack space, causing a LoadStoreAlignmentCause exception or silent corruption.
|
||||
//
|
||||
// By deferring to the main loop, we ensure callbacks execute with a fresh, full-size
|
||||
// stack in the normal application context rather than the constrained network task.
|
||||
//
|
||||
// DO NOT REMOVE THIS DEFER without understanding the above. It may appear to work
|
||||
// in simple tests but will cause crashes with complex automations.
|
||||
this->defer([this, topic, payload]() {
|
||||
#endif
|
||||
for (auto &subscription : this->subscriptions_) {
|
||||
if (topic_match(topic.c_str(), subscription.topic.c_str()))
|
||||
subscription.callback(topic, payload);
|
||||
}
|
||||
#ifdef USE_ESP8266
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
// Setters
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pmsx003 {
|
||||
namespace esphome::pmsx003 {
|
||||
|
||||
static const char *const TAG = "pmsx003";
|
||||
|
||||
static const uint8_t START_CHARACTER_1 = 0x42;
|
||||
static const uint8_t START_CHARACTER_2 = 0x4D;
|
||||
|
||||
static const uint16_t PMS_STABILISING_MS = 30000; // time taken for the sensor to become stable after power on in ms
|
||||
static const uint16_t STABILISING_MS = 30000; // time taken for the sensor to become stable after power on in ms
|
||||
|
||||
static const uint16_t PMS_CMD_MEASUREMENT_MODE_PASSIVE =
|
||||
0x0000; // use `PMS_CMD_MANUAL_MEASUREMENT` to trigger a measurement
|
||||
static const uint16_t PMS_CMD_MEASUREMENT_MODE_ACTIVE = 0x0001; // automatically perform measurements
|
||||
static const uint16_t PMS_CMD_SLEEP_MODE_SLEEP = 0x0000; // go to sleep mode
|
||||
static const uint16_t PMS_CMD_SLEEP_MODE_WAKEUP = 0x0001; // wake up from sleep mode
|
||||
static const uint16_t CMD_MEASUREMENT_MODE_PASSIVE =
|
||||
0x0000; // use `Command::MANUAL_MEASUREMENT` to trigger a measurement
|
||||
static const uint16_t CMD_MEASUREMENT_MODE_ACTIVE = 0x0001; // automatically perform measurements
|
||||
static const uint16_t CMD_SLEEP_MODE_SLEEP = 0x0000; // go to sleep mode
|
||||
static const uint16_t CMD_SLEEP_MODE_WAKEUP = 0x0001; // wake up from sleep mode
|
||||
|
||||
void PMSX003Component::setup() {}
|
||||
|
||||
@@ -42,7 +41,7 @@ void PMSX003Component::dump_config() {
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
|
||||
if (this->update_interval_ <= PMS_STABILISING_MS) {
|
||||
if (this->update_interval_ <= STABILISING_MS) {
|
||||
ESP_LOGCONFIG(TAG, " Mode: active continuous (sensor default)");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Mode: passive with sleep/wake cycles");
|
||||
@@ -55,44 +54,44 @@ void PMSX003Component::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
|
||||
// Initialize sensor mode on first loop
|
||||
if (this->initialised_ == 0) {
|
||||
if (this->update_interval_ > PMS_STABILISING_MS) {
|
||||
if (!this->initialised_) {
|
||||
if (this->update_interval_ > STABILISING_MS) {
|
||||
// Long update interval: use passive mode with sleep/wake cycles
|
||||
this->send_command_(PMS_CMD_MEASUREMENT_MODE, PMS_CMD_MEASUREMENT_MODE_PASSIVE);
|
||||
this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_WAKEUP);
|
||||
this->send_command_(Command::MEASUREMENT_MODE, CMD_MEASUREMENT_MODE_PASSIVE);
|
||||
this->send_command_(Command::SLEEP_MODE, CMD_SLEEP_MODE_WAKEUP);
|
||||
} else {
|
||||
// Short/zero update interval: use active continuous mode
|
||||
this->send_command_(PMS_CMD_MEASUREMENT_MODE, PMS_CMD_MEASUREMENT_MODE_ACTIVE);
|
||||
this->send_command_(Command::MEASUREMENT_MODE, CMD_MEASUREMENT_MODE_ACTIVE);
|
||||
}
|
||||
this->initialised_ = 1;
|
||||
this->initialised_ = true;
|
||||
}
|
||||
|
||||
// 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
|
||||
// need to keep track of what state we're in.
|
||||
if (this->update_interval_ > PMS_STABILISING_MS) {
|
||||
if (this->update_interval_ > STABILISING_MS) {
|
||||
switch (this->state_) {
|
||||
case PMSX003_STATE_IDLE:
|
||||
case State::IDLE:
|
||||
// Power on the sensor now so it'll be ready when we hit the update time
|
||||
if (now - this->last_update_ < (this->update_interval_ - PMS_STABILISING_MS))
|
||||
if (now - this->last_update_ < (this->update_interval_ - STABILISING_MS))
|
||||
return;
|
||||
|
||||
this->state_ = PMSX003_STATE_STABILISING;
|
||||
this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_WAKEUP);
|
||||
this->state_ = State::STABILISING;
|
||||
this->send_command_(Command::SLEEP_MODE, CMD_SLEEP_MODE_WAKEUP);
|
||||
this->fan_on_time_ = now;
|
||||
return;
|
||||
case PMSX003_STATE_STABILISING:
|
||||
case State::STABILISING:
|
||||
// wait for the sensor to be stable
|
||||
if (now - this->fan_on_time_ < PMS_STABILISING_MS)
|
||||
if (now - this->fan_on_time_ < STABILISING_MS)
|
||||
return;
|
||||
// consume any command responses that are in the serial buffer
|
||||
while (this->available())
|
||||
this->read_byte(&this->data_[0]);
|
||||
// Trigger a new read
|
||||
this->send_command_(PMS_CMD_MANUAL_MEASUREMENT, 0);
|
||||
this->state_ = PMSX003_STATE_WAITING;
|
||||
this->send_command_(Command::MANUAL_MEASUREMENT, 0);
|
||||
this->state_ = State::WAITING;
|
||||
break;
|
||||
case PMSX003_STATE_WAITING:
|
||||
case State::WAITING:
|
||||
// Just go ahead and read stuff
|
||||
break;
|
||||
}
|
||||
@@ -180,27 +179,31 @@ 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 PMSX003_TYPE_X003:
|
||||
// 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 PMSX003_TYPE_5003T:
|
||||
case PMSX003_TYPE_5003S:
|
||||
return payload_length == 28; // 2*13+2 (Data 13 not set/reserved)
|
||||
case PMSX003_TYPE_5003ST:
|
||||
return payload_length == 36; // 2*17+2 (Data 16 not set/reserved)
|
||||
case Type::PMS5003S:
|
||||
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;
|
||||
}
|
||||
|
||||
void PMSX003Component::send_command_(PMSX0003Command cmd, uint16_t data) {
|
||||
void PMSX003Component::send_command_(Command cmd, uint16_t data) {
|
||||
uint8_t send_data[7] = {
|
||||
START_CHARACTER_1, // Start Byte 1
|
||||
START_CHARACTER_2, // Start Byte 2
|
||||
cmd, // Command
|
||||
static_cast<uint8_t>(cmd), // Command
|
||||
uint8_t((data >> 8) & 0xFF), // Data 1
|
||||
uint8_t((data >> 0) & 0xFF), // Data 2
|
||||
0, // Verify Byte 1
|
||||
@@ -265,7 +268,7 @@ void PMSX003Component::parse_data_() {
|
||||
if (this->pm_particles_25um_sensor_ != nullptr)
|
||||
this->pm_particles_25um_sensor_->publish_state(pm_particles_25um);
|
||||
|
||||
if (this->type_ == PMSX003_TYPE_5003T) {
|
||||
if (this->type_ == Type::PMS5003T) {
|
||||
ESP_LOGD(TAG,
|
||||
"Got PM0.3 Particles: %u Count/0.1L, PM0.5 Particles: %u Count/0.1L, PM1.0 Particles: %u Count/0.1L, "
|
||||
"PM2.5 Particles %u Count/0.1L",
|
||||
@@ -289,7 +292,7 @@ void PMSX003Component::parse_data_() {
|
||||
}
|
||||
|
||||
// Formaldehyde
|
||||
if (this->type_ == PMSX003_TYPE_5003ST || this->type_ == PMSX003_TYPE_5003S) {
|
||||
if (this->type_ == Type::PMS5003S || this->type_ == Type::PMS5003ST) {
|
||||
const uint16_t formaldehyde = this->get_16_bit_uint_(28);
|
||||
|
||||
ESP_LOGD(TAG, "Got Formaldehyde: %u µg/m^3", formaldehyde);
|
||||
@@ -299,8 +302,8 @@ void PMSX003Component::parse_data_() {
|
||||
}
|
||||
|
||||
// Temperature and Humidity
|
||||
if (this->type_ == PMSX003_TYPE_5003ST || this->type_ == PMSX003_TYPE_5003T) {
|
||||
const uint8_t temperature_offset = (this->type_ == PMSX003_TYPE_5003T) ? 24 : 30;
|
||||
if (this->type_ == Type::PMS5003T || this->type_ == Type::PMS5003ST) {
|
||||
const uint8_t temperature_offset = (this->type_ == Type::PMS5003T) ? 24 : 30;
|
||||
|
||||
const float temperature = static_cast<int16_t>(this->get_16_bit_uint_(temperature_offset)) / 10.0f;
|
||||
const float humidity = this->get_16_bit_uint_(temperature_offset + 2) / 10.0f;
|
||||
@@ -314,22 +317,22 @@ void PMSX003Component::parse_data_() {
|
||||
}
|
||||
|
||||
// Firmware Version and Error Code
|
||||
if (this->type_ == PMSX003_TYPE_5003ST) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Spin down the sensor again if we aren't going to need it until more time has
|
||||
// passed than it takes to stabilise
|
||||
if (this->update_interval_ > PMS_STABILISING_MS) {
|
||||
this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_SLEEP);
|
||||
this->state_ = PMSX003_STATE_IDLE;
|
||||
if (this->update_interval_ > STABILISING_MS) {
|
||||
this->send_command_(Command::SLEEP_MODE, CMD_SLEEP_MODE_SLEEP);
|
||||
this->state_ = State::IDLE;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
} // namespace pmsx003
|
||||
} // namespace esphome
|
||||
} // namespace esphome::pmsx003
|
||||
|
||||
@@ -5,27 +5,28 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pmsx003 {
|
||||
namespace esphome::pmsx003 {
|
||||
|
||||
enum PMSX0003Command : uint8_t {
|
||||
PMS_CMD_MEASUREMENT_MODE =
|
||||
0xE1, // Data Options: `PMS_CMD_MEASUREMENT_MODE_PASSIVE`, `PMS_CMD_MEASUREMENT_MODE_ACTIVE`
|
||||
PMS_CMD_MANUAL_MEASUREMENT = 0xE2,
|
||||
PMS_CMD_SLEEP_MODE = 0xE4, // Data Options: `PMS_CMD_SLEEP_MODE_SLEEP`, `PMS_CMD_SLEEP_MODE_WAKEUP`
|
||||
enum class Type : uint8_t {
|
||||
PMS1003 = 0,
|
||||
PMS3003,
|
||||
PMSX003, // PMS5003, PMS6003, PMS7003, PMSA003 (NOT PMSA003I - see `pmsa003i` component)
|
||||
PMS5003S,
|
||||
PMS5003T,
|
||||
PMS5003ST,
|
||||
PMS9003M,
|
||||
};
|
||||
|
||||
enum PMSX003Type {
|
||||
PMSX003_TYPE_X003 = 0,
|
||||
PMSX003_TYPE_5003T,
|
||||
PMSX003_TYPE_5003ST,
|
||||
PMSX003_TYPE_5003S,
|
||||
enum class Command : uint8_t {
|
||||
MEASUREMENT_MODE = 0xE1, // Data Options: `CMD_MEASUREMENT_MODE_PASSIVE`, `CMD_MEASUREMENT_MODE_ACTIVE`
|
||||
MANUAL_MEASUREMENT = 0xE2,
|
||||
SLEEP_MODE = 0xE4, // Data Options: `CMD_SLEEP_MODE_SLEEP`, `CMD_SLEEP_MODE_WAKEUP`
|
||||
};
|
||||
|
||||
enum PMSX003State {
|
||||
PMSX003_STATE_IDLE = 0,
|
||||
PMSX003_STATE_STABILISING,
|
||||
PMSX003_STATE_WAITING,
|
||||
enum class State : uint8_t {
|
||||
IDLE = 0,
|
||||
STABILISING,
|
||||
WAITING,
|
||||
};
|
||||
|
||||
class PMSX003Component : public uart::UARTDevice, public Component {
|
||||
@@ -37,7 +38,7 @@ class PMSX003Component : public uart::UARTDevice, public Component {
|
||||
|
||||
void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
|
||||
|
||||
void set_type(PMSX003Type type) { this->type_ = type; }
|
||||
void set_type(Type type) { this->type_ = type; }
|
||||
|
||||
void set_pm_1_0_std_sensor(sensor::Sensor *pm_1_0_std_sensor) { this->pm_1_0_std_sensor_ = pm_1_0_std_sensor; }
|
||||
void set_pm_2_5_std_sensor(sensor::Sensor *pm_2_5_std_sensor) { this->pm_2_5_std_sensor_ = pm_2_5_std_sensor; }
|
||||
@@ -77,20 +78,20 @@ class PMSX003Component : public uart::UARTDevice, public Component {
|
||||
optional<bool> check_byte_();
|
||||
void parse_data_();
|
||||
bool check_payload_length_(uint16_t payload_length);
|
||||
void send_command_(PMSX0003Command cmd, uint16_t data);
|
||||
void send_command_(Command cmd, uint16_t data);
|
||||
uint16_t get_16_bit_uint_(uint8_t start_index) const {
|
||||
return encode_uint16(this->data_[start_index], this->data_[start_index + 1]);
|
||||
}
|
||||
|
||||
Type type_;
|
||||
State state_{State::IDLE};
|
||||
bool initialised_{false};
|
||||
uint8_t data_[64];
|
||||
uint8_t data_index_{0};
|
||||
uint8_t initialised_{0};
|
||||
uint32_t fan_on_time_{0};
|
||||
uint32_t last_update_{0};
|
||||
uint32_t last_transmission_{0};
|
||||
uint32_t update_interval_{0};
|
||||
PMSX003State state_{PMSX003_STATE_IDLE};
|
||||
PMSX003Type type_;
|
||||
|
||||
// "Standard Particle"
|
||||
sensor::Sensor *pm_1_0_std_sensor_{nullptr};
|
||||
@@ -118,5 +119,4 @@ class PMSX003Component : public uart::UARTDevice, public Component {
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace pmsx003
|
||||
} // namespace esphome
|
||||
} // namespace esphome::pmsx003
|
||||
|
||||
@@ -40,34 +40,128 @@ 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_PMS5003S = "PMS5003S"
|
||||
TYPE_PMS9003M = "PMS9003M"
|
||||
|
||||
PMSX003Type = pmsx003_ns.enum("PMSX003Type")
|
||||
Type = pmsx003_ns.enum("Type", is_class=True)
|
||||
|
||||
PMSX003_TYPES = {
|
||||
TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003,
|
||||
TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T,
|
||||
TYPE_PMS5003ST: PMSX003Type.PMSX003_TYPE_5003ST,
|
||||
TYPE_PMS5003S: PMSX003Type.PMSX003_TYPE_5003S,
|
||||
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: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S],
|
||||
CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S],
|
||||
CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S],
|
||||
CONF_PM_1_0_STD: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S],
|
||||
CONF_PM_2_5_STD: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S],
|
||||
CONF_PM_10_0_STD: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S],
|
||||
CONF_PM_0_3UM: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S],
|
||||
CONF_PM_0_5UM: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S],
|
||||
CONF_PM_1_0UM: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S],
|
||||
CONF_PM_2_5UM: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S],
|
||||
CONF_PM_5_0UM: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S],
|
||||
CONF_PM_10_0UM: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S],
|
||||
CONF_FORMALDEHYDE: [TYPE_PMS5003ST, TYPE_PMS5003S],
|
||||
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],
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
namespace esphome::sensor {
|
||||
|
||||
@@ -30,20 +31,13 @@ void log_sensor(const char *tag, const char *prefix, const char *type, Sensor *o
|
||||
}
|
||||
}
|
||||
|
||||
// State class strings indexed by StateClass enum (0-4): NONE, MEASUREMENT, TOTAL_INCREASING, TOTAL, MEASUREMENT_ANGLE
|
||||
PROGMEM_STRING_TABLE(StateClassStrings, "", "measurement", "total_increasing", "total", "measurement_angle");
|
||||
static_assert(StateClassStrings::COUNT == STATE_CLASS_LAST + 1, "StateClassStrings must match StateClass enum");
|
||||
|
||||
const LogString *state_class_to_string(StateClass state_class) {
|
||||
switch (state_class) {
|
||||
case STATE_CLASS_MEASUREMENT:
|
||||
return LOG_STR("measurement");
|
||||
case STATE_CLASS_TOTAL_INCREASING:
|
||||
return LOG_STR("total_increasing");
|
||||
case STATE_CLASS_TOTAL:
|
||||
return LOG_STR("total");
|
||||
case STATE_CLASS_MEASUREMENT_ANGLE:
|
||||
return LOG_STR("measurement_angle");
|
||||
case STATE_CLASS_NONE:
|
||||
default:
|
||||
return LOG_STR("");
|
||||
}
|
||||
// Fallback to index 0 (empty string for STATE_CLASS_NONE) if out of range
|
||||
return StateClassStrings::get_log_str(static_cast<uint8_t>(state_class), 0);
|
||||
}
|
||||
|
||||
Sensor::Sensor() : state(NAN), raw_state(NAN) {}
|
||||
|
||||
@@ -32,6 +32,7 @@ enum StateClass : uint8_t {
|
||||
STATE_CLASS_TOTAL = 3,
|
||||
STATE_CLASS_MEASUREMENT_ANGLE = 4
|
||||
};
|
||||
constexpr uint8_t STATE_CLASS_LAST = static_cast<uint8_t>(STATE_CLASS_MEASUREMENT_ANGLE);
|
||||
|
||||
const LogString *state_class_to_string(StateClass state_class);
|
||||
|
||||
|
||||
@@ -23,17 +23,11 @@ const LogString *valve_command_to_str(float pos) {
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
// Valve operation strings indexed by ValveOperation enum (0-2): IDLE, OPENING, CLOSING, plus UNKNOWN
|
||||
PROGMEM_STRING_TABLE(ValveOperationStrings, "IDLE", "OPENING", "CLOSING", "UNKNOWN");
|
||||
|
||||
const LogString *valve_operation_to_str(ValveOperation op) {
|
||||
switch (op) {
|
||||
case VALVE_OPERATION_IDLE:
|
||||
return LOG_STR("IDLE");
|
||||
case VALVE_OPERATION_OPENING:
|
||||
return LOG_STR("OPENING");
|
||||
case VALVE_OPERATION_CLOSING:
|
||||
return LOG_STR("CLOSING");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
return ValveOperationStrings::get_log_str(static_cast<uint8_t>(op), ValveOperationStrings::LAST_INDEX);
|
||||
}
|
||||
|
||||
Valve::Valve() : position{VALVE_OPEN} {}
|
||||
|
||||
@@ -53,4 +53,4 @@ async def to_code(config):
|
||||
"lib_ignore", ["ESPAsyncTCP", "AsyncTCP", "AsyncTCP_RP2040W"]
|
||||
)
|
||||
# https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json
|
||||
cg.add_library("ESP32Async/ESPAsyncWebServer", "3.9.5")
|
||||
cg.add_library("ESP32Async/ESPAsyncWebServer", "3.9.6")
|
||||
|
||||
@@ -36,6 +36,7 @@ extern "C" {
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#include "esphome/core/util.h"
|
||||
|
||||
namespace esphome::wifi {
|
||||
@@ -398,37 +399,23 @@ class WiFiMockClass : public ESP8266WiFiGenericClass {
|
||||
static void _event_callback(void *event) { ESP8266WiFiGenericClass::_eventCallback(event); } // NOLINT
|
||||
};
|
||||
|
||||
// Auth mode strings indexed by AUTH_* constants (0-4), with UNKNOWN at last index
|
||||
// Static asserts verify the SDK constants are contiguous as expected
|
||||
static_assert(AUTH_OPEN == 0 && AUTH_WEP == 1 && AUTH_WPA_PSK == 2 && AUTH_WPA2_PSK == 3 && AUTH_WPA_WPA2_PSK == 4,
|
||||
"AUTH_* constants are not contiguous");
|
||||
PROGMEM_STRING_TABLE(AuthModeStrings, "OPEN", "WEP", "WPA PSK", "WPA2 PSK", "WPA/WPA2 PSK", "UNKNOWN");
|
||||
|
||||
const LogString *get_auth_mode_str(uint8_t mode) {
|
||||
switch (mode) {
|
||||
case AUTH_OPEN:
|
||||
return LOG_STR("OPEN");
|
||||
case AUTH_WEP:
|
||||
return LOG_STR("WEP");
|
||||
case AUTH_WPA_PSK:
|
||||
return LOG_STR("WPA PSK");
|
||||
case AUTH_WPA2_PSK:
|
||||
return LOG_STR("WPA2 PSK");
|
||||
case AUTH_WPA_WPA2_PSK:
|
||||
return LOG_STR("WPA/WPA2 PSK");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
const LogString *get_op_mode_str(uint8_t mode) {
|
||||
switch (mode) {
|
||||
case WIFI_OFF:
|
||||
return LOG_STR("OFF");
|
||||
case WIFI_STA:
|
||||
return LOG_STR("STA");
|
||||
case WIFI_AP:
|
||||
return LOG_STR("AP");
|
||||
case WIFI_AP_STA:
|
||||
return LOG_STR("AP+STA");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
return AuthModeStrings::get_log_str(mode, AuthModeStrings::LAST_INDEX);
|
||||
}
|
||||
|
||||
// WiFi op mode strings indexed by WIFI_* constants (0-3), with UNKNOWN at last index
|
||||
static_assert(WIFI_OFF == 0 && WIFI_STA == 1 && WIFI_AP == 2 && WIFI_AP_STA == 3,
|
||||
"WIFI_* op mode constants are not contiguous");
|
||||
PROGMEM_STRING_TABLE(OpModeStrings, "OFF", "STA", "AP", "AP+STA", "UNKNOWN");
|
||||
|
||||
const LogString *get_op_mode_str(uint8_t mode) { return OpModeStrings::get_log_str(mode, OpModeStrings::LAST_INDEX); }
|
||||
|
||||
const LogString *get_disconnect_reason_str(uint8_t reason) {
|
||||
/* If this were one big switch statement, GCC would generate a lookup table for it. However, the values of the
|
||||
* REASON_* constants aren't continuous, and GCC will fill in the gap with the default value -- wasting 4 bytes of RAM
|
||||
|
||||
@@ -210,7 +210,7 @@ void Application::loop() {
|
||||
#ifdef USE_ESP32
|
||||
esp_chip_info_t chip_info;
|
||||
esp_chip_info(&chip_info);
|
||||
ESP_LOGI(TAG, "ESP32 Chip: %s r%d.%d, %d core(s)", ESPHOME_VARIANT, chip_info.revision / 100,
|
||||
ESP_LOGI(TAG, "ESP32 Chip: %s rev%d.%d, %d core(s)", ESPHOME_VARIANT, chip_info.revision / 100,
|
||||
chip_info.revision % 100, chip_info.cores);
|
||||
#if defined(USE_ESP32_VARIANT_ESP32) && !defined(USE_ESP32_MIN_CHIP_REVISION_SET)
|
||||
// Suggest optimization for chips that don't need the PSRAM cache workaround
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "esphome/core/hal.h" // For PROGMEM definition
|
||||
|
||||
// Platform-agnostic macros for PROGMEM string handling
|
||||
// On ESP8266/Arduino: Use Arduino's F() macro for PROGMEM strings
|
||||
// On other platforms: Use plain strings (no PROGMEM)
|
||||
@@ -32,3 +38,80 @@ using ProgmemStr = const __FlashStringHelper *;
|
||||
// Type for pointers to strings (no PROGMEM on non-ESP8266 platforms)
|
||||
using ProgmemStr = const char *;
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
||||
/// Helper for C++20 string literal template arguments
|
||||
template<size_t N> struct FixedString {
|
||||
char data[N]{};
|
||||
constexpr FixedString(const char (&str)[N]) {
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
data[i] = str[i];
|
||||
}
|
||||
constexpr size_t size() const { return N - 1; } // exclude null terminator
|
||||
};
|
||||
|
||||
/// Compile-time string table that packs strings into a single blob with offset lookup.
|
||||
/// Use PROGMEM_STRING_TABLE macro to instantiate with proper flash placement on ESP8266.
|
||||
///
|
||||
/// Example:
|
||||
/// PROGMEM_STRING_TABLE(MyStrings, "foo", "bar", "baz");
|
||||
/// ProgmemStr str = MyStrings::get_progmem_str(idx, MyStrings::LAST_INDEX); // For ArduinoJson
|
||||
/// const LogString *log_str = MyStrings::get_log_str(idx, MyStrings::LAST_INDEX); // For logging
|
||||
///
|
||||
template<FixedString... Strs> struct ProgmemStringTable {
|
||||
static constexpr size_t COUNT = sizeof...(Strs);
|
||||
static constexpr size_t BLOB_SIZE = (0 + ... + (Strs.size() + 1));
|
||||
|
||||
/// Generate packed string blob at compile time
|
||||
static constexpr auto make_blob() {
|
||||
std::array<char, BLOB_SIZE> result{};
|
||||
size_t pos = 0;
|
||||
auto copy = [&](const auto &str) {
|
||||
for (size_t i = 0; i <= str.size(); ++i)
|
||||
result[pos++] = str.data[i];
|
||||
};
|
||||
(copy(Strs), ...);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Generate offset table at compile time (uint8_t limits blob to 255 bytes)
|
||||
static constexpr auto make_offsets() {
|
||||
static_assert(COUNT > 0, "PROGMEM_STRING_TABLE must contain at least one string");
|
||||
static_assert(COUNT <= 255, "PROGMEM_STRING_TABLE supports at most 255 strings with uint8_t indices");
|
||||
static_assert(BLOB_SIZE <= 255, "PROGMEM_STRING_TABLE blob exceeds 255 bytes; use fewer/shorter strings");
|
||||
std::array<uint8_t, COUNT> result{};
|
||||
size_t pos = 0, idx = 0;
|
||||
((result[idx++] = static_cast<uint8_t>(pos), pos += Strs.size() + 1), ...);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// Forward declaration for LogString (defined in log.h)
|
||||
struct LogString;
|
||||
|
||||
/// Instantiate a ProgmemStringTable with PROGMEM storage.
|
||||
/// Creates: Name::get_progmem_str(idx, fallback), Name::get_log_str(idx, fallback)
|
||||
/// If idx >= COUNT, returns string at fallback. Use LAST_INDEX for common patterns.
|
||||
#define PROGMEM_STRING_TABLE(Name, ...) \
|
||||
struct Name { \
|
||||
using Table = ::esphome::ProgmemStringTable<__VA_ARGS__>; \
|
||||
static constexpr size_t COUNT = Table::COUNT; \
|
||||
static constexpr uint8_t LAST_INDEX = COUNT - 1; \
|
||||
static constexpr size_t BLOB_SIZE = Table::BLOB_SIZE; \
|
||||
static constexpr auto BLOB PROGMEM = Table::make_blob(); \
|
||||
static constexpr auto OFFSETS PROGMEM = Table::make_offsets(); \
|
||||
static const char *get_(uint8_t idx, uint8_t fallback) { \
|
||||
if (idx >= COUNT) \
|
||||
idx = fallback; \
|
||||
return &BLOB[::esphome::progmem_read_byte(&OFFSETS[idx])]; \
|
||||
} \
|
||||
static ::ProgmemStr get_progmem_str(uint8_t idx, uint8_t fallback) { \
|
||||
return reinterpret_cast<::ProgmemStr>(get_(idx, fallback)); \
|
||||
} \
|
||||
static const ::esphome::LogString *get_log_str(uint8_t idx, uint8_t fallback) { \
|
||||
return reinterpret_cast<const ::esphome::LogString *>(get_(idx, fallback)); \
|
||||
} \
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -114,7 +114,7 @@ lib_deps =
|
||||
ESP8266WiFi ; wifi (Arduino built-in)
|
||||
Update ; ota (Arduino built-in)
|
||||
ESP32Async/ESPAsyncTCP@2.0.0 ; async_tcp
|
||||
ESP32Async/ESPAsyncWebServer@3.9.5 ; web_server_base
|
||||
ESP32Async/ESPAsyncWebServer@3.9.6 ; web_server_base
|
||||
makuna/NeoPixelBus@2.7.3 ; neopixelbus
|
||||
ESP8266HTTPClient ; http_request (Arduino built-in)
|
||||
ESP8266mDNS ; mdns (Arduino built-in)
|
||||
@@ -202,7 +202,7 @@ lib_deps =
|
||||
${common:arduino.lib_deps}
|
||||
ayushsharma82/RPAsyncTCP@1.3.2 ; async_tcp
|
||||
bblanchon/ArduinoJson@7.4.2 ; json
|
||||
ESP32Async/ESPAsyncWebServer@3.9.5 ; web_server_base
|
||||
ESP32Async/ESPAsyncWebServer@3.9.6 ; web_server_base
|
||||
build_flags =
|
||||
${common:arduino.build_flags}
|
||||
-DUSE_RP2040
|
||||
@@ -218,7 +218,7 @@ framework = arduino
|
||||
lib_compat_mode = soft
|
||||
lib_deps =
|
||||
bblanchon/ArduinoJson@7.4.2 ; json
|
||||
ESP32Async/ESPAsyncWebServer@3.9.5 ; web_server_base
|
||||
ESP32Async/ESPAsyncWebServer@3.9.6 ; web_server_base
|
||||
droscy/esp_wireguard@0.4.2 ; wireguard
|
||||
build_flags =
|
||||
${common:arduino.build_flags}
|
||||
|
||||
10
tests/components/mipi_spi/test.esp8266-ard.yaml
Normal file
10
tests/components/mipi_spi/test.esp8266-ard.yaml
Normal 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
|
||||
@@ -8,11 +8,11 @@ sensor:
|
||||
pm_10_0:
|
||||
name: PM 10.0 Concentration
|
||||
pm_1_0_std:
|
||||
name: PM 1.0 Standard Atmospher Concentration
|
||||
name: PM 1.0 Standard Atmospheric Concentration
|
||||
pm_2_5_std:
|
||||
name: PM 2.5 Standard Atmospher Concentration
|
||||
name: PM 2.5 Standard Atmospheric Concentration
|
||||
pm_10_0_std:
|
||||
name: PM 10.0 Standard Atmospher Concentration
|
||||
name: PM 10.0 Standard Atmospheric Concentration
|
||||
pm_0_3um:
|
||||
name: Particulate Count >0.3um
|
||||
pm_0_5um:
|
||||
|
||||
Reference in New Issue
Block a user