mirror of
https://github.com/esphome/esphome.git
synced 2024-11-30 10:44:13 +01:00
remove dependencies to components from core part 1
This commit is contained in:
parent
c5b1a8eb81
commit
7e9ed0b5db
30 changed files with 255 additions and 276 deletions
|
@ -26,6 +26,7 @@ from esphome.cpp_generator import ( # noqa: F401
|
|||
add_global,
|
||||
add_library,
|
||||
add_platformio_option,
|
||||
define_entity,
|
||||
get_variable,
|
||||
get_variable_with_full_id,
|
||||
is_template,
|
||||
|
@ -33,6 +34,7 @@ from esphome.cpp_generator import ( # noqa: F401
|
|||
new_variable,
|
||||
process_lambda,
|
||||
progmem_array,
|
||||
register_entity,
|
||||
safe_exp,
|
||||
statement,
|
||||
static_const_array,
|
||||
|
|
|
@ -206,7 +206,7 @@ async def setup_alarm_control_panel_core_(var, config):
|
|||
async def register_alarm_control_panel(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_alarm_control_panel(var))
|
||||
cg.register_entity(AlarmControlPanel, var)
|
||||
await setup_alarm_control_panel_core_(var, config)
|
||||
|
||||
|
||||
|
@ -315,3 +315,4 @@ async def alarm_control_panel_is_armed_to_code(
|
|||
async def to_code(config):
|
||||
cg.add_global(alarm_control_panel_ns.using)
|
||||
cg.add_define("USE_ALARM_CONTROL_PANEL")
|
||||
cg.define_entity(AlarmControlPanel)
|
||||
|
|
|
@ -551,7 +551,7 @@ async def setup_binary_sensor_core_(var, config):
|
|||
async def register_binary_sensor(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_binary_sensor(var))
|
||||
cg.register_entity(BinarySensor, var)
|
||||
await setup_binary_sensor_core_(var, config)
|
||||
|
||||
|
||||
|
@ -588,3 +588,4 @@ async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args)
|
|||
async def to_code(config):
|
||||
cg.add_define("USE_BINARY_SENSOR")
|
||||
cg.add_global(binary_sensor_ns.using)
|
||||
cg.define_entity(BinarySensor)
|
||||
|
|
|
@ -105,7 +105,7 @@ async def setup_button_core_(var, config):
|
|||
async def register_button(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_button(var))
|
||||
cg.register_entity(Button, var)
|
||||
await setup_button_core_(var, config)
|
||||
|
||||
|
||||
|
@ -132,3 +132,4 @@ async def button_press_to_code(config, action_id, template_arg, args):
|
|||
async def to_code(config):
|
||||
cg.add_global(button_ns.using)
|
||||
cg.add_define("USE_BUTTON")
|
||||
cg.define_entity(Button)
|
||||
|
|
|
@ -416,7 +416,7 @@ async def setup_climate_core_(var, config):
|
|||
async def register_climate(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_climate(var))
|
||||
cg.register_entity(Climate, var)
|
||||
await setup_climate_core_(var, config)
|
||||
|
||||
|
||||
|
@ -485,3 +485,4 @@ async def climate_control_to_code(config, action_id, template_arg, args):
|
|||
async def to_code(config):
|
||||
cg.add_define("USE_CLIMATE")
|
||||
cg.add_global(climate_ns.using)
|
||||
cg.define_entity(Climate)
|
||||
|
|
|
@ -160,7 +160,7 @@ async def setup_cover_core_(var, config):
|
|||
async def register_cover(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_cover(var))
|
||||
cg.register_entity(Cover, var)
|
||||
await setup_cover_core_(var, config)
|
||||
|
||||
|
||||
|
@ -229,3 +229,4 @@ async def cover_control_to_code(config, action_id, template_arg, args):
|
|||
async def to_code(config):
|
||||
cg.add_define("USE_COVER")
|
||||
cg.add_global(cover_ns.using)
|
||||
cg.define_entity(Cover)
|
||||
|
|
|
@ -151,9 +151,12 @@ async def setup_datetime_core_(var, config):
|
|||
async def register_datetime(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(getattr(cg.App, f"register_{config[CONF_TYPE].lower()}")(var))
|
||||
entity = f"datetime::{str(config[CONF_ID].type).split('Template')[1]}Entity"
|
||||
print(entity)
|
||||
cg.register_entity(entity, var)
|
||||
await setup_datetime_core_(var, config)
|
||||
cg.add_define(f"USE_DATETIME_{config[CONF_TYPE]}")
|
||||
cg.define_entity(entity)
|
||||
|
||||
|
||||
async def new_datetime(config, *args):
|
||||
|
|
|
@ -101,7 +101,7 @@ async def setup_event_core_(var, config, *, event_types: list[str]):
|
|||
async def register_event(var, config, *, event_types: list[str]):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_event(var))
|
||||
cg.register_entity(Event, var)
|
||||
await setup_event_core_(var, config, event_types=event_types)
|
||||
|
||||
|
||||
|
@ -132,3 +132,4 @@ async def event_fire_to_code(config, action_id, template_arg, args):
|
|||
async def to_code(config):
|
||||
cg.add_define("USE_EVENT")
|
||||
cg.add_global(event_ns.using)
|
||||
cg.define_entity(Event)
|
||||
|
|
|
@ -248,7 +248,7 @@ async def setup_fan_core_(var, config):
|
|||
async def register_fan(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_fan(var))
|
||||
cg.register_entity(Fan, var)
|
||||
await setup_fan_core_(var, config)
|
||||
|
||||
|
||||
|
@ -352,3 +352,4 @@ async def fan_is_on_off_to_code(config, condition_id, template_arg, args):
|
|||
async def to_code(config):
|
||||
cg.add_define("USE_FAN")
|
||||
cg.add_global(fan_ns.using)
|
||||
cg.define_entity(Fan)
|
||||
|
|
|
@ -188,7 +188,7 @@ async def setup_light_core_(light_var, output_var, config):
|
|||
|
||||
async def register_light(output_var, config):
|
||||
light_var = cg.new_Pvariable(config[CONF_ID], output_var)
|
||||
cg.add(cg.App.register_light(light_var))
|
||||
cg.register_entity(LightState, light_var)
|
||||
await cg.register_component(light_var, config)
|
||||
await setup_light_core_(light_var, output_var, config)
|
||||
|
||||
|
@ -197,3 +197,4 @@ async def register_light(output_var, config):
|
|||
async def to_code(config):
|
||||
cg.add_define("USE_LIGHT")
|
||||
cg.add_global(light_ns.using)
|
||||
cg.define_entity(LightState)
|
||||
|
|
|
@ -74,7 +74,7 @@ async def setup_lock_core_(var, config):
|
|||
async def register_lock(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_lock(var))
|
||||
cg.register_entity(Lock, var)
|
||||
await setup_lock_core_(var, config)
|
||||
|
||||
|
||||
|
@ -109,3 +109,4 @@ async def lock_is_off_to_code(config, condition_id, template_arg, args):
|
|||
async def to_code(config):
|
||||
cg.add_global(lock_ns.using)
|
||||
cg.add_define("USE_LOCK")
|
||||
cg.define_entity(Lock)
|
||||
|
|
|
@ -85,7 +85,7 @@ async def setup_media_player_core_(var, config):
|
|||
async def register_media_player(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_media_player(var))
|
||||
cg.register_entity(MediaPlayer, var)
|
||||
await setup_media_player_core_(var, config)
|
||||
|
||||
|
||||
|
@ -191,3 +191,4 @@ async def media_player_volume_set_action(config, action_id, template_arg, args):
|
|||
async def to_code(config):
|
||||
cg.add_global(media_player_ns.using)
|
||||
cg.add_define("USE_MEDIA_PLAYER")
|
||||
cg.define_entity(MediaPlayer)
|
||||
|
|
|
@ -265,7 +265,7 @@ async def register_number(
|
|||
):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_number(var))
|
||||
cg.register_entity(Number, var)
|
||||
await setup_number_core_(
|
||||
var, config, min_value=min_value, max_value=max_value, step=step
|
||||
)
|
||||
|
@ -308,6 +308,7 @@ async def number_in_range_to_code(config, condition_id, template_arg, args):
|
|||
async def to_code(config):
|
||||
cg.add_define("USE_NUMBER")
|
||||
cg.add_global(number_ns.using)
|
||||
cg.define_entity(Number)
|
||||
|
||||
|
||||
OPERATION_BASE_SCHEMA = cv.Schema(
|
||||
|
|
|
@ -112,7 +112,7 @@ async def setup_select_core_(var, config, *, options: list[str]):
|
|||
async def register_select(var, config, *, options: list[str]):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_select(var))
|
||||
cg.register_entity(Select, var)
|
||||
await setup_select_core_(var, config, options=options)
|
||||
|
||||
|
||||
|
@ -126,6 +126,7 @@ async def new_select(config, *, options: list[str]):
|
|||
async def to_code(config):
|
||||
cg.add_define("USE_SELECT")
|
||||
cg.add_global(select_ns.using)
|
||||
cg.define_entity(Select)
|
||||
|
||||
|
||||
OPERATION_BASE_SCHEMA = cv.Schema(
|
||||
|
|
|
@ -808,7 +808,7 @@ async def setup_sensor_core_(var, config):
|
|||
async def register_sensor(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_sensor(var))
|
||||
cg.register_entity(Sensor, var)
|
||||
await setup_sensor_core_(var, config)
|
||||
|
||||
|
||||
|
@ -948,3 +948,4 @@ def _lstsq(a, b):
|
|||
async def to_code(config):
|
||||
cg.add_define("USE_SENSOR")
|
||||
cg.add_global(sensor_ns.using)
|
||||
cg.define_entity(Sensor)
|
||||
|
|
|
@ -169,7 +169,7 @@ async def setup_switch_core_(var, config):
|
|||
async def register_switch(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_switch(var))
|
||||
cg.register_entity(Switch, var)
|
||||
await setup_switch_core_(var, config)
|
||||
|
||||
|
||||
|
@ -210,3 +210,4 @@ async def switch_is_off_to_code(config, condition_id, template_arg, args):
|
|||
async def to_code(config):
|
||||
cg.add_global(switch_ns.using)
|
||||
cg.add_define("USE_SWITCH")
|
||||
cg.define_entity(Switch)
|
||||
|
|
|
@ -97,7 +97,7 @@ async def register_text(
|
|||
):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_text(var))
|
||||
cg.register_entity(Text, var)
|
||||
await setup_text_core_(
|
||||
var, config, min_length=min_length, max_length=max_length, pattern=pattern
|
||||
)
|
||||
|
@ -121,6 +121,7 @@ async def new_text(
|
|||
async def to_code(config):
|
||||
cg.add_define("USE_TEXT")
|
||||
cg.add_global(text_ns.using)
|
||||
cg.define_entity(Text)
|
||||
|
||||
|
||||
OPERATION_BASE_SCHEMA = cv.Schema(
|
||||
|
|
|
@ -220,7 +220,7 @@ async def setup_text_sensor_core_(var, config):
|
|||
async def register_text_sensor(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_text_sensor(var))
|
||||
cg.register_entity(TextSensor, var)
|
||||
await setup_text_sensor_core_(var, config)
|
||||
|
||||
|
||||
|
@ -234,6 +234,7 @@ async def new_text_sensor(config, *args):
|
|||
async def to_code(config):
|
||||
cg.add_define("USE_TEXT_SENSOR")
|
||||
cg.add_global(text_sensor_ns.using)
|
||||
cg.define_entity(TextSensor)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
|
|
|
@ -81,7 +81,7 @@ async def setup_update_core_(var, config):
|
|||
async def register_update(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_update(var))
|
||||
cg.register_entity(UpdateEntity, var)
|
||||
await setup_update_core_(var, config)
|
||||
|
||||
|
||||
|
@ -95,6 +95,7 @@ async def new_update(config):
|
|||
async def to_code(config):
|
||||
cg.add_define("USE_UPDATE")
|
||||
cg.add_global(update_ns.using)
|
||||
cg.define_entity(UpdateEntity)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
|
|
|
@ -132,7 +132,7 @@ async def setup_valve_core_(var, config):
|
|||
async def register_valve(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_valve(var))
|
||||
cg.register_entity(Valve, var)
|
||||
await setup_valve_core_(var, config)
|
||||
|
||||
|
||||
|
@ -203,3 +203,4 @@ async def valve_control_to_code(config, action_id, template_arg, args):
|
|||
async def to_code(config):
|
||||
cg.add_define("USE_VALVE")
|
||||
cg.add_global(valve_ns.using)
|
||||
cg.define_entity(Valve)
|
||||
|
|
|
@ -529,6 +529,8 @@ class EsphomeCore:
|
|||
self.verbose = False
|
||||
# Whether ESPHome was started in quiet mode
|
||||
self.quiet = False
|
||||
# A set of available entities
|
||||
self.entities: set[str] = set()
|
||||
|
||||
def reset(self):
|
||||
from esphome.pins import PIN_SCHEMA_REGISTRY
|
||||
|
@ -553,6 +555,7 @@ class EsphomeCore:
|
|||
self.loaded_integrations = set()
|
||||
self.component_ids = set()
|
||||
PIN_SCHEMA_REGISTRY.reset()
|
||||
self.entities = set()
|
||||
|
||||
@property
|
||||
def address(self) -> Optional[str]:
|
||||
|
@ -779,6 +782,9 @@ class EsphomeCore:
|
|||
_LOGGER.debug("Adding define: %s", define)
|
||||
return define
|
||||
|
||||
def define_entity(self, class_):
|
||||
self.entities.add(class_)
|
||||
|
||||
def add_platformio_option(self, key: str, value: Union[str, list[str]]) -> None:
|
||||
new_val = value
|
||||
old_val = self.platformio_options.get(key)
|
||||
|
|
|
@ -72,10 +72,16 @@
|
|||
#ifdef USE_UPDATE
|
||||
#include "esphome/components/update/update_entity.h"
|
||||
#endif
|
||||
#include "esphome/core/entities.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
class Application {
|
||||
template<typename Tuple> struct entityRegistry;
|
||||
template<typename... Args> struct entityRegistry<std::tuple<Args...>> {
|
||||
using type = std::tuple<std::vector<Args>...>;
|
||||
};
|
||||
|
||||
public:
|
||||
void pre_setup(const std::string &name, const std::string &friendly_name, const std::string &area,
|
||||
const char *comment, const char *compilation_time, bool name_add_mac_suffix) {
|
||||
|
@ -97,93 +103,22 @@ class Application {
|
|||
this->compilation_time_ = compilation_time;
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void register_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
|
||||
this->binary_sensors_.push_back(binary_sensor);
|
||||
template<typename Entity> void register_entity(Entity *entity) {
|
||||
get_by_type<std::vector<Entity *>>(entities_).push_back(entity);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void register_sensor(sensor::Sensor *sensor) { this->sensors_.push_back(sensor); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
void register_switch(switch_::Switch *a_switch) { this->switches_.push_back(a_switch); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_BUTTON
|
||||
void register_button(button::Button *button) { this->buttons_.push_back(button); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void register_text_sensor(text_sensor::TextSensor *sensor) { this->text_sensors_.push_back(sensor); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
void register_fan(fan::Fan *state) { this->fans_.push_back(state); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
void register_cover(cover::Cover *cover) { this->covers_.push_back(cover); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
void register_climate(climate::Climate *climate) { this->climates_.push_back(climate); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
void register_light(light::LightState *light) { this->lights_.push_back(light); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
void register_number(number::Number *number) { this->numbers_.push_back(number); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void register_date(datetime::DateEntity *date) { this->dates_.push_back(date); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_TIME
|
||||
void register_time(datetime::TimeEntity *time) { this->times_.push_back(time); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
void register_datetime(datetime::DateTimeEntity *datetime) { this->datetimes_.push_back(datetime); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
void register_text(text::Text *text) { this->texts_.push_back(text); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
void register_select(select::Select *select) { this->selects_.push_back(select); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void register_lock(lock::Lock *a_lock) { this->locks_.push_back(a_lock); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_VALVE
|
||||
void register_valve(valve::Valve *valve) { this->valves_.push_back(valve); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
void register_media_player(media_player::MediaPlayer *media_player) { this->media_players_.push_back(media_player); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
void register_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
|
||||
this->alarm_control_panels_.push_back(a_alarm_control_panel);
|
||||
template<typename Entity> const std::vector<Entity *> &get_entities() {
|
||||
return get_by_type<std::vector<Entity *>>(entities_);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void register_event(event::Event *event) { this->events_.push_back(event); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
void register_update(update::UpdateEntity *update) { this->updates_.push_back(update); }
|
||||
#endif
|
||||
template<typename Entity> Entity *get_entity_by_key(uint32_t key, bool include_internal) {
|
||||
for (auto *obj : this->get_entities<Entity>()) {
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Register the component in this Application instance.
|
||||
template<class C> C *register_component(C *c) {
|
||||
|
@ -244,197 +179,142 @@ class Application {
|
|||
uint32_t get_app_state() const { return this->app_state_; }
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
const std::vector<binary_sensor::BinarySensor *> &get_binary_sensors() { return this->binary_sensors_; }
|
||||
const std::vector<binary_sensor::BinarySensor *> &get_binary_sensors() {
|
||||
return this->get_entities<binary_sensor::BinarySensor>();
|
||||
}
|
||||
binary_sensor::BinarySensor *get_binary_sensor_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->binary_sensors_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<binary_sensor::BinarySensor>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
const std::vector<switch_::Switch *> &get_switches() { return this->switches_; }
|
||||
const std::vector<switch_::Switch *> &get_switches() { return this->get_entities<switch_::Switch>(); }
|
||||
switch_::Switch *get_switch_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->switches_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<switch_::Switch>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
const std::vector<button::Button *> &get_buttons() { return this->buttons_; }
|
||||
const std::vector<button::Button *> &get_buttons() { return this->get_entities<button::Button>(); }
|
||||
button::Button *get_button_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->buttons_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<button::Button>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
const std::vector<sensor::Sensor *> &get_sensors() { return this->sensors_; }
|
||||
const std::vector<sensor::Sensor *> &get_sensors() { return this->get_entities<sensor::Sensor>(); }
|
||||
sensor::Sensor *get_sensor_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->sensors_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<sensor::Sensor>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
const std::vector<text_sensor::TextSensor *> &get_text_sensors() { return this->text_sensors_; }
|
||||
const std::vector<text_sensor::TextSensor *> &get_text_sensors() {
|
||||
return this->get_entities<text_sensor::TextSensor>();
|
||||
}
|
||||
text_sensor::TextSensor *get_text_sensor_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->text_sensors_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<text_sensor::TextSensor>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
const std::vector<fan::Fan *> &get_fans() { return this->fans_; }
|
||||
const std::vector<fan::Fan *> &get_fans() { return this->get_entities<fan::Fan>(); }
|
||||
fan::Fan *get_fan_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->fans_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<fan::Fan>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
const std::vector<cover::Cover *> &get_covers() { return this->covers_; }
|
||||
const std::vector<cover::Cover *> &get_covers() { return this->get_entities<cover::Cover>(); }
|
||||
cover::Cover *get_cover_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->covers_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<cover::Cover>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
const std::vector<light::LightState *> &get_lights() { return this->lights_; }
|
||||
const std::vector<light::LightState *> &get_lights() { return this->get_entities<light::LightState>(); }
|
||||
light::LightState *get_light_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->lights_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<light::LightState>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
const std::vector<climate::Climate *> &get_climates() { return this->climates_; }
|
||||
const std::vector<climate::Climate *> &get_climates() { return this->get_entities<climate::Climate>(); }
|
||||
climate::Climate *get_climate_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->climates_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<climate::Climate>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
const std::vector<number::Number *> &get_numbers() { return this->numbers_; }
|
||||
const std::vector<number::Number *> &get_numbers() { return this->get_entities<number::Number>(); }
|
||||
number::Number *get_number_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->numbers_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<number::Number>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
const std::vector<datetime::DateEntity *> &get_dates() { return this->dates_; }
|
||||
const std::vector<datetime::DateEntity *> &get_dates() { return this->get_entities<datetime::DateEntity>(); }
|
||||
datetime::DateEntity *get_date_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->dates_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<datetime::DateEntity>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
const std::vector<datetime::TimeEntity *> &get_times() { return this->times_; }
|
||||
const std::vector<datetime::TimeEntity *> &get_times() { return this->get_entities<datetime::TimeEntity>(); }
|
||||
datetime::TimeEntity *get_time_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->times_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<datetime::TimeEntity>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
const std::vector<datetime::DateTimeEntity *> &get_datetimes() { return this->datetimes_; }
|
||||
const std::vector<datetime::DateTimeEntity *> &get_datetimes() {
|
||||
return this->get_entities<datetime::DateTimeEntity>();
|
||||
}
|
||||
datetime::DateTimeEntity *get_datetime_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->datetimes_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<datetime::DateTimeEntity>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
const std::vector<text::Text *> &get_texts() { return this->texts_; }
|
||||
const std::vector<text::Text *> &get_texts() { return this->get_entities<text::Text>(); }
|
||||
text::Text *get_text_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->texts_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<text::Text>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
const std::vector<select::Select *> &get_selects() { return this->selects_; }
|
||||
const std::vector<select::Select *> &get_selects() { return this->get_entities<select::Select>(); }
|
||||
select::Select *get_select_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->selects_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<select::Select>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
const std::vector<lock::Lock *> &get_locks() { return this->locks_; }
|
||||
const std::vector<lock::Lock *> &get_locks() { return this->get_entities<lock::Lock>(); }
|
||||
lock::Lock *get_lock_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->locks_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<lock::Lock>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
const std::vector<valve::Valve *> &get_valves() { return this->valves_; }
|
||||
const std::vector<valve::Valve *> &get_valves() { return this->get_entities<valve::Valve>(); }
|
||||
valve::Valve *get_valve_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->valves_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<valve::Valve>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
const std::vector<media_player::MediaPlayer *> &get_media_players() { return this->media_players_; }
|
||||
const std::vector<media_player::MediaPlayer *> &get_media_players() {
|
||||
return this->get_entities<media_player::MediaPlayer>();
|
||||
}
|
||||
media_player::MediaPlayer *get_media_player_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->media_players_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<media_player::MediaPlayer>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
const std::vector<alarm_control_panel::AlarmControlPanel *> &get_alarm_control_panels() {
|
||||
return this->alarm_control_panels_;
|
||||
return this->get_entities<alarm_control_panel::AlarmControlPanel>();
|
||||
}
|
||||
alarm_control_panel::AlarmControlPanel *get_alarm_control_panel_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->alarm_control_panels_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<alarm_control_panel::AlarmControlPanel>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
const std::vector<event::Event *> &get_events() { return this->events_; }
|
||||
const std::vector<event::Event *> &get_events() { return this->get_entities<event::Event>(); }
|
||||
event::Event *get_event_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->events_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<event::Event>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
const std::vector<update::UpdateEntity *> &get_updates() { return this->updates_; }
|
||||
const std::vector<update::UpdateEntity *> &get_updates() { return this->get_entities<update::UpdateEntity>(); }
|
||||
update::UpdateEntity *get_update_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->updates_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
return this->get_entity_by_key<update::UpdateEntity>(key, include_internal);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -452,70 +332,6 @@ class Application {
|
|||
std::vector<Component *> components_{};
|
||||
std::vector<Component *> looping_components_{};
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
std::vector<binary_sensor::BinarySensor *> binary_sensors_{};
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
std::vector<switch_::Switch *> switches_{};
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
std::vector<button::Button *> buttons_{};
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
std::vector<event::Event *> events_{};
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
std::vector<sensor::Sensor *> sensors_{};
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
std::vector<text_sensor::TextSensor *> text_sensors_{};
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
std::vector<fan::Fan *> fans_{};
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
std::vector<cover::Cover *> covers_{};
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
std::vector<climate::Climate *> climates_{};
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
std::vector<light::LightState *> lights_{};
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
std::vector<number::Number *> numbers_{};
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
std::vector<datetime::DateEntity *> dates_{};
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
std::vector<datetime::TimeEntity *> times_{};
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
std::vector<datetime::DateTimeEntity *> datetimes_{};
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
std::vector<select::Select *> selects_{};
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
std::vector<text::Text *> texts_{};
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
std::vector<lock::Lock *> locks_{};
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
std::vector<valve::Valve *> valves_{};
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
std::vector<media_player::MediaPlayer *> media_players_{};
|
||||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
std::vector<alarm_control_panel::AlarmControlPanel *> alarm_control_panels_{};
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
std::vector<update::UpdateEntity *> updates_{};
|
||||
#endif
|
||||
|
||||
std::string name_;
|
||||
std::string friendly_name_;
|
||||
std::string area_;
|
||||
|
@ -526,6 +342,7 @@ class Application {
|
|||
uint32_t loop_interval_{16};
|
||||
size_t dump_config_at_{SIZE_MAX};
|
||||
uint32_t app_state_{0};
|
||||
entityRegistry<entities_t>::type entities_;
|
||||
};
|
||||
|
||||
/// Global storage of Application pointer - only one Application can exist.
|
||||
|
|
78
esphome/core/entities.h
Normal file
78
esphome/core/entities.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
#pragma once
|
||||
|
||||
// This file is not used by the runtime, instead, a version is generated during
|
||||
// compilation with only the relevant feature flags for the current build.
|
||||
//
|
||||
// This file is only used by static analyzers and IDEs.
|
||||
|
||||
namespace esphome {
|
||||
namespace update {
|
||||
class UpdateEntity;
|
||||
}
|
||||
namespace alarm_control_panel {
|
||||
class AlarmControlPanel;
|
||||
}
|
||||
namespace number {
|
||||
class Number;
|
||||
}
|
||||
namespace light {
|
||||
class LightState;
|
||||
}
|
||||
namespace switch_ {
|
||||
class Switch;
|
||||
}
|
||||
namespace climate {
|
||||
class Climate;
|
||||
}
|
||||
namespace fan {
|
||||
class Fan;
|
||||
}
|
||||
namespace datetime {
|
||||
class DateTimeEntity;
|
||||
}
|
||||
namespace binary_sensor {
|
||||
class BinarySensor;
|
||||
}
|
||||
namespace datetime {
|
||||
class TimeEntity;
|
||||
}
|
||||
namespace text {
|
||||
class Text;
|
||||
}
|
||||
namespace lock {
|
||||
class Lock;
|
||||
}
|
||||
namespace event {
|
||||
class Event;
|
||||
}
|
||||
namespace text_sensor {
|
||||
class TextSensor;
|
||||
}
|
||||
namespace datetime {
|
||||
class DateEntity;
|
||||
}
|
||||
namespace cover {
|
||||
class Cover;
|
||||
}
|
||||
namespace sensor {
|
||||
class Sensor;
|
||||
}
|
||||
namespace select {
|
||||
class Select;
|
||||
}
|
||||
namespace valve {
|
||||
class Valve;
|
||||
}
|
||||
namespace button {
|
||||
class Button;
|
||||
}
|
||||
namespace media_player {
|
||||
class MediaPlayer;
|
||||
}
|
||||
using entities_t =
|
||||
std::tuple<update::UpdateEntity *, alarm_control_panel::AlarmControlPanel *, number::Number *, light::LightState *,
|
||||
switch_::Switch *, climate::Climate *, fan::Fan *, datetime::DateTimeEntity *,
|
||||
binary_sensor::BinarySensor *, datetime::TimeEntity *, text::Text *, lock::Lock *, event::Event *,
|
||||
text_sensor::TextSensor *, datetime::DateEntity *, cover::Cover *, sensor::Sensor *, select::Select *,
|
||||
valve::Valve *, button::Button *, media_player::MediaPlayer *>;
|
||||
} // namespace esphome
|
|
@ -715,4 +715,22 @@ std::string hexencode(const T &data) {
|
|||
|
||||
///@}
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Helper struct to get the index of a type T in a parameter pack
|
||||
template<typename T, typename... Args> struct index_of;
|
||||
|
||||
template<typename T, typename... Args> struct index_of<T, T, Args...> {
|
||||
static constexpr size_t value = 0; // NOLINT(readability-identifier-naming)
|
||||
};
|
||||
|
||||
template<typename T, typename U, typename... Args> struct index_of<T, U, Args...> {
|
||||
static constexpr size_t value = 1 + index_of<T, Args...>::value; // NOLINT(readability-identifier-naming)
|
||||
};
|
||||
} // end namespace detail
|
||||
|
||||
template<typename T, typename... Args> T &get_by_type(std::tuple<Args...> &tup) {
|
||||
return std::get<detail::index_of<T, Args...>::value>(tup);
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
|
|
@ -619,6 +619,16 @@ def add_define(name: str, value: SafeExpType = None):
|
|||
CORE.add_define(Define(name, safe_exp(value)))
|
||||
|
||||
|
||||
def register_entity(class_, var):
|
||||
"""Add an entity to the codegen section."""
|
||||
add(MockObj(f"App.register_entity<{class_}>")(var))
|
||||
|
||||
|
||||
def define_entity(class_):
|
||||
"""Add a entity to std::tuple inside the auto-generated entities.h file."""
|
||||
CORE.define_entity(str(class_))
|
||||
|
||||
|
||||
def add_platformio_option(key: str, value: Union[str, list[str]]):
|
||||
CORE.add_platformio_option(key, value)
|
||||
|
||||
|
|
|
@ -216,8 +216,16 @@ VERSION_H_FORMAT = """\
|
|||
#define ESPHOME_VERSION "{}"
|
||||
#define ESPHOME_VERSION_CODE VERSION_CODE({}, {}, {})
|
||||
"""
|
||||
ENTITIES_H_FORMAT = """\
|
||||
#pragma once
|
||||
namespace esphome {{
|
||||
{}
|
||||
using entities_t = std::tuple<{}>;
|
||||
}}
|
||||
"""
|
||||
DEFINES_H_TARGET = "esphome/core/defines.h"
|
||||
VERSION_H_TARGET = "esphome/core/version.h"
|
||||
ENTITIES_H_TARGET = "esphome/core/entities.h"
|
||||
ESPHOME_README_TXT = """
|
||||
THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY
|
||||
|
||||
|
@ -251,7 +259,9 @@ def copy_src_tree():
|
|||
include_s = "\n".join(include_l)
|
||||
|
||||
source_files_copy = source_files_map.copy()
|
||||
ignore_targets = [Path(x) for x in (DEFINES_H_TARGET, VERSION_H_TARGET)]
|
||||
ignore_targets = [
|
||||
Path(x) for x in (DEFINES_H_TARGET, VERSION_H_TARGET, ENTITIES_H_TARGET)
|
||||
]
|
||||
for t in ignore_targets:
|
||||
source_files_copy.pop(t)
|
||||
|
||||
|
@ -290,6 +300,9 @@ def copy_src_tree():
|
|||
write_file_if_changed(
|
||||
CORE.relative_src_path("esphome", "core", "version.h"), generate_version_h()
|
||||
)
|
||||
write_file_if_changed(
|
||||
CORE.relative_src_path("esphome", "core", "entities.h"), generate_entities_h()
|
||||
)
|
||||
|
||||
if CORE.is_esp32:
|
||||
from esphome.components.esp32 import copy_files
|
||||
|
@ -327,6 +340,20 @@ def generate_version_h():
|
|||
)
|
||||
|
||||
|
||||
def generate_entities_h():
|
||||
forward_declaration_l = []
|
||||
entities_l = []
|
||||
for type in CORE.entities:
|
||||
namespace = type.split("::")[0]
|
||||
class_ = type.split("::")[1]
|
||||
forward_declaration_l += [f"""namespace {namespace} {{class {class_}; }}"""]
|
||||
entities_l += [f"""{type} *"""]
|
||||
|
||||
return ENTITIES_H_FORMAT.format(
|
||||
"\n".join(forward_declaration_l), ", ".join(entities_l)
|
||||
)
|
||||
|
||||
|
||||
def write_cpp(code_s):
|
||||
path = CORE.relative_src_path("main.cpp")
|
||||
if os.path.isfile(path):
|
||||
|
|
|
@ -14,7 +14,7 @@ def test_binary_sensor_is_setup(generate_main):
|
|||
|
||||
# Then
|
||||
assert "new gpio::GPIOBinarySensor();" in main_cpp
|
||||
assert "App.register_binary_sensor" in main_cpp
|
||||
assert "App.register_entity<binary_sensor::BinarySensor>" in main_cpp
|
||||
|
||||
|
||||
def test_binary_sensor_sets_mandatory_fields(generate_main):
|
||||
|
|
|
@ -12,7 +12,7 @@ def test_button_is_setup(generate_main):
|
|||
|
||||
# Then
|
||||
assert "new wake_on_lan::WakeOnLanButton();" in main_cpp
|
||||
assert "App.register_button" in main_cpp
|
||||
assert "App.register_entity<button::Button>" in main_cpp
|
||||
assert "App.register_component" in main_cpp
|
||||
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ def test_text_is_setup(generate_main):
|
|||
|
||||
# Then
|
||||
assert "new template_::TemplateText();" in main_cpp
|
||||
assert "App.register_text" in main_cpp
|
||||
assert "App.register_entity<text::Text>" in main_cpp
|
||||
|
||||
|
||||
def test_text_sets_mandatory_fields(generate_main):
|
||||
|
|
|
@ -12,7 +12,7 @@ def test_text_sensor_is_setup(generate_main):
|
|||
|
||||
# Then
|
||||
assert "new template_::TemplateTextSensor();" in main_cpp
|
||||
assert "App.register_text_sensor" in main_cpp
|
||||
assert "App.register_entity<text_sensor::TextSensor>" in main_cpp
|
||||
|
||||
|
||||
def test_text_sensor_sets_mandatory_fields(generate_main):
|
||||
|
|
Loading…
Reference in a new issue