mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 06:33:51 +00:00 
			
		
		
		
	
							
								
								
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -25,7 +25,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - name: Set up env variables |       - name: Set up env variables | ||||||
|         run: | |         run: | | ||||||
|           base_version="2.4.1" |           base_version="2.6.0" | ||||||
|  |  | ||||||
|           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then |           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then | ||||||
|             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" |             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							| @@ -190,7 +190,7 @@ jobs: | |||||||
|           echo "::set-env name=TAG::${TAG}" |           echo "::set-env name=TAG::${TAG}" | ||||||
|       - name: Set up env variables |       - name: Set up env variables | ||||||
|         run: | |         run: | | ||||||
|           base_version="2.4.1" |           base_version="2.6.0" | ||||||
|  |  | ||||||
|           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then |           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then | ||||||
|             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" |             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -210,7 +210,7 @@ jobs: | |||||||
|           echo "::set-env name=TAG::${TAG}" |           echo "::set-env name=TAG::${TAG}" | ||||||
|       - name: Set up env variables |       - name: Set up env variables | ||||||
|         run: | |         run: | | ||||||
|           base_version="2.4.1" |           base_version="2.6.0" | ||||||
|  |  | ||||||
|           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then |           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then | ||||||
|             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" |             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" | ||||||
| @@ -226,7 +226,7 @@ jobs: | |||||||
|             cache_tag="beta" |             cache_tag="beta" | ||||||
|           else |           else | ||||||
|             cache_tag="latest" |             cache_tag="latest" | ||||||
|           end |           fi | ||||||
|  |  | ||||||
|           # Set env variables so these values don't need to be calculated again |           # Set env variables so these values don't need to be calculated again | ||||||
|           echo "::set-env name=BUILD_FROM::${build_from}" |           echo "::set-env name=BUILD_FROM::${build_from}" | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| ARG BUILD_FROM=esphome/esphome-base-amd64:2.4.1 | ARG BUILD_FROM=esphome/esphome-base-amd64:2.6.0 | ||||||
| FROM ${BUILD_FROM} | FROM ${BUILD_FROM} | ||||||
|  |  | ||||||
| # First install requirements to leverage caching when requirements don't change | # First install requirements to leverage caching when requirements don't change | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| FROM esphome/esphome-base-amd64:2.4.1 | FROM esphome/esphome-base-amd64:2.6.0 | ||||||
|  |  | ||||||
| COPY . . | COPY . . | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| FROM esphome/esphome-lint-base:2.4.1 | FROM esphome/esphome-lint-base:2.6.0 | ||||||
|  |  | ||||||
| COPY requirements.txt requirements_test.txt / | COPY requirements.txt requirements_test.txt / | ||||||
| RUN pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt | RUN pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt | ||||||
|   | |||||||
| @@ -12,4 +12,4 @@ def to_code(config): | |||||||
|         cg.add_library('AsyncTCP-esphome', '1.1.1') |         cg.add_library('AsyncTCP-esphome', '1.1.1') | ||||||
|     elif CORE.is_esp8266: |     elif CORE.is_esp8266: | ||||||
|         # https://github.com/OttoWinter/ESPAsyncTCP |         # https://github.com/OttoWinter/ESPAsyncTCP | ||||||
|         cg.add_library('ESPAsyncTCP-esphome', '1.2.2') |         cg.add_library('ESPAsyncTCP-esphome', '1.2.3') | ||||||
|   | |||||||
| @@ -204,31 +204,33 @@ void DisplayBuffer::vprintf_(int x, int y, Font *font, Color color, TextAlign al | |||||||
|   if (ret > 0) |   if (ret > 0) | ||||||
|     this->print(x, y, font, color, align, buffer); |     this->print(x, y, font, color, align, buffer); | ||||||
| } | } | ||||||
| void DisplayBuffer::image(int x, int y, Image *image) { this->image(x, y, COLOR_ON, image); } |  | ||||||
| void DisplayBuffer::image(int x, int y, Color color, Image *image, bool invert) { | void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color color_off) { | ||||||
|   if (image->get_type() == BINARY) { |   switch (image->get_type()) { | ||||||
|     for (int img_x = 0; img_x < image->get_width(); img_x++) { |     case IMAGE_TYPE_BINARY: | ||||||
|       for (int img_y = 0; img_y < image->get_height(); img_y++) { |       for (int img_x = 0; img_x < image->get_width(); img_x++) { | ||||||
|         if (invert) |         for (int img_y = 0; img_y < image->get_height(); img_y++) { | ||||||
|           this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? COLOR_OFF : color); |           this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? color_on : color_off); | ||||||
|         else |         } | ||||||
|           this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? color : COLOR_OFF); |  | ||||||
|       } |       } | ||||||
|     } |       break; | ||||||
|   } else if (image->get_type() == GRAYSCALE) { |     case IMAGE_TYPE_GRAYSCALE: | ||||||
|     for (int img_x = 0; img_x < image->get_width(); img_x++) { |       for (int img_x = 0; img_x < image->get_width(); img_x++) { | ||||||
|       for (int img_y = 0; img_y < image->get_height(); img_y++) { |         for (int img_y = 0; img_y < image->get_height(); img_y++) { | ||||||
|         this->draw_pixel_at(x + img_x, y + img_y, image->get_grayscale_pixel(img_x, img_y)); |           this->draw_pixel_at(x + img_x, y + img_y, image->get_grayscale_pixel(img_x, img_y)); | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     } |       break; | ||||||
|   } else if (image->get_type() == RGB) { |     case IMAGE_TYPE_RGB24: | ||||||
|     for (int img_x = 0; img_x < image->get_width(); img_x++) { |       for (int img_x = 0; img_x < image->get_width(); img_x++) { | ||||||
|       for (int img_y = 0; img_y < image->get_height(); img_y++) { |         for (int img_y = 0; img_y < image->get_height(); img_y++) { | ||||||
|         this->draw_pixel_at(x + img_x, y + img_y, image->get_color_pixel(img_x, img_y)); |           this->draw_pixel_at(x + img_x, y + img_y, image->get_color_pixel(img_x, img_y)); | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     } |       break; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1, | void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1, | ||||||
|                                     int *width, int *height) { |                                     int *width, int *height) { | ||||||
|   int x_offset, baseline; |   int x_offset, baseline; | ||||||
| @@ -463,15 +465,14 @@ Color Image::get_grayscale_pixel(int x, int y) const { | |||||||
|   if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) |   if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) | ||||||
|     return 0; |     return 0; | ||||||
|   const uint32_t pos = (x + y * this->width_); |   const uint32_t pos = (x + y * this->width_); | ||||||
|   return Color(pgm_read_byte(this->data_start_ + pos) << 24); |   const uint8_t gray = pgm_read_byte(this->data_start_ + pos); | ||||||
|  |   return Color(gray | gray << 8 | gray << 16 | gray << 24); | ||||||
| } | } | ||||||
| int Image::get_width() const { return this->width_; } | int Image::get_width() const { return this->width_; } | ||||||
| int Image::get_height() const { return this->height_; } | int Image::get_height() const { return this->height_; } | ||||||
| ImageType Image::get_type() const { return this->type_; } | ImageType Image::get_type() const { return this->type_; } | ||||||
| Image::Image(const uint8_t *data_start, int width, int height) | Image::Image(const uint8_t *data_start, int width, int height, ImageType type) | ||||||
|     : width_(width), height_(height), data_start_(data_start) {} |     : width_(width), height_(height), type_(type), data_start_(data_start) {} | ||||||
| Image::Image(const uint8_t *data_start, int width, int height, int type) |  | ||||||
|     : width_(width), height_(height), type_((ImageType) type), data_start_(data_start) {} |  | ||||||
|  |  | ||||||
| DisplayPage::DisplayPage(const display_writer_t &writer) : writer_(writer) {} | DisplayPage::DisplayPage(const display_writer_t &writer) : writer_(writer) {} | ||||||
| void DisplayPage::show() { this->parent_->show_page(this); } | void DisplayPage::show() { this->parent_->show_page(this); } | ||||||
|   | |||||||
| @@ -68,7 +68,7 @@ extern const Color COLOR_OFF; | |||||||
| /// Turn the pixel ON. | /// Turn the pixel ON. | ||||||
| extern const Color COLOR_ON; | extern const Color COLOR_ON; | ||||||
|  |  | ||||||
| enum ImageType { BINARY = 0, GRAYSCALE = 1, RGB = 2 }; | enum ImageType { IMAGE_TYPE_BINARY = 0, IMAGE_TYPE_GRAYSCALE = 1, IMAGE_TYPE_RGB24 = 2 }; | ||||||
|  |  | ||||||
| enum DisplayRotation { | enum DisplayRotation { | ||||||
|   DISPLAY_ROTATION_0_DEGREES = 0, |   DISPLAY_ROTATION_0_DEGREES = 0, | ||||||
| @@ -262,9 +262,15 @@ class DisplayBuffer { | |||||||
|       __attribute__((format(strftime, 5, 0))); |       __attribute__((format(strftime, 5, 0))); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   /// Draw the `image` with the top-left corner at [x,y] to the screen. |   /** Draw the `image` with the top-left corner at [x,y] to the screen. | ||||||
|   void image(int x, int y, Image *image); |    * | ||||||
|   void image(int x, int y, Color color, Image *image, bool invert = false); |    * @param x The x coordinate of the upper left corner. | ||||||
|  |    * @param y The y coordinate of the upper left corner. | ||||||
|  |    * @param image The image to draw | ||||||
|  |    * @param color_on The color to replace in binary images for the on bits. | ||||||
|  |    * @param color_off The color to replace in binary images for the off bits. | ||||||
|  |    */ | ||||||
|  |   void image(int x, int y, Image *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF); | ||||||
|  |  | ||||||
|   /** Get the text bounds of the given string. |   /** Get the text bounds of the given string. | ||||||
|    * |    * | ||||||
| @@ -381,8 +387,7 @@ class Font { | |||||||
|  |  | ||||||
| class Image { | class Image { | ||||||
|  public: |  public: | ||||||
|   Image(const uint8_t *data_start, int width, int height); |   Image(const uint8_t *data_start, int width, int height, ImageType type); | ||||||
|   Image(const uint8_t *data_start, int width, int height, int type); |  | ||||||
|   bool get_pixel(int x, int y) const; |   bool get_pixel(int x, int y) const; | ||||||
|   Color get_color_pixel(int x, int y) const; |   Color get_color_pixel(int x, int y) const; | ||||||
|   Color get_grayscale_pixel(int x, int y) const; |   Color get_grayscale_pixel(int x, int y) const; | ||||||
| @@ -393,7 +398,7 @@ class Image { | |||||||
|  protected: |  protected: | ||||||
|   int width_; |   int width_; | ||||||
|   int height_; |   int height_; | ||||||
|   ImageType type_{BINARY}; |   ImageType type_; | ||||||
|   const uint8_t *data_start_; |   const uint8_t *data_start_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,14 +4,20 @@ from esphome import core | |||||||
| from esphome.components import display, font | from esphome.components import display, font | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.const import CONF_FILE, CONF_ID, CONF_RESIZE, CONF_TYPE | from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE | ||||||
| from esphome.core import CORE, HexInt | from esphome.core import CORE, HexInt | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| DEPENDENCIES = ['display'] | DEPENDENCIES = ['display'] | ||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
|  |  | ||||||
| ImageType = {'binary': 0, 'grayscale': 1, 'rgb': 2} |  | ||||||
|  | ImageType = display.display_ns.enum('ImageType') | ||||||
|  | IMAGE_TYPE = { | ||||||
|  |     'BINARY': ImageType.IMAGE_TYPE_BINARY, | ||||||
|  |     'GRAYSCALE': ImageType.IMAGE_TYPE_GRAYSCALE, | ||||||
|  |     'RGB24': ImageType.IMAGE_TYPE_RGB24, | ||||||
|  | } | ||||||
|  |  | ||||||
| Image_ = display.display_ns.class_('Image') | Image_ = display.display_ns.class_('Image') | ||||||
|  |  | ||||||
| @@ -21,7 +27,7 @@ IMAGE_SCHEMA = cv.Schema({ | |||||||
|     cv.Required(CONF_ID): cv.declare_id(Image_), |     cv.Required(CONF_ID): cv.declare_id(Image_), | ||||||
|     cv.Required(CONF_FILE): cv.file_, |     cv.Required(CONF_FILE): cv.file_, | ||||||
|     cv.Optional(CONF_RESIZE): cv.dimensions, |     cv.Optional(CONF_RESIZE): cv.dimensions, | ||||||
|     cv.Optional(CONF_TYPE): cv.string, |     cv.Optional(CONF_TYPE, default='BINARY'): cv.enum(IMAGE_TYPE, upper=True), | ||||||
|     cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), |     cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), | ||||||
| }) | }) | ||||||
|  |  | ||||||
| @@ -37,44 +43,40 @@ def to_code(config): | |||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         raise core.EsphomeError(f"Could not load image file {path}: {e}") |         raise core.EsphomeError(f"Could not load image file {path}: {e}") | ||||||
|  |  | ||||||
|  |     width, height = image.size | ||||||
|  |  | ||||||
|     if CONF_RESIZE in config: |     if CONF_RESIZE in config: | ||||||
|         image.thumbnail(config[CONF_RESIZE]) |         image.thumbnail(config[CONF_RESIZE]) | ||||||
|  |  | ||||||
|     if CONF_TYPE in config: |  | ||||||
|         if config[CONF_TYPE].startswith('GRAYSCALE'): |  | ||||||
|             width, height = image.size |  | ||||||
|             image = image.convert('L', dither=Image.NONE) |  | ||||||
|             pixels = list(image.getdata()) |  | ||||||
|             data = [0 for _ in range(height * width)] |  | ||||||
|             pos = 0 |  | ||||||
|             for pix in pixels: |  | ||||||
|                 data[pos] = pix |  | ||||||
|                 pos += 1 |  | ||||||
|             rhs = [HexInt(x) for x in data] |  | ||||||
|             prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) |  | ||||||
|             cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, ImageType['grayscale']) |  | ||||||
|         elif config[CONF_TYPE].startswith('RGB'): |  | ||||||
|             width, height = image.size |  | ||||||
|             image = image.convert('RGB') |  | ||||||
|             pixels = list(image.getdata()) |  | ||||||
|             data = [0 for _ in range(height * width * 3)] |  | ||||||
|             pos = 0 |  | ||||||
|             for pix in pixels: |  | ||||||
|                 data[pos] = pix[0] |  | ||||||
|                 pos += 1 |  | ||||||
|                 data[pos] = pix[1] |  | ||||||
|                 pos += 1 |  | ||||||
|                 data[pos] = pix[2] |  | ||||||
|                 pos += 1 |  | ||||||
|             rhs = [HexInt(x) for x in data] |  | ||||||
|             prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) |  | ||||||
|             cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, ImageType['rgb']) |  | ||||||
|     else: |  | ||||||
|         image = image.convert('1', dither=Image.NONE) |  | ||||||
|         width, height = image.size |         width, height = image.size | ||||||
|  |     else: | ||||||
|         if width > 500 or height > 500: |         if width > 500 or height > 500: | ||||||
|             _LOGGER.warning("The image you requested is very big. Please consider using" |             _LOGGER.warning("The image you requested is very big. Please consider using" | ||||||
|                             " the resize parameter.") |                             " the resize parameter.") | ||||||
|  |  | ||||||
|  |     if config[CONF_TYPE] == 'GRAYSCALE': | ||||||
|  |         image = image.convert('L', dither=Image.NONE) | ||||||
|  |         pixels = list(image.getdata()) | ||||||
|  |         data = [0 for _ in range(height * width)] | ||||||
|  |         pos = 0 | ||||||
|  |         for pix in pixels: | ||||||
|  |             data[pos] = pix | ||||||
|  |             pos += 1 | ||||||
|  |  | ||||||
|  |     elif config[CONF_TYPE] == 'RGB24': | ||||||
|  |         image = image.convert('RGB') | ||||||
|  |         pixels = list(image.getdata()) | ||||||
|  |         data = [0 for _ in range(height * width * 3)] | ||||||
|  |         pos = 0 | ||||||
|  |         for pix in pixels: | ||||||
|  |             data[pos] = pix[0] | ||||||
|  |             pos += 1 | ||||||
|  |             data[pos] = pix[1] | ||||||
|  |             pos += 1 | ||||||
|  |             data[pos] = pix[2] | ||||||
|  |             pos += 1 | ||||||
|  |  | ||||||
|  |     elif config[CONF_TYPE] == 'BINARY': | ||||||
|  |         image = image.convert('1', dither=Image.NONE) | ||||||
|         width8 = ((width + 7) // 8) * 8 |         width8 = ((width + 7) // 8) * 8 | ||||||
|         data = [0 for _ in range(height * width8 // 8)] |         data = [0 for _ in range(height * width8 // 8)] | ||||||
|         for y in range(height): |         for y in range(height): | ||||||
| @@ -84,6 +86,7 @@ def to_code(config): | |||||||
|                 pos = x + y * width8 |                 pos = x + y * width8 | ||||||
|                 data[pos // 8] |= 0x80 >> (pos % 8) |                 data[pos // 8] |= 0x80 >> (pos % 8) | ||||||
|  |  | ||||||
|         rhs = [HexInt(x) for x in data] |     rhs = [HexInt(x) for x in data] | ||||||
|         prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) |     prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) | ||||||
|         cg.new_Pvariable(config[CONF_ID], prog_arr, width, height) |     cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, | ||||||
|  |                      IMAGE_TYPE[config[CONF_TYPE]]) | ||||||
|   | |||||||
| @@ -18,14 +18,14 @@ ParallelScript = script_ns.class_('ParallelScript', Script) | |||||||
|  |  | ||||||
| CONF_SINGLE = 'single' | CONF_SINGLE = 'single' | ||||||
| CONF_RESTART = 'restart' | CONF_RESTART = 'restart' | ||||||
| CONF_QUEUE = 'queue' | CONF_QUEUED = 'queued' | ||||||
| CONF_PARALLEL = 'parallel' | CONF_PARALLEL = 'parallel' | ||||||
| CONF_MAX_RUNS = 'max_runs' | CONF_MAX_RUNS = 'max_runs' | ||||||
|  |  | ||||||
| SCRIPT_MODES = { | SCRIPT_MODES = { | ||||||
|     CONF_SINGLE: SingleScript, |     CONF_SINGLE: SingleScript, | ||||||
|     CONF_RESTART: RestartScript, |     CONF_RESTART: RestartScript, | ||||||
|     CONF_QUEUE: QueueingScript, |     CONF_QUEUED: QueueingScript, | ||||||
|     CONF_PARALLEL: ParallelScript, |     CONF_PARALLEL: ParallelScript, | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -33,7 +33,7 @@ SCRIPT_MODES = { | |||||||
| def check_max_runs(value): | def check_max_runs(value): | ||||||
|     if CONF_MAX_RUNS not in value: |     if CONF_MAX_RUNS not in value: | ||||||
|         return value |         return value | ||||||
|     if value[CONF_MODE] not in [CONF_QUEUE, CONF_PARALLEL]: |     if value[CONF_MODE] not in [CONF_QUEUED, CONF_PARALLEL]: | ||||||
|         raise cv.Invalid("The option 'max_runs' is only valid in 'queue' and 'parallel' mode.", |         raise cv.Invalid("The option 'max_runs' is only valid in 'queue' and 'parallel' mode.", | ||||||
|                          path=[CONF_MAX_RUNS]) |                          path=[CONF_MAX_RUNS]) | ||||||
|     return value |     return value | ||||||
| @@ -65,7 +65,7 @@ def to_code(config): | |||||||
|         if CONF_MAX_RUNS in conf: |         if CONF_MAX_RUNS in conf: | ||||||
|             cg.add(trigger.set_max_runs(conf[CONF_MAX_RUNS])) |             cg.add(trigger.set_max_runs(conf[CONF_MAX_RUNS])) | ||||||
|  |  | ||||||
|         if conf[CONF_MODE] == CONF_QUEUE: |         if conf[CONF_MODE] == CONF_QUEUED: | ||||||
|             yield cg.register_component(trigger, conf) |             yield cg.register_component(trigger, conf) | ||||||
|  |  | ||||||
|         triggers.append((trigger, conf)) |         triggers.append((trigger, conf)) | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ void QueueingScript::execute() { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     ESP_LOGD(TAG, "Script '%s' queueing new instance (mode: queue)", this->name_.c_str()); |     ESP_LOGD(TAG, "Script '%s' queueing new instance (mode: queued)", this->name_.c_str()); | ||||||
|     this->num_runs_++; |     this->num_runs_++; | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -41,7 +41,7 @@ def setup_ssd1036(var, config): | |||||||
|         reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN]) |         reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN]) | ||||||
|         cg.add(var.set_reset_pin(reset)) |         cg.add(var.set_reset_pin(reset)) | ||||||
|     if CONF_BRIGHTNESS in config: |     if CONF_BRIGHTNESS in config: | ||||||
|         cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) |         cg.add(var.init_brightness(config[CONF_BRIGHTNESS])) | ||||||
|     if CONF_EXTERNAL_VCC in config: |     if CONF_EXTERNAL_VCC in config: | ||||||
|         cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC])) |         cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC])) | ||||||
|     if CONF_LAMBDA in config: |     if CONF_LAMBDA in config: | ||||||
|   | |||||||
| @@ -5,7 +5,11 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace ssd1306_base { | namespace ssd1306_base { | ||||||
|  |  | ||||||
| static const char *TAG = "sd1306"; | static const char *TAG = "ssd1306"; | ||||||
|  |  | ||||||
|  | static const uint8_t BLACK = 0; | ||||||
|  | static const uint8_t WHITE = 1; | ||||||
|  | static const uint8_t SSD1306_MAX_CONTRAST = 255; | ||||||
|  |  | ||||||
| static const uint8_t SSD1306_COMMAND_DISPLAY_OFF = 0xAE; | static const uint8_t SSD1306_COMMAND_DISPLAY_OFF = 0xAE; | ||||||
| static const uint8_t SSD1306_COMMAND_DISPLAY_ON = 0xAF; | static const uint8_t SSD1306_COMMAND_DISPLAY_ON = 0xAF; | ||||||
| @@ -69,27 +73,6 @@ void SSD1306::setup() { | |||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->command(SSD1306_COMMAND_SET_CONTRAST); |  | ||||||
|   switch (this->model_) { |  | ||||||
|     case SSD1306_MODEL_128_32: |  | ||||||
|     case SH1106_MODEL_128_32: |  | ||||||
|       this->command(0x8F); |  | ||||||
|       break; |  | ||||||
|     case SSD1306_MODEL_128_64: |  | ||||||
|     case SH1106_MODEL_128_64: |  | ||||||
|     case SSD1306_MODEL_64_48: |  | ||||||
|     case SH1106_MODEL_64_48: |  | ||||||
|       this->command(int(255 * (this->brightness_))); |  | ||||||
|       break; |  | ||||||
|     case SSD1306_MODEL_96_16: |  | ||||||
|     case SH1106_MODEL_96_16: |  | ||||||
|       if (this->external_vcc_) |  | ||||||
|         this->command(0x10); |  | ||||||
|       else |  | ||||||
|         this->command(0xAF); |  | ||||||
|       break; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   this->command(SSD1306_COMMAND_SET_PRE_CHARGE); |   this->command(SSD1306_COMMAND_SET_PRE_CHARGE); | ||||||
|   if (this->external_vcc_) |   if (this->external_vcc_) | ||||||
|     this->command(0x22); |     this->command(0x22); | ||||||
| @@ -104,7 +87,12 @@ void SSD1306::setup() { | |||||||
|  |  | ||||||
|   this->command(SSD1306_COMMAND_DEACTIVATE_SCROLL); |   this->command(SSD1306_COMMAND_DEACTIVATE_SCROLL); | ||||||
|  |  | ||||||
|   this->command(SSD1306_COMMAND_DISPLAY_ON); |   set_brightness(this->brightness_); | ||||||
|  |  | ||||||
|  |   this->fill(BLACK);  // clear display - ensures we do not see garbage at power-on | ||||||
|  |   this->display();    // ...write buffer, which actually clears the display's memory | ||||||
|  |  | ||||||
|  |   this->turn_on(); | ||||||
| } | } | ||||||
| void SSD1306::display() { | void SSD1306::display() { | ||||||
|   if (this->is_sh1106_()) { |   if (this->is_sh1106_()) { | ||||||
| @@ -140,6 +128,22 @@ void SSD1306::update() { | |||||||
|   this->do_update_(); |   this->do_update_(); | ||||||
|   this->display(); |   this->display(); | ||||||
| } | } | ||||||
|  | void SSD1306::set_brightness(float brightness) { | ||||||
|  |   // validation | ||||||
|  |   this->brightness_ = clamp(brightness, 0, 1); | ||||||
|  |   // now write the new brightness level to the display | ||||||
|  |   this->command(SSD1306_COMMAND_SET_CONTRAST); | ||||||
|  |   this->command(int(SSD1306_MAX_CONTRAST * (this->brightness_))); | ||||||
|  | } | ||||||
|  | bool SSD1306::is_on() { return this->is_on_; } | ||||||
|  | void SSD1306::turn_on() { | ||||||
|  |   this->command(SSD1306_COMMAND_DISPLAY_ON); | ||||||
|  |   this->is_on_ = true; | ||||||
|  | } | ||||||
|  | void SSD1306::turn_off() { | ||||||
|  |   this->command(SSD1306_COMMAND_DISPLAY_OFF); | ||||||
|  |   this->is_on_ = false; | ||||||
|  | } | ||||||
| int SSD1306::get_height_internal() { | int SSD1306::get_height_internal() { | ||||||
|   switch (this->model_) { |   switch (this->model_) { | ||||||
|     case SSD1306_MODEL_128_32: |     case SSD1306_MODEL_128_32: | ||||||
| @@ -178,7 +182,6 @@ int SSD1306::get_width_internal() { | |||||||
| size_t SSD1306::get_buffer_length_() { | size_t SSD1306::get_buffer_length_() { | ||||||
|   return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u; |   return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u; | ||||||
| } | } | ||||||
|  |  | ||||||
| void HOT SSD1306::draw_absolute_pixel_internal(int x, int y, Color color) { | void HOT SSD1306::draw_absolute_pixel_internal(int x, int y, Color color) { | ||||||
|   if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) |   if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) | ||||||
|     return; |     return; | ||||||
|   | |||||||
| @@ -29,8 +29,11 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer { | |||||||
|   void set_model(SSD1306Model model) { this->model_ = model; } |   void set_model(SSD1306Model model) { this->model_ = model; } | ||||||
|   void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } |   void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } | ||||||
|   void set_external_vcc(bool external_vcc) { this->external_vcc_ = external_vcc; } |   void set_external_vcc(bool external_vcc) { this->external_vcc_ = external_vcc; } | ||||||
|   void set_brightness(float brightness) { this->brightness_ = brightness; } |   void init_brightness(float brightness) { this->brightness_ = brightness; } | ||||||
|  |   void set_brightness(float brightness); | ||||||
|  |   bool is_on(); | ||||||
|  |   void turn_on(); | ||||||
|  |   void turn_off(); | ||||||
|   float get_setup_priority() const override { return setup_priority::PROCESSOR; } |   float get_setup_priority() const override { return setup_priority::PROCESSOR; } | ||||||
|   void fill(Color color) override; |   void fill(Color color) override; | ||||||
|  |  | ||||||
| @@ -51,6 +54,7 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer { | |||||||
|   SSD1306Model model_{SSD1306_MODEL_128_64}; |   SSD1306Model model_{SSD1306_MODEL_128_64}; | ||||||
|   GPIOPin *reset_pin_{nullptr}; |   GPIOPin *reset_pin_{nullptr}; | ||||||
|   bool external_vcc_{false}; |   bool external_vcc_{false}; | ||||||
|  |   bool is_on_{false}; | ||||||
|   float brightness_{1.0}; |   float brightness_{1.0}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ | |||||||
| #ifdef ARDUINO_ARCH_ESP8266 | #ifdef ARDUINO_ARCH_ESP8266 | ||||||
| #include "sys/time.h" | #include "sys/time.h" | ||||||
| #endif | #endif | ||||||
|  | #include "errno.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace time { | namespace time { | ||||||
| @@ -20,8 +21,18 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) { | |||||||
|   struct timeval timev { |   struct timeval timev { | ||||||
|     .tv_sec = static_cast<time_t>(epoch), .tv_usec = 0, |     .tv_sec = static_cast<time_t>(epoch), .tv_usec = 0, | ||||||
|   }; |   }; | ||||||
|  |   ESP_LOGVV(TAG, "Got epoch %u", epoch); | ||||||
|   timezone tz = {0, 0}; |   timezone tz = {0, 0}; | ||||||
|   settimeofday(&timev, &tz); |   int ret = settimeofday(&timev, &tz); | ||||||
|  |   if (ret == EINVAL) { | ||||||
|  |     // Some ESP8266 frameworks abort when timezone parameter is not NULL | ||||||
|  |     // while ESP32 expects it not to be NULL | ||||||
|  |     ret = settimeofday(&timev, nullptr); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (ret != 0) { | ||||||
|  |     ESP_LOGW(TAG, "setimeofday() failed with code %d", ret); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   auto time = this->now(); |   auto time = this->now(); | ||||||
|   char buf[128]; |   char buf[128]; | ||||||
|   | |||||||
| @@ -232,7 +232,7 @@ uint8_t TM1637Display::print(uint8_t start_pos, const char* str) { | |||||||
|   uint8_t pos = start_pos; |   uint8_t pos = start_pos; | ||||||
|   for (; *str != '\0'; str++) { |   for (; *str != '\0'; str++) { | ||||||
|     uint8_t data = TM1637_UNKNOWN_CHAR; |     uint8_t data = TM1637_UNKNOWN_CHAR; | ||||||
|     if (*str >= ' ' && *str <= '}') |     if (*str >= ' ' && *str <= '~') | ||||||
|       data = pgm_read_byte(&TM1637_ASCII_TO_RAW[*str - ' ']); |       data = pgm_read_byte(&TM1637_ASCII_TO_RAW[*str - ' ']); | ||||||
|  |  | ||||||
|     if (data == TM1637_UNKNOWN_CHAR) { |     if (data == TM1637_UNKNOWN_CHAR) { | ||||||
|   | |||||||
| @@ -22,8 +22,8 @@ void Tuya::loop() { | |||||||
| void Tuya::dump_config() { | void Tuya::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "Tuya:"); |   ESP_LOGCONFIG(TAG, "Tuya:"); | ||||||
|   if (this->init_state_ != TuyaInitState::INIT_DONE) { |   if (this->init_state_ != TuyaInitState::INIT_DONE) { | ||||||
|     ESP_LOGCONFIG(TAG, "  Configuration will be reported when setup is complete. Current init_state: %u",  // NOLINT |     ESP_LOGCONFIG(TAG, "  Configuration will be reported when setup is complete. Current init_state: %u", | ||||||
|                   this->init_state_); |                   static_cast<uint8_t>(this->init_state_)); | ||||||
|     ESP_LOGCONFIG(TAG, "  If no further output is received, confirm that this is a supported Tuya device."); |     ESP_LOGCONFIG(TAG, "  If no further output is received, confirm that this is a supported Tuya device."); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -41,11 +41,11 @@ void WhirlpoolClimate::transmit_state() { | |||||||
|   remote_state[18] = 0x08; |   remote_state[18] = 0x08; | ||||||
|  |  | ||||||
|   auto powered_on = this->mode != climate::CLIMATE_MODE_OFF; |   auto powered_on = this->mode != climate::CLIMATE_MODE_OFF; | ||||||
|   if (powered_on != this->powered_on_assumed_) { |   if (powered_on != this->powered_on_assumed) { | ||||||
|     // Set power toggle command |     // Set power toggle command | ||||||
|     remote_state[2] = 4; |     remote_state[2] = 4; | ||||||
|     remote_state[15] = 1; |     remote_state[15] = 1; | ||||||
|     this->powered_on_assumed_ = powered_on; |     this->powered_on_assumed = powered_on; | ||||||
|   } |   } | ||||||
|   switch (this->mode) { |   switch (this->mode) { | ||||||
|     case climate::CLIMATE_MODE_AUTO: |     case climate::CLIMATE_MODE_AUTO: | ||||||
| @@ -215,14 +215,14 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { | |||||||
|  |  | ||||||
|     if (powered_on) { |     if (powered_on) { | ||||||
|       this->mode = climate::CLIMATE_MODE_OFF; |       this->mode = climate::CLIMATE_MODE_OFF; | ||||||
|       this->powered_on_assumed_ = false; |       this->powered_on_assumed = false; | ||||||
|     } else { |     } else { | ||||||
|       this->powered_on_assumed_ = true; |       this->powered_on_assumed = true; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Set received mode |   // Set received mode | ||||||
|   if (powered_on_assumed_) { |   if (powered_on_assumed) { | ||||||
|     auto mode = remote_state[3] & 0x7; |     auto mode = remote_state[3] & 0x7; | ||||||
|     ESP_LOGV(TAG, "Mode: %02X", mode); |     ESP_LOGV(TAG, "Mode: %02X", mode); | ||||||
|     switch (mode) { |     switch (mode) { | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ class WhirlpoolClimate : public climate_ir::ClimateIR { | |||||||
|   void setup() override { |   void setup() override { | ||||||
|     climate_ir::ClimateIR::setup(); |     climate_ir::ClimateIR::setup(); | ||||||
|  |  | ||||||
|     this->powered_on_assumed_ = this->mode != climate::CLIMATE_MODE_OFF; |     this->powered_on_assumed = this->mode != climate::CLIMATE_MODE_OFF; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /// Override control to change settings of the climate device. |   /// Override control to change settings of the climate device. | ||||||
| @@ -39,15 +39,15 @@ class WhirlpoolClimate : public climate_ir::ClimateIR { | |||||||
|  |  | ||||||
|   void set_model(Model model) { this->model_ = model; } |   void set_model(Model model) { this->model_ = model; } | ||||||
|  |  | ||||||
|  |   // used to track when to send the power toggle command | ||||||
|  |   bool powered_on_assumed; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   /// Transmit via IR the state of this climate controller. |   /// Transmit via IR the state of this climate controller. | ||||||
|   void transmit_state() override; |   void transmit_state() override; | ||||||
|   /// Handle received IR Buffer |   /// Handle received IR Buffer | ||||||
|   bool on_receive(remote_base::RemoteReceiveData data) override; |   bool on_receive(remote_base::RemoteReceiveData data) override; | ||||||
|  |  | ||||||
|   // used to track when to send the power toggle command |  | ||||||
|   bool powered_on_assumed_; |  | ||||||
|  |  | ||||||
|   bool send_swing_cmd_{false}; |   bool send_swing_cmd_{false}; | ||||||
|   Model model_; |   Model model_; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
|  |  | ||||||
| MAJOR_VERSION = 1 | MAJOR_VERSION = 1 | ||||||
| MINOR_VERSION = 15 | MINOR_VERSION = 15 | ||||||
| PATCH_VERSION = '0b3' | PATCH_VERSION = '0b4' | ||||||
| __short_version__ = f'{MAJOR_VERSION}.{MINOR_VERSION}' | __short_version__ = f'{MAJOR_VERSION}.{MINOR_VERSION}' | ||||||
| __version__ = f'{__short_version__}.{PATCH_VERSION}' | __version__ = f'{__short_version__}.{PATCH_VERSION}' | ||||||
|  |  | ||||||
| @@ -25,6 +25,8 @@ ARDUINO_VERSION_ESP32 = { | |||||||
| # See also https://github.com/platformio/platform-espressif8266/releases | # See also https://github.com/platformio/platform-espressif8266/releases | ||||||
| ARDUINO_VERSION_ESP8266 = { | ARDUINO_VERSION_ESP8266 = { | ||||||
|     'dev': 'https://github.com/platformio/platform-espressif8266.git', |     'dev': 'https://github.com/platformio/platform-espressif8266.git', | ||||||
|  |     '2.7.4': 'espressif8266@2.6.2', | ||||||
|  |     '2.7.3': 'espressif8266@2.6.1', | ||||||
|     '2.7.2': 'espressif8266@2.6.0', |     '2.7.2': 'espressif8266@2.6.0', | ||||||
|     '2.7.1': 'espressif8266@2.5.3', |     '2.7.1': 'espressif8266@2.5.3', | ||||||
|     '2.7.0': 'espressif8266@2.5.0', |     '2.7.0': 'espressif8266@2.5.0', | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ validate_platform = cv.one_of(*ESP_PLATFORMS, upper=True) | |||||||
|  |  | ||||||
| PLATFORMIO_ESP8266_LUT = { | PLATFORMIO_ESP8266_LUT = { | ||||||
|     **ARDUINO_VERSION_ESP8266, |     **ARDUINO_VERSION_ESP8266, | ||||||
|     'RECOMMENDED': ARDUINO_VERSION_ESP8266['2.7.2'], |     'RECOMMENDED': ARDUINO_VERSION_ESP8266['2.7.4'], | ||||||
|     'LATEST': 'espressif8266', |     'LATEST': 'espressif8266', | ||||||
|     'DEV': ARDUINO_VERSION_ESP8266['dev'], |     'DEV': ARDUINO_VERSION_ESP8266['dev'], | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ lib_deps = | |||||||
|     ESPAsyncWebServer-esphome@1.2.7 |     ESPAsyncWebServer-esphome@1.2.7 | ||||||
|     FastLED@3.3.2 |     FastLED@3.3.2 | ||||||
|     NeoPixelBus-esphome@2.5.7 |     NeoPixelBus-esphome@2.5.7 | ||||||
|     ESPAsyncTCP-esphome@1.2.2 |     ESPAsyncTCP-esphome@1.2.3 | ||||||
|     1655@1.0.2  ; TinyGPSPlus (has name conflict) |     1655@1.0.2  ; TinyGPSPlus (has name conflict) | ||||||
|     6865@1.0.0  ; TM1651 Battery Display |     6865@1.0.0  ; TM1651 Battery Display | ||||||
|     6306@1.0.3  ; HM3301 |     6306@1.0.3  ; HM3301 | ||||||
|   | |||||||
							
								
								
									
										59
									
								
								script/bump-docker-base-version.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										59
									
								
								script/bump-docker-base-version.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  |  | ||||||
|  | import argparse | ||||||
|  | import re | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def sub(path, pattern, repl, expected_count=1): | ||||||
|  |     with open(path) as fh: | ||||||
|  |         content = fh.read() | ||||||
|  |     content, count = re.subn(pattern, repl, content, flags=re.MULTILINE) | ||||||
|  |     if expected_count is not None: | ||||||
|  |         assert count == expected_count, f"Pattern {pattern} replacement failed!" | ||||||
|  |     with open(path, "wt") as fh: | ||||||
|  |         fh.write(content) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def write_version(version: str): | ||||||
|  |     for p in [ | ||||||
|  |             ".github/workflows/ci-docker.yml", | ||||||
|  |             ".github/workflows/release-dev.yml", | ||||||
|  |             ".github/workflows/release.yml" | ||||||
|  |     ]: | ||||||
|  |         sub( | ||||||
|  |             p, | ||||||
|  |             r'base_version=".*"', | ||||||
|  |             f'base_version="{version}"' | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     sub( | ||||||
|  |         "docker/Dockerfile", | ||||||
|  |         r"ARG BUILD_FROM=esphome/esphome-base-amd64:.*", | ||||||
|  |         f"ARG BUILD_FROM=esphome/esphome-base-amd64:{version}" | ||||||
|  |     ) | ||||||
|  |     sub( | ||||||
|  |         "docker/Dockerfile.dev", | ||||||
|  |         r"FROM esphome/esphome-base-amd64:.*", | ||||||
|  |         f"FROM esphome/esphome-base-amd64:{version}" | ||||||
|  |     ) | ||||||
|  |     sub( | ||||||
|  |         "docker/Dockerfile.lint", | ||||||
|  |         r"FROM esphome/esphome-lint-base:.*", | ||||||
|  |         f"FROM esphome/esphome-lint-base:{version}" | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def main(): | ||||||
|  |     parser = argparse.ArgumentParser() | ||||||
|  |     parser.add_argument('new_version', type=str) | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     version = args.new_version | ||||||
|  |     print(f"Bumping to {version}") | ||||||
|  |     write_version(version) | ||||||
|  |     return 0 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     sys.exit(main() or 0) | ||||||
| @@ -292,6 +292,21 @@ text_sensor: | |||||||
|  |  | ||||||
| script: | script: | ||||||
|   - id: my_script |   - id: my_script | ||||||
|  |     mode: single | ||||||
|  |     then: | ||||||
|  |       - lambda: 'ESP_LOGD("main", "Hello World!");' | ||||||
|  |   - id: my_script_queued | ||||||
|  |     mode: queued | ||||||
|  |     max_runs: 2 | ||||||
|  |     then: | ||||||
|  |       - lambda: 'ESP_LOGD("main", "Hello World!");' | ||||||
|  |   - id: my_script_parallel | ||||||
|  |     mode: parallel | ||||||
|  |     max_runs: 2 | ||||||
|  |     then: | ||||||
|  |       - lambda: 'ESP_LOGD("main", "Hello World!");' | ||||||
|  |   - id: my_script_restart | ||||||
|  |     mode: restart | ||||||
|     then: |     then: | ||||||
|       - lambda: 'ESP_LOGD("main", "Hello World!");' |       - lambda: 'ESP_LOGD("main", "Hello World!");' | ||||||
|  |  | ||||||
| @@ -316,5 +331,3 @@ interval: | |||||||
|     - logger.log: "Interval Run" |     - logger.log: "Interval Run" | ||||||
|  |  | ||||||
| display: | display: | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user