mirror of
https://github.com/esphome/esphome.git
synced 2026-10-25 07:25:35 +00:00
Compare commits
51
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc451c933b | ||
|
|
c74e02eba8 | ||
|
|
ea4ee0c4e0 | ||
|
|
1edb99424b | ||
|
|
146bed3a98 | ||
|
|
f6fb0171c5 | ||
|
|
274abdac3c | ||
|
|
8cfafc3fe8 | ||
|
|
533b181d4d | ||
|
|
481648a512 | ||
|
|
3932ecc9b3 | ||
|
|
af7c68b35d | ||
|
|
5e30279ab6 | ||
|
|
760ed98f42 | ||
|
|
9bc5efd7fc | ||
|
|
160481acf7 | ||
|
|
8a17d3c14a | ||
|
|
cb5140cd57 | ||
|
|
25c6bf837b | ||
|
|
2c392c7e8b | ||
|
|
651c3621ea | ||
|
|
67f31ccf85 | ||
|
|
6c51825569 | ||
|
|
c4224dcbe0 | ||
|
|
959ac66b26 | ||
|
|
2cdc85b20a | ||
|
|
9bebff7228 | ||
|
|
9fbe04746c | ||
|
|
ceb65f61c1 | ||
|
|
05cddd8157 | ||
|
|
cb6be0d6ca | ||
|
|
5295a51d2f | ||
|
|
097b50256c | ||
|
|
7837d212c4 | ||
|
|
c1498a280d | ||
|
|
0334236c73 | ||
|
|
9b7f9057e7 | ||
|
|
3220915d82 | ||
|
|
fb37dc8b75 | ||
|
|
0ac066405f | ||
|
|
c2b23311f2 | ||
|
|
6d6f7bd2f8 | ||
|
|
af788e2c37 | ||
|
|
7e40bcd334 | ||
|
|
6069b820fa | ||
|
|
e263d52405 | ||
|
|
2bc39e50eb | ||
|
|
448790f483 | ||
|
|
a9c8d052df | ||
|
|
f77796877d | ||
|
|
2931a58a9b |
No files matched your search
@@ -1,4 +1,6 @@
|
||||
from collections.abc import Callable
|
||||
import dataclasses
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -132,9 +134,22 @@ def add_listener(receiver: MockObj, listener: MockObj) -> None:
|
||||
cg.add(receiver.register_listener(listener))
|
||||
|
||||
|
||||
@dataclass
|
||||
class _RemoteBaseData:
|
||||
# receiver id -> (receiver, its dumpers in registration order)
|
||||
tables: dict[str, tuple[MockObj, list[MockObj]]] = dataclasses.field(
|
||||
default_factory=dict
|
||||
)
|
||||
# dumper type -> its one flash object, shared by every receiver that lists it
|
||||
flash_dumpers: dict[str, str] = dataclasses.field(default_factory=dict)
|
||||
|
||||
|
||||
def _get_data() -> _RemoteBaseData:
|
||||
return CORE.data.setdefault(DOMAIN, _RemoteBaseData())
|
||||
|
||||
|
||||
def _get_dumper_tables() -> dict[str, tuple[MockObj, list[MockObj]]]:
|
||||
"""Receiver id -> (receiver, its dumpers in registration order)."""
|
||||
return CORE.data.setdefault(DOMAIN, {})
|
||||
return _get_data().tables
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
@@ -255,7 +270,32 @@ def register_trigger(name, type, data_type):
|
||||
return decorator
|
||||
|
||||
|
||||
def _new_flash_dumper(dumper_id: ID) -> cg.RawExpression:
|
||||
"""Place a stateless dumper in flash; the receiver only ever calls it."""
|
||||
flash_dumpers = _get_data().flash_dumpers
|
||||
if (storage := flash_dumpers.get(str(dumper_id.type))) is None:
|
||||
# A generated name a config id cannot take; registered so later globals avoid it too
|
||||
storage_id = cg.unique_global_id(f"{dumper_id.id}__flash", dumper_id.type)
|
||||
storage = flash_dumpers[str(dumper_id.type)] = storage_id.id
|
||||
CORE.register_variable(storage_id, cg.MockObj(storage))
|
||||
CORE.add_global_after_includes(
|
||||
cg.RawStatement(
|
||||
f"static_assert(sizeof({dumper_id.type}) == sizeof(void *) && "
|
||||
f"std::is_trivially_destructible_v<{dumper_id.type}>, "
|
||||
'"a dumper kept in flash must have no data members or destructor");'
|
||||
)
|
||||
)
|
||||
CORE.add_global_after_includes(
|
||||
cg.RawStatement(f"static constexpr {dumper_id.type} {storage} PROGMEM{{}};")
|
||||
)
|
||||
# A constant address for the receiver's flash table; lambdas get a pointer-like object
|
||||
address = f"const_cast<{dumper_id.type} *>(&{storage})"
|
||||
CORE.register_variable(dumper_id, cg.MockObj(address, "->"))
|
||||
return cg.RawExpression(address)
|
||||
|
||||
|
||||
def register_dumper(name, type, schema=None):
|
||||
"""Dumpers are stateless, so each is a flash object; one with data members fails the build."""
|
||||
if schema is None:
|
||||
schema = {}
|
||||
registerer = DUMPER_REGISTRY.register(name, type, schema)
|
||||
@@ -263,7 +303,7 @@ def register_dumper(name, type, schema=None):
|
||||
def decorator(func):
|
||||
async def new_func(config, dumper_id):
|
||||
_request_protocol_if_in_tree(name)
|
||||
var = cg.new_Pvariable(dumper_id)
|
||||
var = _new_flash_dumper(dumper_id)
|
||||
await coroutine(func)(var, config)
|
||||
return var
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
esphome:
|
||||
name: test
|
||||
on_boot:
|
||||
- lambda: |-
|
||||
ESP_LOGD("test", "secondary %d", id(my_nec).is_secondary());
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
logger:
|
||||
|
||||
remote_receiver:
|
||||
- id: ir_rcvr
|
||||
pin: GPIO4
|
||||
dump:
|
||||
- nec:
|
||||
type_id: my_nec
|
||||
- id: my_nec__flash
|
||||
pin: GPIO5
|
||||
dump:
|
||||
- rc_switch
|
||||
@@ -149,15 +149,20 @@ def test_dumper_count_is_the_busiest_receivers(
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restore_protocol_registries")
|
||||
def test_external_dumpers_are_constructed_in_ram(
|
||||
def test_external_dumpers_live_in_flash(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""A dumper registered outside remote_base may hold state."""
|
||||
"""External protocol dumpers are stateless too; the size check guards one that is not."""
|
||||
main_cpp = generate_main(
|
||||
component_config_path("receiver_with_external_protocol.yaml")
|
||||
)
|
||||
assert re.search(r"new\(\w+\) fake_protocol::FakeDumper\(\);", main_cpp)
|
||||
assert re.search(
|
||||
r"static constexpr fake_protocol::FakeDumper \w+ PROGMEM\{\};", main_cpp
|
||||
)
|
||||
assert (
|
||||
"static_assert(sizeof(fake_protocol::FakeDumper) == sizeof(void *)" in main_cpp
|
||||
)
|
||||
|
||||
|
||||
def test_each_receiver_gets_its_own_dumper_table(
|
||||
@@ -178,3 +183,35 @@ def test_add_dumper_after_the_tables_were_emitted_raises(
|
||||
generate_main(component_config_path("receiver_two_dumper_lists.yaml"))
|
||||
with pytest.raises(ValueError, match="after the count define was emitted"):
|
||||
remote_base.add_dumper(MockObj("rcvr", "->"), MockObj("dumper", "->"))
|
||||
|
||||
|
||||
def test_in_tree_dumpers_live_in_flash(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
main_cpp = generate_main(component_config_path("receiver_two_dumper_lists.yaml"))
|
||||
assert re.search(
|
||||
r"static constexpr remote_base::NECDumper \w+ PROGMEM\{\};", main_cpp
|
||||
)
|
||||
assert not re.search(r"new\(\w+\) remote_base::\w+Dumper\(\);", main_cpp)
|
||||
# A dumper that gains a member must fail the build, not fault on the device
|
||||
assert "static_assert(sizeof(remote_base::NECDumper) == sizeof(void *)" in main_cpp
|
||||
# rc_switch is on both receivers but has one flash object and one size check
|
||||
assert (
|
||||
len(re.findall(r"static constexpr remote_base::RCSwitchDumper ", main_cpp)) == 1
|
||||
)
|
||||
assert main_cpp.count("static_assert(sizeof(remote_base::RCSwitchDumper)") == 1
|
||||
|
||||
|
||||
def test_a_dumper_id_works_in_lambdas_and_cannot_collide(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""A user type_id is usable from a lambda, and a config id cannot take the flash storage name."""
|
||||
main_cpp = generate_main(component_config_path("receiver_dumper_type_id.yaml"))
|
||||
assert "const_cast<remote_base::NECDumper *>(&my_nec__flash_" in main_cpp
|
||||
assert "->is_secondary()" in main_cpp
|
||||
assert re.search(
|
||||
r"static constexpr remote_base::NECDumper my_nec__flash_\w+ PROGMEM\{\};",
|
||||
main_cpp,
|
||||
)
|
||||
Reference in new issue
Block a user