mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 13:34:54 +01:00
Add friendly_name to device (#4296)
This commit is contained in:
parent
3d2d681a7b
commit
c301ae3645
18 changed files with 137 additions and 18 deletions
|
@ -206,6 +206,8 @@ message DeviceInfoResponse {
|
|||
uint32 bluetooth_proxy_version = 11;
|
||||
|
||||
string manufacturer = 12;
|
||||
|
||||
string friendly_name = 13;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
|
|
|
@ -930,6 +930,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
|||
DeviceInfoResponse resp{};
|
||||
resp.uses_password = this->parent_->uses_password();
|
||||
resp.name = App.get_name();
|
||||
resp.friendly_name = App.get_friendly_name();
|
||||
resp.mac_address = get_mac_address_pretty();
|
||||
resp.esphome_version = ESPHOME_VERSION;
|
||||
resp.compilation_time = App.get_compilation_time();
|
||||
|
|
|
@ -628,6 +628,10 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
|
|||
this->manufacturer = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 13: {
|
||||
this->friendly_name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -645,6 +649,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_uint32(10, this->webserver_port);
|
||||
buffer.encode_uint32(11, this->bluetooth_proxy_version);
|
||||
buffer.encode_string(12, this->manufacturer);
|
||||
buffer.encode_string(13, this->friendly_name);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
|
@ -699,6 +704,10 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
|||
out.append(" manufacturer: ");
|
||||
out.append("'").append(this->manufacturer).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" friendly_name: ");
|
||||
out.append("'").append(this->friendly_name).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -276,6 +276,7 @@ class DeviceInfoResponse : public ProtoMessage {
|
|||
uint32_t webserver_port{0};
|
||||
uint32_t bluetooth_proxy_version{0};
|
||||
std::string manufacturer{};
|
||||
std::string friendly_name{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import git
|
||||
from esphome.components.packages import validate_source_shorthand
|
||||
from esphome.const import CONF_WIFI, CONF_REF
|
||||
from esphome.const import CONF_REF, CONF_WIFI
|
||||
from esphome.wizard import wizard_file
|
||||
from esphome.yaml_util import dump
|
||||
from esphome import git
|
||||
|
||||
|
||||
dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import")
|
||||
|
||||
|
@ -66,7 +67,12 @@ async def to_code(config):
|
|||
|
||||
|
||||
def import_config(
|
||||
path: str, name: str, project_name: str, import_url: str, network: str = CONF_WIFI
|
||||
path: str,
|
||||
name: str,
|
||||
friendly_name: Optional[str],
|
||||
project_name: str,
|
||||
import_url: str,
|
||||
network: str = CONF_WIFI,
|
||||
) -> None:
|
||||
p = Path(path)
|
||||
|
||||
|
@ -77,6 +83,7 @@ def import_config(
|
|||
p.write_text(
|
||||
wizard_file(
|
||||
name=name,
|
||||
friendly_name=friendly_name,
|
||||
platform="ESP32" if "esp32" in import_url else "ESP8266",
|
||||
board="esp32dev" if "esp32" in import_url else "esp01_1m",
|
||||
ssid="!secret wifi_ssid",
|
||||
|
@ -98,13 +105,15 @@ def import_config(
|
|||
p.write_text(req.text, encoding="utf8")
|
||||
|
||||
else:
|
||||
substitutions = {"name": name}
|
||||
esphome_core = {"name": "${name}", "name_add_mac_suffix": False}
|
||||
if friendly_name:
|
||||
substitutions["friendly_name"] = friendly_name
|
||||
esphome_core["friendly_name"] = "${friendly_name}"
|
||||
config = {
|
||||
"substitutions": {"name": name},
|
||||
"substitutions": substitutions,
|
||||
"packages": {project_name: import_url},
|
||||
"esphome": {
|
||||
"name": "${name}",
|
||||
"name_add_mac_suffix": False,
|
||||
},
|
||||
"esphome": esphome_core,
|
||||
}
|
||||
output = dump(config)
|
||||
|
||||
|
|
|
@ -30,6 +30,9 @@ void MDNSComponent::compile_records_() {
|
|||
service.service_type = "_esphomelib";
|
||||
service.proto = "_tcp";
|
||||
service.port = api::global_api_server->get_port();
|
||||
if (App.get_friendly_name().empty()) {
|
||||
service.txt_records.push_back({"friendly_name", App.get_friendly_name()});
|
||||
}
|
||||
service.txt_records.push_back({"version", ESPHOME_VERSION});
|
||||
service.txt_records.push_back({"mac", get_mac_address()});
|
||||
const char *platform = nullptr;
|
||||
|
|
|
@ -104,7 +104,7 @@ void WebServer::setup() {
|
|||
// Configure reconnect timeout and send config
|
||||
|
||||
client->send(json::build_json([this](JsonObject root) {
|
||||
root["title"] = App.get_name();
|
||||
root["title"] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name();
|
||||
root["ota"] = this->allow_ota_;
|
||||
root["lang"] = "en";
|
||||
}).c_str(),
|
||||
|
|
|
@ -260,6 +260,7 @@ CONF_FRAGMENTATION = "fragmentation"
|
|||
CONF_FRAMEWORK = "framework"
|
||||
CONF_FREE = "free"
|
||||
CONF_FREQUENCY = "frequency"
|
||||
CONF_FRIENDLY_NAME = "friendly_name"
|
||||
CONF_FROM = "from"
|
||||
CONF_FULL_SPECTRUM = "full_spectrum"
|
||||
CONF_FULL_UPDATE_EVERY = "full_update_every"
|
||||
|
|
|
@ -453,6 +453,8 @@ class EsphomeCore:
|
|||
self.ace = False
|
||||
# The name of the node
|
||||
self.name: Optional[str] = None
|
||||
# The friendly name of the node
|
||||
self.friendly_name: Optional[str] = None
|
||||
# Additional data components can store temporary data in
|
||||
# The first key to this dict should always be the integration name
|
||||
self.data = {}
|
||||
|
@ -492,6 +494,7 @@ class EsphomeCore:
|
|||
def reset(self):
|
||||
self.dashboard = False
|
||||
self.name = None
|
||||
self.friendly_name = None
|
||||
self.data = {}
|
||||
self.config_path = None
|
||||
self.build_path = None
|
||||
|
|
|
@ -53,13 +53,20 @@ namespace esphome {
|
|||
|
||||
class Application {
|
||||
public:
|
||||
void pre_setup(const std::string &name, const char *compilation_time, bool name_add_mac_suffix) {
|
||||
void pre_setup(const std::string &name, const std::string &friendly_name, const char *compilation_time,
|
||||
bool name_add_mac_suffix) {
|
||||
arch_init();
|
||||
this->name_add_mac_suffix_ = name_add_mac_suffix;
|
||||
if (name_add_mac_suffix) {
|
||||
this->name_ = name + "-" + get_mac_address().substr(6);
|
||||
if (friendly_name.empty()) {
|
||||
this->friendly_name_ = "";
|
||||
} else {
|
||||
this->friendly_name_ = friendly_name + " " + get_mac_address().substr(6);
|
||||
}
|
||||
} else {
|
||||
this->name_ = name;
|
||||
this->friendly_name_ = friendly_name;
|
||||
}
|
||||
this->compilation_time_ = compilation_time;
|
||||
}
|
||||
|
@ -134,6 +141,9 @@ class Application {
|
|||
/// Get the name of this Application set by set_name().
|
||||
const std::string &get_name() const { return this->name_; }
|
||||
|
||||
/// Get the friendly name of this Application set by set_friendly_name().
|
||||
const std::string &get_friendly_name() const { return this->friendly_name_; }
|
||||
|
||||
bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; }
|
||||
|
||||
const std::string &get_compilation_time() const { return this->compilation_time_; }
|
||||
|
@ -338,6 +348,7 @@ class Application {
|
|||
#endif
|
||||
|
||||
std::string name_;
|
||||
std::string friendly_name_;
|
||||
std::string compilation_time_;
|
||||
bool name_add_mac_suffix_;
|
||||
uint32_t last_loop_{0};
|
||||
|
|
|
@ -19,6 +19,7 @@ from esphome.const import (
|
|||
CONF_LIBRARIES,
|
||||
CONF_MIN_VERSION,
|
||||
CONF_NAME,
|
||||
CONF_FRIENDLY_NAME,
|
||||
CONF_ON_BOOT,
|
||||
CONF_ON_LOOP,
|
||||
CONF_ON_SHUTDOWN,
|
||||
|
@ -124,6 +125,7 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.valid_name,
|
||||
cv.Optional(CONF_FRIENDLY_NAME, ""): cv.string,
|
||||
cv.Optional(CONF_COMMENT): cv.string,
|
||||
cv.Required(CONF_BUILD_PATH): cv.string,
|
||||
cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema(
|
||||
|
@ -192,6 +194,7 @@ def preload_core_config(config, result):
|
|||
conf = PRELOAD_CONFIG_SCHEMA(config[CONF_ESPHOME])
|
||||
|
||||
CORE.name = conf[CONF_NAME]
|
||||
CORE.friendly_name = conf.get(CONF_FRIENDLY_NAME, CORE.name)
|
||||
CORE.data[KEY_CORE] = {}
|
||||
|
||||
if CONF_BUILD_PATH not in conf:
|
||||
|
@ -346,6 +349,7 @@ async def to_code(config):
|
|||
cg.add(
|
||||
cg.App.pre_setup(
|
||||
config[CONF_NAME],
|
||||
config[CONF_FRIENDLY_NAME],
|
||||
cg.RawExpression('__DATE__ ", " __TIME__'),
|
||||
config[CONF_NAME_ADD_MAC_SUFFIX],
|
||||
)
|
||||
|
|
|
@ -40,7 +40,7 @@ from esphome.storage_json import (
|
|||
from esphome.util import get_serial_ports, shlex_quote
|
||||
from esphome.zeroconf import DashboardImportDiscovery, DashboardStatus, EsphomeZeroconf
|
||||
|
||||
from .util import password_hash
|
||||
from .util import password_hash, friendly_name_slugify
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -390,12 +390,24 @@ class WizardRequestHandler(BaseHandler):
|
|||
for k, v in json.loads(self.request.body.decode()).items()
|
||||
if k in ("name", "platform", "board", "ssid", "psk", "password")
|
||||
}
|
||||
if not kwargs["name"]:
|
||||
self.set_status(422)
|
||||
self.set_header("content-type", "application/json")
|
||||
self.write(json.dumps({"error": "Name is required"}))
|
||||
return
|
||||
|
||||
kwargs["friendly_name"] = kwargs["name"]
|
||||
kwargs["name"] = friendly_name_slugify(kwargs["friendly_name"])
|
||||
|
||||
kwargs["ota_password"] = secrets.token_hex(16)
|
||||
noise_psk = secrets.token_bytes(32)
|
||||
kwargs["api_encryption_key"] = base64.b64encode(noise_psk).decode()
|
||||
destination = settings.rel_path(f"{kwargs['name']}.yaml")
|
||||
filename = f"{kwargs['name']}.yaml"
|
||||
destination = settings.rel_path(filename)
|
||||
wizard.wizard_write(path=destination, **kwargs)
|
||||
self.set_status(200)
|
||||
self.set_header("content-type", "application/json")
|
||||
self.write(json.dumps({"configuration": filename}))
|
||||
self.finish()
|
||||
|
||||
|
||||
|
@ -407,6 +419,7 @@ class ImportRequestHandler(BaseHandler):
|
|||
args = json.loads(self.request.body.decode())
|
||||
try:
|
||||
name = args["name"]
|
||||
friendly_name = args.get("friendly_name")
|
||||
|
||||
imported_device = next(
|
||||
(res for res in IMPORT_RESULT.values() if res.device_name == name), None
|
||||
|
@ -414,12 +427,15 @@ class ImportRequestHandler(BaseHandler):
|
|||
|
||||
if imported_device is not None:
|
||||
network = imported_device.network
|
||||
if friendly_name is None:
|
||||
friendly_name = imported_device.friendly_name
|
||||
else:
|
||||
network = const.CONF_WIFI
|
||||
|
||||
import_config(
|
||||
settings.rel_path(f"{name}.yaml"),
|
||||
name,
|
||||
friendly_name,
|
||||
args["project_name"],
|
||||
args["package_import_url"],
|
||||
network,
|
||||
|
@ -434,6 +450,8 @@ class ImportRequestHandler(BaseHandler):
|
|||
return
|
||||
|
||||
self.set_status(200)
|
||||
self.set_header("content-type", "application/json")
|
||||
self.write(json.dumps({"configuration": f"{name}.yaml"}))
|
||||
self.finish()
|
||||
|
||||
|
||||
|
@ -581,6 +599,12 @@ class DashboardEntry:
|
|||
return self.filename.replace(".yml", "").replace(".yaml", "")
|
||||
return self.storage.name
|
||||
|
||||
@property
|
||||
def friendly_name(self):
|
||||
if self.storage is None:
|
||||
return self.name
|
||||
return self.storage.friendly_name
|
||||
|
||||
@property
|
||||
def comment(self):
|
||||
if self.storage is None:
|
||||
|
@ -628,6 +652,7 @@ class ListDevicesHandler(BaseHandler):
|
|||
"configured": [
|
||||
{
|
||||
"name": entry.name,
|
||||
"friendly_name": entry.friendly_name,
|
||||
"configuration": entry.filename,
|
||||
"loaded_integrations": entry.loaded_integrations,
|
||||
"deployed_version": entry.update_old,
|
||||
|
@ -643,6 +668,7 @@ class ListDevicesHandler(BaseHandler):
|
|||
"importable": [
|
||||
{
|
||||
"name": res.device_name,
|
||||
"friendly_name": res.friendly_name,
|
||||
"package_import_url": res.package_import_url,
|
||||
"project_name": res.project_name,
|
||||
"project_version": res.project_version,
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import hashlib
|
||||
import unicodedata
|
||||
|
||||
from esphome.const import ALLOWED_NAME_CHARS
|
||||
|
||||
|
||||
def password_hash(password: str) -> bytes:
|
||||
|
@ -7,3 +10,23 @@ def password_hash(password: str) -> bytes:
|
|||
Note this is not meant for secure storage, but for securely comparing passwords.
|
||||
"""
|
||||
return hashlib.sha256(password.encode()).digest()
|
||||
|
||||
|
||||
def strip_accents(value):
|
||||
return "".join(
|
||||
c
|
||||
for c in unicodedata.normalize("NFD", str(value))
|
||||
if unicodedata.category(c) != "Mn"
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
|
|
|
@ -36,6 +36,7 @@ class StorageJSON:
|
|||
self,
|
||||
storage_version,
|
||||
name,
|
||||
friendly_name,
|
||||
comment,
|
||||
esphome_version,
|
||||
src_version,
|
||||
|
@ -51,6 +52,8 @@ class StorageJSON:
|
|||
self.storage_version: int = storage_version
|
||||
# The name of the node
|
||||
self.name: str = name
|
||||
# The friendly name of the node
|
||||
self.friendly_name: str = friendly_name
|
||||
# The comment of the node
|
||||
self.comment: str = comment
|
||||
# The esphome version this was compiled with
|
||||
|
@ -77,6 +80,7 @@ class StorageJSON:
|
|||
return {
|
||||
"storage_version": self.storage_version,
|
||||
"name": self.name,
|
||||
"friendly_name": self.friendly_name,
|
||||
"comment": self.comment,
|
||||
"esphome_version": self.esphome_version,
|
||||
"src_version": self.src_version,
|
||||
|
@ -106,6 +110,7 @@ class StorageJSON:
|
|||
return StorageJSON(
|
||||
storage_version=1,
|
||||
name=esph.name,
|
||||
friendly_name=esph.friendly_name,
|
||||
comment=esph.comment,
|
||||
esphome_version=const.__version__,
|
||||
src_version=1,
|
||||
|
@ -118,10 +123,13 @@ class StorageJSON:
|
|||
)
|
||||
|
||||
@staticmethod
|
||||
def from_wizard(name: str, address: str, platform: str) -> "StorageJSON":
|
||||
def from_wizard(
|
||||
name: str, friendly_name: str, address: str, platform: str
|
||||
) -> "StorageJSON":
|
||||
return StorageJSON(
|
||||
storage_version=1,
|
||||
name=name,
|
||||
friendly_name=friendly_name,
|
||||
comment=None,
|
||||
esphome_version=None,
|
||||
src_version=1,
|
||||
|
@ -139,6 +147,7 @@ class StorageJSON:
|
|||
storage = json.load(f_handle)
|
||||
storage_version = storage["storage_version"]
|
||||
name = storage.get("name")
|
||||
friendly_name = storage.get("friendly_name")
|
||||
comment = storage.get("comment")
|
||||
esphome_version = storage.get(
|
||||
"esphome_version", storage.get("esphomeyaml_version")
|
||||
|
@ -153,6 +162,7 @@ class StorageJSON:
|
|||
return StorageJSON(
|
||||
storage_version,
|
||||
name,
|
||||
friendly_name,
|
||||
comment,
|
||||
esphome_version,
|
||||
src_version,
|
||||
|
|
|
@ -46,6 +46,11 @@ BASE_CONFIG = """esphome:
|
|||
name: {name}
|
||||
"""
|
||||
|
||||
BASE_CONFIG_FRIENDLY = """esphome:
|
||||
name: {name}
|
||||
friendly_name: {friendly_name}
|
||||
"""
|
||||
|
||||
LOGGER_API_CONFIG = """
|
||||
# Enable logging
|
||||
logger:
|
||||
|
@ -110,7 +115,12 @@ def wizard_file(**kwargs):
|
|||
kwargs["fallback_name"] = ap_name
|
||||
kwargs["fallback_psk"] = "".join(random.choice(letters) for _ in range(12))
|
||||
|
||||
config = BASE_CONFIG.format(**kwargs)
|
||||
if kwargs.get("friendly_name"):
|
||||
base = BASE_CONFIG_FRIENDLY
|
||||
else:
|
||||
base = BASE_CONFIG
|
||||
|
||||
config = base.format(**kwargs)
|
||||
|
||||
config += HARDWARE_BASE_CONFIGS[kwargs["platform"]].format(**kwargs)
|
||||
|
||||
|
@ -192,7 +202,7 @@ def wizard_write(path, **kwargs):
|
|||
hardware = kwargs["platform"]
|
||||
|
||||
write_file(path, wizard_file(**kwargs))
|
||||
storage = StorageJSON.from_wizard(name, f"{name}.local", hardware)
|
||||
storage = StorageJSON.from_wizard(name, name, f"{name}.local", hardware)
|
||||
storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path))
|
||||
storage.save(storage_path)
|
||||
|
||||
|
|
|
@ -119,10 +119,12 @@ TXT_RECORD_PACKAGE_IMPORT_URL = b"package_import_url"
|
|||
TXT_RECORD_PROJECT_NAME = b"project_name"
|
||||
TXT_RECORD_PROJECT_VERSION = b"project_version"
|
||||
TXT_RECORD_NETWORK = b"network"
|
||||
TXT_RECORD_FRIENDLY_NAME = b"friendly_name"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DiscoveredImport:
|
||||
friendly_name: Optional[str]
|
||||
device_name: str
|
||||
package_import_url: str
|
||||
project_name: str
|
||||
|
@ -174,8 +176,12 @@ class DashboardImportDiscovery:
|
|||
project_name = info.properties[TXT_RECORD_PROJECT_NAME].decode()
|
||||
project_version = info.properties[TXT_RECORD_PROJECT_VERSION].decode()
|
||||
network = info.properties.get(TXT_RECORD_NETWORK, b"wifi").decode()
|
||||
friendly_name = info.properties.get(TXT_RECORD_FRIENDLY_NAME)
|
||||
if friendly_name is not None:
|
||||
friendly_name = friendly_name.decode()
|
||||
|
||||
self.import_state[name] = DiscoveredImport(
|
||||
friendly_name=friendly_name,
|
||||
device_name=node_name,
|
||||
package_import_url=import_url,
|
||||
project_name=project_name,
|
||||
|
|
|
@ -9,7 +9,7 @@ pyserial==3.5
|
|||
platformio==6.1.5 # When updating platformio, also update Dockerfile
|
||||
esptool==4.4
|
||||
click==8.1.3
|
||||
esphome-dashboard==20221231.0
|
||||
esphome-dashboard==20230117.0
|
||||
aioesphomeapi==13.0.2
|
||||
zeroconf==0.47.1
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
using namespace esphome;
|
||||
|
||||
void setup() {
|
||||
App.pre_setup("livingroom", __DATE__ ", " __TIME__, false);
|
||||
App.pre_setup("livingroom", "LivingRoom", __DATE__ ", " __TIME__, false);
|
||||
auto *log = new logger::Logger(115200, 512); // NOLINT
|
||||
log->pre_setup();
|
||||
log->set_uart_selection(logger::UART_SELECTION_UART0);
|
||||
|
|
Loading…
Reference in a new issue