mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00:00 
			
		
		
		
	Compare commits
	
		
			126 Commits
		
	
	
		
			jesserockz
			...
			2023.9.0b3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					0a1ed58454 | ||
| 
						 | 
					5f5ee9c920 | ||
| 
						 | 
					0aeebdd289 | ||
| 
						 | 
					33e2aa341e | ||
| 
						 | 
					a42788812e | ||
| 
						 | 
					b07a038bc8 | ||
| 
						 | 
					55e36ab982 | ||
| 
						 | 
					90835ab917 | ||
| 
						 | 
					5b46088ae4 | ||
| 
						 | 
					d7e267eca5 | ||
| 
						 | 
					807c47a076 | ||
| 
						 | 
					7ebe6a5894 | ||
| 
						 | 
					41c829fa32 | ||
| 
						 | 
					8f1ce8c7f7 | ||
| 
						 | 
					e55636ed52 | ||
| 
						 | 
					e886262055 | ||
| 
						 | 
					2fa7f8c511 | ||
| 
						 | 
					4622ef770d | ||
| 
						 | 
					d76f18b4f2 | ||
| 
						 | 
					ec20778d83 | ||
| 
						 | 
					b3ca71c6fb | ||
| 
						 | 
					2d53dd05d8 | ||
| 
						 | 
					68a2c45edf | ||
| 
						 | 
					d2616cd6c6 | ||
| 
						 | 
					01ec414873 | ||
| 
						 | 
					150c9b5fa3 | ||
| 
						 | 
					55df88d7ae | ||
| 
						 | 
					619787e6d2 | ||
| 
						 | 
					3f8bad3ed1 | ||
| 
						 | 
					c146712b16 | ||
| 
						 | 
					2cabe59c22 | ||
| 
						 | 
					a67b92a04c | ||
| 
						 | 
					9fb8e9edef | ||
| 
						 | 
					d2bccbe8ac | ||
| 
						 | 
					e44a60e814 | ||
| 
						 | 
					02a71cb6a7 | ||
| 
						 | 
					e600784ebf | ||
| 
						 | 
					5e19a3b892 | ||
| 
						 | 
					8bf112669f | ||
| 
						 | 
					4278664208 | ||
| 
						 | 
					0789657fd5 | ||
| 
						 | 
					b566c78f00 | ||
| 
						 | 
					a35122231c | ||
| 
						 | 
					7e4ee32b54 | ||
| 
						 | 
					7df80eadcf | ||
| 
						 | 
					2aaba1d2b8 | ||
| 
						 | 
					7c129a4018 | ||
| 
						 | 
					cb66ce069e | ||
| 
						 | 
					a27e72362a | ||
| 
						 | 
					f44e5d3142 | ||
| 
						 | 
					532163738e | ||
| 
						 | 
					63fa922547 | ||
| 
						 | 
					48e4cb5ae2 | ||
| 
						 | 
					ff8a73c2d1 | ||
| 
						 | 
					afd26c6f1a | ||
| 
						 | 
					67b06a88b2 | ||
| 
						 | 
					265e019381 | ||
| 
						 | 
					560e36a65c | ||
| 
						 | 
					b05a3fbb55 | ||
| 
						 | 
					3a899e28dc | ||
| 
						 | 
					f26238e824 | ||
| 
						 | 
					3717e34bba | ||
| 
						 | 
					be6f95d43e | ||
| 
						 | 
					99a765dc06 | ||
| 
						 | 
					351e7ea16b | ||
| 
						 | 
					2fa79a2e2f | ||
| 
						 | 
					44a917929d | ||
| 
						 | 
					21ebc7f95b | ||
| 
						 | 
					72e72d7d4b | ||
| 
						 | 
					02ed2c0ebe | ||
| 
						 | 
					0f506ea8eb | ||
| 
						 | 
					b914d6e305 | ||
| 
						 | 
					956e19be7d | ||
| 
						 | 
					b3d5a4dfdb | ||
| 
						 | 
					c63cdae84f | ||
| 
						 | 
					dec044ad8b | ||
| 
						 | 
					2a12ec09fb | ||
| 
						 | 
					91e920c498 | ||
| 
						 | 
					9b19c45735 | ||
| 
						 | 
					3843d21dbf | ||
| 
						 | 
					73db164fb1 | ||
| 
						 | 
					ab32dd7420 | ||
| 
						 | 
					2a7aa2fc0d | ||
| 
						 | 
					f5e98eb86f | ||
| 
						 | 
					362a19c2e1 | ||
| 
						 | 
					f4a4956dd4 | ||
| 
						 | 
					746488cabf | ||
| 
						 | 
					4449248c6f | ||
| 
						 | 
					036e14ab7f | ||
| 
						 | 
					f840eee1b7 | ||
| 
						 | 
					553132443f | ||
| 
						 | 
					d20242f589 | ||
| 
						 | 
					68affce274 | ||
| 
						 | 
					c4b9065749 | ||
| 
						 | 
					d57a5d1793 | ||
| 
						 | 
					74e062fdb3 | ||
| 
						 | 
					6bdc0c92fe | ||
| 
						 | 
					d7945de001 | ||
| 
						 | 
					3ba2a29e54 | ||
| 
						 | 
					76b438f79c | ||
| 
						 | 
					bc14f06a07 | ||
| 
						 | 
					feee075122 | ||
| 
						 | 
					a77cf1beec | ||
