mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-03 00:21:56 +00:00 
			
		
		
		
	Compare commits
	
		
			32 Commits
		
	
	
		
			2021.11.0b
			...
			2021.11.3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					21db43db06 | ||
| 
						 | 
					5009b3029f | ||
| 
						 | 
					57a029189c | ||
| 
						 | 
					0cb715bb76 | ||
| 
						 | 
					7d03823afd | ||
| 
						 | 
					8e1c9f5042 | ||
| 
						 | 
					980b7cda8f | ||
| 
						 | 
					3a72dd5cb6 | ||
| 
						 | 
					3178243811 | ||
| 
						 | 
					d30e2f2a4f | ||
| 
						 | 
					6226dae05c | ||
| 
						 | 
					9c6a475a6e | ||
| 
						 | 
					8294d10d5b | ||
| 
						 | 
					67558bec47 | ||
| 
						 | 
					84873d4074 | ||
| 
						 | 
					58a0b28a39 | ||
| 
						 | 
					b37d3a66cc | ||
| 
						 | 
					7e495a5e27 | ||
| 
						 | 
					c41547fd4a | ||
| 
						 | 
					0d47d41c85 | ||
| 
						 | 
					41a3a17456 | ||
| 
						 | 
					cbbafbcca2 | ||
| 
						 | 
					c75566b374 | ||
| 
						 | 
					7279f1fcc1 | ||
| 
						 | 
					d7432f7c10 | ||
| 
						 | 
					b0a0a153f3 | ||
| 
						 | 
					024632dbd0 | ||
| 
						 | 
					0a545a28b9 | ||
| 
						 | 
					0f2df59998 | ||
| 
						 | 
					29a7d32f77 | ||
| 
						 | 
					687a7e9b2f | ||
| 
						 | 
					09e8782318 | 
@@ -60,6 +60,10 @@ async def to_code(config):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            frame = image.convert("L", dither=Image.NONE)
 | 
			
		||||
            pixels = list(frame.getdata())
 | 
			
		||||
            if len(pixels) != height * width:
 | 
			
		||||
                raise core.EsphomeError(
 | 
			
		||||
                    f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}"
 | 
			
		||||
                )
 | 
			
		||||
            for pix in pixels:
 | 
			
		||||
                data[pos] = pix
 | 
			
		||||
                pos += 1
 | 
			
		||||
@@ -69,8 +73,14 @@ async def to_code(config):
 | 
			
		||||
        pos = 0
 | 
			
		||||
        for frameIndex in range(frames):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            if CONF_RESIZE in config:
 | 
			
		||||
                image.thumbnail(config[CONF_RESIZE])
 | 
			
		||||
            frame = image.convert("RGB")
 | 
			
		||||
            pixels = list(frame.getdata())
 | 
			
		||||
            if len(pixels) != height * width:
 | 
			
		||||
                raise core.EsphomeError(
 | 
			
		||||
                    f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}"
 | 
			
		||||
                )
 | 
			
		||||
            for pix in pixels:
 | 
			
		||||
                data[pos] = pix[0]
 | 
			
		||||
                pos += 1
 | 
			
		||||
 
 | 
			
		||||
