1
0
mirror of https://github.com/esphome/esphome.git synced 2025-03-23 11:08:16 +00:00

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