| 
						 | 
					d7bfdd0efc | ||
| 
						 | 
					62aee36f82 | ||
| 
						 | 
					0709367587 | ||
| 
						 | 
					98277f6ceb | ||
| 
						 | 
					8dd509ba53 | ||
| 
						 | 
					8df455f55b | ||
| 
						 | 
					36782f13bf | ||
| 
						 | 
					e823067a6b | ||
| 
						 | 
					c3ef12d580 | ||
| 
						 | 
					321155eb40 | ||
| 
						 | 
					d34c074b92 | ||
| 
						 | 
					abc8e903c1 | ||
| 
						 | 
					832ba38f1b | ||
| 
						 | 
					70de2f5278 | ||
| 
						 | 
					604d4eec79 | ||
| 
						 | 
					b806eb6a61 | ||
| 
						 | 
					39948db59a | ||
| 
						 | 
					fbfb4e2a73 | ||
| 
						 | 
					595ac84779 | ||
| 
						 | 
					746f72a279 | ||
| 
						 | 
					dec6f04499 | ||
| 
						 | 
					a90d266017 | ||
| 
						 | 
					df9fcf9850 | 
@@ -29,7 +29,8 @@ RUN \
 | 
			
		||||
        curl=7.74.0-1.3+deb11u7 \
 | 
			
		||||
        openssh-client=1:8.4p1-5+deb11u1 \
 | 
			
		||||
        python3-cffi=1.14.5-1 \
 | 
			
		||||
        libcairo2=1.16.0-5; \
 | 
			
		||||
        libcairo2=1.16.0-5 \
 | 
			
		||||
        patch=2.7.6-7; \
 | 
			
		||||
    if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
 | 
			
		||||
        apt-get install -y --no-install-recommends \
 | 
			
		||||
          build-essential=12.9 \
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    ).extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(CONF_DALLAS_ID): cv.use_id(DallasComponent),
 | 
			
		||||
            cv.Optional(CONF_ADDRESS): cv.hex_int,
 | 
			
		||||
            cv.Optional(CONF_ADDRESS): cv.hex_uint64_t,
 | 
			
		||||
            cv.Optional(CONF_INDEX): cv.positive_int,
 | 
			
		||||
            cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -80,8 +80,6 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
 | 
			
		||||
  TEMPLATABLE_VALUE(std::string, url)
 | 
			
		||||
  TEMPLATABLE_VALUE(const char *, method)
 | 
			
		||||
  TEMPLATABLE_VALUE(std::string, body)
 | 
			
		||||
  TEMPLATABLE_VALUE(const char *, useragent)
 | 
			
		||||
  TEMPLATABLE_VALUE(uint16_t, timeout)
 | 
			
		||||
 | 
			
		||||
  void add_header(const char *key, TemplatableValue<const char *, Ts...> value) { this->headers_.insert({key, value}); }
 | 
			
		||||
 | 
			
		||||
@@ -105,13 +103,6 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
 | 
			
		||||
      auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_func_, this, x..., std::placeholders::_1);
 | 
			
		||||
      this->parent_->set_body(json::build_json(f));
 | 
			
		||||
    }
 | 
			
		||||
    if (this->useragent_.has_value()) {
 | 
			
		||||
      this->parent_->set_useragent(this->useragent_.value(x...));
 | 
			
		||||
    }
 | 
			
		||||
    if (this->timeout_.has_value()) {
 | 
			
		||||
      this->parent_->set_timeout(this->timeout_.value(x...));
 | 
			
		||||
    }
 | 
			
		||||
    if (!this->headers_.empty()) {
 | 
			
		||||
    std::list<Header> headers;
 | 
			
		||||
    for (const auto &item : this->headers_) {
 | 
			
		||||
      auto val = item.second;
 | 
			
		||||
@@ -121,9 +112,9 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
 | 
			
		||||
      headers.push_back(header);
 | 
			
		||||
    }
 | 
			
		||||
    this->parent_->set_headers(headers);
 | 
			
		||||
    }
 | 
			
		||||
    this->parent_->send(this->response_triggers_);
 | 
			
		||||
    this->parent_->close();
 | 
			
		||||
    this->parent_->set_body("");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ CONF_COLUMNS = "columns"
 | 
			
		||||
CONF_KEYS = "keys"
 | 
			
		||||
CONF_DEBOUNCE_TIME = "debounce_time"
 | 
			
		||||
CONF_HAS_DIODES = "has_diodes"
 | 
			
		||||
CONF_HAS_PULLDOWNS = "has_pulldowns"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def check_keys(obj):
 | 
			
		||||
@@ -45,6 +46,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.Optional(CONF_KEYS): cv.string,
 | 
			
		||||
            cv.Optional(CONF_DEBOUNCE_TIME, default=1): cv.int_range(min=1, max=100),
 | 
			
		||||
            cv.Optional(CONF_HAS_DIODES): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_HAS_PULLDOWNS): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    check_keys,
 | 
			
		||||
@@ -69,3 +71,5 @@ async def to_code(config):
 | 
			
		||||
    cg.add(var.set_debounce_time(config[CONF_DEBOUNCE_TIME]))
 | 
			
		||||
    if CONF_HAS_DIODES in config:
 | 
			
		||||
        cg.add(var.set_has_diodes(config[CONF_HAS_DIODES]))
 | 
			
		||||
    if CONF_HAS_PULLDOWNS in config:
 | 
			
		||||
        cg.add(var.set_has_pulldowns(config[CONF_HAS_PULLDOWNS]))
 | 
			
		||||
 
 | 
			
		||||