@@ -73,51 +73,52 @@ AnovaPacket *AnovaCodec::get_stop_request() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
 | 
			
		||||
  memset(this->buf_, 0, 32);
 | 
			
		||||
  strncpy(this->buf_, (char *) data, length);
 | 
			
		||||
  char buf[32];
 | 
			
		||||
  memset(buf, 0, sizeof(buf));
 | 
			
		||||
  strncpy(buf, (char *) data, std::min<uint16_t>(length, sizeof(buf) - 1));
 | 
			
		||||
  this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false;
 | 
			
		||||
  switch (this->current_query_) {
 | 
			
		||||
    case READ_DEVICE_STATUS: {
 | 
			
		||||
      if (!strncmp(this->buf_, "stopped", 7)) {
 | 
			
		||||
      if (!strncmp(buf, "stopped", 7)) {
 | 
			
		||||
        this->has_running_ = true;
 | 
			
		||||
        this->running_ = false;
 | 
			
		||||
      }
 | 
			
		||||
      if (!strncmp(this->buf_, "running", 7)) {
 | 
			
		||||
      if (!strncmp(buf, "running", 7)) {
 | 
			
		||||
        this->has_running_ = true;
 | 
			
		||||
        this->running_ = true;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case START: {
 | 
			
		||||
      if (!strncmp(this->buf_, "start", 5)) {
 | 
			
		||||
      if (!strncmp(buf, "start", 5)) {
 | 
			
		||||
        this->has_running_ = true;
 | 
			
		||||
        this->running_ = true;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case STOP: {
 | 
			
		||||
      if (!strncmp(this->buf_, "stop", 4)) {
 | 
			
		||||
      if (!strncmp(buf, "stop", 4)) {
 | 
			
		||||
        this->has_running_ = true;
 | 
			
		||||
        this->running_ = false;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case READ_TARGET_TEMPERATURE: {
 | 
			
		||||
      this->target_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
 | 
			
		||||
      this->target_temp_ = parse_number<float>(buf, sizeof(buf)).value_or(0.0f);
 | 
			
		||||
      if (this->fahrenheit_)
 | 
			
		||||
        this->target_temp_ = ftoc(this->target_temp_);
 | 
			
		||||
      this->has_target_temp_ = true;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case SET_TARGET_TEMPERATURE: {
 | 
			
		||||
      this->target_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
 | 
			
		||||
      this->target_temp_ = parse_number<float>(buf, sizeof(buf)).value_or(0.0f);
 | 
			
		||||
      if (this->fahrenheit_)
 | 
			
		||||
        this->target_temp_ = ftoc(this->target_temp_);
 | 
			
		||||
      this->has_target_temp_ = true;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case READ_CURRENT_TEMPERATURE: {
 | 
			
		||||
      this->current_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
 | 
			
		||||
      this->current_temp_ = parse_number<float>(buf, sizeof(buf)).value_or(0.0f);
 | 
			
		||||
      if (this->fahrenheit_)
 | 
			
		||||
        this->current_temp_ = ftoc(this->current_temp_);
 | 
			
		||||
      this->has_current_temp_ = true;
 | 
			
		||||
@@ -125,8 +126,8 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
 | 
			
		||||
    }
 | 
			
		||||
    case SET_UNIT:
 | 
			
		||||
    case READ_UNIT: {
 | 
			
		||||
      this->unit_ = this->buf_[0];
 | 
			
		||||
      this->fahrenheit_ = this->buf_[0] == 'f';
 | 
			
		||||
      this->unit_ = buf[0];
 | 
			
		||||
      this->fahrenheit_ = buf[0] == 'f';
 | 
			
		||||
      this->has_unit_ = true;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -70,7 +70,6 @@ class AnovaCodec {
 | 
			
		||||
  bool has_current_temp_;
 | 
			
		||||
  bool has_unit_;
 | 
			
		||||
  bool has_running_;
 | 
			
		||||
  char buf_[32];
 | 
			
		||||
  bool fahrenheit_;
 | 
			
		||||
 | 
			
		||||
  CurrentQuery current_query_;
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,7 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_SMOKE,
 | 
			
		||||
    DEVICE_CLASS_SOUND,
 | 
			
		||||
    DEVICE_CLASS_TAMPER,
 | 
			
		||||
    DEVICE_CLASS_UPDATE,
 | 
			
		||||
    DEVICE_CLASS_VIBRATION,
 | 
			
		||||
    DEVICE_CLASS_WINDOW,
 | 
			
		||||
)
 | 
			
		||||
@@ -82,6 +83,7 @@ DEVICE_CLASSES = [
 | 
			
		||||
    DEVICE_CLASS_SMOKE,
 | 
			
		||||
    DEVICE_CLASS_SOUND,
 | 
			
		||||
    DEVICE_CLASS_TAMPER,
 | 
			
		||||
    DEVICE_CLASS_UPDATE,
 | 
			
		||||
    DEVICE_CLASS_VIBRATION,
 | 
			
		||||
    DEVICE_CLASS_WINDOW,
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -67,7 +67,7 @@ async def to_code(config):
 | 
			
		||||
            var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
 | 
			
		||||
        )
 | 
			
		||||
    elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
 | 
			
		||||
        uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
 | 
			
		||||
        uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
 | 
			
		||||
        cg.add(var.set_service_uuid128(uuid128))
 | 
			
		||||
 | 
			
		||||
    if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
			
		||||
@@ -87,7 +87,9 @@ async def to_code(config):
 | 
			
		||||
    elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
 | 
			
		||||
        esp32_ble_tracker.bt_uuid128_format
 | 
			
		||||
    ):
 | 
			
		||||
        uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_CHARACTERISTIC_UUID])
 | 
			
		||||
        uuid128 = esp32_ble_tracker.as_reversed_hex_array(
 | 
			
		||||
            config[CONF_CHARACTERISTIC_UUID]
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_char_uuid128(uuid128))
 | 
			
		||||
 | 
			
		||||
    if CONF_DESCRIPTOR_UUID in config:
 | 
			
		||||
@@ -108,7 +110,9 @@ async def to_code(config):
 | 
			
		||||
        elif len(config[CONF_DESCRIPTOR_UUID]) == len(
 | 
			
		||||
            esp32_ble_tracker.bt_uuid128_format
 | 
			
		||||
        ):
 | 
			
		||||
            uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_DESCRIPTOR_UUID])
 | 
			
		||||
            uuid128 = esp32_ble_tracker.as_reversed_hex_array(
 | 
			
		||||
                config[CONF_DESCRIPTOR_UUID]
 | 
			
		||||
            )
 | 
			
		||||
            cg.add(var.set_descr_uuid128(uuid128))
 | 
			
		||||
 | 
			
		||||
    if CONF_LAMBDA in config:
 | 
			
		||||
 
 | 
			
		||||
@@ -76,6 +76,7 @@ class ESP32Preferences : public ESPPreferences {
 | 
			
		||||
  uint32_t current_offset = 0;
 | 
			
		||||
 | 
			
		||||
  void open() {
 | 
			
		||||
    nvs_flash_init();
 | 
			
		||||
    esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle);
 | 
			
		||||
    if (err == 0)
 | 
			
		||||
      return;
 | 
			
		||||
 
 | 
			
		||||
@@ -21,12 +21,19 @@ static const char *const TAG = "esp32_camera_web_server";
 | 
			
		||||
#define CONTENT_TYPE "image/jpeg"
 | 
			
		||||
#define CONTENT_LENGTH "Content-Length"
 | 
			
		||||
 | 
			
		||||
static const char *const STREAM_HEADER =
 | 
			
		||||
    "HTTP/1.1 200\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY
 | 
			
		||||
    "\r\n";
 | 
			
		||||
static const char *const STREAM_500 = "HTTP/1.1 500\r\nContent-Type: text/plain\r\n\r\nNo frames send.\r\n";
 | 
			
		||||
static const char *const STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
 | 
			
		||||
static const char *const STREAM_HEADER = "HTTP/1.0 200 OK\r\n"
 | 
			
		||||
                                         "Access-Control-Allow-Origin: *\r\n"
 | 
			
		||||
                                         "Connection: close\r\n"
 | 
			
		||||
                                         "Content-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY "\r\n"
 | 
			
		||||
                                         "\r\n"
 | 
			
		||||
                                         "--" PART_BOUNDARY "\r\n";
 | 
			
		||||
static const char *const STREAM_ERROR = "Content-Type: text/plain\r\n"
 | 
			
		||||
                                        "\r\n"
 | 
			
		||||
                                        "No frames send.\r\n"
 | 
			
		||||
                                        "--" PART_BOUNDARY "\r\n";
 | 
			
		||||
static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n";
 | 
			
		||||
static const char *const STREAM_BOUNDARY = "\r\n"
 | 
			
		||||
                                           "--" PART_BOUNDARY "\r\n";
 | 
			
		||||
 | 
			
		||||
CameraWebServer::CameraWebServer() {}
 | 
			
		||||
 | 
			
		||||
@@ -45,6 +52,7 @@ void CameraWebServer::setup() {
 | 
			
		||||
  config.ctrl_port = this->port_;
 | 
			
		||||
  config.max_open_sockets = 1;
 | 
			
		||||
  config.backlog_conn = 2;
 | 
			
		||||
  config.lru_purge_enable = true;
 | 
			
		||||
 | 
			
		||||
  if (httpd_start(&this->httpd_, &config) != ESP_OK) {
 | 
			
		||||
    mark_failed();
 | 
			
		||||
@@ -172,9 +180,6 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
 | 
			
		||||
      ESP_LOGW(TAG, "STREAM: failed to acquire frame");
 | 
			
		||||
      res = ESP_FAIL;
 | 
			
		||||
    }
 | 
			
		||||
    if (res == ESP_OK) {
 | 
			
		||||
      res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
 | 
			
		||||
    }
 | 
			
		||||
    if (res == ESP_OK) {
 | 
			
		||||
      size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length());
 | 
			
		||||
      res = httpd_send_all(req, part_buf, hlen);
 | 
			
		||||
@@ -182,6 +187,9 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
 | 
			
		||||
    if (res == ESP_OK) {
 | 
			
		||||
      res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length());
 | 
			
		||||
    }
 | 
			
		||||
    if (res == ESP_OK) {
 | 
			
		||||
      res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
 | 
			
		||||
    }
 | 
			
		||||
    if (res == ESP_OK) {
 | 
			
		||||
      frames++;
 | 
			
		||||
      int64_t frame_time = millis() - last_frame;
 | 
			
		||||
@@ -193,7 +201,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!frames) {
 | 
			
		||||
    res = httpd_send_all(req, STREAM_500, strlen(STREAM_500));
 | 
			
		||||
    res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,6 @@ void ESP32TouchComponent::dump_config() {
 | 
			
		||||
 | 
			
		||||
  if (this->iir_filter_enabled_()) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    IIR Filter: %ums", this->iir_filter_);
 | 
			
		||||
    touch_pad_filter_start(this->iir_filter_);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  IIR Filter DISABLED");
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ void EZOSensor::update() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::loop() {
 | 
			
		||||
  uint8_t buf[20];
 | 
			
		||||
  uint8_t buf[21];
 | 
			
		||||
  if (!(this->state_ & EZO_STATE_WAIT)) {
 | 
			
		||||
    if (this->state_ & EZO_STATE_SEND_TEMP) {
 | 
			
		||||
      int len = sprintf((char *) buf, "T,%0.3f", this->tempcomp_);
 | 
			
		||||
@@ -74,7 +74,7 @@ void EZOSensor::loop() {
 | 
			
		||||
  if (buf[0] != 1)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  float val = parse_number<float>((char *) &buf[1], sizeof(buf) - 1).value_or(0);
 | 
			
		||||
  float val = parse_number<float>((char *) &buf[1], sizeof(buf) - 2).value_or(0);
 | 
			
		||||
  this->publish_state(val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ class AQICalculator : public AbstractAQICalculator {
 | 
			
		||||
    int conc_lo = array[grid_index][0];
 | 
			
		||||
    int conc_hi = array[grid_index][1];
 | 
			
		||||
 | 
			
		||||
    return ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo;
 | 
			
		||||
    return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
 | 
			
		||||
 
 | 
			
		||||
@@ -37,9 +37,7 @@ class CAQICalculator : public AbstractAQICalculator {
 | 
			
		||||
    int conc_lo = array[grid_index][0];
 | 
			
		||||
    int conc_hi = array[grid_index][1];
 | 
			
		||||
 | 
			
		||||
    int aqi = ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo;
 | 
			
		||||
 | 
			
		||||
    return aqi;
 | 
			
		||||
    return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,30 +2,32 @@
 | 
			
		||||
 | 
			
		||||
namespace improv {
 | 
			
		||||
 | 
			
		||||
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data) {
 | 
			
		||||
  return parse_improv_data(data.data(), data.size());
 | 
			
		||||
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data, bool check_checksum) {
 | 
			
		||||
  return parse_improv_data(data.data(), data.size(), check_checksum);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
 | 
			
		||||
ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum) {
 | 
			
		||||
  ImprovCommand improv_command;
 | 
			
		||||
  Command command = (Command) data[0];
 | 
			
		||||
  uint8_t data_length = data[1];
 | 
			
		||||
 | 
			
		||||
  if (data_length != length - 3) {
 | 
			
		||||
  if (data_length != length - 2 - check_checksum) {
 | 
			
		||||
    improv_command.command = UNKNOWN;
 | 
			
		||||
    return improv_command;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t checksum = data[length - 1];
 | 
			
		||||
  if (check_checksum) {
 | 
			
		||||
    uint8_t checksum = data[length - 1];
 | 
			
		||||
 | 
			
		||||
  uint32_t calculated_checksum = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < length - 1; i++) {
 | 
			
		||||
    calculated_checksum += data[i];
 | 
			
		||||
  }
 | 
			
		||||
    uint32_t calculated_checksum = 0;
 | 
			
		||||
    for (uint8_t i = 0; i < length - 1; i++) {
 | 
			
		||||
      calculated_checksum += data[i];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  if ((uint8_t) calculated_checksum != checksum) {
 | 
			
		||||
    improv_command.command = BAD_CHECKSUM;
 | 
			
		||||
    return improv_command;
 | 
			
		||||
    if ((uint8_t) calculated_checksum != checksum) {
 | 
			
		||||
      improv_command.command = BAD_CHECKSUM;
 | 
			
		||||
      return improv_command;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (command == WIFI_SETTINGS) {
 | 
			
		||||
@@ -46,7 +48,7 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
 | 
			
		||||
  return improv_command;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum) {
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum, bool add_checksum) {
 | 
			
		||||
  std::vector<uint8_t> out;
 | 
			
		||||
  uint32_t length = 0;
 | 
			
		||||
  out.push_back(command);
 | 
			
		||||
@@ -58,17 +60,19 @@ std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::
 | 
			
		||||
  }
 | 
			
		||||
  out.insert(out.begin() + 1, length);
 | 
			
		||||
 | 
			
		||||
  uint32_t calculated_checksum = 0;
 | 
			
		||||
  if (add_checksum) {
 | 
			
		||||
    uint32_t calculated_checksum = 0;
 | 
			
		||||
 | 
			
		||||
  for (uint8_t byte : out) {
 | 
			
		||||
    calculated_checksum += byte;
 | 
			
		||||
    for (uint8_t byte : out) {
 | 
			
		||||
      calculated_checksum += byte;
 | 
			
		||||
    }
 | 
			
		||||
    out.push_back(calculated_checksum);
 | 
			
		||||
  }
 | 
			
		||||
  out.push_back(calculated_checksum);
 | 
			
		||||
  return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum) {
 | 
			
		||||
#ifdef ARDUINO
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum, bool add_checksum) {
 | 
			
		||||
  std::vector<uint8_t> out;
 | 
			
		||||
  uint32_t length = 0;
 | 
			
		||||
  out.push_back(command);
 | 
			
		||||
@@ -80,14 +84,16 @@ std::vector<uint8_t> build_rpc_response(Command command, const std::vector<Strin
 | 
			
		||||
  }
 | 
			
		||||
  out.insert(out.begin() + 1, length);
 | 
			
		||||
 | 
			
		||||
  uint32_t calculated_checksum = 0;
 | 
			
		||||
  if (add_checksum) {
 | 
			
		||||
    uint32_t calculated_checksum = 0;
 | 
			
		||||
 | 
			
		||||
  for (uint8_t byte : out) {
 | 
			
		||||
    calculated_checksum += byte;
 | 
			
		||||
    for (uint8_t byte : out) {
 | 
			
		||||
      calculated_checksum += byte;
 | 
			
		||||
    }
 | 
			
		||||
    out.push_back(calculated_checksum);
 | 
			
		||||
  }
 | 
			
		||||
  out.push_back(calculated_checksum);
 | 
			
		||||
  return out;
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
#endif  // ARDUINO
 | 
			
		||||
 | 
			
		||||
}  // namespace improv
 | 
			
		||||
 
 | 
			
		||||
@@ -51,12 +51,13 @@ struct ImprovCommand {
 | 
			
		||||
  std::string password;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data);
 | 
			
		||||
ImprovCommand parse_improv_data(const uint8_t *data, size_t length);
 | 
			
		||||
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data, bool check_checksum = true);
 | 
			
		||||
ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum = true);
 | 
			
		||||
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum);
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum,
 | 
			
		||||
                                        bool add_checksum = true);
 | 
			
		||||
#ifdef ARDUINO
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum);
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum, bool add_checksum = true);
 | 
			
		||||
#endif  // ARDUINO
 | 
			
		||||
 | 
			
		||||
}  // namespace improv
 | 
			
		||||
 
 | 
			
		||||
@@ -98,13 +98,13 @@ std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv:
 | 
			
		||||
  std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
 | 
			
		||||
  urls.push_back(webserver_url);
 | 
			
		||||
#endif
 | 
			
		||||
  std::vector<uint8_t> data = improv::build_rpc_response(command, urls);
 | 
			
		||||
  std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
 | 
			
		||||
  return data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
 | 
			
		||||
  std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
 | 
			
		||||
  std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos);
 | 
			
		||||
  std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
 | 
			
		||||
  return data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -140,22 +140,33 @@ bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
 | 
			
		||||
  if (at < 8 + data_len)
 | 
			
		||||
    return true;
 | 
			
		||||
 | 
			
		||||
  if (at == 8 + data_len) {
 | 
			
		||||
  if (at == 8 + data_len)
 | 
			
		||||
    return true;
 | 
			
		||||
 | 
			
		||||
  if (at == 8 + data_len + 1) {
 | 
			
		||||
    uint8_t checksum = 0x00;
 | 
			
		||||
    for (uint8_t i = 0; i < at; i++)
 | 
			
		||||
      checksum += raw[i];
 | 
			
		||||
 | 
			
		||||
    if (checksum != byte) {
 | 
			
		||||
      ESP_LOGW(TAG, "Error decoding Improv payload");
 | 
			
		||||
      this->set_error_(improv::ERROR_INVALID_RPC);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (type == TYPE_RPC) {
 | 
			
		||||
      this->set_error_(improv::ERROR_NONE);
 | 
			
		||||
      auto command = improv::parse_improv_data(&raw[9], data_len);
 | 
			
		||||
      auto command = improv::parse_improv_data(&raw[9], data_len, false);
 | 
			
		||||
      return this->parse_improv_payload_(command);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
 | 
			
		||||
  // If we got here then the command coming is is improv, but not an RPC command
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
 | 
			
		||||
  switch (command.command) {
 | 
			
		||||
    case improv::BAD_CHECKSUM:
 | 
			
		||||
      ESP_LOGW(TAG, "Error decoding Improv payload");
 | 
			
		||||
      this->set_error_(improv::ERROR_INVALID_RPC);
 | 
			
		||||
      return false;
 | 
			
		||||
    case improv::WIFI_SETTINGS: {
 | 
			
		||||
      wifi::WiFiAP sta{};
 | 
			
		||||
      sta.set_ssid(command.ssid);
 | 
			
		||||
@@ -232,6 +243,12 @@ void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
 | 
			
		||||
  data[7] = TYPE_RPC_RESPONSE;
 | 
			
		||||
  data[8] = response.size();
 | 
			
		||||
  data.insert(data.end(), response.begin(), response.end());
 | 
			
		||||
 | 
			
		||||
  uint8_t checksum = 0x00;
 | 
			
		||||
  for (uint8_t d : data)
 | 
			
		||||
    checksum += d;
 | 
			
		||||
  data.push_back(checksum);
 | 
			
		||||
 | 
			
		||||
  this->write_data_(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -141,12 +141,16 @@ void SenseAirComponent::abc_get_period() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length) {
 | 
			
		||||
  // Verify we have somewhere to store the response
 | 
			
		||||
  if (response == nullptr) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  // Write wake up byte required by some S8 sensor models
 | 
			
		||||
  this->write_byte(0);
 | 
			
		||||
  this->flush();
 | 
			
		||||
  delay(5);
 | 
			
		||||
  this->write_array(command, SENSEAIR_REQUEST_LENGTH);
 | 
			
		||||
 | 
			
		||||
  if (response == nullptr)
 | 
			
		||||
    return true;
 | 
			
		||||
 | 
			
		||||
  bool ret = this->read_array(response, response_length);
 | 
			
		||||
  this->flush();
 | 
			
		||||
  return ret;
 | 
			
		||||
 
 | 
			
		||||
@@ -94,17 +94,21 @@ UART_DIRECTIONS = {
 | 
			
		||||
    "BOTH": UARTDirection.UART_DIRECTION_BOTH,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AFTER_DEFAULTS = {CONF_BYTES: 256, CONF_TIMEOUT: "100ms"}
 | 
			
		||||
 | 
			
		||||
DEBUG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UARTDebugger),
 | 
			
		||||
        cv.Optional(CONF_DIRECTION, default="BOTH"): cv.enum(
 | 
			
		||||
            UART_DIRECTIONS, upper=True
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_AFTER): cv.Schema(
 | 
			
		||||
        cv.Optional(CONF_AFTER, default=AFTER_DEFAULTS): cv.Schema(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Optional(CONF_BYTES, default=256): cv.validate_bytes,
 | 
			
		||||
                cv.Optional(
 | 
			
		||||
                    CONF_TIMEOUT, default="100ms"
 | 
			
		||||
                    CONF_BYTES, default=AFTER_DEFAULTS[CONF_BYTES]
 | 
			
		||||
                ): cv.validate_bytes,
 | 
			
		||||
                cv.Optional(
 | 
			
		||||
                    CONF_TIMEOUT, default=AFTER_DEFAULTS[CONF_TIMEOUT]
 | 
			
		||||
                ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data),
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -57,38 +57,46 @@ void IRAM_ATTR ZaSensorStore::interrupt(ZaSensorStore *arg) {
 | 
			
		||||
void IRAM_ATTR ZaSensorStore::set_data_(ZaMessage *message) {
 | 
			
		||||
  switch (message->type) {
 | 
			
		||||
    case HUMIDITY:
 | 
			
		||||
      this->humidity = (message->value > 10000) ? NAN : (message->value / 100.0f);
 | 
			
		||||
      this->humidity = message->value;
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case TEMPERATURE:
 | 
			
		||||
      this->temperature = (message->value > 5970) ? NAN : (message->value / 16.0f - 273.15f);
 | 
			
		||||
      this->temperature = message->value;
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case CO2:
 | 
			
		||||
      this->co2 = (message->value > 10000) ? NAN : message->value;
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    default:
 | 
			
		||||
      this->co2 = message->value;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ZyAuraSensor::publish_state_(sensor::Sensor *sensor, float *value) {
 | 
			
		||||
  // Sensor doesn't added to configuration
 | 
			
		||||
bool ZyAuraSensor::publish_state_(ZaDataType data_type, sensor::Sensor *sensor, uint16_t *data_value) {
 | 
			
		||||
  // Sensor wasn't added to configuration
 | 
			
		||||
  if (sensor == nullptr) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  sensor->publish_state(*value);
 | 
			
		||||
  float value = NAN;
 | 
			
		||||
  switch (data_type) {
 | 
			
		||||
    case HUMIDITY:
 | 
			
		||||
      value = (*data_value > 10000) ? NAN : (*data_value / 100.0f);
 | 
			
		||||
      break;
 | 
			
		||||
    case TEMPERATURE:
 | 
			
		||||
      value = (*data_value > 5970) ? NAN : (*data_value / 16.0f - 273.15f);
 | 
			
		||||
      break;
 | 
			
		||||
    case CO2:
 | 
			
		||||
      value = (*data_value > 10000) ? NAN : *data_value;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  sensor->publish_state(value);
 | 
			
		||||
 | 
			
		||||
  // Sensor reported wrong value
 | 
			
		||||
  if (std::isnan(*value)) {
 | 
			
		||||
  if (std::isnan(value)) {
 | 
			
		||||
    ESP_LOGW(TAG, "Sensor reported invalid data. Is the update interval too small?");
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  *value = NAN;
 | 
			
		||||
  *data_value = -1;
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -104,9 +112,9 @@ void ZyAuraSensor::dump_config() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ZyAuraSensor::update() {
 | 
			
		||||
  bool co2_result = this->publish_state_(this->co2_sensor_, &this->store_.co2);
 | 
			
		||||
  bool temperature_result = this->publish_state_(this->temperature_sensor_, &this->store_.temperature);
 | 
			
		||||
  bool humidity_result = this->publish_state_(this->humidity_sensor_, &this->store_.humidity);
 | 
			
		||||
  bool co2_result = this->publish_state_(CO2, this->co2_sensor_, &this->store_.co2);
 | 
			
		||||
  bool temperature_result = this->publish_state_(TEMPERATURE, this->temperature_sensor_, &this->store_.temperature);
 | 
			
		||||
  bool humidity_result = this->publish_state_(HUMIDITY, this->humidity_sensor_, &this->store_.humidity);
 | 
			
		||||
 | 
			
		||||
  if (co2_result && temperature_result && humidity_result) {
 | 
			
		||||
    this->status_clear_warning();
 | 
			
		||||
 
 | 
			
		||||
@@ -42,9 +42,9 @@ class ZaDataProcessor {
 | 
			
		||||
 | 
			
		||||
class ZaSensorStore {
 | 
			
		||||
 public:
 | 
			
		||||
  float co2 = NAN;
 | 
			
		||||
  float temperature = NAN;
 | 
			
		||||
  float humidity = NAN;
 | 
			
		||||
  uint16_t co2 = -1;
 | 
			
		||||
  uint16_t temperature = -1;
 | 
			
		||||
  uint16_t humidity = -1;
 | 
			
		||||
 | 
			
		||||
  void setup(InternalGPIOPin *pin_clock, InternalGPIOPin *pin_data);
 | 
			
		||||
  static void interrupt(ZaSensorStore *arg);
 | 
			
		||||
@@ -79,7 +79,7 @@ class ZyAuraSensor : public PollingComponent {
 | 
			
		||||
  sensor::Sensor *temperature_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *humidity_sensor_{nullptr};
 | 
			
		||||
 | 
			
		||||
  bool publish_state_(sensor::Sensor *sensor, float *value);
 | 
			
		||||
  bool publish_state_(ZaDataType data_type, sensor::Sensor *sensor, uint16_t *data_value);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace zyaura
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
"""Constants used by esphome."""
 | 
			
		||||
 | 
			
		||||
__version__ = "2021.11.0b4"
 | 
			
		||||
__version__ = "2021.11.3"
 | 
			
		||||
 | 
			
		||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
 | 
			
		||||
 | 
			
		||||
@@ -883,6 +883,7 @@ DEVICE_CLASS_SAFETY = "safety"
 | 
			
		||||
DEVICE_CLASS_SMOKE = "smoke"
 | 
			
		||||
DEVICE_CLASS_SOUND = "sound"
 | 
			
		||||
DEVICE_CLASS_TAMPER = "tamper"
 | 
			
		||||
DEVICE_CLASS_UPDATE = "update"
 | 
			
		||||
DEVICE_CLASS_VIBRATION = "vibration"
 | 
			
		||||
DEVICE_CLASS_WINDOW = "window"
 | 
			
		||||
# device classes of both binary_sensor and sensor component
 | 
			
		||||
 
 | 
			
		||||
@@ -362,45 +362,47 @@ std::string str_sanitize(const std::string &str);
 | 
			
		||||
/// @name Parsing & formatting
 | 
			
		||||
///@{
 | 
			
		||||
 | 
			
		||||
/// Parse a unsigned decimal number.
 | 
			
		||||
/// Parse an unsigned decimal number (requires null-terminated string).
 | 
			
		||||
template<typename T, enable_if_t<(std::is_integral<T>::value && std::is_unsigned<T>::value), int> = 0>
 | 
			
		||||
optional<T> parse_number(const char *str, size_t len) {
 | 
			
		||||
  char *end = nullptr;
 | 
			
		||||
  unsigned long value = ::strtoul(str, &end, 10);  // NOLINT(google-runtime-int)
 | 
			
		||||
  if (end == nullptr || end != str + len || value > std::numeric_limits<T>::max())
 | 
			
		||||
  if (end == str || *end != '\0' || value > std::numeric_limits<T>::max())
 | 
			
		||||
    return {};
 | 
			
		||||
  return value;
 | 
			
		||||
}
 | 
			
		||||
/// Parse an unsigned decimal number.
 | 
			
		||||
template<typename T, enable_if_t<(std::is_integral<T>::value && std::is_unsigned<T>::value), int> = 0>
 | 
			
		||||
optional<T> parse_number(const std::string &str) {
 | 
			
		||||
  return parse_number<T>(str.c_str(), str.length());
 | 
			
		||||
  return parse_number<T>(str.c_str(), str.length() + 1);
 | 
			
		||||
}
 | 
			
		||||
/// Parse a signed decimal number.
 | 
			
		||||
/// Parse a signed decimal number (requires null-terminated string).
 | 
			
		||||
template<typename T, enable_if_t<(std::is_integral<T>::value && std::is_signed<T>::value), int> = 0>
 | 
			
		||||
optional<T> parse_number(const char *str, size_t len) {
 | 
			
		||||
  char *end = nullptr;
 | 
			
		||||
  signed long value = ::strtol(str, &end, 10);  // NOLINT(google-runtime-int)
 | 
			
		||||
  if (end == nullptr || end != str + len || value < std::numeric_limits<T>::min() ||
 | 
			
		||||
      value > std::numeric_limits<T>::max())
 | 
			
		||||
  if (end == str || *end != '\0' || value < std::numeric_limits<T>::min() || value > std::numeric_limits<T>::max())
 | 
			
		||||
    return {};
 | 
			
		||||
  return value;
 | 
			
		||||
}
 | 
			
		||||
/// Parse a signed decimal number.
 | 
			
		||||
template<typename T, enable_if_t<(std::is_integral<T>::value && std::is_signed<T>::value), int> = 0>
 | 
			
		||||
optional<T> parse_number(const std::string &str) {
 | 
			
		||||
  return parse_number<T>(str.c_str(), str.length());
 | 
			
		||||
  return parse_number<T>(str.c_str(), str.length() + 1);
 | 
			
		||||
}
 | 
			
		||||
/// Parse a decimal floating-point number.
 | 
			
		||||
/// Parse a decimal floating-point number (requires null-terminated string).
 | 
			
		||||
template<typename T, enable_if_t<(std::is_same<T, float>::value), int> = 0>
 | 
			
		||||
optional<T> parse_number(const char *str, size_t len) {
 | 
			
		||||
  char *end = nullptr;
 | 
			
		||||
  float value = ::strtof(str, &end);
 | 
			
		||||
  if (end == nullptr || end != str + len || value == HUGE_VALF)
 | 
			
		||||
  if (end == str || *end != '\0' || value == HUGE_VALF)
 | 
			
		||||
    return {};
 | 
			
		||||
  return value;
 | 
			
		||||
}
 | 
			
		||||
/// Parse a decimal floating-point number.
 | 
			
		||||
template<typename T, enable_if_t<(std::is_same<T, float>::value), int> = 0>
 | 
			
		||||
optional<T> parse_number(const std::string &str) {
 | 
			
		||||
  return parse_number<T>(str.c_str(), str.length());
 | 
			
		||||
  return parse_number<T>(str.c_str(), str.length() + 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
///@}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,9 @@ from zeroconf import (
 | 
			
		||||
    RecordUpdateListener,
 | 
			
		||||
    Zeroconf,
 | 
			
		||||
    ServiceBrowser,
 | 
			
		||||
    ServiceStateChange,
 | 
			
		||||
    current_time_millis,
 | 
			
		||||
)
 | 
			
		||||
from zeroconf._services import ServiceStateChange
 | 
			
		||||
 | 
			
		||||
_CLASS_IN = 1
 | 
			
		||||
_FLAGS_QR_QUERY = 0x0000  # query
 | 
			
		||||
@@ -88,7 +89,7 @@ class DashboardStatus(threading.Thread):
 | 
			
		||||
        entries = self.zc.cache.entries_with_name(key)
 | 
			
		||||
        if not entries:
 | 
			
		||||
            return False
 | 
			
		||||
        now = time.time() * 1000
 | 
			
		||||
        now = current_time_millis()
 | 
			
		||||
 | 
			
		||||
        return any(
 | 
			
		||||
            (entry.created + DashboardStatus.OFFLINE_AFTER) >= now for entry in entries
 | 
			
		||||
@@ -99,7 +100,7 @@ class DashboardStatus(threading.Thread):
 | 
			
		||||
            self.on_update(
 | 
			
		||||
                {key: self.host_status(host) for key, host in self.key_to_host.items()}
 | 
			
		||||
            )
 | 
			
		||||
            now = time.time() * 1000
 | 
			
		||||
            now = current_time_millis()
 | 
			
		||||
            for host in self.query_hosts:
 | 
			
		||||
                entries = self.zc.cache.entries_with_name(host)
 | 
			
		||||
                if not entries or all(
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ esptool==3.2
 | 
			
		||||
click==8.0.3
 | 
			
		||||
esphome-dashboard==20211021.1
 | 
			
		||||
aioesphomeapi==10.2.0
 | 
			
		||||
zeroconf==0.36.13
 | 
			
		||||
 | 
			
		||||
# esp-idf requires this, but doesn't bundle it by default
 | 
			
		||||
# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,12 @@ uart:
 | 
			
		||||
  tx_pin: GPIO22
 | 
			
		||||
  rx_pin: GPIO23
 | 
			
		||||
  baud_rate: 115200
 | 
			
		||||
  # Specifically added for testing debug with no after: definition.
 | 
			
		||||
  debug:
 | 
			
		||||
    dummy_receiver: false
 | 
			
		||||
    direction: rx
 | 
			
		||||
    sequence:
 | 
			
		||||
      - lambda: UARTDebug::log_hex(direction, bytes, ':');
 | 
			
		||||
 | 
			
		||||
ota:
 | 
			
		||||
  safe_mode: True
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user