Add validate to components (#1631)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
Guillermo Ruffino 2021-05-30 19:06:45 -03:00 committed by GitHub
parent 482a3aebc9
commit c79d700d03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 174 additions and 36 deletions

View file

@ -59,3 +59,9 @@ async def to_code(config):
conf = config[CONF_POWER] conf = config[CONF_POWER]
sens = await sensor.new_sensor(conf) sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens)) 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
)

View file

@ -78,6 +78,12 @@ async def to_code(config):
await automation.build_automation(trigger, [], conf) 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( @automation.register_action(
"dfplayer.play_next", "dfplayer.play_next",
NextAction, NextAction,

View file

@ -59,7 +59,7 @@ IPAddress = cg.global_ns.class_("IPAddress")
ManualIP = ethernet_ns.struct("ManualIP") ManualIP = ethernet_ns.struct("ManualIP")
def validate(config): def _validate(config):
if CONF_USE_ADDRESS not in config: if CONF_USE_ADDRESS not in config:
if CONF_MANUAL_IP in config: if CONF_MANUAL_IP in config:
use_address = str(config[CONF_MANUAL_IP][CONF_STATIC_IP]) use_address = str(config[CONF_MANUAL_IP][CONF_STATIC_IP])
@ -90,7 +90,7 @@ CONFIG_SCHEMA = cv.All(
), ),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
validate, _validate,
) )

View file

@ -34,7 +34,7 @@ CHIPSETS = [
] ]
def validate(value): def _validate(value):
if value[CONF_CHIPSET] == "NEOPIXEL" and CONF_RGB_ORDER in value: if value[CONF_CHIPSET] == "NEOPIXEL" and CONF_RGB_ORDER in value:
raise cv.Invalid("NEOPIXEL doesn't support RGB order") raise cv.Invalid("NEOPIXEL doesn't support RGB order")
return value return value
@ -47,7 +47,7 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_PIN): pins.output_pin, cv.Required(CONF_PIN): pins.output_pin,
} }
), ),
validate, _validate,
) )

View file

@ -89,3 +89,7 @@ async def to_code(config):
# https://platformio.org/lib/show/1655/TinyGPSPlus # https://platformio.org/lib/show/1655/TinyGPSPlus
cg.add_library("1655", "1.0.2") # TinyGPSPlus, has name conflict 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)

View file

@ -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: if CONF_AQI in config and CONF_PM_2_5 not in config:
raise cv.Invalid("AQI sensor requires PM 2.5") raise cv.Invalid("AQI sensor requires PM 2.5")
if CONF_AQI in config and CONF_PM_10_0 not in config: 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(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x40)), .extend(i2c.i2c_device_schema(0x40)),
validate, _validate,
) )

View file

@ -150,7 +150,7 @@ def format_method(config):
raise NotImplementedError raise NotImplementedError
def validate(config): def _validate(config):
if CONF_PIN in config: if CONF_PIN in config:
if CONF_CLOCK_PIN in config or CONF_DATA_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'") 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, cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
validate, _validate,
validate_method_pin, validate_method_pin,
) )

View file

@ -24,3 +24,8 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await rc522.setup_rc522(var, config) await rc522.setup_rc522(var, config)
await spi.register_spi_device(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)

View file

@ -1,8 +1,11 @@
import logging
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.components.output import FloatOutput 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"] CODEOWNERS = ["@glmnet"]
CONF_RTTTL = "rtttl" CONF_RTTTL = "rtttl"
@ -33,6 +36,33 @@ CONFIG_SCHEMA = cv.Schema(
).extend(cv.COMPONENT_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): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)

View file

@ -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( SIM800L_SEND_SMS_SCHEMA = cv.Schema(
{ {
cv.GenerateID(): cv.use_id(Sim800LComponent), cv.GenerateID(): cv.use_id(Sim800LComponent),

View file

@ -67,3 +67,11 @@ async def register_spi_device(var, config):
if CONF_CS_PIN in config: if CONF_CS_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_CS_PIN]) pin = await cg.gpio_pin_expression(config[CONF_CS_PIN])
cg.add(var.set_cs_pin(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")

View file

@ -92,6 +92,42 @@ async def to_code(config):
cg.add(var.set_parity(config[CONF_PARITY])) 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! # A schema to use for all UART devices, all UART integrations must extend this!
UART_DEVICE_SCHEMA = cv.Schema( UART_DEVICE_SCHEMA = cv.Schema(
{ {

View file

@ -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: if CONF_PASSWORD in config and CONF_SSID not in config:
raise cv.Invalid("Cannot have WiFi password without SSID!") raise cv.Invalid("Cannot have WiFi password without SSID!")
@ -207,7 +207,7 @@ CONFIG_SCHEMA = cv.All(
), ),
} }
), ),
validate, _validate,
) )

View file

@ -63,6 +63,8 @@ class Config(OrderedDict):
# The values will be the paths to all "domain", for example (['logger'], 'logger') # The values will be the paths to all "domain", for example (['logger'], 'logger')
# or (['sensor', 'ultrasonic'], 'sensor.ultrasonic') # or (['sensor', 'ultrasonic'], 'sensor.ultrasonic')
self.output_paths = [] # type: List[Tuple[ConfigPath, str]] 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): def add_error(self, error):
# type: (vol.Invalid) -> None # type: (vol.Invalid) -> None
@ -161,6 +163,12 @@ class Config(OrderedDict):
part.append(item_index) part.append(item_index)
return part 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): def iter_ids(config, path=None):
path = path or [] path = path or []
@ -181,7 +189,7 @@ def do_id_pass(result): # type: (Config) -> None
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_types import Component 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]] searching_ids = [] # type: List[Tuple[core.ID, ConfigPath]]
for id, path in iter_ids(result): for id, path in iter_ids(result):
if id.is_declaration: if id.is_declaration:
@ -546,6 +554,19 @@ def validate_config(config, command_line_substitutions):
# Only parse IDs if no validation error. Otherwise # Only parse IDs if no validation error. Otherwise
# user gets confusing messages # user gets confusing messages
do_id_pass(result) 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 return result

