mirror of
https://github.com/esphome/esphome.git
synced 2025-03-23 11:08:16 +00:00
266 lines
9.1 KiB
Python
266 lines
9.1 KiB
Python
from esphome import automation
|
|
import esphome.codegen as cg
|
|
from esphome.components import esp32
|
|
import esphome.config_validation as cv
|
|
from esphome.const import (
|
|
CONF_ESP8266_DISABLE_SSL_SUPPORT,
|
|
CONF_ID,
|
|
CONF_METHOD,
|
|
CONF_TIMEOUT,
|
|
CONF_TRIGGER_ID,
|
|
CONF_URL,
|
|
__version__,
|
|
)
|
|
from esphome.core import CORE, Lambda
|
|
|
|
DEPENDENCIES = ["network"]
|
|
AUTO_LOAD = ["json"]
|
|
|
|
http_request_ns = cg.esphome_ns.namespace("http_request")
|
|
HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component)
|
|
HttpRequestArduino = http_request_ns.class_("HttpRequestArduino", HttpRequestComponent)
|
|
HttpRequestIDF = http_request_ns.class_("HttpRequestIDF", HttpRequestComponent)
|
|
|
|
HttpContainer = http_request_ns.class_("HttpContainer")
|
|
|
|
HttpRequestSendAction = http_request_ns.class_(
|
|
"HttpRequestSendAction", automation.Action
|
|
)
|
|
HttpRequestResponseTrigger = http_request_ns.class_(
|
|
"HttpRequestResponseTrigger",
|
|
automation.Trigger.template(
|
|
cg.std_shared_ptr.template(HttpContainer), cg.std_string
|
|
),
|
|
)
|
|
|
|
CONF_HTTP_REQUEST_ID = "http_request_id"
|
|
|
|
CONF_USERAGENT = "useragent"
|
|
CONF_VERIFY_SSL = "verify_ssl"
|
|
CONF_FOLLOW_REDIRECTS = "follow_redirects"
|
|
CONF_REDIRECT_LIMIT = "redirect_limit"
|
|
CONF_WATCHDOG_TIMEOUT = "watchdog_timeout"
|
|
|
|
CONF_MAX_RESPONSE_BUFFER_SIZE = "max_response_buffer_size"
|
|
CONF_ON_RESPONSE = "on_response"
|
|
CONF_HEADERS = "headers"
|
|
CONF_BODY = "body"
|
|
CONF_JSON = "json"
|
|
CONF_CAPTURE_RESPONSE = "capture_response"
|
|
|
|
|
|
def validate_url(value):
|
|
value = cv.url(value)
|
|
if value.startswith("http://") or value.startswith("https://"):
|
|
return value
|
|
raise cv.Invalid("URL must start with 'http://' or 'https://'")
|
|
|
|
|
|
def validate_ssl_verification(config):
|
|
error_message = ""
|
|
|
|
if CORE.is_esp32:
|
|
if not CORE.using_esp_idf and config[CONF_VERIFY_SSL]:
|
|
error_message = "ESPHome supports certificate verification only via ESP-IDF"
|
|
|
|
if CORE.is_rp2040 and config[CONF_VERIFY_SSL]:
|
|
error_message = "ESPHome does not support certificate verification on RP2040"
|
|
|
|
if (
|
|
CORE.is_esp8266
|
|
and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]
|
|
and config[CONF_VERIFY_SSL]
|
|
):
|
|
error_message = "ESPHome does not support certificate verification on ESP8266"
|
|
|
|
if len(error_message) > 0:
|
|
raise cv.Invalid(
|
|
f"{error_message}. Set '{CONF_VERIFY_SSL}: false' to skip certificate validation and allow less secure HTTPS connections."
|
|
)
|
|
|
|
return config
|
|
|
|
|
|
def _declare_request_class(value):
|
|
if CORE.using_esp_idf:
|
|
return cv.declare_id(HttpRequestIDF)(value)
|
|
if CORE.is_esp8266 or CORE.is_esp32 or CORE.is_rp2040:
|
|
return cv.declare_id(HttpRequestArduino)(value)
|
|
return NotImplementedError
|
|
|
|
|
|
CONFIG_SCHEMA = cv.All(
|
|
cv.Schema(
|
|
{
|
|
cv.GenerateID(): _declare_request_class,
|
|
cv.Optional(
|
|
CONF_USERAGENT, f"ESPHome/{__version__} (https://esphome.io)"
|
|
): cv.string,
|
|
cv.Optional(CONF_FOLLOW_REDIRECTS, True): cv.boolean,
|
|
cv.Optional(CONF_REDIRECT_LIMIT, 3): cv.int_,
|
|
cv.Optional(
|
|
CONF_TIMEOUT, default="4.5s"
|
|
): cv.positive_time_period_milliseconds,
|
|
cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All(
|
|
cv.only_on_esp8266, cv.boolean
|
|
),
|
|
cv.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
|
|
cv.Optional(CONF_WATCHDOG_TIMEOUT): cv.All(
|
|
cv.Any(cv.only_on_esp32, cv.only_on_rp2040),
|
|
cv.positive_not_null_time_period,
|
|
cv.positive_time_period_milliseconds,
|
|
),
|
|
}
|
|
).extend(cv.COMPONENT_SCHEMA),
|
|
cv.require_framework_version(
|
|
esp8266_arduino=cv.Version(2, 5, 1),
|
|
esp32_arduino=cv.Version(0, 0, 0),
|
|
esp_idf=cv.Version(0, 0, 0),
|
|
rp2040_arduino=cv.Version(0, 0, 0),
|
|
),
|
|
validate_ssl_verification,
|
|
)
|
|
|
|
|
|
async def to_code(config):
|
|
var = cg.new_Pvariable(config[CONF_ID])
|
|
cg.add(var.set_timeout(config[CONF_TIMEOUT]))
|
|
cg.add(var.set_useragent(config[CONF_USERAGENT]))
|
|
cg.add(var.set_follow_redirects(config[CONF_FOLLOW_REDIRECTS]))
|
|
cg.add(var.set_redirect_limit(config[CONF_REDIRECT_LIMIT]))
|
|
|
|
if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]:
|
|
cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS")
|
|
|
|
if timeout_ms := config.get(CONF_WATCHDOG_TIMEOUT):
|
|
cg.add(var.set_watchdog_timeout(timeout_ms))
|
|
|
|
if CORE.is_esp32:
|
|
if CORE.using_esp_idf:
|
|
esp32.add_idf_sdkconfig_option(
|
|
"CONFIG_MBEDTLS_CERTIFICATE_BUNDLE",
|
|
config.get(CONF_VERIFY_SSL),
|
|
)
|
|
esp32.add_idf_sdkconfig_option(
|
|
"CONFIG_ESP_TLS_INSECURE",
|
|
not config.get(CONF_VERIFY_SSL),
|
|
)
|
|
esp32.add_idf_sdkconfig_option(
|
|
"CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY",
|
|
not config.get(CONF_VERIFY_SSL),
|
|
)
|
|
else:
|
|
cg.add_library("WiFiClientSecure", None)
|
|
cg.add_library("HTTPClient", None)
|
|
if CORE.is_esp8266:
|
|
cg.add_library("ESP8266HTTPClient", None)
|
|
if CORE.is_rp2040 and CORE.using_arduino:
|
|
cg.add_library("HTTPClient", None)
|
|
|
|
await cg.register_component(var, config)
|
|
|
|
|
|
HTTP_REQUEST_ACTION_SCHEMA = cv.Schema(
|
|
{
|
|
cv.GenerateID(): cv.use_id(HttpRequestComponent),
|
|
cv.Required(CONF_URL): cv.templatable(validate_url),
|
|
cv.Optional(CONF_HEADERS): cv.All(
|
|
cv.Schema({cv.string: cv.templatable(cv.string)})
|
|
),
|
|
cv.Optional(CONF_VERIFY_SSL): cv.invalid(
|
|
f"{CONF_VERIFY_SSL} has moved to the base component configuration."
|
|
),
|
|
cv.Optional(CONF_CAPTURE_RESPONSE, default=False): cv.boolean,
|
|
cv.Optional(CONF_ON_RESPONSE): automation.validate_automation(
|
|
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(HttpRequestResponseTrigger)}
|
|
),
|
|
cv.Optional(CONF_MAX_RESPONSE_BUFFER_SIZE, default="1kB"): cv.validate_bytes,
|
|
}
|
|
)
|
|
HTTP_REQUEST_GET_ACTION_SCHEMA = automation.maybe_conf(
|
|
CONF_URL,
|
|
HTTP_REQUEST_ACTION_SCHEMA.extend(
|
|
{
|
|
cv.Optional(CONF_METHOD, default="GET"): cv.one_of("GET", upper=True),
|
|
}
|
|
),
|
|
)
|
|
HTTP_REQUEST_POST_ACTION_SCHEMA = automation.maybe_conf(
|
|
CONF_URL,
|
|
HTTP_REQUEST_ACTION_SCHEMA.extend(
|
|
{
|
|
cv.Optional(CONF_METHOD, default="POST"): cv.one_of("POST", upper=True),
|
|
cv.Exclusive(CONF_BODY, "body"): cv.templatable(cv.string),
|
|
cv.Exclusive(CONF_JSON, "body"): cv.Any(
|
|
cv.lambda_,
|
|
cv.All(cv.Schema({cv.string: cv.templatable(cv.string_strict)})),
|
|
),
|
|
}
|
|
),
|
|
)
|
|
HTTP_REQUEST_SEND_ACTION_SCHEMA = HTTP_REQUEST_ACTION_SCHEMA.extend(
|
|
{
|
|
cv.Required(CONF_METHOD): cv.one_of(
|
|
"GET", "POST", "PUT", "DELETE", "PATCH", upper=True
|
|
),
|
|
cv.Exclusive(CONF_BODY, "body"): cv.templatable(cv.string),
|
|
cv.Exclusive(CONF_JSON, "body"): cv.Any(
|
|
cv.lambda_,
|
|
cv.All(cv.Schema({cv.string: cv.templatable(cv.string_strict)})),
|
|
),
|
|
}
|
|
)
|
|
|
|
|
|
@automation.register_action(
|
|
"http_request.get", HttpRequestSendAction, HTTP_REQUEST_GET_ACTION_SCHEMA
|
|
)
|
|
@automation.register_action(
|
|
"http_request.post", HttpRequestSendAction, HTTP_REQUEST_POST_ACTION_SCHEMA
|
|
)
|
|
@automation.register_action(
|
|
"http_request.send", HttpRequestSendAction, HTTP_REQUEST_SEND_ACTION_SCHEMA
|
|
)
|
|
async def http_request_action_to_code(config, action_id, template_arg, args):
|
|
paren = await cg.get_variable(config[CONF_ID])
|
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
|
|
|
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
|
|
cg.add(var.set_url(template_))
|
|
cg.add(var.set_method(config[CONF_METHOD]))
|
|
cg.add(var.set_capture_response(config[CONF_CAPTURE_RESPONSE]))
|
|
cg.add(var.set_max_response_buffer_size(config[CONF_MAX_RESPONSE_BUFFER_SIZE]))
|
|
|
|
if CONF_BODY in config:
|
|
template_ = await cg.templatable(config[CONF_BODY], args, cg.std_string)
|
|
cg.add(var.set_body(template_))
|
|
if CONF_JSON in config:
|
|
json_ = config[CONF_JSON]
|
|
if isinstance(json_, Lambda):
|
|
args_ = args + [(cg.JsonObject, "root")]
|
|
lambda_ = await cg.process_lambda(json_, args_, return_type=cg.void)
|
|
cg.add(var.set_json(lambda_))
|
|
else:
|
|
for key in json_:
|
|
template_ = await cg.templatable(json_[key], args, cg.std_string)
|
|
cg.add(var.add_json(key, template_))
|
|
for key in config.get(CONF_HEADERS, []):
|
|
template_ = await cg.templatable(
|
|
config[CONF_HEADERS][key], args, cg.const_char_ptr
|
|
)
|
|
cg.add(var.add_header(key, template_))
|
|
|
|
for conf in config.get(CONF_ON_RESPONSE, []):
|
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
|
cg.add(var.register_response_trigger(trigger))
|
|
await automation.build_automation(
|
|
trigger,
|
|
[
|
|
(cg.std_shared_ptr.template(HttpContainer), "response"),
|
|
(cg.std_string_ref, "body"),
|
|
],
|
|
conf,
|
|
)
|
|
|
|
return var
|