mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 09:01:49 +00:00 
			
		
		
		
	Compare commits
	
		
			15 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					e20ec00071 | ||
| 
						 | 
					150114d774 | ||
| 
						 | 
					89dfa5ea82 | ||
| 
						 | 
					97aa930ad2 | ||
| 
						 | 
					2a5def10e7 | ||
| 
						 | 
					969834e037 | ||
| 
						 | 
					d73a44c504 | ||
| 
						 | 
					8aec092ab6 | ||
| 
						 | 
					4fa959ba45 | ||
| 
						 | 
					b43712d78d | ||
| 
						 | 
					01904a0f10 | ||
| 
						 | 
					dd875e7529 | ||
| 
						 | 
					f1dcf0f0b8 | ||
| 
						 | 
					a045d001bf | ||
| 
						 | 
					066c1022d0 | 
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ ClimateTraits = climate_ns.class_("ClimateTraits")
 | 
			
		||||
ClimateMode = climate_ns.enum("ClimateMode")
 | 
			
		||||
CLIMATE_MODES = {
 | 
			
		||||
    "OFF": ClimateMode.CLIMATE_MODE_OFF,
 | 
			
		||||
    "HEAT_COOL": ClimateMode.CLIMATE_HEAT_COOL,
 | 
			
		||||
    "HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL,
 | 
			
		||||
    "COOL": ClimateMode.CLIMATE_MODE_COOL,
 | 
			
		||||
    "HEAT": ClimateMode.CLIMATE_MODE_HEAT,
 | 
			
		||||
    "DRY": ClimateMode.CLIMATE_MODE_DRY,
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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};
 | 
			
		||||
 
 | 
			
		||||
@@ -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_);
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(HttpRequestComponent),
 | 
			
		||||
            cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string,
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_TIMEOUT, default="5s"
 | 
			
		||||
            ): cv.positive_time_period_milliseconds,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .add_extra(validate_framework)
 | 
			
		||||
    .extend(cv.COMPONENT_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,
 | 
			
		||||
    }
 | 
			
		||||
).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,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = cv.Schema(validate_framework)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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, 0x81,
 | 
			
		||||
                                    0x00, 0xFF, 0x03, 0xFF, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
                                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x37, 0x31};
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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),
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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_));
 | 
			
		||||
 
 | 
			
		||||
@@ -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};
 | 
			
		||||
 
 | 
			
		||||
@@ -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 */
 | 
			
		||||
 
 | 
			
		||||
@@ -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_) {
 | 
			
		||||
 
 | 
			
		||||
@@ -333,7 +333,7 @@ void Tuya::send_raw_command_(TuyaCommand command) {
 | 
			
		||||
void Tuya::process_command_queue_() {
 | 
			
		||||
  uint32_t delay = millis() - this->last_command_timestamp_;
 | 
			
		||||
  // Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly
 | 
			
		||||
  if (delay > COMMAND_DELAY && !command_queue_.empty()) {
 | 
			
		||||
  if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty()) {
 | 
			
		||||
    this->send_raw_command_(command_queue_.front());
 | 
			
		||||
    this->command_queue_.erase(command_queue_.begin());
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -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,16 +138,17 @@ 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(
 | 
			
		||||
                "Please specify at least an SSID or an Access Point to create."
 | 
			
		||||
            )
 | 
			
		||||
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):
 | 
			
		||||
 
 | 
			
		||||
@@ -587,7 +587,7 @@ void WiFiComponent::retry_connect() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool WiFiComponent::can_proceed() {
 | 
			
		||||
  if (this->has_ap() && !this->has_sta()) {
 | 
			
		||||
  if (!this->has_sta()) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  return this->is_connected();
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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):
 | 
			
		||||
        message += "'{}' is a required option for [{}].".format(ex.path[-1], paren)
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
 | 
			
		||||
MAJOR_VERSION = 1
 | 
			
		||||
MINOR_VERSION = 19
 | 
			
		||||
PATCH_VERSION = "0"
 | 
			
		||||
PATCH_VERSION = "3"
 | 
			
		||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
 | 
			
		||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
    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 base.replace("index.js", esphome_dashboard.entrypoint())
 | 
			
		||||
 | 
			
		||||
    path = get_static_path(name)
 | 
			
		||||
    with open(path, "rb") as f_handle:
 | 
			
		||||
        hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8]
 | 
			
		||||
    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
									
								
							
							
						
						
									
										57
									
								
								esphome/final_validate.py
									
									
									
									
									
										Normal 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
 | 
			
		||||
@@ -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]:
 | 
			
		||||
 
 | 
			
		||||
@@ -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
									
								
							
							
						
						
									
										18
									
								
								esphome/types.py
									
									
									
									
									
										Normal 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]
 | 
			
		||||
@@ -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==20210622.0
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user