mirror of
https://github.com/esphome/esphome.git
synced 2026-01-31 08:52:07 -07:00
Compare commits
23 Commits
lazy_time_
...
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 |
@@ -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
|
||||
|
||||
@@ -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} {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user