mirror of
https://github.com/esphome/esphome.git
synced 2025-10-30 06:33:51 +00:00
Add validate to components (#1631)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
482a3aebc9
commit
c79d700d03
@@ -59,3 +59,9 @@ async def to_code(config):
|
||||
conf = config[CONF_POWER]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
|
||||
|
||||
def validate(config, item_config):
|
||||
uart.validate_device(
|
||||
"cse7766", config, item_config, baud_rate=4800, require_tx=False
|
||||
)
|
||||
|
||||
@@ -78,6 +78,12 @@ async def to_code(config):
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
|
||||
def validate(config, item_config):
|
||||
uart.validate_device(
|
||||
"dfplayer", config, item_config, baud_rate=9600, require_rx=False
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.play_next",
|
||||
NextAction,
|
||||
|
||||
@@ -59,7 +59,7 @@ IPAddress = cg.global_ns.class_("IPAddress")
|
||||
ManualIP = ethernet_ns.struct("ManualIP")
|
||||
|
||||
|
||||
def validate(config):
|
||||
def _validate(config):
|
||||
if CONF_USE_ADDRESS not in config:
|
||||
if CONF_MANUAL_IP in config:
|
||||
use_address = str(config[CONF_MANUAL_IP][CONF_STATIC_IP])
|
||||
@@ -90,7 +90,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate,
|
||||
_validate,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ CHIPSETS = [
|
||||
]
|
||||
|
||||
|
||||
def validate(value):
|
||||
def _validate(value):
|
||||
if value[CONF_CHIPSET] == "NEOPIXEL" and CONF_RGB_ORDER in value:
|
||||
raise cv.Invalid("NEOPIXEL doesn't support RGB order")
|
||||
return value
|
||||
@@ -47,7 +47,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_PIN): pins.output_pin,
|
||||
}
|
||||
),
|
||||
validate,
|
||||
_validate,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -89,3 +89,7 @@ async def to_code(config):
|
||||
|
||||
# https://platformio.org/lib/show/1655/TinyGPSPlus
|
||||
cg.add_library("1655", "1.0.2") # TinyGPSPlus, has name conflict
|
||||
|
||||
|
||||
def validate(config, item_config):
|
||||
uart.validate_device("gps", config, item_config, require_tx=False)
|
||||
|
||||
@@ -29,7 +29,7 @@ AQI_CALCULATION_TYPE = {
|
||||
}
|
||||
|
||||
|
||||
def validate(config):
|
||||
def _validate(config):
|
||||
if CONF_AQI in config and CONF_PM_2_5 not in config:
|
||||
raise cv.Invalid("AQI sensor requires PM 2.5")
|
||||
if CONF_AQI in config and CONF_PM_10_0 not in config:
|
||||
@@ -72,7 +72,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x40)),
|
||||
validate,
|
||||
_validate,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ def format_method(config):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def validate(config):
|
||||
def _validate(config):
|
||||
if CONF_PIN in config:
|
||||
if CONF_CLOCK_PIN in config or CONF_DATA_PIN in config:
|
||||
raise cv.Invalid("Cannot specify both 'pin' and 'clock_pin'+'data_pin'")
|
||||
@@ -176,7 +176,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate,
|
||||
_validate,
|
||||
validate_method_pin,
|
||||
)
|
||||
|
||||
|
||||
@@ -24,3 +24,8 @@ async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await rc522.setup_rc522(var, config)
|
||||
await spi.register_spi_device(var, config)
|
||||
|
||||
|
||||
def validate(config, item_config):
|
||||
# validate given SPI hub is suitable for rc522_spi, it needs both miso and mosi
|
||||
spi.validate_device("rc522_spi", config, item_config, True, True)
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import logging
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components.output import FloatOutput
|
||||
from esphome.const import CONF_ID, CONF_OUTPUT, CONF_TRIGGER_ID
|
||||
from esphome.const import CONF_ID, CONF_OUTPUT, CONF_PLATFORM, CONF_TRIGGER_ID
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CODEOWNERS = ["@glmnet"]
|
||||
CONF_RTTTL = "rtttl"
|
||||
@@ -33,6 +36,33 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def validate(config, item_config):
|
||||
# Not adding this to FloatOutput as this is the only component which needs `update_frequency`
|
||||
|
||||
parent_config = config.get_config_by_id(item_config[CONF_OUTPUT])
|
||||
platform = parent_config[CONF_PLATFORM]
|
||||
|
||||
PWM_GOOD = ["esp8266_pwm", "ledc"]
|
||||
PWM_BAD = [
|
||||
"ac_dimmer ",
|
||||
"esp32_dac",
|
||||
"slow_pwm",
|
||||
"mcp4725",
|
||||
"pca9685",
|
||||
"tlc59208f",
|
||||
"my9231",
|
||||
"sm16716",
|
||||
]
|
||||
|
||||
if platform in PWM_BAD:
|
||||
raise ValueError(f"Component rtttl cannot use {platform} as output component")
|
||||
|
||||
if platform not in PWM_GOOD:
|
||||
_LOGGER.warning(
|
||||
"Component rtttl is not known to work with the selected output type. Make sure this output supports custom frequency output method."
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -54,6 +54,10 @@ async def to_code(config):
|
||||
)
|
||||
|
||||
|
||||
def validate(config, item_config):
|
||||
uart.validate_device("sim800l", config, item_config, baud_rate=9600)
|
||||
|
||||
|
||||
SIM800L_SEND_SMS_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(Sim800LComponent),
|
||||
|
||||
@@ -67,3 +67,11 @@ async def register_spi_device(var, config):
|
||||
if CONF_CS_PIN in config:
|
||||
pin = await cg.gpio_pin_expression(config[CONF_CS_PIN])
|
||||
cg.add(var.set_cs_pin(pin))
|
||||
|
||||
|
||||
def validate_device(name, config, item_config, require_mosi, require_miso):
|
||||
spi_config = config.get_config_by_id(item_config[CONF_SPI_ID])
|
||||
if require_mosi and CONF_MISO_PIN not in spi_config:
|
||||
raise ValueError(f"Component {name} requires parent spi to declare miso_pin")
|
||||
if require_miso and CONF_MOSI_PIN not in spi_config:
|
||||
raise ValueError(f"Component {name} requires parent spi to declare mosi_pin")
|
||||
|
||||
@@ -92,6 +92,42 @@ async def to_code(config):
|
||||
cg.add(var.set_parity(config[CONF_PARITY]))
|
||||
|
||||
|
||||
def validate_device(
|
||||
name, config, item_config, baud_rate=None, require_tx=True, require_rx=True
|
||||
):
|
||||
if not hasattr(config, "uart_devices"):
|
||||
config.uart_devices = {}
|
||||
devices = config.uart_devices
|
||||
|
||||
uart_config = config.get_config_by_id(item_config[CONF_UART_ID])
|
||||
|
||||
uart_id = uart_config[CONF_ID]
|
||||
device = devices.setdefault(uart_id, {})
|
||||
|
||||
if require_tx:
|
||||
if CONF_TX_PIN not in uart_config:
|
||||
raise ValueError(f"Component {name} requires parent uart to declare tx_pin")
|
||||
if CONF_TX_PIN in device:
|
||||
raise ValueError(
|
||||
f"Component {name} cannot use the same uart.{CONF_TX_PIN} as component {device[CONF_TX_PIN]} is already using it"
|
||||
)
|
||||
device[CONF_TX_PIN] = name
|
||||
|
||||
if require_rx:
|
||||
if CONF_RX_PIN not in uart_config:
|
||||
raise ValueError(f"Component {name} requires parent uart to declare rx_pin")
|
||||
if CONF_RX_PIN in device:
|
||||
raise ValueError(
|
||||
f"Component {name} cannot use the same uart.{CONF_RX_PIN} as component {device[CONF_RX_PIN]} is already using it"
|
||||
)
|
||||
device[CONF_RX_PIN] = name
|
||||
|
||||
if baud_rate and uart_config[CONF_BAUD_RATE] != baud_rate:
|
||||
raise ValueError(
|
||||
f"Component {name} requires parent uart baud rate be {baud_rate}"
|
||||
)
|
||||
|
||||
|
||||
# A schema to use for all UART devices, all UART integrations must extend this!
|
||||
UART_DEVICE_SCHEMA = cv.Schema(
|
||||
{
|
||||
|
||||
@@ -137,7 +137,7 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend(
|
||||
)
|
||||
|
||||
|
||||
def validate(config):
|
||||
def _validate(config):
|
||||
if CONF_PASSWORD in config and CONF_SSID not in config:
|
||||
raise cv.Invalid("Cannot have WiFi password without SSID!")
|
||||
|
||||
@@ -207,7 +207,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
}
|
||||
),
|
||||
validate,
|
||||
_validate,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -63,6 +63,8 @@ class Config(OrderedDict):
|
||||
# The values will be the paths to all "domain", for example (['logger'], 'logger')
|
||||
# or (['sensor', 'ultrasonic'], 'sensor.ultrasonic')
|
||||
self.output_paths = [] # type: List[Tuple[ConfigPath, str]]
|
||||
# A list of components ids with the config path
|
||||
self.declare_ids = [] # type: List[Tuple[core.ID, ConfigPath]]
|
||||
|
||||
def add_error(self, error):
|
||||
# type: (vol.Invalid) -> None
|
||||
@@ -161,6 +163,12 @@ class Config(OrderedDict):
|
||||
part.append(item_index)
|
||||
return part
|
||||
|
||||
def get_config_by_id(self, id):
|
||||
for declared_id, path in self.declare_ids:
|
||||
if declared_id.id == str(id):
|
||||
return self.get_nested_item(path[:-1])
|
||||
return None
|
||||
|
||||
|
||||
def iter_ids(config, path=None):
|
||||
path = path or []
|
||||
@@ -181,7 +189,7 @@ def do_id_pass(result): # type: (Config) -> None
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_types import Component
|
||||
|
||||
declare_ids = [] # type: List[Tuple[core.ID, ConfigPath]]
|
||||
declare_ids = result.declare_ids # type: List[Tuple[core.ID, ConfigPath]]
|
||||
searching_ids = [] # type: List[Tuple[core.ID, ConfigPath]]
|
||||
for id, path in iter_ids(result):
|
||||
if id.is_declaration:
|
||||
@@ -546,6 +554,19 @@ def validate_config(config, command_line_substitutions):
|
||||
# Only parse IDs if no validation error. Otherwise
|
||||
# user gets confusing messages
|
||||
do_id_pass(result)
|
||||
|
||||
# 7. Final validation
|
||||
if not result.errors:
|
||||
# Inter - components validation
|
||||
for path, conf, comp in validate_queue:
|
||||
if comp.config_schema is None:
|
||||
continue
|
||||
if callable(comp.validate):
|
||||
try:
|
||||
comp.validate(result, result.get_nested_item(path))
|
||||
except ValueError as err:
|
||||
result.add_str_error(err, path)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -80,6 +80,10 @@ class ComponentManifest:
|
||||
def codeowners(self) -> List[str]:
|
||||
return getattr(self.module, "CODEOWNERS", [])
|
||||
|
||||
@property
|
||||
def validate(self):
|
||||
return getattr(self.module, "validate", None)
|
||||
|
||||
@property
|
||||
def source_files(self) -> Dict[Path, SourceFile]:
|
||||
ret = {}
|
||||
|
||||
Reference in New Issue
Block a user