2023-02-07 00:27:07 +01:00
|
|
|
import base64
|
|
|
|
import secrets
|
2021-09-27 19:10:53 +02:00
|
|
|
from pathlib import Path
|
2023-01-16 22:28:09 +01:00
|
|
|
from typing import Optional
|
|
|
|
|
2022-12-06 19:29:56 +01:00
|
|
|
import requests
|
2021-09-27 19:10:53 +02:00
|
|
|
|
|
|
|
import esphome.codegen as cg
|
|
|
|
import esphome.config_validation as cv
|
2023-05-10 22:55:05 +02:00
|
|
|
import esphome.final_validate as fv
|
2023-01-16 22:28:09 +01:00
|
|
|
from esphome import git
|
2021-09-27 19:10:53 +02:00
|
|
|
from esphome.components.packages import validate_source_shorthand
|
2023-05-10 22:55:05 +02:00
|
|
|
from esphome.const import CONF_REF, CONF_WIFI, CONF_ESPHOME, CONF_PROJECT
|
2022-01-12 07:37:56 +01:00
|
|
|
from esphome.wizard import wizard_file
|
2021-09-27 19:10:53 +02:00
|
|
|
from esphome.yaml_util import dump
|
|
|
|
|
|
|
|
dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import")
|
|
|
|
|
|
|
|
# payload is in `esphomelib` mdns record, which only exists if api
|
|
|
|
# is enabled
|
|
|
|
DEPENDENCIES = ["api"]
|
|
|
|
CODEOWNERS = ["@esphome/core"]
|
|
|
|
|
|
|
|
|
|
|
|
def validate_import_url(value):
|
|
|
|
value = cv.string_strict(value)
|
|
|
|
value = cv.Length(max=255)(value)
|
|
|
|
validate_source_shorthand(value)
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
2022-12-16 07:46:56 +01:00
|
|
|
def validate_full_url(config):
|
|
|
|
if not config[CONF_IMPORT_FULL_CONFIG]:
|
|
|
|
return config
|
|
|
|
source = validate_source_shorthand(config[CONF_PACKAGE_IMPORT_URL])
|
|
|
|
if CONF_REF not in source:
|
|
|
|
raise cv.Invalid(
|
|
|
|
"Must specify a ref (branch or tag) to import from when importing full config"
|
|
|
|
)
|
|
|
|
return config
|
|
|
|
|
|
|
|
|
2021-09-27 19:10:53 +02:00
|
|
|
CONF_PACKAGE_IMPORT_URL = "package_import_url"
|
2022-12-06 19:29:56 +01:00
|
|
|
CONF_IMPORT_FULL_CONFIG = "import_full_config"
|
|
|
|
|
2022-12-16 07:46:56 +01:00
|
|
|
CONFIG_SCHEMA = cv.All(
|
|
|
|
cv.Schema(
|
|
|
|
{
|
|
|
|
cv.Required(CONF_PACKAGE_IMPORT_URL): validate_import_url,
|
|
|
|
cv.Optional(CONF_IMPORT_FULL_CONFIG, default=False): cv.boolean,
|
|
|
|
}
|
|
|
|
),
|
|
|
|
validate_full_url,
|
2021-09-27 19:10:53 +02:00
|
|
|
)
|
|
|
|
|
2023-05-10 22:55:05 +02:00
|
|
|
|
|
|
|
def _final_validate(config):
|
|
|
|
full_config = fv.full_config.get()[CONF_ESPHOME]
|
|
|
|
if CONF_PROJECT not in full_config:
|
|
|
|
raise cv.Invalid(
|
|
|
|
"Dashboard import requires the `esphome` -> `project` information to be provided."
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
FINAL_VALIDATE_SCHEMA = _final_validate
|
|
|
|
|
2021-12-06 08:57:56 +01:00
|
|
|
WIFI_CONFIG = """
|
2021-11-09 20:38:20 +01:00
|
|
|
|
2021-12-06 08:57:56 +01:00
|
|
|
wifi:
|
|
|
|
ssid: !secret wifi_ssid
|
|
|
|
password: !secret wifi_password
|
2021-11-09 20:38:20 +01:00
|
|
|
"""
|
|
|
|
|
2021-09-27 19:10:53 +02:00
|
|
|
|
|
|
|
async def to_code(config):
|
|
|
|
cg.add_define("USE_DASHBOARD_IMPORT")
|
2022-12-06 19:29:56 +01:00
|
|
|
url = config[CONF_PACKAGE_IMPORT_URL]
|
|
|
|
if config[CONF_IMPORT_FULL_CONFIG]:
|
|
|
|
url += "?full_config"
|
2022-12-16 07:46:56 +01:00
|
|
|
cg.add(dashboard_import_ns.set_package_import_url(url))
|
2021-09-27 19:10:53 +02:00
|
|
|
|
|
|
|
|
2022-10-07 04:35:48 +02:00
|
|
|
def import_config(
|
2023-01-16 22:28:09 +01:00
|
|
|
path: str,
|
|
|
|
name: str,
|
|
|
|
friendly_name: Optional[str],
|
|
|
|
project_name: str,
|
|
|
|
import_url: str,
|
|
|
|
network: str = CONF_WIFI,
|
2023-02-07 00:27:07 +01:00
|
|
|
encryption: bool = False,
|
2022-10-07 04:35:48 +02:00
|
|
|
) -> None:
|
2021-09-27 19:10:53 +02:00
|
|
|
p = Path(path)
|
|
|
|
|
|
|
|
if p.exists():
|
|
|
|
raise FileExistsError
|
|
|
|
|
2022-01-12 07:37:56 +01:00
|
|
|
if project_name == "esphome.web":
|
2023-02-17 07:06:18 +01:00
|
|
|
if "esp32c3" in import_url:
|
|
|
|
board = "esp32-c3-devkitm-1"
|
|
|
|
platform = "ESP32"
|
|
|
|
elif "esp32s2" in import_url:
|
|
|
|
board = "esp32-s2-saola-1"
|
|
|
|
platform = "ESP32"
|
|
|
|
elif "esp32s3" in import_url:
|
|
|
|
board = "esp32-s3-devkitc-1"
|
|
|
|
platform = "ESP32"
|
|
|
|
elif "esp32" in import_url:
|
|
|
|
board = "esp32dev"
|
|
|
|
platform = "ESP32"
|
|
|
|
elif "esp8266" in import_url:
|
|
|
|
board = "esp01_1m"
|
|
|
|
platform = "ESP8266"
|
|
|
|
elif "pico-w" in import_url:
|
|
|
|
board = "pico-w"
|
|
|
|
platform = "RP2040"
|
|
|
|
|
2023-02-07 00:27:07 +01:00
|
|
|
kwargs = {
|
|
|
|
"name": name,
|
|
|
|
"friendly_name": friendly_name,
|
2023-02-17 07:06:18 +01:00
|
|
|
"platform": platform,
|
|
|
|
"board": board,
|
2023-02-07 00:27:07 +01:00
|
|
|
"ssid": "!secret wifi_ssid",
|
|
|
|
"psk": "!secret wifi_password",
|
|
|
|
}
|
|
|
|
if encryption:
|
|
|
|
noise_psk = secrets.token_bytes(32)
|
|
|
|
key = base64.b64encode(noise_psk).decode()
|
|
|
|
kwargs["api_encryption_key"] = key
|
|
|
|
|
2022-01-12 07:37:56 +01:00
|
|
|
p.write_text(
|
2023-02-07 00:27:07 +01:00
|
|
|
wizard_file(**kwargs),
|
2022-01-12 07:37:56 +01:00
|
|
|
encoding="utf8",
|
|
|
|
)
|
|
|
|
else:
|
2022-12-06 19:29:56 +01:00
|
|
|
git_file = git.GitFile.from_shorthand(import_url)
|
|
|
|
|
|
|
|
if git_file.query and "full_config" in git_file.query:
|
|
|
|
url = git_file.raw_url
|
|
|
|
try:
|
|
|
|
req = requests.get(url, timeout=30)
|
|
|
|
req.raise_for_status()
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
|
|
raise ValueError(f"Error while fetching {url}: {e}") from e
|
|
|
|
|
|
|
|
p.write_text(req.text, encoding="utf8")
|
|
|
|
|
|
|
|
else:
|
2023-01-16 22:28:09 +01:00
|
|
|
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}"
|
2022-12-06 19:29:56 +01:00
|
|
|
config = {
|
2023-01-16 22:28:09 +01:00
|
|
|
"substitutions": substitutions,
|
2022-12-06 19:29:56 +01:00
|
|
|
"packages": {project_name: import_url},
|
2023-01-16 22:28:09 +01:00
|
|
|
"esphome": esphome_core,
|
2022-12-06 19:29:56 +01:00
|
|
|
}
|
2023-02-07 00:27:07 +01:00
|
|
|
if encryption:
|
|
|
|
noise_psk = secrets.token_bytes(32)
|
|
|
|
key = base64.b64encode(noise_psk).decode()
|
|
|
|
config["api"] = {"encryption": {"key": key}}
|
|
|
|
|
2022-12-06 19:29:56 +01:00
|
|
|
output = dump(config)
|
|
|
|
|
|
|
|
if network == CONF_WIFI:
|
|
|
|
output += WIFI_CONFIG
|
|
|
|
|
|
|
|
p.write_text(output, encoding="utf8")
|