@@ -11,12 +11,17 @@ void MatrixKeypad::setup() {
 | 
			
		||||
    if (!has_diodes_) {
 | 
			
		||||
      pin->pin_mode(gpio::FLAG_INPUT);
 | 
			
		||||
    } else {
 | 
			
		||||
      pin->digital_write(true);
 | 
			
		||||
      pin->digital_write(!has_pulldowns_);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  for (auto *pin : this->columns_)
 | 
			
		||||
  for (auto *pin : this->columns_) {
 | 
			
		||||
    if (has_pulldowns_) {
 | 
			
		||||
      pin->pin_mode(gpio::FLAG_INPUT);
 | 
			
		||||
    } else {
 | 
			
		||||
      pin->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MatrixKeypad::loop() {
 | 
			
		||||
  static uint32_t active_start = 0;
 | 
			
		||||
@@ -28,9 +33,9 @@ void MatrixKeypad::loop() {
 | 
			
		||||
  for (auto *row : this->rows_) {
 | 
			
		||||
    if (!has_diodes_)
 | 
			
		||||
      row->pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
    row->digital_write(false);
 | 
			
		||||
    row->digital_write(has_pulldowns_);
 | 
			
		||||
    for (auto *col : this->columns_) {
 | 
			
		||||
      if (!col->digital_read()) {
 | 
			
		||||
      if (col->digital_read() == has_pulldowns_) {
 | 
			
		||||
        if (key != -1) {
 | 
			
		||||
          error = true;
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -39,7 +44,7 @@ void MatrixKeypad::loop() {
 | 
			
		||||
      }
 | 
			
		||||
      pos++;
 | 
			
		||||
    }
 | 
			
		||||
    row->digital_write(true);
 | 
			
		||||
    row->digital_write(!has_pulldowns_);
 | 
			
		||||
    if (!has_diodes_)
 | 
			
		||||
      row->pin_mode(gpio::FLAG_INPUT);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component {
 | 
			
		||||
  void set_keys(std::string keys) { keys_ = std::move(keys); };
 | 
			
		||||
  void set_debounce_time(int debounce_time) { debounce_time_ = debounce_time; };
 | 
			
		||||
  void set_has_diodes(int has_diodes) { has_diodes_ = has_diodes; };
 | 
			
		||||
  void set_has_pulldowns(int has_pulldowns) { has_pulldowns_ = has_pulldowns; };
 | 
			
		||||
 | 
			
		||||
  void register_listener(MatrixKeypadListener *listener);
 | 
			
		||||
 | 
			
		||||
@@ -37,6 +38,7 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component {
 | 
			
		||||
  std::string keys_;
 | 
			
		||||
  int debounce_time_ = 0;
 | 
			
		||||
  bool has_diodes_{false};
 | 
			
		||||
  bool has_pulldowns_{false};
 | 
			
		||||
  int pressed_key_ = -1;
 | 
			
		||||
 | 
			
		||||
  std::vector<MatrixKeypadListener *> listeners_{};
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ from esphome.components.climate import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@dudanov"]
 | 
			
		||||
DEPENDENCIES = ["climate", "uart", "wifi"]
 | 
			
		||||
DEPENDENCIES = ["climate", "uart"]
 | 
			
		||||
AUTO_LOAD = ["sensor"]
 | 
			
		||||
CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature"
 | 
			
		||||
CONF_POWER_USAGE = "power_usage"
 | 
			
		||||
 
 | 
			
		||||
@@ -71,7 +71,8 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
 | 
			
		||||
  const auto *mopeka_data = (const mopeka_std_package *) manu_data.data.data();
 | 
			
		||||
 | 
			
		||||
  const u_int8_t hardware_id = mopeka_data->data_1 & 0xCF;
 | 
			
		||||
  if (static_cast<SensorType>(hardware_id) != STANDARD && static_cast<SensorType>(hardware_id) != XL) {
 | 
			
		||||
  if (static_cast<SensorType>(hardware_id) != STANDARD && static_cast<SensorType>(hardware_id) != XL &&
 | 
			
		||||
      static_cast<SensorType>(hardware_id) != ETRAILER) {
 | 
			
		||||
    ESP_LOGE(TAG, "[%s] Unsupported Sensor Type (0x%X)", device.address_str().c_str(), hardware_id);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ namespace mopeka_std_check {
 | 
			
		||||
enum SensorType {
 | 
			
		||||
  STANDARD = 0x02,
 | 
			
		||||
  XL = 0x03,
 | 
			
		||||
  ETRAILER = 0x46,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 4 values in one struct so it aligns to 8 byte. One `mopeka_std_values` is 40 bit long.
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,9 @@ void PulseMeterSensor::setup() {
 | 
			
		||||
  this->pin_->setup();
 | 
			
		||||
  this->isr_pin_ = pin_->to_isr();
 | 
			
		||||
 | 
			
		||||
  // Set the last processed edge to now for the first timeout
 | 
			
		||||
  this->last_processed_edge_us_ = micros();
 | 
			
		||||
 | 
			
		||||
  if (this->filter_mode_ == FILTER_EDGE) {
 | 
			
		||||
    this->pin_->attach_interrupt(PulseMeterSensor::edge_intr, this, gpio::INTERRUPT_RISING_EDGE);
 | 
			
		||||
  } else if (this->filter_mode_ == FILTER_PULSE) {
 | 
			
		||||
@@ -38,12 +41,16 @@ void PulseMeterSensor::loop() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // We need to detect at least two edges to have a valid pulse width
 | 
			
		||||
    if (!this->initialized_) {
 | 
			
		||||
      this->initialized_ = true;
 | 
			
		||||
    } else {
 | 
			
		||||
    switch (this->meter_state_) {
 | 
			
		||||
      case MeterState::INITIAL:
 | 
			
		||||
      case MeterState::TIMED_OUT: {
 | 
			
		||||
        this->meter_state_ = MeterState::RUNNING;
 | 
			
		||||
      } break;
 | 
			
		||||
      case MeterState::RUNNING: {
 | 
			
		||||
        uint32_t delta_us = this->get_->last_detected_edge_us_ - this->last_processed_edge_us_;
 | 
			
		||||
        float pulse_width_us = delta_us / float(this->get_->count_);
 | 
			
		||||
        this->publish_state((60.0f * 1000000.0f) / pulse_width_us);
 | 
			
		||||
      } break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->last_processed_edge_us_ = this->get_->last_detected_edge_us_;
 | 
			
		||||
@@ -53,11 +60,19 @@ void PulseMeterSensor::loop() {
 | 
			
		||||
    const uint32_t now = micros();
 | 
			
		||||
    const uint32_t time_since_valid_edge_us = now - this->last_processed_edge_us_;
 | 
			
		||||
 | 
			
		||||
    if (this->initialized_ && time_since_valid_edge_us > this->timeout_us_) {
 | 
			
		||||
    switch (this->meter_state_) {
 | 
			
		||||
        // Running and initial states can timeout
 | 
			
		||||
      case MeterState::INITIAL:
 | 
			
		||||
      case MeterState::RUNNING: {
 | 
			
		||||
        if (time_since_valid_edge_us > this->timeout_us_) {
 | 
			
		||||
          this->meter_state_ = MeterState::TIMED_OUT;
 | 
			
		||||
          ESP_LOGD(TAG, "No pulse detected for %us, assuming 0 pulses/min", time_since_valid_edge_us / 1000000);
 | 
			
		||||
      this->initialized_ = false;
 | 
			
		||||
          this->publish_state(0.0f);
 | 
			
		||||
        }
 | 
			
		||||
      } break;
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,8 @@ class PulseMeterSensor : public sensor::Sensor, public Component {
 | 
			
		||||
  InternalFilterMode filter_mode_{FILTER_EDGE};
 | 
			
		||||
 | 
			
		||||
  // Variables used in the loop
 | 
			
		||||
  bool initialized_ = false;
 | 
			
		||||
  enum class MeterState { INITIAL, RUNNING, TIMED_OUT };
 | 
			
		||||
  MeterState meter_state_ = MeterState::INITIAL;
 | 
			
		||||
  uint32_t total_pulses_ = 0;
 | 
			
		||||
  uint32_t last_processed_edge_us_ = 0;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ static const char *const TAG = "radon_eye_ble";
 | 
			
		||||
 | 
			
		||||
bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
			
		||||
  if (not device.get_name().empty()) {
 | 
			
		||||
    if (device.get_name().rfind("FR:R20:SN", 0) == 0) {
 | 
			
		||||
    if (device.get_name().rfind("FR:R", 0) == 0) {
 | 
			
		||||
      // This is an RD200, I think
 | 
			
		||||
      ESP_LOGD(TAG, "Found Radon Eye RD200 device Name: %s (MAC: %s)", device.get_name().c_str(),
 | 
			
		||||
               device.address_str().c_str());
 | 
			
		||||
 
 | 
			
		||||
@@ -152,6 +152,9 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    cg.add(rp2040_ns.setup_preferences())
 | 
			
		||||
 | 
			
		||||
    # Allow LDF to properly discover dependency including those in preprocessor
 | 
			
		||||
    # conditionals
 | 
			
		||||
    cg.add_platformio_option("lib_ldf_mode", "chain+")
 | 
			
		||||
    cg.add_platformio_option("board", config[CONF_BOARD])
 | 
			
		||||
    cg.add_build_flag("-DUSE_RP2040")
 | 
			
		||||
    cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
 | 
			
		||||
 
 | 
			
		||||
@@ -57,6 +57,10 @@ KNOWN_FIRMWARE = {
 | 
			
		||||
        "https://github.com/jamesturton/shelly-dimmer-stm32/releases/download/v51.6/shelly-dimmer-stm32_v51.6.bin",
 | 
			
		||||
        "eda483e111c914723a33f5088f1397d5c0b19333db4a88dc965636b976c16c36",
 | 
			
		||||
    ),
 | 
			
		||||
    "51.7": (
 | 
			
		||||
        "https://github.com/jamesturton/shelly-dimmer-stm32/releases/download/v51.7/shelly-dimmer-stm32_v51.7.bin",
 | 
			
		||||
        "7a20f1c967c469917368a79bc56498009045237080408cef7190743e08031889",
 | 
			
		||||
    ),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -351,6 +351,7 @@ class SPIClient {
 | 
			
		||||
      : bit_order_(bit_order), mode_(mode), data_rate_(data_rate) {}
 | 
			
		||||
 | 
			
		||||
  virtual void spi_setup() {
 | 
			
		||||
    esph_log_d("spi_device", "mode %u, data_rate %ukHz", (unsigned) this->mode_, (unsigned) (this->data_rate_ / 1000));
 | 
			
		||||
    this->delegate_ = this->parent_->register_device(this, this->mode_, this->bit_order_, this->data_rate_, this->cs_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -398,10 +399,7 @@ class SPIDevice : public SPIClient {
 | 
			
		||||
 | 
			
		||||
  void set_data_rate(uint32_t data_rate) { this->data_rate_ = data_rate; }
 | 
			
		||||
 | 
			
		||||
  void set_bit_order(SPIBitOrder order) {
 | 
			
		||||
    this->bit_order_ = order;
 | 
			
		||||
    esph_log_d("spi.h", "bit order set to %d", order);
 | 
			
		||||
  }
 | 
			
		||||
  void set_bit_order(SPIBitOrder order) { this->bit_order_ = order; }
 | 
			
		||||
 | 
			
		||||
  void set_mode(SPIMode mode) { this->mode_ = mode; }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,11 @@ class SPIDelegateHw : public SPIDelegate {
 | 
			
		||||
  void begin_transaction() override {
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
    SPISettings const settings(this->data_rate_, static_cast<BitOrder>(this->bit_order_), this->mode_);
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
    // Arduino ESP8266 library has mangled values for SPI modes :-(
 | 
			
		||||
    auto mode = (this->mode_ & 0x01) + ((this->mode_ & 0x02) << 3);
 | 
			
		||||
    ESP_LOGV(TAG, "8266 mangled SPI mode 0x%X", mode);
 | 
			
		||||
    SPISettings const settings(this->data_rate_, this->bit_order_, mode);
 | 
			
		||||
#else
 | 
			
		||||
    SPISettings const settings(this->data_rate_, this->bit_order_, this->mode_);
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -986,6 +986,7 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) {
 | 
			
		||||
      // Fire any preset changed trigger if defined
 | 
			
		||||
      Trigger<> *trig = this->preset_change_trigger_;
 | 
			
		||||
      assert(trig != nullptr);
 | 
			
		||||
      this->preset = preset;
 | 
			
		||||
      trig->trigger();
 | 
			
		||||
 | 
			
		||||
      this->refresh();
 | 
			
		||||
@@ -1010,6 +1011,7 @@ void ThermostatClimate::change_custom_preset_(const std::string &custom_preset)
 | 
			
		||||
      // Fire any preset changed trigger if defined
 | 
			
		||||
      Trigger<> *trig = this->preset_change_trigger_;
 | 
			
		||||
      assert(trig != nullptr);
 | 
			
		||||
      this->custom_preset = custom_preset;
 | 
			
		||||
      trig->trigger();
 | 
			
		||||
 | 
			
		||||
      this->refresh();
 | 
			
		||||
 
 | 
			
		||||
@@ -1561,6 +1561,23 @@ void WaveshareEPaper7P5In::dump_config() {
 | 
			
		||||
  LOG_PIN("  Busy Pin: ", this->busy_pin_);
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
bool WaveshareEPaper7P5InV2::wait_until_idle_() {
 | 
			
		||||
  if (this->busy_pin_ == nullptr) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const uint32_t start = millis();
 | 
			
		||||
  while (this->busy_pin_->digital_read()) {
 | 
			
		||||
    this->command(0x71);
 | 
			
		||||
    if (millis() - start > this->idle_timeout_()) {
 | 
			
		||||
      ESP_LOGE(TAG, "Timeout while displaying image!");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    App.feed_wdt();
 | 
			
		||||
    delay(10);
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
void WaveshareEPaper7P5InV2::initialize() {
 | 
			
		||||
  // COMMAND POWER SETTING
 | 
			
		||||
  this->command(0x01);
 | 
			
		||||
@@ -1568,10 +1585,21 @@ void WaveshareEPaper7P5InV2::initialize() {
 | 
			
		||||
  this->data(0x07);
 | 
			
		||||
  this->data(0x3f);
 | 
			
		||||
  this->data(0x3f);
 | 
			
		||||
  this->command(0x04);
 | 
			
		||||
 | 
			
		||||
  // We don't want the display to be powered at this point
 | 
			
		||||
 | 
			
		||||
  delay(100);  // NOLINT
 | 
			
		||||
  this->wait_until_idle_();
 | 
			
		||||
 | 
			
		||||
  // COMMAND VCOM AND DATA INTERVAL SETTING
 | 
			
		||||
  this->command(0x50);
 | 
			
		||||
  this->data(0x10);
 | 
			
		||||
  this->data(0x07);
 | 
			
		||||
 | 
			
		||||
  // COMMAND TCON SETTING
 | 
			
		||||
  this->command(0x60);
 | 
			
		||||
  this->data(0x22);
 | 
			
		||||
 | 
			
		||||
  // COMMAND PANEL SETTING
 | 
			
		||||
  this->command(0x00);
 | 
			
		||||
  this->data(0x1F);
 | 
			
		||||
@@ -1582,19 +1610,30 @@ void WaveshareEPaper7P5InV2::initialize() {
 | 
			
		||||
  this->data(0x20);
 | 
			
		||||
  this->data(0x01);
 | 
			
		||||
  this->data(0xE0);
 | 
			
		||||
  // COMMAND ...?
 | 
			
		||||
 | 
			
		||||
  // COMMAND DUAL SPI MM_EN, DUSPI_EN
 | 
			
		||||
  this->command(0x15);
 | 
			
		||||
  this->data(0x00);
 | 
			
		||||
  // COMMAND VCOM AND DATA INTERVAL SETTING
 | 
			
		||||
  this->command(0x50);
 | 
			
		||||
  this->data(0x10);
 | 
			
		||||
  this->data(0x07);
 | 
			
		||||
  // COMMAND TCON SETTING
 | 
			
		||||
  this->command(0x60);
 | 
			
		||||
  this->data(0x22);
 | 
			
		||||
 | 
			
		||||
  // COMMAND POWER DRIVER HAT DOWN
 | 
			
		||||
  // This command will turn off booster, controller, source driver, gate driver, VCOM, and
 | 
			
		||||
  // temperature sensor, but register data will be kept until VDD turned OFF or Deep Sleep Mode.
 | 
			
		||||
  // Source/Gate/Border/VCOM will be released to floating.
 | 
			
		||||
  this->command(0x02);
 | 
			
		||||
}
 | 
			
		||||
void HOT WaveshareEPaper7P5InV2::display() {
 | 
			
		||||
  uint32_t buf_len = this->get_buffer_length_();
 | 
			
		||||
 | 
			
		||||
  // COMMAND POWER ON
 | 
			
		||||
  ESP_LOGI(TAG, "Power on the display and hat");
 | 
			
		||||
 | 
			
		||||
  // This command will turn on booster, controller, regulators, and temperature sensor will be
 | 
			
		||||
  // activated for one-time sensing before enabling booster. When all voltages are ready, the
 | 
			
		||||
  // BUSY_N signal will return to high.
 | 
			
		||||
  this->command(0x04);
 | 
			
		||||
  delay(200);  // NOLINT
 | 
			
		||||
  this->wait_until_idle_();
 | 
			
		||||
 | 
			
		||||
  // COMMAND DATA START TRANSMISSION NEW DATA
 | 
			
		||||
  this->command(0x13);
 | 
			
		||||
  delay(2);
 | 
			
		||||
@@ -1602,14 +1641,23 @@ void HOT WaveshareEPaper7P5InV2::display() {
 | 
			
		||||
    this->data(~(this->buffer_[i]));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  delay(100);  // NOLINT
 | 
			
		||||
  this->wait_until_idle_();
 | 
			
		||||
 | 
			
		||||
  // COMMAND DISPLAY REFRESH
 | 
			
		||||
  this->command(0x12);
 | 
			
		||||
  delay(100);  // NOLINT
 | 
			
		||||
  this->wait_until_idle_();
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Before command(0x02) (>> power off)");
 | 
			
		||||
  this->command(0x02);
 | 
			
		||||
  this->wait_until_idle_();
 | 
			
		||||
  ESP_LOGV(TAG, "After command(0x02) (>> power off)");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int WaveshareEPaper7P5InV2::get_width_internal() { return 800; }
 | 
			
		||||
int WaveshareEPaper7P5InV2::get_height_internal() { return 480; }
 | 
			
		||||
uint32_t WaveshareEPaper7P5InV2::idle_timeout_() { return 10000; }
 | 
			
		||||
void WaveshareEPaper7P5InV2::dump_config() {
 | 
			
		||||
  LOG_DISPLAY("", "Waveshare E-Paper", this);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Model: 7.5inV2rev2");
 | 
			
		||||
 
 | 
			
		||||
@@ -472,6 +472,8 @@ class WaveshareEPaper7P5InBC : public WaveshareEPaper {
 | 
			
		||||
 | 
			
		||||
class WaveshareEPaper7P5InV2 : public WaveshareEPaper {
 | 
			
		||||
 public:
 | 
			
		||||
  bool wait_until_idle_();
 | 
			
		||||
 | 
			
		||||
  void initialize() override;
 | 
			
		||||
 | 
			
		||||
  void display() override;
 | 
			
		||||
@@ -491,6 +493,8 @@ class WaveshareEPaper7P5InV2 : public WaveshareEPaper {
 | 
			
		||||
  int get_width_internal() override;
 | 
			
		||||
 | 
			
		||||
  int get_height_internal() override;
 | 
			
		||||
 | 
			
		||||
  uint32_t idle_timeout_() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class WaveshareEPaper7P5InV2alt : public WaveshareEPaper7P5InV2 {
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(WebServer),
 | 
			
		||||
            cv.Optional(CONF_PORT, default=80): cv.port,
 | 
			
		||||
            cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2),
 | 
			
		||||
            cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, int=True),
 | 
			
		||||
            cv.Optional(CONF_CSS_URL): cv.string,
 | 
			
		||||
            cv.Optional(CONF_CSS_INCLUDE): cv.file_,
 | 
			
		||||
            cv.Optional(CONF_JS_URL): cv.string,
 | 
			
		||||
 
 | 
			
		||||
@@ -98,6 +98,7 @@ bool WiFiComponent::wifi_apply_power_save_() {
 | 
			
		||||
      power_save = NONE_SLEEP_T;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  wifi_fpm_auto_sleep_set_in_null_mode(1);
 | 
			
		||||
  return wifi_set_sleep_type(power_save);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
"""Constants used by esphome."""
 | 
			
		||||
 | 
			
		||||
__version__ = "2023.9.0-dev"
 | 
			
		||||
__version__ = "2023.9.0b3"
 | 
			
		||||
 | 
			
		||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
 | 
			
		||||
VALID_SUBSTITUTIONS_CHARACTERS = (
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,7 @@ class SimpleRegistry(dict):
 | 
			
		||||
        return decorator
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def safe_print(message=""):
 | 
			
		||||
def safe_print(message="", end="\n"):
 | 
			
		||||
    from esphome.core import CORE
 | 
			
		||||
 | 
			
		||||
    if CORE.dashboard:
 | 
			
		||||
@@ -67,20 +67,26 @@ def safe_print(message=""):
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        print(message)
 | 
			
		||||
        print(message, end=end)
 | 
			
		||||
        return
 | 
			
		||||
    except UnicodeEncodeError:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        print(message.encode("utf-8", "backslashreplace"))
 | 
			
		||||
        print(message.encode("utf-8", "backslashreplace"), end=end)
 | 
			
		||||
    except UnicodeEncodeError:
 | 
			
		||||
        try:
 | 
			
		||||
            print(message.encode("ascii", "backslashreplace"))
 | 
			
		||||
            print(message.encode("ascii", "backslashreplace"), end=end)
 | 
			
		||||
        except UnicodeEncodeError:
 | 
			
		||||
            print("Cannot print line because of invalid locale!")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def safe_input(prompt=""):
 | 
			
		||||
    if prompt:
 | 
			
		||||
        safe_print(prompt, end="")
 | 
			
		||||
    return input()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def shlex_quote(s):
 | 
			
		||||
    if not s:
 | 
			
		||||
        return "''"
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ from esphome.core import CORE
 | 
			
		||||
from esphome.helpers import get_bool_env, write_file
 | 
			
		||||
from esphome.log import Fore, color
 | 
			
		||||
from esphome.storage_json import StorageJSON, ext_storage_path
 | 
			
		||||
from esphome.util import safe_print
 | 
			
		||||
from esphome.util import safe_input, safe_print
 | 
			
		||||
 | 
			
		||||
CORE_BIG = r"""    _____ ____  _____  ______
 | 
			
		||||
   / ____/ __ \|  __ \|  ____|
 | 
			
		||||
@@ -252,7 +252,7 @@ def safe_print_step(step, big):
 | 
			
		||||
def default_input(text, default):
 | 
			
		||||
    safe_print()
 | 
			
		||||
    safe_print(f"Press ENTER for default ({default})")
 | 
			
		||||
    return input(text.format(default)) or default
 | 
			
		||||
    return safe_input(text.format(default)) or default
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# From https://stackoverflow.com/a/518232/8924614
 | 
			
		||||
@@ -306,7 +306,7 @@ def wizard(path):
 | 
			
		||||
    )
 | 
			
		||||
    safe_print()
 | 
			
		||||
    sleep(1)
 | 
			
		||||
    name = input(color(Fore.BOLD_WHITE, "(name): "))
 | 
			
		||||
    name = safe_input(color(Fore.BOLD_WHITE, "(name): "))
 | 
			
		||||
 | 
			
		||||
    while True:
 | 
			
		||||
        try:
 | 
			
		||||
@@ -343,7 +343,9 @@ def wizard(path):
 | 
			
		||||
    while True:
 | 
			
		||||
        sleep(0.5)
 | 
			
		||||
        safe_print()
 | 
			
		||||
        platform = input(color(Fore.BOLD_WHITE, f"({'/'.join(wizard_platforms)}): "))
 | 
			
		||||
        platform = safe_input(
 | 
			
		||||
            color(Fore.BOLD_WHITE, f"({'/'.join(wizard_platforms)}): ")
 | 
			
		||||
        )
 | 
			
		||||
        try:
 | 
			
		||||
            platform = vol.All(vol.Upper, vol.Any(*wizard_platforms))(platform.upper())
 | 
			
		||||
            break
 | 
			
		||||
@@ -397,7 +399,7 @@ def wizard(path):
 | 
			
		||||
        boards.append(board_id)
 | 
			
		||||
 | 
			
		||||
    while True:
 | 
			
		||||
        board = input(color(Fore.BOLD_WHITE, "(board): "))
 | 
			
		||||
        board = safe_input(color(Fore.BOLD_WHITE, "(board): "))
 | 
			
		||||
        try:
 | 
			
		||||
            board = vol.All(vol.Lower, vol.Any(*boards))(board)
 | 
			
		||||
            break
 | 
			
		||||
@@ -423,7 +425,7 @@ def wizard(path):
 | 
			
		||||
    sleep(1.5)
 | 
			
		||||
    safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'Abraham Linksys')}\".")
 | 
			
		||||
    while True:
 | 
			
		||||
        ssid = input(color(Fore.BOLD_WHITE, "(ssid): "))
 | 
			
		||||
        ssid = safe_input(color(Fore.BOLD_WHITE, "(ssid): "))
 | 
			
		||||
        try:
 | 
			
		||||
            ssid = cv.ssid(ssid)
 | 
			
		||||
            break
 | 
			
		||||
@@ -449,7 +451,7 @@ def wizard(path):
 | 
			
		||||
    safe_print()
 | 
			
		||||
    safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'PASSWORD42')}\"")
 | 
			
		||||
    sleep(0.5)
 | 
			
		||||
    psk = input(color(Fore.BOLD_WHITE, "(PSK): "))
 | 
			
		||||
    psk = safe_input(color(Fore.BOLD_WHITE, "(PSK): "))
 | 
			
		||||
    safe_print(
 | 
			
		||||
        "Perfect! WiFi is now set up (you can create static IPs and so on later)."
 | 
			
		||||
    )
 | 
			
		||||
@@ -466,7 +468,7 @@ def wizard(path):
 | 
			
		||||
    safe_print()
 | 
			
		||||
    sleep(0.25)
 | 
			
		||||
    safe_print("Press ENTER for no password")
 | 
			
		||||
    password = input(color(Fore.BOLD_WHITE, "(password): "))
 | 
			
		||||
    password = safe_input(color(Fore.BOLD_WHITE, "(password): "))
 | 
			
		||||
 | 
			
		||||
    if not wizard_write(
 | 
			
		||||
        path=path,
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ esptool==4.6.2
 | 
			
		||||
click==8.1.7
 | 
			
		||||
esphome-dashboard==20230904.0
 | 
			
		||||
aioesphomeapi==15.0.0
 | 
			
		||||
zeroconf==0.108.0
 | 
			
		||||
zeroconf==0.112.0
 | 
			
		||||
 | 
			
		||||
# esp-idf requires this, but doesn't bundle it by default
 | 
			
		||||
# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24
 | 
			
		||||
 
 | 
			
		||||
@@ -667,6 +667,7 @@ matrix_keypad:
 | 
			
		||||
    - pin: 17
 | 
			
		||||
    - pin: 16
 | 
			
		||||
  keys: "1234"
 | 
			
		||||
  has_pulldowns: true
 | 
			
		||||
 | 
			
		||||
key_collector:
 | 
			
		||||
  - id: reader
 | 
			
		||||
 
 | 
			
		||||
@@ -319,7 +319,7 @@ def test_wizard_accepts_default_answers_esp8266(tmpdir, monkeypatch, wizard_answ
 | 
			
		||||
    config_file = tmpdir.join("test.yaml")
 | 
			
		||||
    input_mock = MagicMock(side_effect=wizard_answers)
 | 
			
		||||
    monkeypatch.setattr("builtins.input", input_mock)
 | 
			
		||||
    monkeypatch.setattr(wz, "safe_print", lambda t=None: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "safe_print", lambda t=None, end=None: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "sleep", lambda _: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "wizard_write", MagicMock())
 | 
			
		||||
 | 
			
		||||
@@ -341,7 +341,7 @@ def test_wizard_accepts_default_answers_esp32(tmpdir, monkeypatch, wizard_answer
 | 
			
		||||
    config_file = tmpdir.join("test.yaml")
 | 
			
		||||
    input_mock = MagicMock(side_effect=wizard_answers)
 | 
			
		||||
    monkeypatch.setattr("builtins.input", input_mock)
 | 
			
		||||
    monkeypatch.setattr(wz, "safe_print", lambda t=None: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "safe_print", lambda t=None, end=None: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "sleep", lambda _: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "wizard_write", MagicMock())
 | 
			
		||||
 | 
			
		||||
@@ -371,7 +371,7 @@ def test_wizard_offers_better_node_name(tmpdir, monkeypatch, wizard_answers):
 | 
			
		||||
    config_file = tmpdir.join("test.yaml")
 | 
			
		||||
    input_mock = MagicMock(side_effect=wizard_answers)
 | 
			
		||||
    monkeypatch.setattr("builtins.input", input_mock)
 | 
			
		||||
    monkeypatch.setattr(wz, "safe_print", lambda t=None: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "safe_print", lambda t=None, end=None: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "sleep", lambda _: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "wizard_write", MagicMock())
 | 
			
		||||
 | 
			
		||||
@@ -394,7 +394,7 @@ def test_wizard_requires_correct_platform(tmpdir, monkeypatch, wizard_answers):
 | 
			
		||||
    config_file = tmpdir.join("test.yaml")
 | 
			
		||||
    input_mock = MagicMock(side_effect=wizard_answers)
 | 
			
		||||
    monkeypatch.setattr("builtins.input", input_mock)
 | 
			
		||||
    monkeypatch.setattr(wz, "safe_print", lambda t=None: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "safe_print", lambda t=None, end=None: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "sleep", lambda _: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "wizard_write", MagicMock())
 | 
			
		||||
 | 
			
		||||
@@ -416,7 +416,7 @@ def test_wizard_requires_correct_board(tmpdir, monkeypatch, wizard_answers):
 | 
			
		||||
    config_file = tmpdir.join("test.yaml")
 | 
			
		||||
    input_mock = MagicMock(side_effect=wizard_answers)
 | 
			
		||||
    monkeypatch.setattr("builtins.input", input_mock)
 | 
			
		||||
    monkeypatch.setattr(wz, "safe_print", lambda t=None: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "safe_print", lambda t=None, end=None: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "sleep", lambda _: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "wizard_write", MagicMock())
 | 
			
		||||
 | 
			
		||||
@@ -438,7 +438,7 @@ def test_wizard_requires_valid_ssid(tmpdir, monkeypatch, wizard_answers):
 | 
			
		||||
    config_file = tmpdir.join("test.yaml")
 | 
			
		||||
    input_mock = MagicMock(side_effect=wizard_answers)
 | 
			
		||||
    monkeypatch.setattr("builtins.input", input_mock)
 | 
			
		||||
    monkeypatch.setattr(wz, "safe_print", lambda t=None: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "safe_print", lambda t=None, end=None: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "sleep", lambda _: 0)
 | 
			
		||||
    monkeypatch.setattr(wz, "wizard_write", MagicMock())
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user