import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import web_server_base from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.const import ( CONF_CSS_INCLUDE, CONF_CSS_URL, CONF_ID, CONF_JS_INCLUDE, CONF_JS_URL, CONF_PORT, CONF_AUTH, CONF_USERNAME, CONF_PASSWORD, CONF_INCLUDE_INTERNAL, CONF_OTA, CONF_LOG, CONF_VERSION, CONF_LOCAL, ) from esphome.core import CORE, coroutine_with_priority AUTO_LOAD = ["json", "web_server_base"] web_server_ns = cg.esphome_ns.namespace("web_server") WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller) def default_url(config): config = config.copy() if config[CONF_VERSION] == 1: if not (CONF_CSS_URL in config): config[CONF_CSS_URL] = "https://esphome.io/_static/webserver-v1.min.css" if not (CONF_JS_URL in config): config[CONF_JS_URL] = "https://esphome.io/_static/webserver-v1.min.js" if config[CONF_VERSION] == 2: if not (CONF_CSS_URL in config): config[CONF_CSS_URL] = "" if not (CONF_JS_URL in config): config[CONF_JS_URL] = "https://oi.esphome.io/v2/www.js" return config def validate_local(config): if CONF_LOCAL in config and config[CONF_VERSION] == 1: raise cv.Invalid("'local' is not supported in version 1") return config def validate_ota(config): if CORE.using_esp_idf and config[CONF_OTA]: raise cv.Invalid("Enabling 'ota' is not supported for IDF framework yet") return config CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(WebServer), cv.Optional(CONF_PORT, default=80): cv.port, cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2), cv.Optional(CONF_CSS_URL): cv.string, cv.Optional(CONF_CSS_INCLUDE): cv.file_, cv.Optional(CONF_JS_URL): cv.string, cv.Optional(CONF_JS_INCLUDE): cv.file_, cv.Optional(CONF_AUTH): cv.Schema( { cv.Required(CONF_USERNAME): cv.All( cv.string_strict, cv.Length(min=1) ), cv.Required(CONF_PASSWORD): cv.All( cv.string_strict, cv.Length(min=1) ), } ), cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( web_server_base.WebServerBase ), cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, cv.SplitDefault( CONF_OTA, esp8266=True, esp32_arduino=True, esp32_idf=False ): cv.boolean, cv.Optional(CONF_LOG, default=True): cv.boolean, cv.Optional(CONF_LOCAL): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA), cv.only_on(["esp32", "esp8266"]), default_url, validate_local, validate_ota, ) def build_index_html(config) -> str: html = "" css_include = config.get(CONF_CSS_INCLUDE) js_include = config.get(CONF_JS_INCLUDE) if css_include: html += "" if config[CONF_CSS_URL]: html += f'' html += "" if js_include: html += "" html += "" if config[CONF_JS_URL]: html += f'' html += "" return html def add_resource_as_progmem(resource_name: str, content: str) -> None: """Add a resource to progmem.""" content_encoded = content.encode("utf-8") content_encoded_size = len(content_encoded) bytes_as_int = ", ".join(str(x) for x in content_encoded) uint8_t = f"const uint8_t ESPHOME_WEBSERVER_{resource_name}[{content_encoded_size}] PROGMEM = {{{bytes_as_int}}}" size_t = ( f"const size_t ESPHOME_WEBSERVER_{resource_name}_SIZE = {content_encoded_size}" ) cg.add_global(cg.RawExpression(uint8_t)) cg.add_global(cg.RawExpression(size_t)) @coroutine_with_priority(40.0) async def to_code(config): paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) var = cg.new_Pvariable(config[CONF_ID], paren) await cg.register_component(var, config) cg.add_define("USE_WEBSERVER") version = config[CONF_VERSION] cg.add(paren.set_port(config[CONF_PORT])) cg.add_define("USE_WEBSERVER") cg.add_define("USE_WEBSERVER_PORT", config[CONF_PORT]) cg.add_define("USE_WEBSERVER_VERSION", version) if version == 2: add_resource_as_progmem("INDEX_HTML", build_index_html(config)) else: cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) cg.add(var.set_allow_ota(config[CONF_OTA])) cg.add(var.set_expose_log(config[CONF_LOG])) if CONF_AUTH in config: cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME])) cg.add(paren.set_auth_password(config[CONF_AUTH][CONF_PASSWORD])) if CONF_CSS_INCLUDE in config: cg.add_define("USE_WEBSERVER_CSS_INCLUDE") path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) with open(file=path, encoding="utf-8") as css_file: add_resource_as_progmem("CSS_INCLUDE", css_file.read()) if CONF_JS_INCLUDE in config: cg.add_define("USE_WEBSERVER_JS_INCLUDE") path = CORE.relative_config_path(config[CONF_JS_INCLUDE]) with open(file=path, encoding="utf-8") as js_file: add_resource_as_progmem("JS_INCLUDE", js_file.read()) cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL])) if CONF_LOCAL in config and config[CONF_LOCAL]: cg.add_define("USE_WEBSERVER_LOCAL")