mirror of
https://github.com/esphome/esphome.git
synced 2026-02-25 04:45:29 -07:00
Merge branch 'scheduler_de_template' into integration
This commit is contained in:
@@ -431,6 +431,14 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
|
||||
return 1
|
||||
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
|
||||
|
||||
process_stacktrace = None
|
||||
|
||||
try:
|
||||
module = importlib.import_module("esphome.components." + CORE.target_platform)
|
||||
process_stacktrace = getattr(module, "process_stacktrace")
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
backtrace_state = False
|
||||
ser = serial.Serial()
|
||||
ser.baudrate = baud_rate
|
||||
@@ -472,9 +480,14 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
|
||||
)
|
||||
safe_print(parser.parse_line(line, time_str))
|
||||
|
||||
backtrace_state = platformio_api.process_stacktrace(
|
||||
config, line, backtrace_state=backtrace_state
|
||||
)
|
||||
if process_stacktrace:
|
||||
backtrace_state = process_stacktrace(
|
||||
config, line, backtrace_state
|
||||
)
|
||||
else:
|
||||
backtrace_state = platformio_api.process_stacktrace(
|
||||
config, line, backtrace_state=backtrace_state
|
||||
)
|
||||
except serial.SerialException:
|
||||
_LOGGER.error("Serial port closed!")
|
||||
return 0
|
||||
@@ -944,12 +957,6 @@ def command_clean_all(args: ArgsProtocol) -> int | None:
|
||||
return 0
|
||||
|
||||
|
||||
def command_mqtt_fingerprint(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
from esphome import mqtt
|
||||
|
||||
return mqtt.get_fingerprint(config)
|
||||
|
||||
|
||||
def command_version(args: ArgsProtocol) -> int | None:
|
||||
safe_print(f"Version: {const.__version__}")
|
||||
return 0
|
||||
@@ -1237,7 +1244,6 @@ POST_CONFIG_ACTIONS = {
|
||||
"run": command_run,
|
||||
"clean": command_clean,
|
||||
"clean-mqtt": command_clean_mqtt,
|
||||
"mqtt-fingerprint": command_mqtt_fingerprint,
|
||||
"idedata": command_idedata,
|
||||
"rename": command_rename,
|
||||
"discover": command_discover,
|
||||
@@ -1451,13 +1457,6 @@ def parse_args(argv):
|
||||
)
|
||||
parser_wizard.add_argument("configuration", help="Your YAML configuration file.")
|
||||
|
||||
parser_fingerprint = subparsers.add_parser(
|
||||
"mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
|
||||
)
|
||||
parser_fingerprint.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
|
||||
subparsers.add_parser("version", help="Print the ESPHome version and exit.")
|
||||
|
||||
parser_clean = subparsers.add_parser(
|
||||
|
||||
@@ -36,6 +36,8 @@ template<typename... X> class TemplatableStringValue : public TemplatableValue<s
|
||||
static std::string value_to_string(const char *val) { return std::string(val); } // For lambdas returning .c_str()
|
||||
static std::string value_to_string(const std::string &val) { return val; }
|
||||
static std::string value_to_string(std::string &&val) { return std::move(val); }
|
||||
static std::string value_to_string(const StringRef &val) { return val.str(); }
|
||||
static std::string value_to_string(StringRef &&val) { return val.str(); }
|
||||
|
||||
public:
|
||||
TemplatableStringValue() : TemplatableValue<std::string, X...>() {}
|
||||
|
||||
@@ -9,6 +9,7 @@ from esphome.const import (
|
||||
CONF_DATA,
|
||||
CONF_FREQUENCY,
|
||||
CONF_ID,
|
||||
CONF_VALUE,
|
||||
CONF_WAIT_TIME,
|
||||
)
|
||||
from esphome.core import ID
|
||||
@@ -333,3 +334,94 @@ async def send_packet_action_to_code(config, action_id, template_arg, args):
|
||||
arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data))
|
||||
cg.add(var.set_data_static(arr, len(data)))
|
||||
return var
|
||||
|
||||
|
||||
# Setter action definitions: (setter_name, validator, template_type, enum_map)
|
||||
_SETTER_ACTIONS = [
|
||||
(
|
||||
"set_frequency",
|
||||
cv.All(cv.frequency, cv.float_range(min=300.0e6, max=928.0e6)),
|
||||
float,
|
||||
None,
|
||||
),
|
||||
("set_output_power", cv.float_range(min=-30.0, max=11.0), float, None),
|
||||
("set_modulation_type", cv.enum(MODULATION, upper=False), Modulation, MODULATION),
|
||||
("set_symbol_rate", cv.float_range(min=600, max=500000), float, None),
|
||||
(
|
||||
"set_rx_attenuation",
|
||||
cv.enum(RX_ATTENUATION, upper=False),
|
||||
RxAttenuation,
|
||||
RX_ATTENUATION,
|
||||
),
|
||||
("set_dc_blocking_filter", cv.boolean, bool, None),
|
||||
("set_manchester", cv.boolean, bool, None),
|
||||
(
|
||||
"set_filter_bandwidth",
|
||||
cv.All(cv.frequency, cv.float_range(min=58000, max=812000)),
|
||||
float,
|
||||
None,
|
||||
),
|
||||
(
|
||||
"set_fsk_deviation",
|
||||
cv.All(cv.frequency, cv.float_range(min=1500, max=381000)),
|
||||
float,
|
||||
None,
|
||||
),
|
||||
("set_msk_deviation", cv.int_range(min=1, max=8), cg.uint8, None),
|
||||
("set_channel", cv.uint8_t, cg.uint8, None),
|
||||
(
|
||||
"set_channel_spacing",
|
||||
cv.All(cv.frequency, cv.float_range(min=25000, max=405000)),
|
||||
float,
|
||||
None,
|
||||
),
|
||||
(
|
||||
"set_if_frequency",
|
||||
cv.All(cv.frequency, cv.float_range(min=25000, max=788000)),
|
||||
float,
|
||||
None,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def _register_setter_actions():
|
||||
for setter_name, validator, templ_type, enum_map in _SETTER_ACTIONS:
|
||||
class_name = (
|
||||
"".join(word.capitalize() for word in setter_name.split("_")) + "Action"
|
||||
)
|
||||
action_cls = ns.class_(
|
||||
class_name, automation.Action, cg.Parented.template(CC1101Component)
|
||||
)
|
||||
schema = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(CC1101Component),
|
||||
cv.Required(CONF_VALUE): cv.templatable(validator),
|
||||
},
|
||||
key=CONF_VALUE,
|
||||
)
|
||||
|
||||
async def _setter_action_to_code(
|
||||
config,
|
||||
action_id,
|
||||
template_arg,
|
||||
args,
|
||||
_setter=setter_name,
|
||||
_type=templ_type,
|
||||
_map=enum_map,
|
||||
):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
data = config[CONF_VALUE]
|
||||
if cg.is_template(data):
|
||||
templ_ = await cg.templatable(data, args, _type)
|
||||
cg.add(getattr(var, _setter)(templ_))
|
||||
else:
|
||||
cg.add(getattr(var, _setter)(_map[data] if _map else data))
|
||||
return var
|
||||
|
||||
automation.register_action(f"cc1101.{setter_name}", action_cls, schema)(
|
||||
_setter_action_to_code
|
||||
)
|
||||
|
||||
|
||||
_register_setter_actions()
|
||||
|
||||
@@ -161,4 +161,82 @@ template<typename... Ts> class SendPacketAction : public Action<Ts...>, public P
|
||||
size_t data_static_len_{0};
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetSymbolRateAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, symbol_rate)
|
||||
void play(const Ts &...x) override { this->parent_->set_symbol_rate(this->symbol_rate_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetFrequencyAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, frequency)
|
||||
void play(const Ts &...x) override { this->parent_->set_frequency(this->frequency_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetOutputPowerAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, output_power)
|
||||
void play(const Ts &...x) override { this->parent_->set_output_power(this->output_power_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetModulationTypeAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(Modulation, modulation_type)
|
||||
void play(const Ts &...x) override { this->parent_->set_modulation_type(this->modulation_type_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetRxAttenuationAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(RxAttenuation, rx_attenuation)
|
||||
void play(const Ts &...x) override { this->parent_->set_rx_attenuation(this->rx_attenuation_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetDcBlockingFilterAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(bool, dc_blocking_filter)
|
||||
void play(const Ts &...x) override { this->parent_->set_dc_blocking_filter(this->dc_blocking_filter_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetManchesterAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(bool, manchester)
|
||||
void play(const Ts &...x) override { this->parent_->set_manchester(this->manchester_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetFilterBandwidthAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, filter_bandwidth)
|
||||
void play(const Ts &...x) override { this->parent_->set_filter_bandwidth(this->filter_bandwidth_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetFskDeviationAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, fsk_deviation)
|
||||
void play(const Ts &...x) override { this->parent_->set_fsk_deviation(this->fsk_deviation_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetMskDeviationAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, msk_deviation)
|
||||
void play(const Ts &...x) override { this->parent_->set_msk_deviation(this->msk_deviation_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetChannelAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, channel)
|
||||
void play(const Ts &...x) override { this->parent_->set_channel(this->channel_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetChannelSpacingAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, channel_spacing)
|
||||
void play(const Ts &...x) override { this->parent_->set_channel_spacing(this->channel_spacing_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetIfFrequencyAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, if_frequency)
|
||||
void play(const Ts &...x) override { this->parent_->set_if_frequency(this->if_frequency_.value(x...)); }
|
||||
};
|
||||
|
||||
} // namespace esphome::cc1101
|
||||
|
||||
@@ -64,6 +64,9 @@ class Dsmr : public Component, public uart::UARTDevice {
|
||||
void dump_config() override;
|
||||
|
||||
void set_decryption_key(const char *decryption_key);
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED("Pass .c_str() - e.g. set_decryption_key(key.c_str()). Removed in 2026.8.0", "2026.2.0")
|
||||
void set_decryption_key(const std::string &decryption_key) { this->set_decryption_key(decryption_key.c_str()); }
|
||||
void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; }
|
||||
void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
|
||||
void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }
|
||||
|
||||
@@ -76,7 +76,7 @@ class EPaperBase : public Display,
|
||||
static uint8_t color_to_bit(Color color) {
|
||||
// It's always a shade of gray. Map to BLACK or WHITE.
|
||||
// We split the luminance at a suitable point
|
||||
if ((static_cast<int>(color.r) + color.g + color.b) > 512) {
|
||||
if ((color.r + color.g + color.b) >= 382) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -5,9 +5,24 @@ namespace esphome::epaper_spi {
|
||||
|
||||
static constexpr const char *const TAG = "epaper_weact_3c";
|
||||
|
||||
enum class BwrState : uint8_t {
|
||||
BWR_BLACK,
|
||||
BWR_WHITE,
|
||||
BWR_RED,
|
||||
};
|
||||
|
||||
static BwrState color_to_bwr(Color color) {
|
||||
if (color.r > color.g + color.b && color.r > 127) {
|
||||
return BwrState::BWR_RED;
|
||||
}
|
||||
if (color.r + color.g + color.b >= 382) {
|
||||
return BwrState::BWR_WHITE;
|
||||
}
|
||||
return BwrState::BWR_BLACK;
|
||||
}
|
||||
// SSD1680 3-color display notes:
|
||||
// - Buffer uses 1 bit per pixel, 8 pixels per byte
|
||||
// - Buffer first half (black_offset): Black/White plane (1=black, 0=white)
|
||||
// - Buffer first half (black_offset): Black/White plane (0=black, 1=white)
|
||||
// - Buffer second half (red_offset): Red plane (1=red, 0=no red)
|
||||
// - Total buffer: width * height / 4 bytes = 2 * (width * height / 8)
|
||||
// - For 128x296: 128*296/4 = 9472 bytes total (4736 per color)
|
||||
@@ -23,20 +38,20 @@ void EPaperWeAct3C::draw_pixel_at(int x, int y, Color color) {
|
||||
|
||||
// Use luminance threshold for B/W mapping
|
||||
// Split at halfway point (382 = (255*3)/2)
|
||||
bool is_white = (static_cast<int>(color.r) + color.g + color.b) > 382;
|
||||
auto bwr = color_to_bwr(color);
|
||||
|
||||
// Update black/white plane (first half of buffer)
|
||||
if (is_white) {
|
||||
// White pixel - clear bit in black plane
|
||||
this->buffer_[pos] &= ~bit;
|
||||
} else {
|
||||
// Black pixel - set bit in black plane
|
||||
if (bwr == BwrState::BWR_WHITE) {
|
||||
// White pixel - set bit in black plane
|
||||
this->buffer_[pos] |= bit;
|
||||
} else {
|
||||
// Black pixel - clear bit in black plane
|
||||
this->buffer_[pos] &= ~bit;
|
||||
}
|
||||
|
||||
// Update red plane (second half of buffer)
|
||||
// Red if red component is dominant (r > g+b)
|
||||
if (color.r > color.g + color.b) {
|
||||
if (bwr == BwrState::BWR_RED) {
|
||||
// Red pixel - set bit in red plane
|
||||
this->buffer_[red_offset + pos] |= bit;
|
||||
} else {
|
||||
@@ -53,21 +68,20 @@ void EPaperWeAct3C::fill(Color color) {
|
||||
const size_t half_buffer = this->buffer_length_ / 2u;
|
||||
|
||||
// Use luminance threshold for B/W mapping
|
||||
bool is_white = (static_cast<int>(color.r) + color.g + color.b) > 382;
|
||||
bool is_red = color.r > color.g + color.b;
|
||||
auto bits = color_to_bwr(color);
|
||||
|
||||
// Fill both planes
|
||||
if (is_white) {
|
||||
// White - both planes = 0x00
|
||||
if (bits == BwrState::BWR_BLACK) {
|
||||
// Black - both planes = 0x00
|
||||
this->buffer_.fill(0x00);
|
||||
} else if (is_red) {
|
||||
} else if (bits == BwrState::BWR_RED) {
|
||||
// Red - black plane = 0x00, red plane = 0xFF
|
||||
for (size_t i = 0; i < half_buffer; i++)
|
||||
this->buffer_[i] = 0x00;
|
||||
for (size_t i = 0; i < half_buffer; i++)
|
||||
this->buffer_[half_buffer + i] = 0xFF;
|
||||
} else {
|
||||
// Black - black plane = 0xFF, red plane = 0x00
|
||||
// White - black plane = 0xFF, red plane = 0x00
|
||||
for (size_t i = 0; i < half_buffer; i++)
|
||||
this->buffer_[i] = 0xFF;
|
||||
for (size_t i = 0; i < half_buffer; i++)
|
||||
@@ -112,7 +126,6 @@ bool HOT EPaperWeAct3C::transfer_data() {
|
||||
ESP_LOGV(TAG, "transfer_data: buffer_length=%u, half_buffer=%u", buffer_length, half_buffer);
|
||||
|
||||
// Use a local buffer for SPI transfers
|
||||
static constexpr size_t MAX_TRANSFER_SIZE = 128;
|
||||
uint8_t bytes_to_send[MAX_TRANSFER_SIZE];
|
||||
|
||||
// First, send the RED buffer (0x26 = WRITE_COLOR)
|
||||
|
||||
@@ -29,10 +29,10 @@ enum class CleaningState : uint8_t {
|
||||
enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE_PARAMETER };
|
||||
|
||||
struct HonSettings {
|
||||
hon_protocol::VerticalSwingMode last_vertiacal_swing;
|
||||
hon_protocol::HorizontalSwingMode last_horizontal_swing;
|
||||
bool beeper_state;
|
||||
bool quiet_mode_state;
|
||||
hon_protocol::VerticalSwingMode last_vertiacal_swing{hon_protocol::VerticalSwingMode::CENTER};
|
||||
hon_protocol::HorizontalSwingMode last_horizontal_swing{hon_protocol::HorizontalSwingMode::CENTER};
|
||||
bool beeper_state{true};
|
||||
bool quiet_mode_state{false};
|
||||
};
|
||||
|
||||
class HonClimate : public HaierClimateBase {
|
||||
@@ -189,7 +189,7 @@ class HonClimate : public HaierClimateBase {
|
||||
int big_data_sensors_{0};
|
||||
esphome::optional<hon_protocol::VerticalSwingMode> current_vertical_swing_{};
|
||||
esphome::optional<hon_protocol::HorizontalSwingMode> current_horizontal_swing_{};
|
||||
HonSettings settings_;
|
||||
HonSettings settings_{};
|
||||
ESPPreferenceObject hon_rtc_;
|
||||
SwitchState quiet_mode_state_{SwitchState::OFF};
|
||||
};
|
||||
|
||||
@@ -87,38 +87,24 @@ COLOR_DEPTHS = {
|
||||
|
||||
def model_schema(config):
|
||||
model = MODELS[config[CONF_MODEL].upper()]
|
||||
model.defaults[CONF_SWAP_XY] = cv.UNDEFINED
|
||||
transform = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MIRROR_X): cv.boolean,
|
||||
cv.Required(CONF_MIRROR_Y): cv.boolean,
|
||||
cv.Optional(CONF_SWAP_XY): cv.invalid(
|
||||
"Axis swapping not supported by DSI displays"
|
||||
),
|
||||
}
|
||||
)
|
||||
if model.get_default(CONF_SWAP_XY) != cv.UNDEFINED:
|
||||
transform = transform.extend(
|
||||
{
|
||||
cv.Optional(CONF_SWAP_XY): cv.invalid(
|
||||
"Axis swapping not supported by this model"
|
||||
)
|
||||
}
|
||||
)
|
||||
else:
|
||||
transform = transform.extend(
|
||||
{
|
||||
cv.Required(CONF_SWAP_XY): cv.boolean,
|
||||
}
|
||||
)
|
||||
# CUSTOM model will need to provide a custom init sequence
|
||||
iseqconf = (
|
||||
cv.Required(CONF_INIT_SEQUENCE)
|
||||
if model.initsequence is None
|
||||
else cv.Optional(CONF_INIT_SEQUENCE)
|
||||
)
|
||||
swap_xy = config.get(CONF_TRANSFORM, {}).get(CONF_SWAP_XY, False)
|
||||
|
||||
# Dimensions are optional if the model has a default width and the swap_xy transform is not overridden
|
||||
cv_dimensions = (
|
||||
cv.Optional if model.get_default(CONF_WIDTH) and not swap_xy else cv.Required
|
||||
)
|
||||
# Dimensions are optional if the model has a default width
|
||||
cv_dimensions = cv.Optional if model.get_default(CONF_WIDTH) else cv.Required
|
||||
pixel_modes = (PIXEL_MODE_16BIT, PIXEL_MODE_24BIT, "16", "24")
|
||||
schema = display.FULL_DISPLAY_SCHEMA.extend(
|
||||
{
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import re
|
||||
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
import esphome.codegen as cg
|
||||
@@ -46,7 +44,6 @@ from esphome.const import (
|
||||
CONF_RETAIN,
|
||||
CONF_SHUTDOWN_MESSAGE,
|
||||
CONF_SKIP_CERT_CN_CHECK,
|
||||
CONF_SSL_FINGERPRINTS,
|
||||
CONF_STATE_TOPIC,
|
||||
CONF_SUBSCRIBE_QOS,
|
||||
CONF_TOPIC,
|
||||
@@ -221,13 +218,6 @@ def validate_config(value):
|
||||
return out
|
||||
|
||||
|
||||
def validate_fingerprint(value):
|
||||
value = cv.string(value)
|
||||
if re.match(r"^[0-9a-f]{40}$", value) is None:
|
||||
raise cv.Invalid("fingerprint must be valid SHA1 hash")
|
||||
return value
|
||||
|
||||
|
||||
def _consume_mqtt_sockets(config: ConfigType) -> ConfigType:
|
||||
"""Register socket needs for MQTT component."""
|
||||
# MQTT needs 1 socket for the broker connection
|
||||
@@ -291,9 +281,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
validate_message_just_topic,
|
||||
),
|
||||
cv.Optional(CONF_SSL_FINGERPRINTS): cv.All(
|
||||
cv.only_on_esp8266, cv.ensure_list(validate_fingerprint)
|
||||
),
|
||||
cv.Optional(CONF_KEEPALIVE, default="15s"): cv.positive_time_period_seconds,
|
||||
cv.Optional(
|
||||
CONF_REBOOT_TIMEOUT, default="15min"
|
||||
@@ -444,14 +431,6 @@ async def to_code(config):
|
||||
if CONF_LEVEL in log_topic:
|
||||
cg.add(var.set_log_level(logger.LOG_LEVELS[log_topic[CONF_LEVEL]]))
|
||||
|
||||
if CONF_SSL_FINGERPRINTS in config:
|
||||
for fingerprint in config[CONF_SSL_FINGERPRINTS]:
|
||||
arr = [
|
||||
cg.RawExpression(f"0x{fingerprint[i : i + 2]}") for i in range(0, 40, 2)
|
||||
]
|
||||
cg.add(var.add_ssl_fingerprint(arr))
|
||||
cg.add_build_flag("-DASYNC_TCP_SSL_ENABLED=1")
|
||||
|
||||
cg.add(var.set_keep_alive(config[CONF_KEEPALIVE]))
|
||||
|
||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||
|
||||
@@ -21,11 +21,6 @@ class MQTTBackendESP8266 final : public MQTTBackend {
|
||||
}
|
||||
void set_server(network::IPAddress ip, uint16_t port) final { mqtt_client_.setServer(ip, port); }
|
||||
void set_server(const char *host, uint16_t port) final { mqtt_client_.setServer(host, port); }
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
void set_secure(bool secure) { mqtt_client.setSecure(secure); }
|
||||
void add_server_fingerprint(const uint8_t *fingerprint) { mqtt_client.addServerFingerprint(fingerprint); }
|
||||
#endif
|
||||
|
||||
void set_on_connect(std::function<on_connect_callback_t> &&callback) final {
|
||||
this->mqtt_client_.onConnect(std::move(callback));
|
||||
}
|
||||
|
||||
@@ -21,11 +21,6 @@ class MQTTBackendLibreTiny final : public MQTTBackend {
|
||||
}
|
||||
void set_server(network::IPAddress ip, uint16_t port) final { mqtt_client_.setServer(IPAddress(ip), port); }
|
||||
void set_server(const char *host, uint16_t port) final { mqtt_client_.setServer(host, port); }
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
void set_secure(bool secure) { mqtt_client.setSecure(secure); }
|
||||
void add_server_fingerprint(const uint8_t *fingerprint) { mqtt_client.addServerFingerprint(fingerprint); }
|
||||
#endif
|
||||
|
||||
void set_on_connect(std::function<on_connect_callback_t> &&callback) final {
|
||||
this->mqtt_client_.onConnect(std::move(callback));
|
||||
}
|
||||
|
||||
@@ -749,13 +749,6 @@ void MQTTClientComponent::set_on_disconnect(mqtt_on_disconnect_callback_t &&call
|
||||
this->on_disconnect_.add(std::move(callback_copy));
|
||||
}
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
void MQTTClientComponent::add_ssl_fingerprint(const std::array<uint8_t, SHA1_SIZE> &fingerprint) {
|
||||
this->mqtt_backend_.setSecure(true);
|
||||
this->mqtt_backend_.addServerFingerprint(fingerprint.data());
|
||||
}
|
||||
#endif
|
||||
|
||||
MQTTClientComponent *global_mqtt_client = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
// MQTTMessageTrigger
|
||||
|
||||
@@ -137,21 +137,6 @@ class MQTTClientComponent : public Component {
|
||||
bool is_discovery_enabled() const;
|
||||
bool is_discovery_ip_enabled() const;
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
/** Add a SSL fingerprint to use for TCP SSL connections to the MQTT broker.
|
||||
*
|
||||
* To use this feature you first have to globally enable the `ASYNC_TCP_SSL_ENABLED` define flag.
|
||||
* This function can be called multiple times and any certificate that matches any of the provided fingerprints
|
||||
* will match. Calling this method will also automatically disable all non-ssl connections.
|
||||
*
|
||||
* @warning This is *not* secure and *not* how SSL is usually done. You'll have to add
|
||||
* a separate fingerprint for every certificate you use. Additionally, the hashing
|
||||
* algorithm used here due to the constraints of the MCU, SHA1, is known to be insecure.
|
||||
*
|
||||
* @param fingerprint The SSL fingerprint as a 20 value long std::array.
|
||||
*/
|
||||
void add_ssl_fingerprint(const std::array<uint8_t, SHA1_SIZE> &fingerprint);
|
||||
#endif
|
||||
#ifdef USE_ESP32
|
||||
void set_ca_certificate(const char *cert) { this->mqtt_backend_.set_ca_certificate(cert); }
|
||||
void set_cl_certificate(const char *cert) { this->mqtt_backend_.set_cl_certificate(cert); }
|
||||
|
||||
@@ -3,6 +3,8 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
@@ -380,3 +382,41 @@ def show_logs(config: ConfigType, args, devices: list[str]) -> bool:
|
||||
asyncio.run(logger_connect(address))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _addr2line(addr2line: str, elf: Path, addr: str) -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[addr2line, "-e", elf, addr],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
return result.stdout.strip().splitlines()[0]
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
_LOGGER.error("Running command failed: %s", err)
|
||||
return ""
|
||||
|
||||
|
||||
def process_stacktrace(config: ConfigType, line: str, backtrace_state: bool) -> bool:
|
||||
if "Last crash:" in line:
|
||||
return True
|
||||
if backtrace_state:
|
||||
match = re.search(r"PC=(0x[0-9a-fA-F]+)\s+LR=(0x[0-9a-fA-F]+)", line)
|
||||
if match:
|
||||
pc = match.group(1)
|
||||
lr = match.group(2)
|
||||
from esphome.analyze_memory.toolchain import find_tool
|
||||
|
||||
addr2line = find_tool("addr2line")
|
||||
if addr2line is None:
|
||||
return False
|
||||
elf = CORE.relative_pioenvs_path(CORE.name, "firmware.elf")
|
||||
if not elf.exists():
|
||||
_LOGGER.warning("%s does not exists", elf)
|
||||
return False
|
||||
_LOGGER.error("=== CRASH ===")
|
||||
_LOGGER.error("PC: %s", _addr2line(addr2line, elf, pc))
|
||||
_LOGGER.error("LR: %s", _addr2line(addr2line, elf, lr))
|
||||
|
||||
return False
|
||||
|
||||
@@ -943,7 +943,6 @@ CONF_SPI = "spi"
|
||||
CONF_SPI_ID = "spi_id"
|
||||
CONF_SPIKE_REJECTION = "spike_rejection"
|
||||
CONF_SSID = "ssid"
|
||||
CONF_SSL_FINGERPRINTS = "ssl_fingerprints"
|
||||
CONF_STARTUP_DELAY = "startup_delay"
|
||||
CONF_STATE = "state"
|
||||
CONF_STATE_CLASS = "state_class"
|
||||
|
||||
@@ -119,10 +119,16 @@ uint32_t Scheduler::calculate_interval_offset_(uint32_t delay) {
|
||||
// Remove before 2026.8.0 along with all retry code
|
||||
bool Scheduler::is_retry_cancelled_locked_(Component *component, NameType name_type, const char *static_name,
|
||||
uint32_t hash_or_id) {
|
||||
return has_cancelled_timeout_in_container_locked_(this->items_, component, name_type, static_name, hash_or_id,
|
||||
/* match_retry= */ true) ||
|
||||
has_cancelled_timeout_in_container_locked_(this->to_add_, component, name_type, static_name, hash_or_id,
|
||||
/* match_retry= */ true);
|
||||
for (auto *container : {&this->items_, &this->to_add_}) {
|
||||
for (auto &item : *container) {
|
||||
if (item && this->is_item_removed_locked_(item.get()) &&
|
||||
this->matches_item_locked_(item, component, name_type, static_name, hash_or_id, SchedulerItem::TIMEOUT,
|
||||
/* match_retry= */ true, /* skip_removed= */ false)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Common implementation for both timeout and interval
|
||||
|
||||
@@ -307,8 +307,8 @@ class Scheduler {
|
||||
SchedulerItem::Type type, bool match_retry, bool skip_removed = true) const {
|
||||
// THREAD SAFETY: Check for nullptr first to prevent LoadProhibited crashes. On multi-threaded
|
||||
// platforms, items can be moved out of defer_queue_ during processing, leaving nullptr entries.
|
||||
// PR #11305 added nullptr checks in callers (mark_matching_items_removed_locked_() and
|
||||
// has_cancelled_timeout_in_container_locked_()), but this check provides defense-in-depth: helper
|
||||
// PR #11305 added nullptr checks in callers (mark_matching_items_removed_locked_()), but this check
|
||||
// provides defense-in-depth: helper
|
||||
// functions should be safe regardless of caller behavior.
|
||||
// Fixes: https://github.com/esphome/esphome/issues/11940
|
||||
if (!item)
|
||||
@@ -402,8 +402,7 @@ class Scheduler {
|
||||
// SAFETY: Moving out the unique_ptr leaves a nullptr in the vector at defer_queue_front_.
|
||||
// This is intentional and safe because:
|
||||
// 1. The vector is only cleaned up by cleanup_defer_queue_locked_() at the end of this function
|
||||
// 2. Any code iterating defer_queue_ MUST check for nullptr items (see mark_matching_items_removed_locked_
|
||||
// and has_cancelled_timeout_in_container_locked_ in scheduler.h)
|
||||
// 2. Any code iterating defer_queue_ MUST check for nullptr items (see mark_matching_items_removed_locked_)
|
||||
// 3. The lock protects concurrent access, but the nullptr remains until cleanup
|
||||
item = std::move(this->defer_queue_[this->defer_queue_front_]);
|
||||
this->defer_queue_front_++;
|
||||
@@ -467,12 +466,8 @@ class Scheduler {
|
||||
// IMPORTANT: Caller must hold the scheduler lock before calling this function.
|
||||
bool is_item_removed_locked_(SchedulerItem *item) const {
|
||||
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
||||
// Lock already held - relaxed is sufficient, mutex provides ordering.
|
||||
// Use GCC __atomic_load_n builtin instead of std::atomic::load() because
|
||||
// GCC for Xtensa emits std::atomic<bool>::load() as an out-of-line
|
||||
// libstdc++ call, adding function call overhead that exceeds the memw
|
||||
// barrier savings this optimization aims to eliminate.
|
||||
return __atomic_load_n(reinterpret_cast<const bool *>(&item->remove), __ATOMIC_RELAXED);
|
||||
// Lock already held - relaxed is sufficient, mutex provides ordering
|
||||
return item->remove.load(std::memory_order_relaxed);
|
||||
#else
|
||||
return item->remove;
|
||||
#endif
|
||||
@@ -500,19 +495,16 @@ class Scheduler {
|
||||
// name_type determines matching: STATIC_STRING uses static_name, others use hash_or_id
|
||||
// Returns the number of items marked for removal
|
||||
// IMPORTANT: Must be called with scheduler lock held
|
||||
template<typename Container>
|
||||
size_t mark_matching_items_removed_locked_(Container &container, Component *component, NameType name_type,
|
||||
const char *static_name, uint32_t hash_or_id, SchedulerItem::Type type,
|
||||
bool match_retry) {
|
||||
size_t mark_matching_items_removed_locked_(std::vector<std::unique_ptr<SchedulerItem>> &container,
|
||||
Component *component, NameType name_type, const char *static_name,
|
||||
uint32_t hash_or_id, SchedulerItem::Type type, bool match_retry) {
|
||||
size_t count = 0;
|
||||
for (auto &item : container) {
|
||||
// Skip nullptr items (can happen in defer_queue_ when items are being processed)
|
||||
// The defer_queue_ uses index-based processing: items are std::moved out but left in the
|
||||
// vector as nullptr until cleanup. Even though this function is called with lock held,
|
||||
// the vector can still contain nullptr items from the processing loop. This check prevents crashes.
|
||||
if (!item)
|
||||
continue;
|
||||
if (this->matches_item_locked_(item, component, name_type, static_name, hash_or_id, type, match_retry)) {
|
||||
if (item && this->matches_item_locked_(item, component, name_type, static_name, hash_or_id, type, match_retry)) {
|
||||
this->set_item_removed_(item.get(), true);
|
||||
count++;
|
||||
}
|
||||
@@ -520,29 +512,6 @@ class Scheduler {
|
||||
return count;
|
||||
}
|
||||
|
||||
// Template helper to check if any item in a container matches our criteria
|
||||
// name_type determines matching: STATIC_STRING uses static_name, others use hash_or_id
|
||||
// IMPORTANT: Must be called with scheduler lock held
|
||||
template<typename Container>
|
||||
bool has_cancelled_timeout_in_container_locked_(const Container &container, Component *component, NameType name_type,
|
||||
const char *static_name, uint32_t hash_or_id,
|
||||
bool match_retry) const {
|
||||
for (const auto &item : container) {
|
||||
// Skip nullptr items (can happen in defer_queue_ when items are being processed)
|
||||
// The defer_queue_ uses index-based processing: items are std::moved out but left in the
|
||||
// vector as nullptr until cleanup. If this function is called during defer queue processing,
|
||||
// it will iterate over these nullptr items. This check prevents crashes.
|
||||
if (!item)
|
||||
continue;
|
||||
if (this->is_item_removed_locked_(item.get()) &&
|
||||
this->matches_item_locked_(item, component, name_type, static_name, hash_or_id, SchedulerItem::TIMEOUT,
|
||||
match_retry, /* skip_removed= */ false)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Mutex lock_;
|
||||
std::vector<std::unique_ptr<SchedulerItem>> items_;
|
||||
std::vector<std::unique_ptr<SchedulerItem>> to_add_;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import contextlib
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import ssl
|
||||
@@ -22,14 +21,12 @@ from esphome.const import (
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_SKIP_CERT_CN_CHECK,
|
||||
CONF_SSL_FINGERPRINTS,
|
||||
CONF_TOPIC,
|
||||
CONF_TOPIC_PREFIX,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.helpers import get_int_env, get_str_env
|
||||
from esphome.log import AnsiFore, color
|
||||
from esphome.types import ConfigType
|
||||
from esphome.util import safe_print
|
||||
|
||||
@@ -102,9 +99,7 @@ def prepare(
|
||||
elif username:
|
||||
client.username_pw_set(username, password)
|
||||
|
||||
if config[CONF_MQTT].get(CONF_SSL_FINGERPRINTS) or config[CONF_MQTT].get(
|
||||
CONF_CERTIFICATE_AUTHORITY
|
||||
):
|
||||
if config[CONF_MQTT].get(CONF_CERTIFICATE_AUTHORITY):
|
||||
context = ssl.create_default_context(
|
||||
cadata=config[CONF_MQTT].get(CONF_CERTIFICATE_AUTHORITY)
|
||||
)
|
||||
@@ -283,23 +278,3 @@ def clear_topic(config, topic, username=None, password=None, client_id=None):
|
||||
client.publish(msg.topic, None, retain=True)
|
||||
|
||||
return initialize(config, [topic], on_message, None, username, password, client_id)
|
||||
|
||||
|
||||
# From marvinroger/async-mqtt-client -> scripts/get-fingerprint/get-fingerprint.py
|
||||
def get_fingerprint(config):
|
||||
addr = str(config[CONF_MQTT][CONF_BROKER]), int(config[CONF_MQTT][CONF_PORT])
|
||||
_LOGGER.info("Getting fingerprint from %s:%s", addr[0], addr[1])
|
||||
try:
|
||||
cert_pem = ssl.get_server_certificate(addr)
|
||||
except OSError as err:
|
||||
_LOGGER.error("Unable to connect to server: %s", err)
|
||||
return 1
|
||||
cert_der = ssl.PEM_cert_to_DER_cert(cert_pem)
|
||||
|
||||
sha1 = hashlib.sha1(cert_der).hexdigest()
|
||||
|
||||
safe_print(f"SHA1 Fingerprint: {color(AnsiFore.CYAN, sha1)}")
|
||||
safe_print(
|
||||
f"Copy the string above into mqtt.ssl_fingerprints section of {CORE.config_path}"
|
||||
)
|
||||
return 0
|
||||
|
||||
@@ -494,6 +494,22 @@ def lint_no_byte_datatype(fname, match):
|
||||
)
|
||||
|
||||
|
||||
@lint_re_check(
|
||||
r"(?:std\s*::\s*string_view|#include\s*<string_view>)" + CPP_RE_EOL,
|
||||
include=cpp_include,
|
||||
)
|
||||
def lint_no_std_string_view(fname, match):
|
||||
return (
|
||||
f"{highlight('std::string_view')} is not allowed in ESPHome. "
|
||||
f"It pulls in significant STL template machinery that bloats flash on "
|
||||
f"resource-constrained embedded targets, does not work well with ArduinoJson, "
|
||||
f"and duplicates functionality already provided by {highlight('StringRef')}.\n"
|
||||
f"Please use {highlight('StringRef')} from {highlight('esphome/core/string_ref.h')} "
|
||||
f"for non-owning string references, or {highlight('const char *')} for simple cases.\n"
|
||||
f"(If strictly necessary, add `{highlight('// NOLINT')}` to the end of the line)"
|
||||
)
|
||||
|
||||
|
||||
@lint_post_check
|
||||
def lint_constants_usage():
|
||||
errs = []
|
||||
|
||||
@@ -15,8 +15,13 @@ esp_ldo:
|
||||
|
||||
display:
|
||||
- platform: mipi_dsi
|
||||
id: p4_nano
|
||||
model: WAVESHARE-P4-NANO-10.1
|
||||
|
||||
rotation: 90
|
||||
- platform: mipi_dsi
|
||||
id: p4_86
|
||||
model: "WAVESHARE-P4-86-PANEL"
|
||||
rotation: 180
|
||||
i2c:
|
||||
sda: GPIO7
|
||||
scl: GPIO8
|
||||
|
||||
@@ -119,9 +119,11 @@ def test_code_generation(
|
||||
|
||||
main_cpp = generate_main(component_fixture_path("mipi_dsi.yaml"))
|
||||
assert (
|
||||
"mipi_dsi_mipi_dsi_id = new mipi_dsi::MIPI_DSI(800, 1280, display::COLOR_BITNESS_565, 16);"
|
||||
"p4_nano = new mipi_dsi::MIPI_DSI(800, 1280, display::COLOR_BITNESS_565, 16);"
|
||||
in main_cpp
|
||||
)
|
||||
assert "set_init_sequence({224, 1, 0, 225, 1, 147, 226, 1," in main_cpp
|
||||
assert "mipi_dsi_mipi_dsi_id->set_lane_bit_rate(1500);" in main_cpp
|
||||
assert "p4_nano->set_lane_bit_rate(1500);" in main_cpp
|
||||
assert "p4_nano->set_rotation(display::DISPLAY_ROTATION_90_DEGREES);" in main_cpp
|
||||
assert "p4_86->set_rotation(display::DISPLAY_ROTATION_0_DEGREES);" in main_cpp
|
||||
# assert "backlight_id = new light::LightState(mipi_dsi_dsibacklight_id);" in main_cpp
|
||||
|
||||
@@ -35,3 +35,99 @@ button:
|
||||
data: [0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef]
|
||||
- cc1101.send_packet: !lambda |-
|
||||
return {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
|
||||
|
||||
- cc1101.set_frequency: !lambda |-
|
||||
return 433.91e6;
|
||||
- cc1101.set_frequency:
|
||||
value: "433.91MHz"
|
||||
- cc1101.set_frequency:
|
||||
value: 433911000
|
||||
- cc1101.set_frequency: 433912000
|
||||
|
||||
- cc1101.set_output_power: !lambda |-
|
||||
return -29.9;
|
||||
- cc1101.set_output_power:
|
||||
value: "-28"
|
||||
- cc1101.set_output_power:
|
||||
value: 10
|
||||
- cc1101.set_output_power: 11
|
||||
|
||||
- cc1101.set_modulation_type: !lambda |-
|
||||
return cc1101::Modulation::MODULATION_2_FSK;
|
||||
- cc1101.set_modulation_type:
|
||||
value: "4-FSK"
|
||||
- cc1101.set_modulation_type: "GFSK"
|
||||
|
||||
- cc1101.set_symbol_rate: !lambda |-
|
||||
return 6000.0;
|
||||
- cc1101.set_symbol_rate:
|
||||
value: "7000.0"
|
||||
- cc1101.set_symbol_rate:
|
||||
value: 8000.0
|
||||
- cc1101.set_symbol_rate: 9000
|
||||
|
||||
- cc1101.set_rx_attenuation: !lambda |-
|
||||
return cc1101::RxAttenuation::RX_ATTENUATION_0DB;
|
||||
- cc1101.set_rx_attenuation:
|
||||
value: "6dB"
|
||||
- cc1101.set_rx_attenuation: "12dB"
|
||||
|
||||
- cc1101.set_dc_blocking_filter: !lambda |-
|
||||
return false;
|
||||
- cc1101.set_dc_blocking_filter:
|
||||
value: true
|
||||
- cc1101.set_dc_blocking_filter: false
|
||||
|
||||
- cc1101.set_manchester: !lambda |-
|
||||
return false;
|
||||
- cc1101.set_manchester:
|
||||
value: true
|
||||
- cc1101.set_manchester: false
|
||||
|
||||
- cc1101.set_filter_bandwidth: !lambda |-
|
||||
return 58e3;
|
||||
- cc1101.set_filter_bandwidth:
|
||||
value: "59kHz"
|
||||
- cc1101.set_filter_bandwidth:
|
||||
value: 60000
|
||||
- cc1101.set_filter_bandwidth: "61kHz"
|
||||
|
||||
- cc1101.set_fsk_deviation: !lambda |-
|
||||
return 1.5e3;
|
||||
- cc1101.set_fsk_deviation:
|
||||
value: "1.6kHz"
|
||||
- cc1101.set_fsk_deviation:
|
||||
value: 1700
|
||||
- cc1101.set_fsk_deviation: "1.8kHz"
|
||||
|
||||
- cc1101.set_msk_deviation: !lambda |-
|
||||
return 1;
|
||||
- cc1101.set_msk_deviation:
|
||||
value: "2"
|
||||
- cc1101.set_msk_deviation:
|
||||
value: 3
|
||||
- cc1101.set_msk_deviation: "4"
|
||||
|
||||
- cc1101.set_channel: !lambda |-
|
||||
return 0;
|
||||
- cc1101.set_channel:
|
||||
value: "1"
|
||||
- cc1101.set_channel:
|
||||
value: 3
|
||||
- cc1101.set_channel: 3
|
||||
|
||||
- cc1101.set_channel_spacing: !lambda |-
|
||||
return 25e3;
|
||||
- cc1101.set_channel_spacing:
|
||||
value: "26kHz"
|
||||
- cc1101.set_channel_spacing:
|
||||
value: 27000
|
||||
- cc1101.set_channel_spacing: "28kHz"
|
||||
|
||||
- cc1101.set_if_frequency: !lambda |-
|
||||
return 25e3;
|
||||
- cc1101.set_if_frequency:
|
||||
value: "26kHz"
|
||||
- cc1101.set_if_frequency:
|
||||
value: 27000
|
||||
- cc1101.set_if_frequency: "28kHz"
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- lambda: |-
|
||||
// Test deprecated std::string overload still compiles
|
||||
std::string key = "00112233445566778899aabbccddeeff";
|
||||
id(dsmr_instance).set_decryption_key(key);
|
||||
|
||||
dsmr:
|
||||
id: dsmr_instance
|
||||
decryption_key: 00112233445566778899aabbccddeeff
|
||||
max_telegram_length: 1000
|
||||
request_pin: ${request_pin}
|
||||
|
||||
@@ -90,6 +90,19 @@ text_sensor:
|
||||
id: ha_hello_world_text2
|
||||
attribute: some_attribute
|
||||
|
||||
event:
|
||||
- platform: template
|
||||
name: Test Event
|
||||
id: test_event
|
||||
event_types:
|
||||
- test_event_type
|
||||
on_event:
|
||||
- homeassistant.event:
|
||||
event: esphome.test_event
|
||||
data:
|
||||
event_name: !lambda |-
|
||||
return event_type;
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
on_time:
|
||||
|
||||
@@ -2951,6 +2951,7 @@ def test_run_miniterm_batches_lines_with_same_timestamp(
|
||||
|
||||
mock_serial = MockSerial([chunk, MOCK_SERIAL_END])
|
||||
|
||||
CORE.data[KEY_CORE] = {KEY_TARGET_PLATFORM: PLATFORM_ESP32}
|
||||
config = {
|
||||
CONF_LOGGER: {
|
||||
CONF_BAUD_RATE: 115200,
|
||||
@@ -2989,6 +2990,7 @@ def test_run_miniterm_different_chunks_different_timestamps(
|
||||
|
||||
mock_serial = MockSerial([chunk1, chunk2, MOCK_SERIAL_END])
|
||||
|
||||
CORE.data[KEY_CORE] = {KEY_TARGET_PLATFORM: PLATFORM_ESP32}
|
||||
config = {
|
||||
CONF_LOGGER: {
|
||||
CONF_BAUD_RATE: 115200,
|
||||
@@ -3019,6 +3021,7 @@ def test_run_miniterm_handles_split_lines() -> None:
|
||||
|
||||
mock_serial = MockSerial([chunk1, chunk2, MOCK_SERIAL_END])
|
||||
|
||||
CORE.data[KEY_CORE] = {KEY_TARGET_PLATFORM: PLATFORM_ESP32}
|
||||
config = {
|
||||
CONF_LOGGER: {
|
||||
CONF_BAUD_RATE: 115200,
|
||||
@@ -3057,6 +3060,7 @@ def test_run_miniterm_backtrace_state_maintained() -> None:
|
||||
|
||||
mock_serial = MockSerial([backtrace_chunk, MOCK_SERIAL_END])
|
||||
|
||||
CORE.data[KEY_CORE] = {KEY_TARGET_PLATFORM: PLATFORM_ESP32}
|
||||
config = {
|
||||
CONF_LOGGER: {
|
||||
CONF_BAUD_RATE: 115200,
|
||||
@@ -3122,6 +3126,7 @@ def test_run_miniterm_handles_empty_reads(
|
||||
|
||||
mock_serial = MockSerial([b"", chunk, b"", MOCK_SERIAL_END])
|
||||
|
||||
CORE.data[KEY_CORE] = {KEY_TARGET_PLATFORM: PLATFORM_ESP32}
|
||||
config = {
|
||||
CONF_LOGGER: {
|
||||
CONF_BAUD_RATE: 115200,
|
||||
@@ -3194,6 +3199,7 @@ def test_run_miniterm_buffer_limit_prevents_unbounded_growth() -> None:
|
||||
|
||||
mock_serial = MockSerial([large_data_no_newline, final_line, MOCK_SERIAL_END])
|
||||
|
||||
CORE.data[KEY_CORE] = {KEY_TARGET_PLATFORM: PLATFORM_ESP32}
|
||||
config = {
|
||||
CONF_LOGGER: {
|
||||
CONF_BAUD_RATE: 115200,
|
||||
|
||||
Reference in New Issue
Block a user