mirror of
https://github.com/esphome/esphome.git
synced 2024-11-24 16:08:10 +01:00
Add validate to components (#1631)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
482a3aebc9
commit
c79d700d03
16 changed files with 174 additions and 36 deletions
|
@ -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 = {}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
esphome:
|
||||
name: $devicename
|
||||
comment: $devicecomment
|
||||
name: $device_name
|
||||
comment: $device_comment
|
||||
platform: ESP8266
|
||||
board: d1_mini
|
||||
build_path: build/test3
|
||||
|
@ -13,8 +13,8 @@ esphome:
|
|||
- custom.h
|
||||
|
||||
substitutions:
|
||||
devicename: test3
|
||||
devicecomment: test3 device
|
||||
device_name: test3
|
||||
device_comment: test3 device
|
||||
min_sub: '0.03'
|
||||
max_sub: '12.0%'
|
||||
|
||||
|
@ -213,9 +213,33 @@ spi:
|
|||
miso_pin: GPIO14
|
||||
|
||||
uart:
|
||||
- tx_pin: GPIO1
|
||||
- id: uart1
|
||||
tx_pin: GPIO1
|
||||
rx_pin: GPIO3
|
||||
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:
|
||||
safe_mode: True
|
||||
|
@ -369,6 +393,7 @@ sensor:
|
|||
active_power_b:
|
||||
name: ADE7953 Active Power B
|
||||
- platform: pzem004t
|
||||
uart_id: uart3
|
||||
voltage:
|
||||
name: 'PZEM00T Voltage'
|
||||
current:
|
||||
|
@ -408,6 +433,7 @@ sensor:
|
|||
name: 'AQI'
|
||||
calculation_type: 'AQI'
|
||||
- platform: pmsx003
|
||||
uart_id: uart2
|
||||
type: PMSX003
|
||||
pm_1_0:
|
||||
name: 'PM 1.0 Concentration'
|
||||
|
@ -415,25 +441,8 @@ sensor:
|
|||
name: 'PM 2.5 Concentration'
|
||||
pm_10_0:
|
||||
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
|
||||
uart_id: uart3
|
||||
voltage:
|
||||
name: 'CSE7766 Voltage'
|
||||
current:
|
||||
|
@ -443,7 +452,7 @@ sensor:
|
|||
- platform: ezo
|
||||
id: ph_ezo
|
||||
address: 99
|
||||
unit_of_measurement: 'pH'
|
||||
unit_of_measurement: 'pH'
|
||||
- platform: tof10120
|
||||
name: "Distance sensor"
|
||||
update_interval: 5s
|
||||
|
@ -867,6 +876,7 @@ light:
|
|||
effects:
|
||||
- wled:
|
||||
- adalight:
|
||||
uart_id: uart3
|
||||
- e131:
|
||||
universe: 1
|
||||
- platform: hbridge
|
||||
|
@ -888,6 +898,7 @@ ttp229_bsf:
|
|||
scl_pin: D1
|
||||
|
||||
sim800l:
|
||||
uart_id: uart4
|
||||
on_sms_received:
|
||||
- lambda: |-
|
||||
std::string str;
|
||||
|
@ -900,6 +911,7 @@ sim800l:
|
|||
recipient: '+1234'
|
||||
|
||||
dfplayer:
|
||||
uart_id: uart5
|
||||
on_finished_playback:
|
||||
then:
|
||||
if:
|
||||
|
@ -913,6 +925,7 @@ tm1651:
|
|||
dio_pin: D5
|
||||
|
||||
rf_bridge:
|
||||
uart_id: uart5
|
||||
on_code_received:
|
||||
- lambda: |-
|
||||
uint32_t test;
|
||||
|
@ -1006,3 +1019,4 @@ fingerprint_grow:
|
|||
event: esphome.${devicename}_fingerprint_grow_enrollment_failed
|
||||
data:
|
||||
finger_id: !lambda 'return finger_id;'
|
||||
uart_id: uart6
|
||||
|
|
Loading…
Reference in a new issue