Merge pull request #1936 from esphome/bump-1.19.1

1.19.1
This commit is contained in:
Jesse Hills 2021-06-18 12:10:30 +12:00 committed by GitHub
commit b43712d78d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 383 additions and 230 deletions

View file

@ -21,7 +21,7 @@ void BangBangClimate::setup() {
restore->to_call(this).perform();
} else {
// restore from defaults, change_away handles those for us
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
this->change_away_(false);
}
}
@ -41,7 +41,7 @@ void BangBangClimate::control(const climate::ClimateCall &call) {
climate::ClimateTraits BangBangClimate::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true);
traits.set_supports_auto_mode(true);
traits.set_supports_heat_cool_mode(true);
traits.set_supports_cool_mode(this->supports_cool_);
traits.set_supports_heat_mode(this->supports_heat_);
traits.set_supports_two_point_target_temperature(true);
@ -50,7 +50,7 @@ climate::ClimateTraits BangBangClimate::traits() {
return traits;
}
void BangBangClimate::compute_state_() {
if (this->mode != climate::CLIMATE_MODE_AUTO) {
if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) {
// in non-auto mode, switch directly to appropriate action
// - HEAT mode -> HEATING action
// - COOL mode -> COOLING action

View file

@ -159,7 +159,7 @@ struct ClimateDeviceRestoreState {
*
* The entire state of the climate device is encoded in public properties of the base class (current_temperature,
* mode etc). These are read-only for the user and rw for integrations. The reason these are public
* is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_AUTO) ...`
* is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_HEAT_COOL) ...`
*/
class Climate : public Nameable {
public:

View file

@ -8,6 +8,8 @@ bool ClimateTraits::supports_mode(ClimateMode mode) const {
switch (mode) {
case CLIMATE_MODE_OFF:
return true;
case CLIMATE_MODE_HEAT_COOL:
return this->supports_heat_cool_mode_;
case CLIMATE_MODE_AUTO:
return this->supports_auto_mode_;
case CLIMATE_MODE_COOL:
@ -31,6 +33,9 @@ void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_
supports_two_point_target_temperature_ = supports_two_point_target_temperature;
}
void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; }
void ClimateTraits::set_supports_heat_cool_mode(bool supports_heat_cool_mode) {
supports_heat_cool_mode_ = supports_heat_cool_mode;
}
void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; }
void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; }
void ClimateTraits::set_supports_fan_only_mode(bool supports_fan_only_mode) {

View file

@ -46,6 +46,7 @@ class ClimateTraits {
bool get_supports_two_point_target_temperature() const;
void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature);
void set_supports_auto_mode(bool supports_auto_mode);
void set_supports_heat_cool_mode(bool supports_heat_cool_mode);
void set_supports_cool_mode(bool supports_cool_mode);
void set_supports_heat_mode(bool supports_heat_mode);
void set_supports_fan_only_mode(bool supports_fan_only_mode);
@ -100,6 +101,7 @@ class ClimateTraits {
bool supports_current_temperature_{false};
bool supports_two_point_target_temperature_{false};
bool supports_auto_mode_{false};
bool supports_heat_cool_mode_{false};
bool supports_cool_mode_{false};
bool supports_heat_mode_{false};
bool supports_fan_only_mode_{false};

View file

@ -9,7 +9,7 @@ static const char *TAG = "climate_ir";
climate::ClimateTraits ClimateIR::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(this->sensor_ != nullptr);
traits.set_supports_auto_mode(true);
traits.set_supports_heat_cool_mode(true);
traits.set_supports_cool_mode(this->supports_cool_);
traits.set_supports_heat_mode(this->supports_heat_);
traits.set_supports_dry_mode(this->supports_dry_);

View file

@ -39,7 +39,7 @@ void LgIrClimate::transmit_state() {
send_swing_cmd_ = false;
remote_state |= COMMAND_SWING;
} else {
if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_AUTO) {
if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
remote_state |= COMMAND_ON_AI;
} else if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode != climate::CLIMATE_MODE_OFF) {
remote_state |= COMMAND_ON;
@ -52,7 +52,7 @@ void LgIrClimate::transmit_state() {
case climate::CLIMATE_MODE_HEAT:
remote_state |= COMMAND_HEAT;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
remote_state |= COMMAND_AUTO;
break;
case climate::CLIMATE_MODE_DRY:
@ -89,7 +89,7 @@ void LgIrClimate::transmit_state() {
}
}
if (this->mode == climate::CLIMATE_MODE_AUTO) {
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
this->fan_mode = climate::CLIMATE_FAN_AUTO;
// remote_state |= FAN_MODE_AUTO_DRY;
}
@ -128,7 +128,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
if ((remote_state & COMMAND_MASK) == COMMAND_ON) {
this->mode = climate::CLIMATE_MODE_COOL;
} else if ((remote_state & COMMAND_MASK) == COMMAND_ON_AI) {
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
}
if ((remote_state & COMMAND_MASK) == COMMAND_OFF) {
@ -138,7 +138,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
} else {
if ((remote_state & COMMAND_MASK) == COMMAND_AUTO)
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN)
this->mode = climate::CLIMATE_MODE_DRY;
else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) {
@ -152,7 +152,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
// Fan Speed
if (this->mode == climate::CLIMATE_MODE_AUTO) {
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
this->fan_mode = climate::CLIMATE_FAN_AUTO;
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT ||
this->mode == climate::CLIMATE_MODE_DRY) {

View file

@ -70,7 +70,7 @@ void CoolixClimate::transmit_state() {
case climate::CLIMATE_MODE_HEAT:
remote_state |= COOLIX_HEAT;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
remote_state |= COOLIX_AUTO;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
@ -89,7 +89,7 @@ void CoolixClimate::transmit_state() {
} else {
remote_state |= COOLIX_FAN_TEMP_CODE;
}
if (this->mode == climate::CLIMATE_MODE_AUTO || this->mode == climate::CLIMATE_MODE_DRY) {
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_DRY) {
this->fan_mode = climate::CLIMATE_FAN_AUTO;
remote_state |= COOLIX_FAN_MODE_AUTO_DRY;
} else {
@ -197,7 +197,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) {
if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT)
this->mode = climate::CLIMATE_MODE_HEAT;
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO)
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) {
if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY)
this->mode = climate::CLIMATE_MODE_DRY;
@ -207,7 +207,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) {
this->mode = climate::CLIMATE_MODE_COOL;
// Fan Speed
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_AUTO ||
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_HEAT_COOL ||
this->mode == climate::CLIMATE_MODE_DRY)
this->fan_mode = climate::CLIMATE_FAN_AUTO;
else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN)

View file

@ -45,6 +45,9 @@ CONFIG_SCHEMA = (
.extend(cv.polling_component_schema("60s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"cse7766", baud_rate=4800, require_rx=True
)
async def to_code(config):
@ -64,9 +67,3 @@ 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
)

View file

@ -77,7 +77,7 @@ uint8_t DaikinClimate::operation_mode_() {
case climate::CLIMATE_MODE_HEAT:
operating_mode |= DAIKIN_MODE_HEAT;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
operating_mode |= DAIKIN_MODE_AUTO;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
@ -131,7 +131,7 @@ uint8_t DaikinClimate::temperature_() {
switch (this->mode) {
case climate::CLIMATE_MODE_FAN_ONLY:
return 0x32;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
case climate::CLIMATE_MODE_DRY:
return 0xc0;
default:
@ -160,7 +160,7 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
this->mode = climate::CLIMATE_MODE_HEAT;
break;
case DAIKIN_MODE_AUTO:
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
break;
case DAIKIN_MODE_FAN:
this->mode = climate::CLIMATE_MODE_FAN_ONLY;

View file

@ -68,6 +68,9 @@ CONFIG_SCHEMA = cv.All(
}
).extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"dfplayer", baud_rate=9600, require_tx=True
)
async def to_code(config):
@ -80,12 +83,6 @@ 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,

View file

@ -132,7 +132,7 @@ void FujitsuGeneralClimate::transmit_state() {
case climate::CLIMATE_MODE_FAN_ONLY:
SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_FAN);
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
default:
SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_AUTO);
break;
@ -343,7 +343,7 @@ bool FujitsuGeneralClimate::on_receive(remote_base::RemoteReceiveData data) {
case FUJITSU_GENERAL_MODE_AUTO:
default:
// TODO: CLIMATE_MODE_10C is missing from esphome
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
break;
}

View file

@ -62,6 +62,7 @@ CONFIG_SCHEMA = (
.extend(cv.polling_component_schema("20s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema("gps", require_rx=True)
async def to_code(config):
@ -95,7 +96,3 @@ 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)

View file

@ -155,7 +155,7 @@ void HitachiClimate::transmit_state() {
case climate::CLIMATE_MODE_HEAT:
set_mode_(HITACHI_AC344_MODE_HEAT);
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
set_mode_(HITACHI_AC344_MODE_AUTO);
break;
case climate::CLIMATE_MODE_FAN_ONLY:
@ -251,7 +251,7 @@ bool HitachiClimate::parse_mode_(const uint8_t remote_state[]) {
this->mode = climate::CLIMATE_MODE_HEAT;
break;
case HITACHI_AC344_MODE_AUTO:
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
break;
case HITACHI_AC344_MODE_FAN:
this->mode = climate::CLIMATE_MODE_FAN_ONLY;

View file

@ -2,6 +2,7 @@ import urllib.parse as urlparse
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome import automation
from esphome.const import (
CONF_ID,
@ -14,7 +15,6 @@ from esphome.const import (
CONF_URL,
)
from esphome.core import CORE, Lambda
from esphome.core.config import PLATFORMIO_ESP8266_LUT
DEPENDENCIES = ["network"]
AUTO_LOAD = ["json"]
@ -36,29 +36,6 @@ CONF_VERIFY_SSL = "verify_ssl"
CONF_ON_RESPONSE = "on_response"
def validate_framework(config):
if CORE.is_esp32:
return config
version = "RECOMMENDED"
if CONF_ARDUINO_VERSION in CORE.raw_config[CONF_ESPHOME]:
version = CORE.raw_config[CONF_ESPHOME][CONF_ARDUINO_VERSION]
if version in ["LATEST", "DEV"]:
return config
framework = (
PLATFORMIO_ESP8266_LUT[version]
if version in PLATFORMIO_ESP8266_LUT
else version
)
if framework < ARDUINO_VERSION_ESP8266["2.5.1"]:
raise cv.Invalid(
"This component is not supported on arduino framework version below 2.5.1"
)
return config
def validate_url(value):
value = cv.string(value)
try:
@ -92,19 +69,36 @@ def validate_secure_url(config):
return config
CONFIG_SCHEMA = (
cv.Schema(
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(HttpRequestComponent),
cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string,
cv.Optional(
CONF_TIMEOUT, default="5s"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_TIMEOUT, default="5s"): cv.positive_time_period_milliseconds,
}
).extend(cv.COMPONENT_SCHEMA)
def validate_framework(config):
if CORE.is_esp32:
return
# only for ESP8266
path = [CONF_ESPHOME, CONF_ARDUINO_VERSION]
version: str = fv.full_config.get().get_config_for_path(path)
reverse_map = {v: k for k, v in ARDUINO_VERSION_ESP8266.items()}
framework_version = reverse_map.get(version)
if framework_version is None or framework_version == "dev":
return
if framework_version < "2.5.1":
raise cv.Invalid(
"This component is not supported on arduino framework version below 2.5.1",
path=[cv.ROOT_CONFIG_PATH] + path,
)
.add_extra(validate_framework)
.extend(cv.COMPONENT_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = cv.Schema(validate_framework)
async def to_code(config):

View file

@ -167,7 +167,7 @@ climate::ClimateTraits MideaAC::traits() {
traits.set_visual_min_temperature(17);
traits.set_visual_max_temperature(30);
traits.set_visual_temperature_step(0.5);
traits.set_supports_auto_mode(true);
traits.set_supports_heat_cool_mode(true);
traits.set_supports_cool_mode(true);
traits.set_supports_dry_mode(true);
traits.set_supports_heat_mode(true);

View file

@ -8,9 +8,9 @@ const std::string MIDEA_SILENT_FAN_MODE = "silent";
const std::string MIDEA_TURBO_FAN_MODE = "turbo";
const std::string MIDEA_FREEZE_PROTECTION_PRESET = "freeze protection";
const uint8_t QueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x00,
0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x68};
const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x61,
0x00, 0xFF, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE3, 0xA8};
const uint8_t PowerQueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x21,
0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -44,7 +44,7 @@ climate::ClimateMode PropertiesFrame::get_mode() const {
return climate::CLIMATE_MODE_OFF;
switch (this->pbuf_[12] >> 5) {
case MIDEA_MODE_AUTO:
return climate::CLIMATE_MODE_AUTO;
return climate::CLIMATE_MODE_HEAT_COOL;
case MIDEA_MODE_COOL:
return climate::CLIMATE_MODE_COOL;
case MIDEA_MODE_DRY:
@ -61,7 +61,7 @@ climate::ClimateMode PropertiesFrame::get_mode() const {
void PropertiesFrame::set_mode(climate::ClimateMode mode) {
uint8_t m;
switch (mode) {
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
m = MIDEA_MODE_AUTO;
break;
case climate::CLIMATE_MODE_COOL:

View file

@ -33,7 +33,7 @@ void MitsubishiClimate::transmit_state() {
case climate::CLIMATE_MODE_HEAT:
remote_state[6] = MITSUBISHI_HEAT;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
remote_state[6] = MITSUBISHI_AUTO;
break;
case climate::CLIMATE_MODE_OFF:

View file

@ -20,7 +20,7 @@ void PIDClimate::setup() {
restore->to_call(this).perform();
} else {
// restore from defaults, change_away handles those for us
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
this->target_temperature = this->default_target_temperature_;
}
}
@ -31,7 +31,7 @@ void PIDClimate::control(const climate::ClimateCall &call) {
this->target_temperature = *call.get_target_temperature();
// If switching to non-auto mode, set output immediately
if (this->mode != climate::CLIMATE_MODE_AUTO)
if (this->mode != climate::CLIMATE_MODE_HEAT_COOL)
this->handle_non_auto_mode_();
this->publish_state();
@ -39,7 +39,7 @@ void PIDClimate::control(const climate::ClimateCall &call) {
climate::ClimateTraits PIDClimate::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true);
traits.set_supports_auto_mode(true);
traits.set_supports_heat_cool_mode(true);
traits.set_supports_two_point_target_temperature(false);
traits.set_supports_cool_mode(this->supports_cool_());
traits.set_supports_heat_mode(this->supports_heat_());
@ -121,14 +121,14 @@ void PIDClimate::update_pid_() {
// keep autotuner instance so that subsequent dump_configs will print the long result message.
} else {
value = res.output;
if (mode != climate::CLIMATE_MODE_AUTO) {
if (mode != climate::CLIMATE_MODE_HEAT_COOL) {
ESP_LOGW(TAG, "For PID autotuner you need to set AUTO (also called heat/cool) mode!");
}
}
}
}
if (this->mode != climate::CLIMATE_MODE_AUTO) {
if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) {
this->handle_non_auto_mode_();
} else {
this->write_output_(value);

View file

@ -19,13 +19,12 @@ CONFIG_SCHEMA = cv.All(
).extend(spi.spi_device_schema(cs_pin_required=True))
)
FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema(
"rc522_spi", require_miso=True, require_mosi=True
)
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)

View file

@ -1,6 +1,7 @@
import logging
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome import automation
from esphome.components.output import FloatOutput
from esphome.const import CONF_ID, CONF_OUTPUT, CONF_PLATFORM, CONF_TRIGGER_ID
@ -36,12 +37,8 @@ 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]
def validate_parent_output_config(value):
platform = value.get(CONF_PLATFORM)
PWM_GOOD = ["esp8266_pwm", "ledc"]
PWM_BAD = [
"ac_dimmer ",
@ -55,14 +52,25 @@ def validate(config, item_config):
]
if platform in PWM_BAD:
raise ValueError(f"Component rtttl cannot use {platform} as output component")
raise cv.Invalid(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."
"Component rtttl is not known to work with the selected output type. "
"Make sure this output supports custom frequency output method."
)
FINAL_VALIDATE_SCHEMA = cv.Schema(
{
cv.Required(CONF_OUTPUT): fv.id_declaration_match_schema(
validate_parent_output_config
)
},
extra=cv.ALLOW_EXTRA,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View file

@ -40,6 +40,9 @@ CONFIG_SCHEMA = cv.All(
.extend(cv.polling_component_schema("5s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"sim800l", baud_rate=9600, require_tx=True, require_rx=True
)
async def to_code(config):
@ -54,10 +57,6 @@ 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),

View file

@ -1,5 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome import pins
from esphome.const import (
CONF_CLK_PIN,
@ -69,9 +70,24 @@ async def register_spi_device(var, config):
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")
def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: bool):
hub_schema = {}
if require_miso:
hub_schema[
cv.Required(
CONF_MISO_PIN,
msg=f"Component {name} requires this spi bus to declare a miso_pin",
)
] = cv.valid
if require_mosi:
hub_schema[
cv.Required(
CONF_MOSI_PIN,
msg=f"Component {name} requires this spi bus to declare a mosi_pin",
)
] = cv.valid
return cv.Schema(
{cv.Required(CONF_SPI_ID): fv.id_declaration_match_schema(hub_schema)},
extra=cv.ALLOW_EXTRA,
)

View file

@ -47,7 +47,7 @@ void Tcl112Climate::transmit_state() {
// Set mode
switch (this->mode) {
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
remote_state[6] &= 0xF0;
remote_state[6] |= TCL112_AUTO;
break;
@ -204,7 +204,7 @@ bool Tcl112Climate::on_receive(remote_base::RemoteReceiveData data) {
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
break;
case TCL112_AUTO:
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
break;
}
}

View file

@ -227,7 +227,7 @@ async def to_code(config):
await cg.register_component(var, config)
await climate.register_climate(var, config)
auto_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config
heat_cool_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config
two_points_available = CONF_HEAT_ACTION in config and (
CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config
)
@ -258,10 +258,10 @@ async def to_code(config):
var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION]
)
if auto_mode_available is True:
cg.add(var.set_supports_auto(True))
if heat_cool_mode_available is True:
cg.add(var.set_supports_heat_cool(True))
else:
cg.add(var.set_supports_auto(False))
cg.add(var.set_supports_heat_cool(False))
if CONF_COOL_ACTION in config:
await automation.build_automation(

View file

@ -21,7 +21,7 @@ void ThermostatClimate::setup() {
restore->to_call(this).perform();
} else {
// restore from defaults, change_away handles temps for us
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
this->change_away_(false);
}
// refresh the climate action based on the restored settings
@ -79,6 +79,7 @@ climate::ClimateTraits ThermostatClimate::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true);
traits.set_supports_auto_mode(this->supports_auto_);
traits.set_supports_heat_cool_mode(this->supports_heat_cool_);
traits.set_supports_cool_mode(this->supports_cool_);
traits.set_supports_dry_mode(this->supports_dry_);
traits.set_supports_fan_only_mode(this->supports_fan_only_);
@ -130,7 +131,7 @@ climate::ClimateAction ThermostatClimate::compute_action_() {
case climate::CLIMATE_MODE_OFF:
target_action = climate::CLIMATE_ACTION_OFF;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
case climate::CLIMATE_MODE_COOL:
case climate::CLIMATE_MODE_HEAT:
if (this->supports_cool_) {
@ -321,7 +322,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) {
case climate::CLIMATE_MODE_OFF:
trig = this->off_mode_trigger_;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
// trig = this->auto_mode_trigger_;
break;
case climate::CLIMATE_MODE_COOL:
@ -339,7 +340,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) {
default:
// we cannot report an invalid mode back to HA (even if it asked for one)
// and must assume some valid value
mode = climate::CLIMATE_MODE_AUTO;
mode = climate::CLIMATE_MODE_HEAT_COOL;
// trig = this->auto_mode_trigger_;
}
assert(trig != nullptr);
@ -434,6 +435,9 @@ ThermostatClimate::ThermostatClimate()
swing_mode_vertical_trigger_(new Trigger<>()) {}
void ThermostatClimate::set_hysteresis(float hysteresis) { this->hysteresis_ = hysteresis; }
void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) {
this->supports_heat_cool_ = supports_heat_cool;
}
void ThermostatClimate::set_supports_auto(bool supports_auto) { this->supports_auto_ = supports_auto; }
void ThermostatClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
void ThermostatClimate::set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; }
@ -521,6 +525,7 @@ void ThermostatClimate::dump_config() {
}
ESP_LOGCONFIG(TAG, " Hysteresis: %.1f°C", this->hysteresis_);
ESP_LOGCONFIG(TAG, " Supports AUTO: %s", YESNO(this->supports_auto_));
ESP_LOGCONFIG(TAG, " Supports HEAT/COOL: %s", YESNO(this->supports_heat_cool_));
ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_));
ESP_LOGCONFIG(TAG, " Supports DRY: %s", YESNO(this->supports_dry_));
ESP_LOGCONFIG(TAG, " Supports FAN_ONLY: %s", YESNO(this->supports_fan_only_));

View file

@ -29,6 +29,7 @@ class ThermostatClimate : public climate::Climate, public Component {
void set_hysteresis(float hysteresis);
void set_sensor(sensor::Sensor *sensor);
void set_supports_auto(bool supports_auto);
void set_supports_heat_cool(bool supports_heat_cool);
void set_supports_cool(bool supports_cool);
void set_supports_dry(bool supports_dry);
void set_supports_fan_only(bool supports_fan_only);
@ -113,6 +114,7 @@ class ThermostatClimate : public climate::Climate, public Component {
/// A false value for any given attribute means that the controller has no such action
/// (for example a thermostat, where only heating and not-heating is possible).
bool supports_auto_{false};
bool supports_heat_cool_{false};
bool supports_cool_{false};
bool supports_dry_{false};
bool supports_fan_only_{false};

View file

@ -80,7 +80,7 @@ void ToshibaClimate::transmit_state() {
mode = TOSHIBA_MODE_COOL;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
default:
mode = TOSHIBA_MODE_AUTO;
}
@ -190,7 +190,7 @@ bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) {
case TOSHIBA_MODE_AUTO:
default:
/* Note: Dry and Fan-only modes are reported as Auto, as they are not supported yet */
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
}
/* Get the target temperature */

View file

@ -13,7 +13,7 @@ void TuyaClimate::setup() {
this->mode = climate::CLIMATE_MODE_OFF;
if (datapoint.value_bool) {
if (this->supports_heat_ && this->supports_cool_) {
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
} else if (this->supports_heat_) {
this->mode = climate::CLIMATE_MODE_HEAT;
} else if (this->supports_cool_) {

View file

@ -1,5 +1,8 @@
from typing import Optional
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome import pins, automation
from esphome.const import (
CONF_BAUD_RATE,
@ -92,42 +95,6 @@ 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(
{
@ -135,6 +102,64 @@ UART_DEVICE_SCHEMA = cv.Schema(
}
)
KEY_UART_DEVICES = "uart_devices"
def final_validate_device_schema(
name: str,
*,
baud_rate: Optional[int] = None,
require_tx: bool = False,
require_rx: bool = False,
):
def validate_baud_rate(value):
if value != baud_rate:
raise cv.Invalid(
f"Component {name} required baud rate {baud_rate} for the uart bus"
)
return value
def validate_pin(opt, device):
def validator(value):
if opt in device:
raise cv.Invalid(
f"The uart {opt} is used both by {name} and {device[opt]}, "
f"but can only be used by one. Please create a new uart bus for {name}."
)
device[opt] = name
return value
return validator
def validate_hub(hub_config):
hub_schema = {}
uart_id = hub_config[CONF_ID]
devices = fv.full_config.get().data.setdefault(KEY_UART_DEVICES, {})
device = devices.setdefault(uart_id, {})
if require_tx:
hub_schema[
cv.Required(
CONF_TX_PIN,
msg=f"Component {name} requires this uart bus to declare a tx_pin",
)
] = validate_pin(CONF_TX_PIN, device)
if require_rx:
hub_schema[
cv.Required(
CONF_RX_PIN,
msg=f"Component {name} requires this uart bus to declare a rx_pin",
)
] = validate_pin(CONF_RX_PIN, device)
if baud_rate is not None:
hub_schema[cv.Required(CONF_BAUD_RATE)] = validate_baud_rate
return cv.Schema(hub_schema, extra=cv.ALLOW_EXTRA)(hub_config)
return cv.Schema(
{cv.Required(CONF_UART_ID): fv.id_declaration_match_schema(validate_hub)},
extra=cv.ALLOW_EXTRA,
)
async def register_uart_device(var, config):
"""Register a UART device, setting up all the internal values.

View file

@ -48,7 +48,7 @@ void WhirlpoolClimate::transmit_state() {
this->powered_on_assumed = powered_on;
}
switch (this->mode) {
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
// set fan auto
// set temp auto temp
// set sleep false
@ -239,7 +239,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) {
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
break;
case WHIRLPOOL_AUTO:
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
break;
}
}

View file

@ -1,5 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome import automation
from esphome.automation import Condition
from esphome.components.network import add_mdns_library
@ -137,18 +138,19 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend(
)
def validate(config, item_config):
if (
(CONF_NETWORKS in item_config)
and (item_config[CONF_NETWORKS] == [])
and (CONF_AP not in item_config)
):
if "esp32_improv" not in config:
raise ValueError(
def final_validate(config):
has_sta = bool(config.get(CONF_NETWORKS, True))
has_ap = CONF_AP in config
has_improv = "esp32_improv" in fv.full_config.get()
if (not has_sta) and (not has_ap) and (not has_improv):
raise cv.Invalid(
"Please specify at least an SSID or an Access Point to create."
)
FINAL_VALIDATE_SCHEMA = cv.Schema(final_validate)
def _validate(config):
if CONF_PASSWORD in config and CONF_SSID not in config:
raise cv.Invalid("Cannot have WiFi password without SSID!")

View file

@ -82,7 +82,7 @@ const uint32_t YASHIMA_CARRIER_FREQUENCY = 38000;
climate::ClimateTraits YashimaClimate::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(this->sensor_ != nullptr);
traits.set_supports_auto_mode(true);
traits.set_supports_heat_cool_mode(true);
traits.set_supports_cool_mode(this->supports_cool_);
traits.set_supports_heat_mode(this->supports_heat_);
traits.set_supports_two_point_target_temperature(false);
@ -139,7 +139,7 @@ void YashimaClimate::transmit_state_() {
// Set mode
switch (this->mode) {
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
remote_state[0] |= YASHIMA_MODE_AUTO_BYTE0;
remote_state[5] |= YASHIMA_MODE_AUTO_BYTE5;
break;

View file

@ -21,11 +21,13 @@ from esphome.helpers import indent
from esphome.util import safe_print, OrderedDict
from typing import List, Optional, Tuple, Union
from esphome.core import ConfigType
from esphome.loader import get_component, get_platform, ComponentManifest
from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue
from esphome.voluptuous_schema import ExtraKeysInvalid
from esphome.log import color, Fore
import esphome.final_validate as fv
import esphome.config_validation as cv
from esphome.types import ConfigType, ConfigPathType, ConfigFragmentType
_LOGGER = logging.getLogger(__name__)
@ -54,7 +56,7 @@ def _path_begins_with(path, other): # type: (ConfigPath, ConfigPath) -> bool
return path[: len(other)] == other
class Config(OrderedDict):
class Config(OrderedDict, fv.FinalValidateConfig):
def __init__(self):
super().__init__()
# A list of voluptuous errors
@ -65,6 +67,7 @@ class Config(OrderedDict):
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]]
self._data = {}
def add_error(self, error):
# type: (vol.Invalid) -> None
@ -72,6 +75,12 @@ class Config(OrderedDict):
for err in error.errors:
self.add_error(err)
return
if cv.ROOT_CONFIG_PATH in error.path:
# Root value means that the path before the root should be ignored
last_root = max(
i for i, v in enumerate(error.path) if v is cv.ROOT_CONFIG_PATH
)
error.path = error.path[last_root + 1 :]
self.errors.append(error)
@contextmanager
@ -140,13 +149,16 @@ class Config(OrderedDict):
return doc_range
def get_nested_item(self, path):
# type: (ConfigPath) -> ConfigType
def get_nested_item(
self, path: ConfigPathType, raise_error: bool = False
) -> ConfigFragmentType:
data = self
for item_index in path:
try:
data = data[item_index]
except (KeyError, IndexError, TypeError):
if raise_error:
raise
return {}
return data
@ -163,11 +175,20 @@ class Config(OrderedDict):
part.append(item_index)
return part
def get_config_by_id(self, id):
def get_path_for_id(self, id: core.ID):
"""Return the config fragment where the given ID is declared."""
for declared_id, path in self.declare_ids:
if declared_id.id == str(id):
return self.get_nested_item(path[:-1])
return None
return path
raise KeyError(f"ID {id} not found in configuration")
def get_config_for_path(self, path: ConfigPathType) -> ConfigFragmentType:
return self.get_nested_item(path, raise_error=True)
@property
def data(self):
"""Return temporary data used by final validation functions."""
return self._data
def iter_ids(config, path=None):
@ -189,23 +210,22 @@ def do_id_pass(result): # type: (Config) -> None
from esphome.cpp_generator import MockObjClass
from esphome.cpp_types import Component
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:
if id.id is not None:
# Look for duplicate definitions
match = next((v for v in declare_ids if v[0].id == id.id), None)
match = next((v for v in result.declare_ids if v[0].id == id.id), None)
if match is not None:
opath = "->".join(str(v) for v in match[1])
result.add_str_error(f"ID {id.id} redefined! Check {opath}", path)
continue
declare_ids.append((id, path))
result.declare_ids.append((id, path))
else:
searching_ids.append((id, path))
# Resolve default ids after manual IDs
for id, _ in declare_ids:
id.resolve([v[0].id for v in declare_ids])
for id, _ in result.declare_ids:
id.resolve([v[0].id for v in result.declare_ids])
if isinstance(id.type, MockObjClass) and id.type.inherits_from(Component):
CORE.component_ids.add(id.id)
@ -213,7 +233,7 @@ def do_id_pass(result): # type: (Config) -> None
for id, path in searching_ids:
if id.id is not None:
# manually declared
match = next((v[0] for v in declare_ids if v[0].id == id.id), None)
match = next((v[0] for v in result.declare_ids if v[0].id == id.id), None)
if match is None or not match.is_manual:
# No declared ID with this name
import difflib
@ -224,7 +244,7 @@ def do_id_pass(result): # type: (Config) -> None
)
# Find candidates
matches = difflib.get_close_matches(
id.id, [v[0].id for v in declare_ids if v[0].is_manual]
id.id, [v[0].id for v in result.declare_ids if v[0].is_manual]
)
if matches:
matches_s = ", ".join(f'"{x}"' for x in matches)
@ -245,7 +265,7 @@ def do_id_pass(result): # type: (Config) -> None
if id.id is None and id.type is not None:
matches = []
for v in declare_ids:
for v in result.declare_ids:
if v[0] is None or not isinstance(v[0].type, MockObjClass):
continue
inherits = v[0].type.inherits_from(id.type)
@ -278,8 +298,6 @@ def do_id_pass(result): # type: (Config) -> None
def recursive_check_replaceme(value):
import esphome.config_validation as cv
if isinstance(value, list):
return cv.Schema([recursive_check_replaceme])(value)
if isinstance(value, dict):
@ -558,14 +576,16 @@ def validate_config(config, command_line_substitutions):
# 7. Final validation
if not result.errors:
# Inter - components validation
for path, conf, comp in validate_queue:
if comp.config_schema is None:
token = fv.full_config.set(result)
for path, _, comp in validate_queue:
if comp.final_validate_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)
conf = result.get_nested_item(path)
with result.catch_error(path):
comp.final_validate_schema(conf)
fv.full_config.reset(token)
return result
@ -621,8 +641,12 @@ def _format_vol_invalid(ex, config):
)
elif "extra keys not allowed" in str(ex):
message += "[{}] is an invalid option for [{}].".format(ex.path[-1], paren)
elif "required key not provided" in str(ex):
elif isinstance(ex, vol.RequiredFieldInvalid):
if ex.msg == "required key not provided":
message += "'{}' is a required option for [{}].".format(ex.path[-1], paren)
else:
# Required has set a custom error message
message += ex.msg
else:
message += humanize_error(config, ex)

View file

@ -75,6 +75,9 @@ Inclusive = vol.Inclusive
ALLOW_EXTRA = vol.ALLOW_EXTRA
UNDEFINED = vol.UNDEFINED
RequiredFieldInvalid = vol.RequiredFieldInvalid
# this sentinel object can be placed in an 'Invalid' path to say
# the rest of the error path is relative to the root config path
ROOT_CONFIG_PATH = object()
RESERVED_IDS = [
# C++ keywords http://en.cppreference.com/w/cpp/keyword
@ -218,8 +221,8 @@ class Required(vol.Required):
- *not* the `config.get(CONF_<KEY>)` syntax.
"""
def __init__(self, key):
super().__init__(key)
def __init__(self, key, msg=None):
super().__init__(key, msg=msg)
def check_not_templatable(value):
@ -1073,6 +1076,7 @@ def invalid(message):
def valid(value):
"""A validator that is always valid and returns the value as-is."""
return value

View file

@ -2,7 +2,7 @@
MAJOR_VERSION = 1
MINOR_VERSION = 19
PATCH_VERSION = "0"
PATCH_VERSION = "1"
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}"

View file

@ -2,7 +2,7 @@ import logging
import math
import os
import re
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
from esphome.const import (
CONF_ARDUINO_VERSION,
@ -23,6 +23,7 @@ from esphome.util import OrderedDict
if TYPE_CHECKING:
from ..cpp_generator import MockObj, MockObjClass, Statement
from ..types import ConfigType
_LOGGER = logging.getLogger(__name__)
@ -462,9 +463,9 @@ class EsphomeCore:
# The board that's used (for example nodemcuv2)
self.board: Optional[str] = None
# The full raw configuration
self.raw_config: Optional[ConfigType] = None
self.raw_config: Optional["ConfigType"] = None
# The validated configuration, this is None until the config has been validated
self.config: Optional[ConfigType] = None
self.config: Optional["ConfigType"] = None
# The pending tasks in the task queue (mostly for C++ generation)
# This is a priority queue (with heapq)
# Each item is a tuple of form: (-priority, unique number, task)
@ -752,6 +753,3 @@ class EnumValue:
CORE = EsphomeCore()
ConfigType = Dict[str, Any]
CoreType = EsphomeCore

View file

@ -8,7 +8,8 @@ from esphome.const import (
)
# pylint: disable=unused-import
from esphome.core import coroutine, ID, CORE, ConfigType
from esphome.core import coroutine, ID, CORE
from esphome.types import ConfigType
from esphome.cpp_generator import RawExpression, add, get_variable
from esphome.cpp_types import App, GPIOPin
from esphome.util import Registry, RegistryEntry

View file

@ -716,9 +716,6 @@ class LogoutHandler(BaseHandler):
self.redirect("./login")
_STATIC_FILE_HASHES = {}
def get_base_frontend_path():
if ENV_DEV not in os.environ:
import esphome_dashboard
@ -741,19 +738,23 @@ def get_static_path(*args):
return os.path.join(get_base_frontend_path(), "static", *args)
@functools.lru_cache(maxsize=None)
def get_static_file_url(name):
base = f"./static/{name}"
if ENV_DEV in os.environ:
return base
# Module imports can't deduplicate if stuff added to url
if name == "js/esphome/index.js":
return f"./static/{name}"
import esphome_dashboard
return base.replace("index.js", esphome_dashboard.entrypoint())
if name in _STATIC_FILE_HASHES:
hash_ = _STATIC_FILE_HASHES[name]
else:
path = get_static_path(name)
with open(path, "rb") as f_handle:
hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8]
_STATIC_FILE_HASHES[name] = hash_
return f"./static/{name}?hash={hash_}"
return f"{base}?hash={hash_}"
def make_app(debug=get_bool_env(ENV_DEV)):
@ -820,9 +821,6 @@ def make_app(debug=get_bool_env(ENV_DEV)):
**app_settings,
)
if debug:
_STATIC_FILE_HASHES.clear()
return app

57
esphome/final_validate.py Normal file
View file

@ -0,0 +1,57 @@
from abc import ABC, abstractmethod, abstractproperty
from typing import Dict, Any
import contextvars
from esphome.types import ConfigFragmentType, ID, ConfigPathType
import esphome.config_validation as cv
class FinalValidateConfig(ABC):
@abstractproperty
def data(self) -> Dict[str, Any]:
"""A dictionary that can be used by post validation functions to store
global data during the validation phase. Each component should store its
data under a unique key
"""
@abstractmethod
def get_path_for_id(self, id: ID) -> ConfigPathType:
"""Get the config path a given ID has been declared in.
This is the location under the _validated_ config (for example, with cv.ensure_list applied)
Raises KeyError if the id was not declared in the configuration.
"""
@abstractmethod
def get_config_for_path(self, path: ConfigPathType) -> ConfigFragmentType:
"""Get the config fragment for the given global path.
Raises KeyError if a key in the path does not exist.
"""
FinalValidateConfig.register(dict)
# Context variable tracking the full config for some final validation functions.
full_config: contextvars.ContextVar[FinalValidateConfig] = contextvars.ContextVar(
"full_config"
)
def id_declaration_match_schema(schema):
"""A final-validation schema function that applies a schema to the outer config fragment of an
ID declaration.
This validator must be applied to ID values.
"""
if not isinstance(schema, cv.Schema):
schema = cv.Schema(schema, extra=cv.ALLOW_EXTRA)
def validator(value):
fconf = full_config.get()
path = fconf.get_path_for_id(value)[:-1]
declaration_config = fconf.get_config_for_path(path)
with cv.prepend_path([cv.ROOT_CONFIG_PATH] + path):
return schema(declaration_config)
return validator

View file

@ -12,6 +12,7 @@ from pathlib import Path
from esphome.const import ESP_PLATFORMS, SOURCE_FILE_EXTENSIONS
import esphome.core.config
from esphome.core import CORE
from esphome.types import ConfigType
_LOGGER = logging.getLogger(__name__)
@ -81,8 +82,13 @@ class ComponentManifest:
return getattr(self.module, "CODEOWNERS", [])
@property
def validate(self):
return getattr(self.module, "validate", None)
def final_validate_schema(self) -> Optional[Callable[[ConfigType], None]]:
"""Components can declare a `FINAL_VALIDATE_SCHEMA` cv.Schema that gets called
after the main validation. In that function checks across components can be made.
Note that the function can't mutate the configuration - no changes are saved
"""
return getattr(self.module, "FINAL_VALIDATE_SCHEMA", None)
@property
def source_files(self) -> Dict[Path, SourceFile]:

View file

@ -4,14 +4,13 @@ from datetime import datetime
import json
import logging
import os
from typing import Any, Optional, List
from esphome import const
from esphome.core import CORE
from esphome.helpers import write_file_if_changed
# pylint: disable=unused-import, wrong-import-order
from esphome.core import CoreType
from typing import Any, Optional, List
from esphome.types import CoreType
_LOGGER = logging.getLogger(__name__)

18
esphome/types.py Normal file
View file

@ -0,0 +1,18 @@
"""This helper module tracks commonly used types in the esphome python codebase."""
from typing import Dict, Union, List
from esphome.core import ID, Lambda, EsphomeCore
ConfigFragmentType = Union[
str,
int,
float,
None,
Dict[Union[str, int], "ConfigFragmentType"],
List["ConfigFragmentType"],
ID,
Lambda,
]
ConfigType = Dict[str, ConfigFragmentType]
CoreType = EsphomeCore
ConfigPathType = Union[str, int]

View file

@ -11,4 +11,4 @@ ifaddr==0.1.7
platformio==5.1.1
esptool==2.8
click==7.1.2
esphome-dashboard==20210615.0
esphome-dashboard==20210617.1