diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 0e2c612279b..9bff9f56355 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -4,6 +4,7 @@ import logging from esphome import automation from esphome.automation import Condition import esphome.codegen as cg +from esphome.components.logger import request_log_listener from esphome.config_helpers import get_logger_level import esphome.config_validation as cv from esphome.const import ( @@ -326,6 +327,9 @@ async def to_code(config: ConfigType) -> None: # Track controller registration for StaticVector sizing CORE.register_controller() + # Request a log listener slot for API log streaming + request_log_listener() + cg.add(var.set_port(config[CONF_PORT])) cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY])) diff --git a/esphome/components/ble_nus/__init__.py b/esphome/components/ble_nus/__init__.py index 9570005902a..6581ce1cfab 100644 --- a/esphome/components/ble_nus/__init__.py +++ b/esphome/components/ble_nus/__init__.py @@ -1,4 +1,5 @@ import esphome.codegen as cg +from esphome.components.logger import request_log_listener from esphome.components.zephyr import zephyr_add_prj_conf import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_LOGS, CONF_TYPE @@ -25,5 +26,8 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) zephyr_add_prj_conf("BT_NUS", True) - cg.add(var.set_expose_log(config[CONF_TYPE] == CONF_LOGS)) + expose_log = config[CONF_TYPE] == CONF_LOGS + cg.add(var.set_expose_log(expose_log)) + if expose_log: + request_log_listener() # Request a log listener slot for BLE NUS log streaming await cg.register_component(var, config) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 7691458df50..f9d5e65bdf2 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -421,6 +421,7 @@ async def to_code(config): await cg.register_component(log, config) for conf in config.get(CONF_ON_MESSAGE, []): + request_log_listener() # Each on_message trigger needs a listener slot trigger = cg.new_Pvariable( conf[CONF_TRIGGER_ID], log, LOG_LEVEL_SEVERITY.index(conf[CONF_LEVEL]) ) @@ -546,6 +547,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( # Keys for CORE.data storage DOMAIN = "logger" KEY_LEVEL_LISTENERS = "level_listeners" +KEY_LOG_LISTENERS = "log_listeners" def request_logger_level_listeners() -> None: @@ -558,8 +560,24 @@ def request_logger_level_listeners() -> None: CORE.data.setdefault(DOMAIN, {})[KEY_LEVEL_LISTENERS] = True +def request_log_listener() -> None: + """Request a log listener slot. + + Components that need to receive log messages should call this function + during their code generation. This increments the listener count used + to size the StaticVector. + """ + data = CORE.data.setdefault(DOMAIN, {}) + data[KEY_LOG_LISTENERS] = data.get(KEY_LOG_LISTENERS, 0) + 1 + + @coroutine_with_priority(CoroPriority.FINAL) async def final_step(): """Final code generation step to configure optional logger features.""" - if CORE.data.get(DOMAIN, {}).get(KEY_LEVEL_LISTENERS, False): + domain_data = CORE.data.get(DOMAIN, {}) + if domain_data.get(KEY_LEVEL_LISTENERS, False): cg.add_define("USE_LOGGER_LEVEL_LISTENERS") + + # Set exact count of log listeners - runtime will error if exceeded + log_listener_count = domain_data.get(KEY_LOG_LISTENERS, 0) + cg.add_define("ESPHOME_LOG_MAX_LISTENERS", log_listener_count) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 306bc9b1434..879afc86897 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -394,7 +394,8 @@ class Logger : public Component { #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS std::map log_levels_{}; #endif - std::vector log_listeners_; // Log message listeners (API, MQTT, syslog, etc.) + StaticVector + log_listeners_; // Log message listeners (API, MQTT, syslog, etc.) #ifdef USE_LOGGER_LEVEL_LISTENERS std::vector level_listeners_; // Log level change listeners #endif diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index f7518771d7a..d7b9b244c0c 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -350,6 +350,10 @@ def exp_mqtt_message(config): async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + + # Request a log listener slot for MQTT log streaming + logger.request_log_listener() + # Add required libraries for ESP8266 and LibreTiny if CORE.is_esp8266 or CORE.is_libretiny: # https://github.com/heman/async-mqtt-client/blob/master/library.json diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index b0c39033e3c..e0e7f94ce08 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -32,6 +32,8 @@ static const uint8_t SSD1306_COMMAND_PAGE_ADDRESS = 0x22; static const uint8_t SSD1306_COMMAND_NORMAL_DISPLAY = 0xA6; static const uint8_t SSD1306_COMMAND_INVERSE_DISPLAY = 0xA7; +static const uint8_t SSD1306B_COMMAND_SELECT_IREF = 0xAD; + static const uint8_t SSD1305_COMMAND_SET_BRIGHTNESS = 0x82; static const uint8_t SSD1305_COMMAND_SET_AREA_COLOR = 0xD8; @@ -95,6 +97,12 @@ void SSD1306::setup() { this->command(0x8B); } } else { + if (this->is_ssd1306b_()) { + // Select external or internal Iref (0xAD) + this->command(SSD1306B_COMMAND_SELECT_IREF); + // Enable internal Iref and change from 19ua (POR) to 30uA + this->command(0x20 | 0x10); + } // Enable charge pump (0x8D) this->command(SSD1306_COMMAND_CHARGE_PUMP); if (this->external_vcc_) { @@ -226,6 +234,8 @@ bool SSD1306::is_sh1107_() const { return this->model_ == SH1107_MODEL_128_64 || bool SSD1306::is_ssd1305_() const { return this->model_ == SSD1305_MODEL_128_64 || this->model_ == SSD1305_MODEL_128_32; } +bool SSD1306::is_ssd1306b_() const { return this->model_ == SSD1306_MODEL_72_40; } + void SSD1306::update() { this->do_update_(); this->display(); diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h index 14ec309ae0d..a5734373861 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.h +++ b/esphome/components/ssd1306_base/ssd1306_base.h @@ -63,6 +63,7 @@ class SSD1306 : public display::DisplayBuffer { bool is_sh1106_() const; bool is_sh1107_() const; bool is_ssd1305_() const; + bool is_ssd1306b_() const; void draw_absolute_pixel_internal(int x, int y, Color color) override; diff --git a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp index ab6fee7b02b..47a21a8ff40 100644 --- a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp +++ b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp @@ -62,9 +62,9 @@ void HOT I2CSSD1306::write_display_data() { } } else { size_t block_size = 16; - if ((this->get_buffer_length_() & 8) == 8) { - // use smaller block size for e.g. 72x40 displays where buffer size is multiple of 8, not 16 - block_size = 8; + if ((this->get_buffer_length_() % 24) == 0) { + // use 24 byte block size for e.g. 72x40 displays where buffer size is multiple of 24, not 16 + block_size = 24; } for (uint32_t i = 0; i < this->get_buffer_length_();) { diff --git a/esphome/components/syslog/__init__.py b/esphome/components/syslog/__init__.py index 80b79d20402..08626404f7e 100644 --- a/esphome/components/syslog/__init__.py +++ b/esphome/components/syslog/__init__.py @@ -1,6 +1,6 @@ import esphome.codegen as cg from esphome.components import udp -from esphome.components.logger import LOG_LEVELS, is_log_level +from esphome.components.logger import LOG_LEVELS, is_log_level, request_log_listener from esphome.components.time import RealTimeClock from esphome.components.udp import CONF_UDP_ID import esphome.config_validation as cv @@ -36,6 +36,7 @@ async def to_code(config): level = LOG_LEVELS[config[CONF_LEVEL]] var = cg.new_Pvariable(config[CONF_ID], level, time) await cg.register_component(var, config) + request_log_listener() # Request a log listener slot for syslog await cg.register_parented(var, parent) cg.add(var.set_strip(config[CONF_STRIP])) cg.add(var.set_facility(config[CONF_FACILITY])) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 7937e7a5403..16ac9d054cb 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -4,6 +4,7 @@ import gzip import esphome.codegen as cg from esphome.components import web_server_base +from esphome.components.logger import request_log_listener from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID import esphome.config_validation as cv from esphome.const import ( @@ -313,6 +314,8 @@ async def to_code(config): if config.get(CONF_OTA) is False: cg.add_define("USE_WEBSERVER_OTA_DISABLED") cg.add(var.set_expose_log(config[CONF_LOG])) + if config[CONF_LOG]: + request_log_listener() # Request a log listener slot for web server log streaming if config[CONF_ENABLE_PRIVATE_NETWORK_ACCESS]: cg.add_define("USE_WEBSERVER_PRIVATE_NETWORK_ACCESS") if CONF_AUTH in config: diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 633b0c6c5e4..2df0552edf5 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -20,6 +20,7 @@ // logger #define ESPHOME_LOG_LEVEL ESPHOME_LOG_LEVEL_VERY_VERBOSE +#define ESPHOME_LOG_MAX_LISTENERS 8 // Feature flags #define USE_ALARM_CONTROL_PANEL