View file

@ -80,6 +80,10 @@ class ComponentManifest:
def codeowners(self) -> List[str]: def codeowners(self) -> List[str]:
return getattr(self.module, "CODEOWNERS", []) return getattr(self.module, "CODEOWNERS", [])
@property
def validate(self):
return getattr(self.module, "validate", None)
@property @property
def source_files(self) -> Dict[Path, SourceFile]: def source_files(self) -> Dict[Path, SourceFile]:
ret = {} ret = {}

View file

@ -1,6 +1,6 @@
esphome: esphome:
name: $devicename name: $device_name
comment: $devicecomment comment: $device_comment
platform: ESP8266 platform: ESP8266
board: d1_mini board: d1_mini
build_path: build/test3 build_path: build/test3
@ -13,8 +13,8 @@ esphome:
- custom.h - custom.h
substitutions: substitutions:
devicename: test3 device_name: test3
devicecomment: test3 device device_comment: test3 device
min_sub: '0.03' min_sub: '0.03'
max_sub: '12.0%' max_sub: '12.0%'
@ -213,9 +213,33 @@ spi:
miso_pin: GPIO14 miso_pin: GPIO14
uart: uart:
- tx_pin: GPIO1 - id: uart1
tx_pin: GPIO1
rx_pin: GPIO3 rx_pin: GPIO3
baud_rate: 115200 baud_rate: 115200
- id: uart2
tx_pin: GPIO4
rx_pin: GPIO5
baud_rate: 9600
- id: uart3
tx_pin: GPIO4
rx_pin: GPIO5
baud_rate: 4800
- id: uart4
tx_pin: GPIO4
rx_pin: GPIO5
baud_rate: 9600
- id: uart5
tx_pin: GPIO4
rx_pin: GPIO5
baud_rate: 9600
- id: uart6
tx_pin: GPIO4
rx_pin: GPIO5
baud_rate: 9600
modbus:
uart_id: uart1
ota: ota:
safe_mode: True safe_mode: True
@ -369,6 +393,7 @@ sensor:
active_power_b: active_power_b:
name: ADE7953 Active Power B name: ADE7953 Active Power B
- platform: pzem004t - platform: pzem004t
uart_id: uart3
voltage: voltage:
name: 'PZEM00T Voltage' name: 'PZEM00T Voltage'
current: current:
@ -408,6 +433,7 @@ sensor:
name: 'AQI' name: 'AQI'
calculation_type: 'AQI' calculation_type: 'AQI'
- platform: pmsx003 - platform: pmsx003
uart_id: uart2
type: PMSX003 type: PMSX003
pm_1_0: pm_1_0:
name: 'PM 1.0 Concentration' name: 'PM 1.0 Concentration'
@ -415,25 +441,8 @@ sensor:
name: 'PM 2.5 Concentration' name: 'PM 2.5 Concentration'
pm_10_0: pm_10_0:
name: 'PM 10.0 Concentration' name: 'PM 10.0 Concentration'
- platform: pmsx003
type: PMS5003T
pm_2_5:
name: 'PM 2.5 Concentration'
temperature:
name: 'PMS Temperature'
humidity:
name: 'PMS Humidity'
- platform: pmsx003
type: PMS5003ST
pm_2_5:
name: 'PM 2.5 Concentration'
temperature:
name: 'PMS Temperature'
humidity:
name: 'PMS Humidity'
formaldehyde:
name: 'PMS Formaldehyde Concentration'
- platform: cse7766 - platform: cse7766
uart_id: uart3
voltage: voltage:
name: 'CSE7766 Voltage' name: 'CSE7766 Voltage'
current: current:
@ -443,7 +452,7 @@ sensor:
- platform: ezo - platform: ezo
id: ph_ezo id: ph_ezo
address: 99 address: 99
unit_of_measurement: 'pH' unit_of_measurement: 'pH'
- platform: tof10120 - platform: tof10120
name: "Distance sensor" name: "Distance sensor"
update_interval: 5s update_interval: 5s
@ -867,6 +876,7 @@ light:
effects: effects:
- wled: - wled:
- adalight: - adalight:
uart_id: uart3
- e131: - e131:
universe: 1 universe: 1
- platform: hbridge - platform: hbridge
@ -888,6 +898,7 @@ ttp229_bsf:
scl_pin: D1 scl_pin: D1
sim800l: sim800l:
uart_id: uart4
on_sms_received: on_sms_received:
- lambda: |- - lambda: |-
std::string str; std::string str;
@ -900,6 +911,7 @@ sim800l:
recipient: '+1234' recipient: '+1234'
dfplayer: dfplayer:
uart_id: uart5
on_finished_playback: on_finished_playback:
then: then:
if: if:
@ -913,6 +925,7 @@ tm1651:
dio_pin: D5 dio_pin: D5
rf_bridge: rf_bridge:
uart_id: uart5
on_code_received: on_code_received:
- lambda: |- - lambda: |-
uint32_t test; uint32_t test;
@ -1006,3 +1019,4 @@ fingerprint_grow:
event: esphome.${devicename}_fingerprint_grow_enrollment_failed event: esphome.${devicename}_fingerprint_grow_enrollment_failed
data: data:
finger_id: !lambda 'return finger_id;' finger_id: !lambda 'return finger_id;'
uart_id: uart6