mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 02:02:51 +00:00
[core] Keep string constants in apply conditions in flash on ESP8266 (#20107)
This commit is contained in:
+21
-6
@@ -248,6 +248,23 @@ def flash_string(config: ConfigType, value: str) -> str:
|
||||
return str(cg.progmem_string(value))
|
||||
|
||||
|
||||
def flash_compare(config: ConfigType, value: str) -> str:
|
||||
"""Default renderer for ``std::string`` constants in conditions; compares in flash on ESP8266."""
|
||||
if CORE.is_esp8266:
|
||||
return f"::esphome::ProgmemStringRef{{PSTR({cg.safe_exp(value)}), {len(value.encode('utf-8'))}}}"
|
||||
return str(cg.safe_exp(value))
|
||||
|
||||
|
||||
def _default_std_string(
|
||||
members: list[tuple[str, Any, Any]], renderer: Callable[[ConfigType, str], str]
|
||||
) -> list[tuple[str, Any, Any]]:
|
||||
"""Give ``cg.std_string`` members without a renderer the given default."""
|
||||
return [
|
||||
(key, t, fn or (renderer if t is cg.std_string else None))
|
||||
for key, t, fn in members
|
||||
]
|
||||
|
||||
|
||||
def literal_with_length(config: ConfigType, value: str) -> str:
|
||||
"""Renderer for a ``(const char *, size_t)`` target: a plain literal plus its byte length.
|
||||
|
||||
@@ -491,10 +508,7 @@ def apply_action_builder(
|
||||
statements_spec = [
|
||||
(
|
||||
c.target,
|
||||
[
|
||||
(key, t, fn or (flash_string if t is cg.std_string else None))
|
||||
for key, t, fn in c.members
|
||||
],
|
||||
_default_std_string(c.members, flash_string),
|
||||
)
|
||||
for c in (f if isinstance(f, ApplyCall) else f.call() for f in fields)
|
||||
]
|
||||
@@ -563,11 +577,12 @@ def register_apply_condition(
|
||||
``check`` is applied to the parent: ``"is_playing()"`` becomes ``parent->is_playing()``; an
|
||||
``ApplyCall`` such as ``ApplyCall("state == {}", ((CONF_STATE, cg.bool_),))`` compares
|
||||
against config values, all of which must be present. Write ``== false`` to negate.
|
||||
String constants are plain literals, so compare a ``std::string`` or ``StringRef`` member.
|
||||
A ``cg.std_string`` constant stays in flash on ESP8266 and supports only ``==`` or ``!=``
|
||||
against a ``std::string`` or ``StringRef`` member; other string types are plain literals.
|
||||
Generates one static predicate and an ``ApplyCondition`` templated on it.
|
||||
"""
|
||||
call = check if isinstance(check, ApplyCall) else ApplyCall(check)
|
||||
members = call.members
|
||||
members = _default_std_string(call.members, flash_compare)
|
||||
_check_key_in_schema(name, schema, id_key)
|
||||
for conf_key, _, _ in members:
|
||||
_check_key_in_schema(name, schema, conf_key)
|
||||
|
||||
@@ -188,6 +188,25 @@ inline bool operator==(const __FlashStringHelper *lhs, const StringRef &rhs) { r
|
||||
inline bool operator!=(const StringRef &lhs, const __FlashStringHelper *rhs) { return !(lhs == rhs); }
|
||||
|
||||
inline bool operator!=(const __FlashStringHelper *lhs, const StringRef &rhs) { return !(rhs == lhs); }
|
||||
|
||||
/// Codegen use only: a flash string literal with its byte length, compared without a runtime strlen.
|
||||
struct ProgmemStringRef {
|
||||
PGM_P str;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
inline bool operator==(const std::string &lhs, ProgmemStringRef rhs) {
|
||||
return lhs.size() == rhs.len && (rhs.len == 0 || memcmp_P(lhs.data(), rhs.str, rhs.len) == 0);
|
||||
}
|
||||
|
||||
inline bool operator!=(const std::string &lhs, ProgmemStringRef rhs) { return !(lhs == rhs); }
|
||||
|
||||
inline bool operator==(const StringRef &lhs, ProgmemStringRef rhs) {
|
||||
// A StringRef may be {nullptr, 0}, and memcmp_P with a null pointer is undefined even for 0 bytes
|
||||
return lhs.size() == rhs.len && (rhs.len == 0 || memcmp_P(lhs.c_str(), rhs.str, rhs.len) == 0);
|
||||
}
|
||||
|
||||
inline bool operator!=(const StringRef &lhs, ProgmemStringRef rhs) { return !(lhs == rhs); }
|
||||
#endif // USE_ESP8266
|
||||
|
||||
inline bool operator<(const StringRef &lhs, const StringRef &rhs) {
|
||||
|
||||
@@ -1293,13 +1293,25 @@ async def test_apply_condition_compares_config_value(
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("platform", ["esp32", "esp8266"])
|
||||
async def test_apply_condition_string_constant_is_a_plain_literal(
|
||||
registries: tuple[Registry, Registry], mock_cg: MockCodegen, platform: str
|
||||
@pytest.mark.parametrize(
|
||||
("platform", "value", "expected"),
|
||||
[
|
||||
("esp32", "two", '"two"'),
|
||||
("esp8266", "two", '::esphome::ProgmemStringRef{PSTR("two"), 3}'),
|
||||
# The length is in bytes, not characters
|
||||
("esp8266", "é", '::esphome::ProgmemStringRef{PSTR("\\303\\251"), 2}'),
|
||||
],
|
||||
)
|
||||
async def test_apply_condition_string_constant(
|
||||
registries: tuple[Registry, Registry],
|
||||
mock_cg: MockCodegen,
|
||||
platform: str,
|
||||
value: str,
|
||||
expected: str,
|
||||
) -> None:
|
||||
check = ApplyCall("state == {}", (("state", cg.std_string),))
|
||||
await _run_apply_condition(registries, check, {"state": "two"}, platform=platform)
|
||||
assert f'return ::{PARENT_OBJ}->state == "two";' in _apply_definition(mock_cg)
|
||||
await _run_apply_condition(registries, check, {"state": value}, platform=platform)
|
||||
assert f"return ::{PARENT_OBJ}->state == {expected};" in _apply_definition(mock_cg)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user