mirror of
https://github.com/esphome/esphome.git
synced 2025-08-10 20:29:24 +00:00
migrate to using same area info for top level and sub devices
This commit is contained in:
@@ -188,7 +188,7 @@ message DeviceInfoRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message SubAreaInfo {
|
||||
message AreaInfo {
|
||||
uint32 area_id = 1;
|
||||
string name = 2;
|
||||
}
|
||||
@@ -249,7 +249,10 @@ message DeviceInfoResponse {
|
||||
bool api_encryption_supported = 19;
|
||||
|
||||
repeated SubDeviceInfo sub_devices = 20;
|
||||
repeated SubAreaInfo sub_areas = 21;
|
||||
repeated AreaInfo areas = 21;
|
||||
|
||||
// Top-level area info to phase out suggested_area
|
||||
AreaInfo area = 22;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
|
@@ -1628,11 +1628,13 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
sub_device_info.area_id = sub_device->get_area_id();
|
||||
resp.sub_devices.push_back(sub_device_info);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
for (auto const &area : App.get_areas()) {
|
||||
SubAreaInfo area_info;
|
||||
AreaInfo area_info;
|
||||
area_info.area_id = area->get_area_id();
|
||||
area_info.name = area->get_name();
|
||||
resp.sub_areas.push_back(area_info);
|
||||
resp.areas.push_back(area_info);
|
||||
}
|
||||
#endif
|
||||
return resp;
|
||||
|
@@ -812,7 +812,7 @@ void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); }
|
||||
#endif
|
||||
bool SubAreaInfo::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
bool AreaInfo::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->area_id = value.as_uint32();
|
||||
@@ -822,7 +822,7 @@ bool SubAreaInfo::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool SubAreaInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
bool AreaInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->name = value.as_string();
|
||||
@@ -832,18 +832,18 @@ bool SubAreaInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void SubAreaInfo::encode(ProtoWriteBuffer buffer) const {
|
||||
void AreaInfo::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(1, this->area_id);
|
||||
buffer.encode_string(2, this->name);
|
||||
}
|
||||
void SubAreaInfo::calculate_size(uint32_t &total_size) const {
|
||||
void AreaInfo::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->area_id, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->name, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void SubAreaInfo::dump_to(std::string &out) const {
|
||||
void AreaInfo::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("SubAreaInfo {\n");
|
||||
out.append("AreaInfo {\n");
|
||||
out.append(" area_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->area_id);
|
||||
out.append(buffer);
|
||||
@@ -998,7 +998,11 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
|
||||
return true;
|
||||
}
|
||||
case 21: {
|
||||
this->sub_areas.push_back(value.as_message<SubAreaInfo>());
|
||||
this->areas.push_back(value.as_message<AreaInfo>());
|
||||
return true;
|
||||
}
|
||||
case 22: {
|
||||
this->area = value.as_message<AreaInfo>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
@@ -1028,9 +1032,10 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->sub_devices) {
|
||||
buffer.encode_message<SubDeviceInfo>(20, it, true);
|
||||
}
|
||||
for (auto &it : this->sub_areas) {
|
||||
buffer.encode_message<SubAreaInfo>(21, it, true);
|
||||
for (auto &it : this->areas) {
|
||||
buffer.encode_message<AreaInfo>(21, it, true);
|
||||
}
|
||||
buffer.encode_message<AreaInfo>(22, this->area);
|
||||
}
|
||||
void DeviceInfoResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->uses_password, false);
|
||||
@@ -1053,7 +1058,8 @@ void DeviceInfoResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 2, this->bluetooth_mac_address, false);
|
||||
ProtoSize::add_bool_field(total_size, 2, this->api_encryption_supported, false);
|
||||
ProtoSize::add_repeated_message(total_size, 2, this->sub_devices);
|
||||
ProtoSize::add_repeated_message(total_size, 2, this->sub_areas);
|
||||
ProtoSize::add_repeated_message(total_size, 2, this->areas);
|
||||
ProtoSize::add_message_object(total_size, 2, this->area, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
@@ -1146,11 +1152,15 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
for (const auto &it : this->sub_areas) {
|
||||
out.append(" sub_areas: ");
|
||||
for (const auto &it : this->areas) {
|
||||
out.append(" areas: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" area: ");
|
||||
this->area.dump_to(out);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@@ -416,7 +416,7 @@ class DeviceInfoRequest : public ProtoMessage {
|
||||
|
||||
protected:
|
||||
};
|
||||
class SubAreaInfo : public ProtoMessage {
|
||||
class AreaInfo : public ProtoMessage {
|
||||
public:
|
||||
uint32_t area_id{0};
|
||||
std::string name{};
|
||||
@@ -448,7 +448,7 @@ class SubDeviceInfo : public ProtoMessage {
|
||||
class DeviceInfoResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 10;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 201;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 219;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "device_info_response"; }
|
||||
#endif
|
||||
@@ -472,7 +472,8 @@ class DeviceInfoResponse : public ProtoMessage {
|
||||
std::string bluetooth_mac_address{};
|
||||
bool api_encryption_supported{false};
|
||||
std::vector<SubDeviceInfo> sub_devices{};
|
||||
std::vector<SubAreaInfo> sub_areas{};
|
||||
std::vector<AreaInfo> areas{};
|
||||
AreaInfo area{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
|
@@ -11,7 +11,9 @@
|
||||
|
||||
#ifdef USE_SUB_DEVICE
|
||||
#include "esphome/core/sub_device.h"
|
||||
#include "esphome/core/sub_area.h"
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
#include "esphome/core/area.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
@@ -92,7 +94,7 @@ static const uint32_t TEARDOWN_TIMEOUT_REBOOT_MS = 1000; // 1 second for quick
|
||||
|
||||
class Application {
|
||||
public:
|
||||
void pre_setup(const std::string &name, const std::string &friendly_name, const char *area, const char *comment,
|
||||
void pre_setup(const std::string &name, const std::string &friendly_name, const char *comment,
|
||||
const char *compilation_time, bool name_add_mac_suffix) {
|
||||
arch_init();
|
||||
this->name_add_mac_suffix_ = name_add_mac_suffix;
|
||||
@@ -107,14 +109,16 @@ class Application {
|
||||
this->name_ = name;
|
||||
this->friendly_name_ = friendly_name;
|
||||
}
|
||||
this->area_ = area;
|
||||
// area is now handled through the areas system
|
||||
this->comment_ = comment;
|
||||
this->compilation_time_ = compilation_time;
|
||||
}
|
||||
|
||||
#ifdef USE_SUB_DEVICE
|
||||
void register_sub_device(SubDevice *sub_device) { this->sub_devices_.push_back(sub_device); }
|
||||
void register_area(SubArea *area) { this->areas_.push_back(area); }
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
void register_area(Area *area) { this->areas_.push_back(area); }
|
||||
#endif
|
||||
|
||||
void set_current_component(Component *component) { this->current_component_ = component; }
|
||||
@@ -295,7 +299,15 @@ class Application {
|
||||
const std::string &get_friendly_name() const { return this->friendly_name_; }
|
||||
|
||||
/// Get the area of this Application set by pre_setup().
|
||||
std::string get_area() const { return this->area_ == nullptr ? "" : this->area_; }
|
||||
std::string get_area() const {
|
||||
#ifdef USE_AREAS
|
||||
// If we have areas registered, return the name of the first one (which is the top-level area)
|
||||
if (!this->areas_.empty() && this->areas_[0] != nullptr) {
|
||||
return this->areas_[0]->get_name();
|
||||
}
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
|
||||
/// Get the comment of this Application set by pre_setup().
|
||||
std::string get_comment() const { return this->comment_; }
|
||||
@@ -346,7 +358,9 @@ class Application {
|
||||
|
||||
#ifdef USE_SUB_DEVICE
|
||||
const std::vector<SubDevice *> &get_sub_devices() { return this->sub_devices_; }
|
||||
const std::vector<SubArea *> &get_areas() { return this->areas_; }
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
const std::vector<Area *> &get_areas() { return this->areas_; }
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
const std::vector<binary_sensor::BinarySensor *> &get_binary_sensors() { return this->binary_sensors_; }
|
||||
@@ -626,7 +640,9 @@ class Application {
|
||||
|
||||
#ifdef USE_SUB_DEVICE
|
||||
std::vector<SubDevice *> sub_devices_{};
|
||||
std::vector<SubArea *> areas_{};
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
std::vector<Area *> areas_{};
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
std::vector<binary_sensor::BinarySensor *> binary_sensors_{};
|
||||
@@ -694,7 +710,6 @@ class Application {
|
||||
|
||||
std::string name_;
|
||||
std::string friendly_name_;
|
||||
const char *area_{nullptr};
|
||||
const char *comment_{nullptr};
|
||||
const char *compilation_time_{nullptr};
|
||||
bool name_add_mac_suffix_;
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome {
|
||||
|
||||
class SubArea {
|
||||
class Area {
|
||||
public:
|
||||
void set_area_id(uint32_t area_id) { area_id_ = area_id; }
|
||||
uint32_t get_area_id() { return area_id_; }
|
@@ -40,6 +40,7 @@ from esphome.helpers import (
|
||||
copy_file_if_changed,
|
||||
fnv1a_32bit_hash,
|
||||
get_str_env,
|
||||
slugify,
|
||||
walk_files,
|
||||
)
|
||||
|
||||
@@ -58,7 +59,7 @@ ProjectUpdateTrigger = cg.esphome_ns.class_(
|
||||
"ProjectUpdateTrigger", cg.Component, automation.Trigger.template(cg.std_string)
|
||||
)
|
||||
SubDevice = cg.esphome_ns.class_("SubDevice")
|
||||
SubArea = cg.esphome_ns.class_("SubArea")
|
||||
Area = cg.esphome_ns.class_("Area")
|
||||
|
||||
VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"}
|
||||
|
||||
@@ -127,7 +128,15 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.valid_name,
|
||||
cv.Optional(CONF_FRIENDLY_NAME, ""): cv.string,
|
||||
cv.Optional(CONF_AREA, ""): cv.string,
|
||||
cv.Optional(CONF_AREA): cv.Any(
|
||||
cv.string, # Old way: just a string
|
||||
cv.Schema( # New way: structured area
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(Area),
|
||||
cv.Required(CONF_NAME): cv.string,
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_COMMENT): cv.string,
|
||||
cv.Required(CONF_BUILD_PATH): cv.string,
|
||||
cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema(
|
||||
@@ -180,7 +189,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_SUB_AREAS, default=[]): cv.ensure_list(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(SubArea),
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(Area),
|
||||
cv.Required(CONF_NAME): cv.string,
|
||||
}
|
||||
),
|
||||
@@ -190,7 +199,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(SubDevice),
|
||||
cv.Required(CONF_NAME): cv.string,
|
||||
cv.Optional(CONF_AREA_ID): cv.use_id(SubArea),
|
||||
cv.Optional(CONF_AREA_ID): cv.use_id(Area),
|
||||
}
|
||||
),
|
||||
),
|
||||
@@ -374,7 +383,6 @@ async def to_code(config):
|
||||
cg.App.pre_setup(
|
||||
config[CONF_NAME],
|
||||
config[CONF_FRIENDLY_NAME],
|
||||
config[CONF_AREA],
|
||||
config.get(CONF_COMMENT, ""),
|
||||
cg.RawExpression('__DATE__ ", " __TIME__'),
|
||||
config[CONF_NAME_ADD_MAC_SUFFIX],
|
||||
@@ -445,6 +453,38 @@ async def to_code(config):
|
||||
if config[CONF_PLATFORMIO_OPTIONS]:
|
||||
CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS])
|
||||
|
||||
# Handle area configuration
|
||||
if area_conf := config.get(CONF_AREA):
|
||||
if isinstance(area_conf, dict):
|
||||
# New way: structured area configuration
|
||||
area_var = cg.new_Pvariable(area_conf[CONF_ID])
|
||||
area_id = fnv1a_32bit_hash(str(area_conf[CONF_ID]))
|
||||
area_name = area_conf[CONF_NAME]
|
||||
else:
|
||||
# Old way: string-based area (deprecated)
|
||||
area_slug = slugify(area_conf)
|
||||
_LOGGER.warning(
|
||||
"Using 'area' as a string is deprecated. Please use the new format:\n"
|
||||
"area:\n"
|
||||
" id: %s\n"
|
||||
' name: "%s"',
|
||||
area_slug,
|
||||
area_conf,
|
||||
)
|
||||
# Create a synthetic area for backwards compatibility
|
||||
area_var = cg.new_Pvariable(
|
||||
cg.ID(f"area_{area_slug}", is_declaration=True, type=Area)
|
||||
)
|
||||
area_id = fnv1a_32bit_hash(area_conf)
|
||||
area_name = area_conf
|
||||
|
||||
# Common setup for both ways
|
||||
cg.add(area_var.set_area_id(area_id))
|
||||
cg.add(area_var.set_name(area_name))
|
||||
cg.add(cg.App.register_area(area_var))
|
||||
# Define USE_AREAS to enable area processing
|
||||
cg.add_define("USE_AREAS")
|
||||
|
||||
# Process sub-devices and areas
|
||||
if sub_devices := config.get(CONF_SUB_DEVICES):
|
||||
# Process areas first
|
||||
@@ -455,6 +495,8 @@ async def to_code(config):
|
||||
cg.add(area.set_area_id(area_id))
|
||||
cg.add(area.set_name(area_conf[CONF_NAME]))
|
||||
cg.add(cg.App.register_area(area))
|
||||
# Define USE_AREAS since we have areas
|
||||
cg.add_define("USE_AREAS")
|
||||
|
||||
# Process sub-devices
|
||||
for dev_conf in sub_devices:
|
||||
|
@@ -1,25 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unicodedata
|
||||
|
||||
from esphome.const import ALLOWED_NAME_CHARS
|
||||
|
||||
|
||||
def strip_accents(value):
|
||||
return "".join(
|
||||
c
|
||||
for c in unicodedata.normalize("NFD", str(value))
|
||||
if unicodedata.category(c) != "Mn"
|
||||
)
|
||||
from esphome.helpers import slugify
|
||||
|
||||
|
||||
def friendly_name_slugify(value):
|
||||
value = (
|
||||
strip_accents(value)
|
||||
.lower()
|
||||
.replace(" ", "-")
|
||||
.replace("_", "-")
|
||||
.replace("--", "-")
|
||||
.strip("-")
|
||||
)
|
||||
return "".join(c for c in value if c in ALLOWED_NAME_CHARS)
|
||||
"""Convert a friendly name to a slug with dashes instead of underscores."""
|
||||
# First use the standard slugify, then convert underscores to dashes
|
||||
return slugify(value).replace("_", "-")
|
||||
|
@@ -38,6 +38,32 @@ def fnv1a_32bit_hash(string: str) -> int:
|
||||
return hash_value
|
||||
|
||||
|
||||
def strip_accents(value: str) -> str:
|
||||
"""Remove accents from a string."""
|
||||
import unicodedata
|
||||
|
||||
return "".join(
|
||||
c
|
||||
for c in unicodedata.normalize("NFD", str(value))
|
||||
if unicodedata.category(c) != "Mn"
|
||||
)
|
||||
|
||||
|
||||
def slugify(value: str) -> str:
|
||||
"""Convert a string to a valid C++ identifier slug."""
|
||||
from esphome.const import ALLOWED_NAME_CHARS
|
||||
|
||||
value = (
|
||||
strip_accents(value)
|
||||
.lower()
|
||||
.replace(" ", "_")
|
||||
.replace("-", "_")
|
||||
.replace("__", "_")
|
||||
.strip("_")
|
||||
)
|
||||
return "".join(c for c in value if c in ALLOWED_NAME_CHARS)
|
||||
|
||||
|
||||
def indent_all_but_first_and_last(text, padding=" "):
|
||||
lines = text.splitlines(True)
|
||||
if len(lines) <= 2:
|
||||
|
Reference in New Issue
Block a user