Merge branch 'dev' into optolink

This commit is contained in:
j0ta29 2023-07-07 18:34:23 +02:00 committed by GitHub
commit 2467a257f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
67 changed files with 2141 additions and 1690 deletions

View file

@ -241,12 +241,6 @@ jobs:
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Cache platformio
uses: actions/cache@v3.3.1
with:
path: ~/.platformio
# yamllint disable-line rule:line-length
key: platformio-test${{ matrix.file }}-${{ hashFiles('platformio.ini') }}
- name: Run esphome compile tests/test${{ matrix.file }}.yaml
run: |
. venv/bin/activate

View file

@ -6,14 +6,12 @@ on:
schedule:
- cron: '45 6 * * *'
permissions:
contents: write
pull-requests: write
jobs:
sync:
name: Sync Device Classes
runs-on: ubuntu-latest
if: github.repository == 'esphome/esphome'
steps:
- name: Checkout
uses: actions/checkout@v3
@ -38,15 +36,6 @@ jobs:
run: |
python ./script/sync-device_class.py
- name: Get PR template
id: pr-template-body
run: |
body=$(cat .github/PULL_REQUEST_TEMPLATE.md)
delimiter="$(openssl rand -hex 8)"
echo "body<<$delimiter" >> $GITHUB_OUTPUT
echo "$body" >> $GITHUB_OUTPUT
echo "$delimiter" >> $GITHUB_OUTPUT
- name: Commit changes
uses: peter-evans/create-pull-request@v5
with:
@ -56,5 +45,5 @@ jobs:
branch: sync/device-classes
delete-branch: true
title: "Synchronise Device Classes from Home Assistant"
body: ${{ steps.pr-template-body.outputs.body }}
body-path: .github/PULL_REQUEST_TEMPLATE.md
token: ${{ secrets.DEVICE_CLASS_SYNC_TOKEN }}

View file

@ -27,7 +27,7 @@ repos:
- --branch=release
- --branch=beta
- repo: https://github.com/asottile/pyupgrade
rev: v3.4.0
rev: v3.7.0
hooks:
- id: pyupgrade
args: [--py39-plus]

View file

@ -1,7 +1,7 @@
import logging
from esphome import automation, core
from esphome.components import display, font
from esphome.components import font
import esphome.components.image as espImage
from esphome.components.image import CONF_USE_TRANSPARENCY
import esphome.config_validation as cv
@ -18,6 +18,7 @@ from esphome.core import CORE, HexInt
_LOGGER = logging.getLogger(__name__)
AUTO_LOAD = ["image"]
CODEOWNERS = ["@syndlex"]
DEPENDENCIES = ["display"]
MULTI_CONF = True
@ -27,16 +28,18 @@ CONF_START_FRAME = "start_frame"
CONF_END_FRAME = "end_frame"
CONF_FRAME = "frame"
Animation_ = display.display_ns.class_("Animation", espImage.Image_)
animation_ns = cg.esphome_ns.namespace("animation")
Animation_ = animation_ns.class_("Animation", espImage.Image_)
# Actions
NextFrameAction = display.display_ns.class_(
NextFrameAction = animation_ns.class_(
"AnimationNextFrameAction", automation.Action, cg.Parented.template(Animation_)
)
PrevFrameAction = display.display_ns.class_(
PrevFrameAction = animation_ns.class_(
"AnimationPrevFrameAction", automation.Action, cg.Parented.template(Animation_)
)
SetFrameAction = display.display_ns.class_(
SetFrameAction = animation_ns.class_(
"AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
)

View file

@ -3,9 +3,10 @@
#include "esphome/core/hal.h"
namespace esphome {
namespace display {
namespace animation {
Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type)
Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count,
image::ImageType type)
: Image(data_start, width, height, type),
animation_data_start_(data_start),
current_frame_(0),
@ -65,5 +66,5 @@ void Animation::update_data_start_() {
this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_;
}
} // namespace display
} // namespace animation
} // namespace esphome

View file

@ -1,14 +1,14 @@
#pragma once
#include "image.h"
#include "esphome/components/image/image.h"
#include "esphome/core/automation.h"
namespace esphome {
namespace display {
namespace animation {
class Animation : public Image {
class Animation : public image::Image {
public:
Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type);
Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, image::ImageType type);
uint32_t get_animation_frame_count() const;
int get_current_frame() const;
@ -63,5 +63,5 @@ template<typename... Ts> class AnimationSetFrameAction : public Action<Ts...> {
Animation *parent_;
};
} // namespace display
} // namespace animation
} // namespace esphome

View file

@ -47,7 +47,7 @@ async def async_run_logs(config, address):
except APIConnectionError:
cli.disconnect()
async def on_disconnect():
async def on_disconnect(expected_disconnect: bool) -> None:
_LOGGER.warning("Disconnected from API")
zc = zeroconf.Zeroconf()

View file

@ -95,6 +95,14 @@ DEVICE_CLASSES = [
IS_PLATFORM_COMPONENT = True
CONF_TIME_OFF = "time_off"
CONF_TIME_ON = "time_on"
DEFAULT_DELAY = "1s"
DEFAULT_TIME_OFF = "100ms"
DEFAULT_TIME_ON = "900ms"
binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor")
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase)
BinarySensorInitiallyOff = binary_sensor_ns.class_(
@ -138,47 +146,75 @@ FILTER_REGISTRY = Registry()
validate_filters = cv.validate_registry("filter", FILTER_REGISTRY)
@FILTER_REGISTRY.register("invert", InvertFilter, {})
def register_filter(name, filter_type, schema):
return FILTER_REGISTRY.register(name, filter_type, schema)
@register_filter("invert", InvertFilter, {})
async def invert_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id)
@FILTER_REGISTRY.register(
"delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds
@register_filter(
"delayed_on_off",
DelayedOnOffFilter,
cv.Any(
cv.templatable(cv.positive_time_period_milliseconds),
cv.Schema(
{
cv.Required(CONF_TIME_ON): cv.templatable(
cv.positive_time_period_milliseconds
),
cv.Required(CONF_TIME_OFF): cv.templatable(
cv.positive_time_period_milliseconds
),
}
),
msg="'delayed_on_off' filter requires either a delay time to be used for both "
"turn-on and turn-off delays, or two parameters 'time_on' and 'time_off' if "
"different delay times are required.",
),
)
async def delayed_on_off_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id, config)
var = cg.new_Pvariable(filter_id)
await cg.register_component(var, {})
if isinstance(config, dict):
template_ = await cg.templatable(config[CONF_TIME_ON], [], cg.uint32)
cg.add(var.set_on_delay(template_))
template_ = await cg.templatable(config[CONF_TIME_OFF], [], cg.uint32)
cg.add(var.set_off_delay(template_))
else:
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_on_delay(template_))
cg.add(var.set_off_delay(template_))
return var
@FILTER_REGISTRY.register(
"delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds
@register_filter(
"delayed_on", DelayedOnFilter, cv.templatable(cv.positive_time_period_milliseconds)
)
async def delayed_on_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id, config)
var = cg.new_Pvariable(filter_id)
await cg.register_component(var, {})
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_delay(template_))
return var
@FILTER_REGISTRY.register(
"delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds
@register_filter(
"delayed_off",
DelayedOffFilter,
cv.templatable(cv.positive_time_period_milliseconds),
)
async def delayed_off_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id, config)
var = cg.new_Pvariable(filter_id)
await cg.register_component(var, {})
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_delay(template_))
return var
CONF_TIME_OFF = "time_off"
CONF_TIME_ON = "time_on"
DEFAULT_DELAY = "1s"
DEFAULT_TIME_OFF = "100ms"
DEFAULT_TIME_ON = "900ms"
@FILTER_REGISTRY.register(
@register_filter(
"autorepeat",
AutorepeatFilter,
cv.All(
@ -215,7 +251,7 @@ async def autorepeat_filter_to_code(config, filter_id):
return var
@FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda)
@register_filter("lambda", LambdaFilter, cv.returning_lambda)
async def lambda_filter_to_code(config, filter_id):
lambda_ = await cg.process_lambda(
config, [(bool, "x")], return_type=cg.optional.template(bool)

View file

@ -26,22 +26,20 @@ void Filter::input(bool value, bool is_initial) {
}
}
DelayedOnOffFilter::DelayedOnOffFilter(uint32_t delay) : delay_(delay) {}
optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) {
if (value) {
this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(true, is_initial); });
this->set_timeout("ON_OFF", this->on_delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
} else {
this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); });
this->set_timeout("ON_OFF", this->off_delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
}
return {};
}
float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
DelayedOnFilter::DelayedOnFilter(uint32_t delay) : delay_(delay) {}
optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
if (value) {
this->set_timeout("ON", this->delay_, [this, is_initial]() { this->output(true, is_initial); });
this->set_timeout("ON", this->delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
return {};
} else {
this->cancel_timeout("ON");
@ -51,10 +49,9 @@ optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
DelayedOffFilter::DelayedOffFilter(uint32_t delay) : delay_(delay) {}
optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
if (!value) {
this->set_timeout("OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); });
this->set_timeout("OFF", this->delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
return {};
} else {
this->cancel_timeout("OFF");
@ -114,15 +111,6 @@ LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }
optional<bool> UniqueFilter::new_value(bool value, bool is_initial) {
if (this->last_value_.has_value() && *this->last_value_ == value) {
return {};
} else {
this->last_value_ = value;
return value;
}
}
} // namespace binary_sensor
} // namespace esphome

View file

@ -1,5 +1,6 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
@ -29,38 +30,40 @@ class Filter {
class DelayedOnOffFilter : public Filter, public Component {
public:
explicit DelayedOnOffFilter(uint32_t delay);
optional<bool> new_value(bool value, bool is_initial) override;
float get_setup_priority() const override;
template<typename T> void set_on_delay(T delay) { this->on_delay_ = delay; }
template<typename T> void set_off_delay(T delay) { this->off_delay_ = delay; }
protected:
uint32_t delay_;
TemplatableValue<uint32_t> on_delay_{};
TemplatableValue<uint32_t> off_delay_{};
};
class DelayedOnFilter : public Filter, public Component {
public:
explicit DelayedOnFilter(uint32_t delay);
optional<bool> new_value(bool value, bool is_initial) override;
float get_setup_priority() const override;
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
protected:
uint32_t delay_;
TemplatableValue<uint32_t> delay_{};
};
class DelayedOffFilter : public Filter, public Component {
public:
explicit DelayedOffFilter(uint32_t delay);
optional<bool> new_value(bool value, bool is_initial) override;
float get_setup_priority() const override;
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
protected:
uint32_t delay_;
TemplatableValue<uint32_t> delay_{};
};
class InvertFilter : public Filter {
@ -105,14 +108,6 @@ class LambdaFilter : public Filter {
std::function<optional<bool>(bool)> f_;
};
class UniqueFilter : public Filter {
public:
optional<bool> new_value(bool value, bool is_initial) override;
protected:
optional<bool> last_value_{};
};
} // namespace binary_sensor
} // namespace esphome

View file

@ -12,6 +12,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_MQTT_ID,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_IDENTIFY,
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
)
@ -24,6 +25,7 @@ IS_PLATFORM_COMPONENT = True
DEVICE_CLASSES = [
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_IDENTIFY,
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
]

View file

@ -38,7 +38,6 @@ CONFIG_SCHEMA = cv.All(
),
}
).extend(cv.polling_component_schema("60s")),
cv.only_on(["esp32", "esp8266"]),
)

View file

@ -5,6 +5,7 @@
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/version.h"
#include <cinttypes>
#ifdef USE_ESP32
@ -13,6 +14,7 @@
#if ESP_IDF_VERSION_MAJOR >= 4
#include <esp32/rom/rtc.h>
#include <esp_chip_info.h>
#else
#include <rom/rtc.h>
#endif
@ -20,8 +22,12 @@
#endif // USE_ESP32
#ifdef USE_ARDUINO
#ifdef USE_RP2040
#include <Arduino.h>
#else
#include <Esp.h>
#endif
#endif
namespace esphome {
namespace debug {
@ -33,6 +39,8 @@ static uint32_t get_free_heap() {
return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
#elif defined(USE_RP2040)
return rp2040.getFreeHeap();
#endif
}
@ -61,9 +69,9 @@ void DebugComponent::dump_config() {
device_info += ESPHOME_VERSION;
this->free_heap_ = get_free_heap();
ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_);
ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2040)
const char *flash_mode;
switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance)
case FM_QIO:
@ -272,6 +280,11 @@ void DebugComponent::dump_config() {
reset_reason = ESP.getResetReason().c_str();
#endif
#ifdef USE_RP2040
ESP_LOGD(TAG, "CPU Frequency: %u", rp2040.f_cpu());
device_info += "CPU Frequency: " + to_string(rp2040.f_cpu());
#endif // USE_RP2040
#ifdef USE_TEXT_SENSOR
if (this->device_info_ != nullptr) {
if (device_info.length() > 255)
@ -289,7 +302,7 @@ void DebugComponent::loop() {
uint32_t new_free_heap = get_free_heap();
if (new_free_heap < this->free_heap_ / 2) {
this->free_heap_ = new_free_heap;
ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_);
ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);
this->status_momentary_warning("heap", 1000);
}

View file

@ -0,0 +1,343 @@
#include "display.h"
#include <utility>
#include "esphome/core/log.h"
namespace esphome {
namespace display {
static const char *const TAG = "display";
const Color COLOR_OFF(0, 0, 0, 0);
const Color COLOR_ON(255, 255, 255, 255);
void Display::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
void Display::clear() { this->fill(COLOR_OFF); }
void Display::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
void HOT Display::line(int x1, int y1, int x2, int y2, Color color) {
const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
int32_t err = dx + dy;
while (true) {
this->draw_pixel_at(x1, y1, color);
if (x1 == x2 && y1 == y2)
break;
int32_t e2 = 2 * err;
if (e2 >= dy) {
err += dy;
x1 += sx;
}
if (e2 <= dx) {
err += dx;
y1 += sy;
}
}
}
void HOT Display::horizontal_line(int x, int y, int width, Color color) {
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
for (int i = x; i < x + width; i++)
this->draw_pixel_at(i, y, color);
}
void HOT Display::vertical_line(int x, int y, int height, Color color) {
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
for (int i = y; i < y + height; i++)
this->draw_pixel_at(x, i, color);
}
void Display::rectangle(int x1, int y1, int width, int height, Color color) {
this->horizontal_line(x1, y1, width, color);
this->horizontal_line(x1, y1 + height - 1, width, color);
this->vertical_line(x1, y1, height, color);
this->vertical_line(x1 + width - 1, y1, height, color);
}
void Display::filled_rectangle(int x1, int y1, int width, int height, Color color) {
// Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
for (int i = y1; i < y1 + height; i++) {
this->horizontal_line(x1, i, width, color);
}
}
void HOT Display::circle(int center_x, int center_xy, int radius, Color color) {
int dx = -radius;
int dy = 0;
int err = 2 - 2 * radius;
int e2;
do {
this->draw_pixel_at(center_x - dx, center_xy + dy, color);
this->draw_pixel_at(center_x + dx, center_xy + dy, color);
this->draw_pixel_at(center_x + dx, center_xy - dy, color);
this->draw_pixel_at(center_x - dx, center_xy - dy, color);
e2 = err;
if (e2 < dy) {
err += ++dy * 2 + 1;
if (-dx == dy && e2 <= dx) {
e2 = 0;
}
}
if (e2 > dx) {
err += ++dx * 2 + 1;
}
} while (dx <= 0);
}
void Display::filled_circle(int center_x, int center_y, int radius, Color color) {
int dx = -int32_t(radius);
int dy = 0;
int err = 2 - 2 * radius;
int e2;
do {
this->draw_pixel_at(center_x - dx, center_y + dy, color);
this->draw_pixel_at(center_x + dx, center_y + dy, color);
this->draw_pixel_at(center_x + dx, center_y - dy, color);
this->draw_pixel_at(center_x - dx, center_y - dy, color);
int hline_width = 2 * (-dx) + 1;
this->horizontal_line(center_x + dx, center_y + dy, hline_width, color);
this->horizontal_line(center_x + dx, center_y - dy, hline_width, color);
e2 = err;
if (e2 < dy) {
err += ++dy * 2 + 1;
if (-dx == dy && e2 <= dx) {
e2 = 0;
}
}
if (e2 > dx) {
err += ++dx * 2 + 1;
}
} while (dx <= 0);
}
void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) {
int x_start, y_start;
int width, height;
this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
font->print(x_start, y_start, this, color, text);
}
void Display::vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg) {
char buffer[256];
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
if (ret > 0)
this->print(x, y, font, color, align, buffer);
}
void Display::image(int x, int y, BaseImage *image, Color color_on, Color color_off) {
this->image(x, y, image, ImageAlign::TOP_LEFT, color_on, color_off);
}
void Display::image(int x, int y, BaseImage *image, ImageAlign align, Color color_on, Color color_off) {
auto x_align = ImageAlign(int(align) & (int(ImageAlign::HORIZONTAL_ALIGNMENT)));
auto y_align = ImageAlign(int(align) & (int(ImageAlign::VERTICAL_ALIGNMENT)));
switch (x_align) {
case ImageAlign::RIGHT:
x -= image->get_width();
break;
case ImageAlign::CENTER_HORIZONTAL:
x -= image->get_width() / 2;
break;
case ImageAlign::LEFT:
default:
break;
}
switch (y_align) {
case ImageAlign::BOTTOM:
y -= image->get_height();
break;
case ImageAlign::CENTER_VERTICAL:
y -= image->get_height() / 2;
break;
case ImageAlign::TOP:
default:
break;
}
image->draw(x, y, this, color_on, color_off);
}
#ifdef USE_GRAPH
void Display::graph(int x, int y, graph::Graph *graph, Color color_on) { graph->draw(this, x, y, color_on); }
void Display::legend(int x, int y, graph::Graph *graph, Color color_on) { graph->draw_legend(this, x, y, color_on); }
#endif // USE_GRAPH
#ifdef USE_QR_CODE
void Display::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, int scale) {
qr_code->draw(this, x, y, color_on, scale);
}
#endif // USE_QR_CODE
void Display::get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1,
int *width, int *height) {
int x_offset, baseline;
font->measure(text, width, &x_offset, &baseline, height);
auto x_align = TextAlign(int(align) & 0x18);
auto y_align = TextAlign(int(align) & 0x07);
switch (x_align) {
case TextAlign::RIGHT:
*x1 = x - *width;
break;
case TextAlign::CENTER_HORIZONTAL:
*x1 = x - (*width) / 2;
break;
case TextAlign::LEFT:
default:
// LEFT
*x1 = x;
break;
}
switch (y_align) {
case TextAlign::BOTTOM:
*y1 = y - *height;
break;
case TextAlign::BASELINE:
*y1 = y - baseline;
break;
case TextAlign::CENTER_VERTICAL:
*y1 = y - (*height) / 2;
break;
case TextAlign::TOP:
default:
*y1 = y;
break;
}
}
void Display::print(int x, int y, BaseFont *font, Color color, const char *text) {
this->print(x, y, font, color, TextAlign::TOP_LEFT, text);
}
void Display::print(int x, int y, BaseFont *font, TextAlign align, const char *text) {
this->print(x, y, font, COLOR_ON, align, text);
}
void Display::print(int x, int y, BaseFont *font, const char *text) {
this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
}
void Display::printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, color, align, format, arg);
va_end(arg);
}
void Display::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg);
va_end(arg);
}
void Display::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, COLOR_ON, align, format, arg);
va_end(arg);
}
void Display::printf(int x, int y, BaseFont *font, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg);
va_end(arg);
}
void Display::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
void Display::set_pages(std::vector<DisplayPage *> pages) {
for (auto *page : pages)
page->set_parent(this);
for (uint32_t i = 0; i < pages.size() - 1; i++) {
pages[i]->set_next(pages[i + 1]);
pages[i + 1]->set_prev(pages[i]);
}
pages[0]->set_prev(pages[pages.size() - 1]);
pages[pages.size() - 1]->set_next(pages[0]);
this->show_page(pages[0]);
}
void Display::show_page(DisplayPage *page) {
this->previous_page_ = this->page_;
this->page_ = page;
if (this->previous_page_ != this->page_) {
for (auto *t : on_page_change_triggers_)
t->process(this->previous_page_, this->page_);
}
}
void Display::show_next_page() { this->page_->show_next(); }
void Display::show_prev_page() { this->page_->show_prev(); }
void Display::do_update_() {
if (this->auto_clear_enabled_) {
this->clear();
}
if (this->page_ != nullptr) {
this->page_->get_writer()(*this);
} else if (this->writer_.has_value()) {
(*this->writer_)(*this);
}
// remove all not ended clipping regions
while (is_clipping()) {
end_clipping();
}
}
void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
this->trigger(from, to);
}
void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) {
char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0)
this->print(x, y, font, color, align, buffer);
}
void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) {
this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
}
void Display::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, align, format, time);
}
void Display::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time);
}
void Display::start_clipping(Rect rect) {
if (!this->clipping_rectangle_.empty()) {
Rect r = this->clipping_rectangle_.back();
rect.shrink(r);
}
this->clipping_rectangle_.push_back(rect);
}
void Display::end_clipping() {
if (this->clipping_rectangle_.empty()) {
ESP_LOGE(TAG, "clear: Clipping is not set.");
} else {
this->clipping_rectangle_.pop_back();
}
}
void Display::extend_clipping(Rect add_rect) {
if (this->clipping_rectangle_.empty()) {
ESP_LOGE(TAG, "add: Clipping is not set.");
} else {
this->clipping_rectangle_.back().extend(add_rect);
}
}
void Display::shrink_clipping(Rect add_rect) {
if (this->clipping_rectangle_.empty()) {
ESP_LOGE(TAG, "add: Clipping is not set.");
} else {
this->clipping_rectangle_.back().shrink(add_rect);
}
}
Rect Display::get_clipping() {
if (this->clipping_rectangle_.empty()) {
return Rect();
} else {
return this->clipping_rectangle_.back();
}
}
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
void DisplayPage::show() { this->parent_->show_page(this); }
void DisplayPage::show_next() { this->next_->show(); }
void DisplayPage::show_prev() { this->prev_->show(); }
void DisplayPage::set_parent(Display *parent) { this->parent_ = parent; }
void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; }
void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; }
const display_writer_t &DisplayPage::get_writer() const { return this->writer_; }
} // namespace display
} // namespace esphome

View file

@ -0,0 +1,575 @@
#pragma once
#include <cstdarg>
#include <vector>
#include "rect.h"
#include "esphome/core/color.h"
#include "esphome/core/automation.h"
#include "esphome/core/time.h"
#ifdef USE_GRAPH
#include "esphome/components/graph/graph.h"
#endif
#ifdef USE_QR_CODE
#include "esphome/components/qr_code/qr_code.h"
#endif
namespace esphome {
namespace display {
/** TextAlign is used to tell the display class how to position a piece of text. By default
* the coordinates you enter for the print*() functions take the upper left corner of the text
* as the "anchor" point. You can customize this behavior to, for example, make the coordinates
* refer to the *center* of the text.
*
* All text alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis
* these options are allowed:
*
* - LEFT (x-coordinate of anchor point is on left)
* - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the text)
* - RIGHT (x-coordinate of anchor point is on right)
*
* For the Y-Axis alignment these options are allowed:
*
* - TOP (y-coordinate of anchor is on the top of the text)
* - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the text)
* - BASELINE (y-coordinate of anchor is on the baseline of the text)
* - BOTTOM (y-coordinate of anchor is on the bottom of the text)
*
* These options are then combined to create combined TextAlignment options like:
* - TOP_LEFT (default)
* - CENTER (anchor point is in the middle of the text bounds)
* - ...
*/
enum class TextAlign {
TOP = 0x00,
CENTER_VERTICAL = 0x01,
BASELINE = 0x02,
BOTTOM = 0x04,
LEFT = 0x00,
CENTER_HORIZONTAL = 0x08,
RIGHT = 0x10,
TOP_LEFT = TOP | LEFT,
TOP_CENTER = TOP | CENTER_HORIZONTAL,
TOP_RIGHT = TOP | RIGHT,
CENTER_LEFT = CENTER_VERTICAL | LEFT,
CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL,
CENTER_RIGHT = CENTER_VERTICAL | RIGHT,
BASELINE_LEFT = BASELINE | LEFT,
BASELINE_CENTER = BASELINE | CENTER_HORIZONTAL,
BASELINE_RIGHT = BASELINE | RIGHT,
BOTTOM_LEFT = BOTTOM | LEFT,
BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL,
BOTTOM_RIGHT = BOTTOM | RIGHT,
};
/** ImageAlign is used to tell the display class how to position a image. By default
* the coordinates you enter for the image() functions take the upper left corner of the image
* as the "anchor" point. You can customize this behavior to, for example, make the coordinates
* refer to the *center* of the image.
*
* All image alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis
* these options are allowed:
*
* - LEFT (x-coordinate of anchor point is on left)
* - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the image)
* - RIGHT (x-coordinate of anchor point is on right)
*
* For the Y-Axis alignment these options are allowed:
*
* - TOP (y-coordinate of anchor is on the top of the image)
* - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the image)
* - BOTTOM (y-coordinate of anchor is on the bottom of the image)
*
* These options are then combined to create combined TextAlignment options like:
* - TOP_LEFT (default)
* - CENTER (anchor point is in the middle of the image bounds)
* - ...
*/
enum class ImageAlign {
TOP = 0x00,
CENTER_VERTICAL = 0x01,
BOTTOM = 0x02,
LEFT = 0x00,
CENTER_HORIZONTAL = 0x04,
RIGHT = 0x08,
TOP_LEFT = TOP | LEFT,
TOP_CENTER = TOP | CENTER_HORIZONTAL,
TOP_RIGHT = TOP | RIGHT,
CENTER_LEFT = CENTER_VERTICAL | LEFT,
CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL,
CENTER_RIGHT = CENTER_VERTICAL | RIGHT,
BOTTOM_LEFT = BOTTOM | LEFT,
BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL,
BOTTOM_RIGHT = BOTTOM | RIGHT,
HORIZONTAL_ALIGNMENT = LEFT | CENTER_HORIZONTAL | RIGHT,
VERTICAL_ALIGNMENT = TOP | CENTER_VERTICAL | BOTTOM
};
enum DisplayType {
DISPLAY_TYPE_BINARY = 1,
DISPLAY_TYPE_GRAYSCALE = 2,
DISPLAY_TYPE_COLOR = 3,
};
enum DisplayRotation {
DISPLAY_ROTATION_0_DEGREES = 0,
DISPLAY_ROTATION_90_DEGREES = 90,
DISPLAY_ROTATION_180_DEGREES = 180,
DISPLAY_ROTATION_270_DEGREES = 270,
};
class Display;
class DisplayBuffer;
class DisplayPage;
class DisplayOnPageChangeTrigger;
using display_writer_t = std::function<void(Display &)>;
using display_buffer_writer_t = std::function<void(DisplayBuffer &)>;
#define LOG_DISPLAY(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, prefix type); \
ESP_LOGCONFIG(TAG, "%s Rotations: %d °", prefix, (obj)->rotation_); \
ESP_LOGCONFIG(TAG, "%s Dimensions: %dpx x %dpx", prefix, (obj)->get_width(), (obj)->get_height()); \
}
/// Turn the pixel OFF.
extern const Color COLOR_OFF;
/// Turn the pixel ON.
extern const Color COLOR_ON;
class BaseImage {
public:
virtual void draw(int x, int y, Display *display, Color color_on, Color color_off) = 0;
virtual int get_width() const = 0;
virtual int get_height() const = 0;
};
class BaseFont {
public:
virtual void print(int x, int y, Display *display, Color color, const char *text) = 0;
virtual void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) = 0;
};
class Display {
public:
/// Fill the entire screen with the given color.
virtual void fill(Color color);
/// Clear the entire screen by filling it with OFF pixels.
void clear();
/// Get the width of the image in pixels with rotation applied.
virtual int get_width() = 0;
/// Get the height of the image in pixels with rotation applied.
virtual int get_height() = 0;
/// Set a single pixel at the specified coordinates to default color.
inline void draw_pixel_at(int x, int y) { this->draw_pixel_at(x, y, COLOR_ON); }
/// Set a single pixel at the specified coordinates to the given color.
virtual void draw_pixel_at(int x, int y, Color color) = 0;
/// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON);
/// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
void horizontal_line(int x, int y, int width, Color color = COLOR_ON);
/// Draw a vertical line from the point [x,y] to [x,y+width] with the given color.
void vertical_line(int x, int y, int height, Color color = COLOR_ON);
/// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at
/// [x1+width,y1+height].
void rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
/// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height].
void filled_rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
/// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color.
void circle(int center_x, int center_xy, int radius, Color color = COLOR_ON);
/// Fill a circle centered around [center_x,center_y] with the radius radius with the given color.
void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON);
/** Print `text` with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param align The alignment of the text.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text);
/** Print `text` with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, Color color, const char *text);
/** Print `text` with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param align The alignment of the text.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, TextAlign align, const char *text);
/** Print `text` with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, const char *text);
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param align The alignment of the text.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...)
__attribute__((format(printf, 7, 8)));
/** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7)));
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param align The alignment of the text.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...)
__attribute__((format(printf, 6, 7)));
/** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param align The alignment of the text.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time)
__attribute__((format(strftime, 7, 0)));
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time)
__attribute__((format(strftime, 6, 0)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param align The alignment of the text.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time)
__attribute__((format(strftime, 6, 0)));
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) __attribute__((format(strftime, 5, 0)));
/** Draw the `image` with the top-left corner at [x,y] to the screen.
*
* @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, BaseImage *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
/** Draw the `image` at [x,y] to the screen.
*
* @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 align The alignment of the image.
* @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, BaseImage *image, ImageAlign align, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
#ifdef USE_GRAPH
/** Draw the `graph` with the top-left corner at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param graph The graph id to draw
* @param color_on The color to replace in binary images for the on bits.
*/
void graph(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
/** Draw the `legend` for graph with the top-left corner at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param graph The graph id for which the legend applies to
* @param graph The graph id for which the legend applies to
* @param graph The graph id for which the legend applies to
* @param name_font The font used for the trace name
* @param value_font The font used for the trace value and units
* @param color_on The color of the border
*/
void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
#endif // USE_GRAPH
#ifdef USE_QR_CODE
/** Draw the `qr_code` with the top-left corner at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param qr_code The qr_code to draw
* @param color_on The color to replace in binary images for the on bits.
*/
void qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on = COLOR_ON, int scale = 1);
#endif
/** Get the text bounds of the given string.
*
* @param x The x coordinate to place the string at, can be 0 if only interested in dimensions.
* @param y The y coordinate to place the string at, can be 0 if only interested in dimensions.
* @param text The text to measure.
* @param font The font to measure the text bounds with.
* @param align The alignment of the text. Set to TextAlign::TOP_LEFT if only interested in dimensions.
* @param x1 A pointer to store the returned x coordinate of the upper left corner in.
* @param y1 A pointer to store the returned y coordinate of the upper left corner in.
* @param width A pointer to store the returned text width in.
* @param height A pointer to store the returned text height in.
*/
void get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1, int *width,
int *height);
/// Internal method to set the display writer lambda.
void set_writer(display_writer_t &&writer);
void set_writer(const display_buffer_writer_t &writer) {
// Temporary mapping to be removed once all lambdas are changed to use `display.DisplayRef`
this->set_writer([writer](Display &display) { return writer((display::DisplayBuffer &) display); });
}
void show_page(DisplayPage *page);
void show_next_page();
void show_prev_page();
void set_pages(std::vector<DisplayPage *> pages);
const DisplayPage *get_active_page() const { return this->page_; }
void add_on_page_change_trigger(DisplayOnPageChangeTrigger *t) { this->on_page_change_triggers_.push_back(t); }
/// Internal method to set the display rotation with.
void set_rotation(DisplayRotation rotation);
// Internal method to set display auto clearing.
void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; }
DisplayRotation get_rotation() const { return this->rotation_; }
/** Get the type of display that the buffer corresponds to. In case of dynamically configurable displays,
* returns the type the display is currently configured to.
*/
virtual DisplayType get_display_type() = 0;
/** Set the clipping rectangle for further drawing
*
* @param[in] rect: Pointer to Rect for clipping (or NULL for entire screen)
*
* return true if success, false if error
*/
void start_clipping(Rect rect);
void start_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
start_clipping(Rect(left, top, right - left, bottom - top));
};
/** Add a rectangular region to the invalidation region
* - This is usually called when an element has been modified
*
* @param[in] rect: Rectangle to add to the invalidation region
*/
void extend_clipping(Rect rect);
void extend_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
this->extend_clipping(Rect(left, top, right - left, bottom - top));
};
/** substract a rectangular region to the invalidation region
* - This is usually called when an element has been modified
*
* @param[in] rect: Rectangle to add to the invalidation region
*/
void shrink_clipping(Rect rect);
void shrink_clipping(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
this->shrink_clipping(Rect(left, top, right - left, bottom - top));
};
/** Reset the invalidation region
*/
void end_clipping();
/** Get the current the clipping rectangle
*
* return rect for active clipping region
*/
Rect get_clipping();
bool is_clipping() const { return !this->clipping_rectangle_.empty(); }
protected:
void vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg);
void do_update_();
DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
optional<display_writer_t> writer_{};
DisplayPage *page_{nullptr};
DisplayPage *previous_page_{nullptr};
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
bool auto_clear_enabled_{true};
std::vector<Rect> clipping_rectangle_;
};
class DisplayPage {
public:
DisplayPage(display_writer_t writer);
// Temporary mapping to be removed once all lambdas are changed to use `display.DisplayRef`
DisplayPage(const display_buffer_writer_t &writer)
: DisplayPage([writer](Display &display) { return writer((display::DisplayBuffer &) display); }) {}
void show();
void show_next();
void show_prev();
void set_parent(Display *parent);
void set_prev(DisplayPage *prev);
void set_next(DisplayPage *next);
const display_writer_t &get_writer() const;
protected:
Display *parent_;
display_writer_t writer_;
DisplayPage *prev_{nullptr};
DisplayPage *next_{nullptr};
};
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
public:
TEMPLATABLE_VALUE(DisplayPage *, page)
void play(Ts... x) override {
auto *page = this->page_.value(x...);
if (page != nullptr) {
page->show();
}
}
};
template<typename... Ts> class DisplayPageShowNextAction : public Action<Ts...> {
public:
DisplayPageShowNextAction(Display *buffer) : buffer_(buffer) {}
void play(Ts... x) override { this->buffer_->show_next_page(); }
Display *buffer_;
};
template<typename... Ts> class DisplayPageShowPrevAction : public Action<Ts...> {
public:
DisplayPageShowPrevAction(Display *buffer) : buffer_(buffer) {}
void play(Ts... x) override { this->buffer_->show_prev_page(); }
Display *buffer_;
};
template<typename... Ts> class DisplayIsDisplayingPageCondition : public Condition<Ts...> {
public:
DisplayIsDisplayingPageCondition(Display *parent) : parent_(parent) {}
void set_page(DisplayPage *page) { this->page_ = page; }
bool check(Ts... x) override { return this->parent_->get_active_page() == this->page_; }
protected:
Display *parent_;
DisplayPage *page_;
};
class DisplayOnPageChangeTrigger : public Trigger<DisplayPage *, DisplayPage *> {
public:
explicit DisplayOnPageChangeTrigger(Display *parent) { parent->add_on_page_change_trigger(this); }
void process(DisplayPage *from, DisplayPage *to);
void set_from(DisplayPage *p) { this->from_ = p; }
void set_to(DisplayPage *p) { this->to_ = p; }
protected:
DisplayPage *from_{nullptr};
DisplayPage *to_{nullptr};
};
} // namespace display
} // namespace esphome

View file

@ -1,10 +1,8 @@
#include "display_buffer.h"
#include <utility>
#include "esphome/core/application.h"
#include "esphome/core/color.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
@ -12,9 +10,6 @@ namespace display {
static const char *const TAG = "display";
const Color COLOR_OFF(0, 0, 0, 0);
const Color COLOR_ON(255, 255, 255, 255);
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
this->buffer_ = allocator.allocate(buffer_length);
@ -25,8 +20,6 @@ void DisplayBuffer::init_internal_(uint32_t buffer_length) {
this->clear();
}
void DisplayBuffer::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
void DisplayBuffer::clear() { this->fill(COLOR_OFF); }
int DisplayBuffer::get_width() {
switch (this->rotation_) {
case DISPLAY_ROTATION_90_DEGREES:
@ -38,6 +31,7 @@ int DisplayBuffer::get_width() {
return this->get_width_internal();
}
}
int DisplayBuffer::get_height() {
switch (this->rotation_) {
case DISPLAY_ROTATION_0_DEGREES:
@ -49,7 +43,7 @@ int DisplayBuffer::get_height() {
return this->get_width_internal();
}
}
void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
if (!this->get_clipping().inside(x, y))
return; // NOLINT
@ -73,333 +67,6 @@ void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
this->draw_absolute_pixel_internal(x, y, color);
App.feed_wdt();
}
void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, Color color) {
const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
int32_t err = dx + dy;
while (true) {
this->draw_pixel_at(x1, y1, color);
if (x1 == x2 && y1 == y2)
break;
int32_t e2 = 2 * err;
if (e2 >= dy) {
err += dy;
x1 += sx;
}
if (e2 <= dx) {
err += dx;
y1 += sy;
}
}
}
void HOT DisplayBuffer::horizontal_line(int x, int y, int width, Color color) {
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
for (int i = x; i < x + width; i++)
this->draw_pixel_at(i, y, color);
}
void HOT DisplayBuffer::vertical_line(int x, int y, int height, Color color) {
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
for (int i = y; i < y + height; i++)
this->draw_pixel_at(x, i, color);
}
void DisplayBuffer::rectangle(int x1, int y1, int width, int height, Color color) {
this->horizontal_line(x1, y1, width, color);
this->horizontal_line(x1, y1 + height - 1, width, color);
this->vertical_line(x1, y1, height, color);
this->vertical_line(x1 + width - 1, y1, height, color);
}
void DisplayBuffer::filled_rectangle(int x1, int y1, int width, int height, Color color) {
// Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
for (int i = y1; i < y1 + height; i++) {
this->horizontal_line(x1, i, width, color);
}
}
void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, Color color) {
int dx = -radius;
int dy = 0;
int err = 2 - 2 * radius;
int e2;
do {
this->draw_pixel_at(center_x - dx, center_xy + dy, color);
this->draw_pixel_at(center_x + dx, center_xy + dy, color);
this->draw_pixel_at(center_x + dx, center_xy - dy, color);
this->draw_pixel_at(center_x - dx, center_xy - dy, color);
e2 = err;
if (e2 < dy) {
err += ++dy * 2 + 1;
if (-dx == dy && e2 <= dx) {
e2 = 0;
}
}
if (e2 > dx) {
err += ++dx * 2 + 1;
}
} while (dx <= 0);
}
void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, Color color) {
int dx = -int32_t(radius);
int dy = 0;
int err = 2 - 2 * radius;
int e2;
do {
this->draw_pixel_at(center_x - dx, center_y + dy, color);
this->draw_pixel_at(center_x + dx, center_y + dy, color);
this->draw_pixel_at(center_x + dx, center_y - dy, color);
this->draw_pixel_at(center_x - dx, center_y - dy, color);
int hline_width = 2 * (-dx) + 1;
this->horizontal_line(center_x + dx, center_y + dy, hline_width, color);
this->horizontal_line(center_x + dx, center_y - dy, hline_width, color);
e2 = err;
if (e2 < dy) {
err += ++dy * 2 + 1;
if (-dx == dy && e2 <= dx) {
e2 = 0;
}
}
if (e2 > dx) {
err += ++dx * 2 + 1;
}
} while (dx <= 0);
}
void DisplayBuffer::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) {
int x_start, y_start;
int width, height;
this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
font->print(x_start, y_start, this, color, text);
}
void DisplayBuffer::vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format,
va_list arg) {
char buffer[256];
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
if (ret > 0)
this->print(x, y, font, color, align, buffer);
}
void DisplayBuffer::image(int x, int y, BaseImage *image, Color color_on, Color color_off) {
this->image(x, y, image, ImageAlign::TOP_LEFT, color_on, color_off);
}
void DisplayBuffer::image(int x, int y, BaseImage *image, ImageAlign align, Color color_on, Color color_off) {
auto x_align = ImageAlign(int(align) & (int(ImageAlign::HORIZONTAL_ALIGNMENT)));
auto y_align = ImageAlign(int(align) & (int(ImageAlign::VERTICAL_ALIGNMENT)));
switch (x_align) {
case ImageAlign::RIGHT:
x -= image->get_width();
break;
case ImageAlign::CENTER_HORIZONTAL:
x -= image->get_width() / 2;
break;
case ImageAlign::LEFT:
default:
break;
}
switch (y_align) {
case ImageAlign::BOTTOM:
y -= image->get_height();
break;
case ImageAlign::CENTER_VERTICAL:
y -= image->get_height() / 2;
break;
case ImageAlign::TOP:
default:
break;
}
image->draw(x, y, this, color_on, color_off);
}
#ifdef USE_GRAPH
void DisplayBuffer::graph(int x, int y, graph::Graph *graph, Color color_on) { graph->draw(this, x, y, color_on); }
void DisplayBuffer::legend(int x, int y, graph::Graph *graph, Color color_on) {
graph->draw_legend(this, x, y, color_on);
}
#endif // USE_GRAPH
#ifdef USE_QR_CODE
void DisplayBuffer::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, int scale) {
qr_code->draw(this, x, y, color_on, scale);
}
#endif // USE_QR_CODE
void DisplayBuffer::get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1,
int *width, int *height) {
int x_offset, baseline;
font->measure(text, width, &x_offset, &baseline, height);
auto x_align = TextAlign(int(align) & 0x18);
auto y_align = TextAlign(int(align) & 0x07);
switch (x_align) {
case TextAlign::RIGHT:
*x1 = x - *width;
break;
case TextAlign::CENTER_HORIZONTAL:
*x1 = x - (*width) / 2;
break;
case TextAlign::LEFT:
default:
// LEFT
*x1 = x;
break;
}
switch (y_align) {
case TextAlign::BOTTOM:
*y1 = y - *height;
break;
case TextAlign::BASELINE:
*y1 = y - baseline;
break;
case TextAlign::CENTER_VERTICAL:
*y1 = y - (*height) / 2;
break;
case TextAlign::TOP:
default:
*y1 = y;
break;
}
}
void DisplayBuffer::print(int x, int y, BaseFont *font, Color color, const char *text) {
this->print(x, y, font, color, TextAlign::TOP_LEFT, text);
}
void DisplayBuffer::print(int x, int y, BaseFont *font, TextAlign align, const char *text) {
this->print(x, y, font, COLOR_ON, align, text);
}
void DisplayBuffer::print(int x, int y, BaseFont *font, const char *text) {
this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
}
void DisplayBuffer::printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, color, align, format, arg);
va_end(arg);
}
void DisplayBuffer::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg);
va_end(arg);
}
void DisplayBuffer::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, COLOR_ON, align, format, arg);
va_end(arg);
}
void DisplayBuffer::printf(int x, int y, BaseFont *font, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg);
va_end(arg);
}
void DisplayBuffer::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
void DisplayBuffer::set_pages(std::vector<DisplayPage *> pages) {
for (auto *page : pages)
page->set_parent(this);
for (uint32_t i = 0; i < pages.size() - 1; i++) {
pages[i]->set_next(pages[i + 1]);
pages[i + 1]->set_prev(pages[i]);
}
pages[0]->set_prev(pages[pages.size() - 1]);
pages[pages.size() - 1]->set_next(pages[0]);
this->show_page(pages[0]);
}
void DisplayBuffer::show_page(DisplayPage *page) {
this->previous_page_ = this->page_;
this->page_ = page;
if (this->previous_page_ != this->page_) {
for (auto *t : on_page_change_triggers_)
t->process(this->previous_page_, this->page_);
}
}
void DisplayBuffer::show_next_page() { this->page_->show_next(); }
void DisplayBuffer::show_prev_page() { this->page_->show_prev(); }
void DisplayBuffer::do_update_() {
if (this->auto_clear_enabled_) {
this->clear();
}
if (this->page_ != nullptr) {
this->page_->get_writer()(*this);
} else if (this->writer_.has_value()) {
(*this->writer_)(*this);
}
// remove all not ended clipping regions
while (is_clipping()) {
end_clipping();
}
}
void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
this->trigger(from, to);
}
void DisplayBuffer::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format,
ESPTime time) {
char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0)
this->print(x, y, font, color, align, buffer);
}
void DisplayBuffer::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) {
this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
}
void DisplayBuffer::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, align, format, time);
}
void DisplayBuffer::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time);
}
void DisplayBuffer::start_clipping(Rect rect) {
if (!this->clipping_rectangle_.empty()) {
Rect r = this->clipping_rectangle_.back();
rect.shrink(r);
}
this->clipping_rectangle_.push_back(rect);
}
void DisplayBuffer::end_clipping() {
if (this->clipping_rectangle_.empty()) {
ESP_LOGE(TAG, "clear: Clipping is not set.");
} else {
this->clipping_rectangle_.pop_back();
}
}
void DisplayBuffer::extend_clipping(Rect add_rect) {
if (this->clipping_rectangle_.empty()) {
ESP_LOGE(TAG, "add: Clipping is not set.");
} else {
this->clipping_rectangle_.back().extend(add_rect);
}
}
void DisplayBuffer::shrink_clipping(Rect add_rect) {
if (this->clipping_rectangle_.empty()) {
ESP_LOGE(TAG, "add: Clipping is not set.");
} else {
this->clipping_rectangle_.back().shrink(add_rect);
}
}
Rect DisplayBuffer::get_clipping() {
if (this->clipping_rectangle_.empty()) {
return Rect();
} else {
return this->clipping_rectangle_.back();
}
}
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
void DisplayPage::show() { this->parent_->show_page(this); }
void DisplayPage::show_next() { this->next_->show(); }
void DisplayPage::show_prev() { this->prev_->show(); }
void DisplayPage::set_parent(DisplayBuffer *parent) { this->parent_ = parent; }
void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; }
void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; }
const display_writer_t &DisplayPage::get_writer() const { return this->writer_; }
} // namespace display
} // namespace esphome

View file

@ -2,568 +2,35 @@
#include <cstdarg>
#include <vector>
#include "rect.h"
#include "display.h"
#include "display_color_utils.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/time.h"
#ifdef USE_GRAPH
#include "esphome/components/graph/graph.h"
#endif
#ifdef USE_QR_CODE
#include "esphome/components/qr_code/qr_code.h"
#endif
namespace esphome {
namespace display {
/** TextAlign is used to tell the display class how to position a piece of text. By default
* the coordinates you enter for the print*() functions take the upper left corner of the text
* as the "anchor" point. You can customize this behavior to, for example, make the coordinates
* refer to the *center* of the text.
*
* All text alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis
* these options are allowed:
*
* - LEFT (x-coordinate of anchor point is on left)
* - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the text)
* - RIGHT (x-coordinate of anchor point is on right)
*
* For the Y-Axis alignment these options are allowed:
*
* - TOP (y-coordinate of anchor is on the top of the text)
* - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the text)
* - BASELINE (y-coordinate of anchor is on the baseline of the text)
* - BOTTOM (y-coordinate of anchor is on the bottom of the text)
*
* These options are then combined to create combined TextAlignment options like:
* - TOP_LEFT (default)
* - CENTER (anchor point is in the middle of the text bounds)
* - ...
*/
enum class TextAlign {
TOP = 0x00,
CENTER_VERTICAL = 0x01,
BASELINE = 0x02,
BOTTOM = 0x04,
LEFT = 0x00,
CENTER_HORIZONTAL = 0x08,
RIGHT = 0x10,
TOP_LEFT = TOP | LEFT,
TOP_CENTER = TOP | CENTER_HORIZONTAL,
TOP_RIGHT = TOP | RIGHT,
CENTER_LEFT = CENTER_VERTICAL | LEFT,
CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL,
CENTER_RIGHT = CENTER_VERTICAL | RIGHT,
BASELINE_LEFT = BASELINE | LEFT,
BASELINE_CENTER = BASELINE | CENTER_HORIZONTAL,
BASELINE_RIGHT = BASELINE | RIGHT,
BOTTOM_LEFT = BOTTOM | LEFT,
BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL,
BOTTOM_RIGHT = BOTTOM | RIGHT,
};
/** ImageAlign is used to tell the display class how to position a image. By default
* the coordinates you enter for the image() functions take the upper left corner of the image
* as the "anchor" point. You can customize this behavior to, for example, make the coordinates
* refer to the *center* of the image.
*
* All image alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis
* these options are allowed:
*
* - LEFT (x-coordinate of anchor point is on left)
* - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the image)
* - RIGHT (x-coordinate of anchor point is on right)
*
* For the Y-Axis alignment these options are allowed:
*
* - TOP (y-coordinate of anchor is on the top of the image)
* - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the image)
* - BOTTOM (y-coordinate of anchor is on the bottom of the image)
*
* These options are then combined to create combined TextAlignment options like:
* - TOP_LEFT (default)
* - CENTER (anchor point is in the middle of the image bounds)
* - ...
*/
enum class ImageAlign {
TOP = 0x00,
CENTER_VERTICAL = 0x01,
BOTTOM = 0x02,
LEFT = 0x00,
CENTER_HORIZONTAL = 0x04,
RIGHT = 0x08,
TOP_LEFT = TOP | LEFT,
TOP_CENTER = TOP | CENTER_HORIZONTAL,
TOP_RIGHT = TOP | RIGHT,
CENTER_LEFT = CENTER_VERTICAL | LEFT,
CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL,
CENTER_RIGHT = CENTER_VERTICAL | RIGHT,
BOTTOM_LEFT = BOTTOM | LEFT,
BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL,
BOTTOM_RIGHT = BOTTOM | RIGHT,
HORIZONTAL_ALIGNMENT = LEFT | CENTER_HORIZONTAL | RIGHT,
VERTICAL_ALIGNMENT = TOP | CENTER_VERTICAL | BOTTOM
};
enum DisplayType {
DISPLAY_TYPE_BINARY = 1,
DISPLAY_TYPE_GRAYSCALE = 2,
DISPLAY_TYPE_COLOR = 3,
};
enum DisplayRotation {
DISPLAY_ROTATION_0_DEGREES = 0,
DISPLAY_ROTATION_90_DEGREES = 90,
DISPLAY_ROTATION_180_DEGREES = 180,
DISPLAY_ROTATION_270_DEGREES = 270,
};
class DisplayBuffer;
class DisplayPage;
class DisplayOnPageChangeTrigger;
using display_writer_t = std::function<void(DisplayBuffer &)>;
#define LOG_DISPLAY(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, prefix type); \
ESP_LOGCONFIG(TAG, "%s Rotations: %d °", prefix, (obj)->rotation_); \
ESP_LOGCONFIG(TAG, "%s Dimensions: %dpx x %dpx", prefix, (obj)->get_width(), (obj)->get_height()); \
}
/// Turn the pixel OFF.
extern const Color COLOR_OFF;
/// Turn the pixel ON.
extern const Color COLOR_ON;
class BaseImage {
class DisplayBuffer : public Display {
public:
virtual void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) = 0;
virtual int get_width() const = 0;
virtual int get_height() const = 0;
};
class BaseFont {
public:
virtual void print(int x, int y, DisplayBuffer *display, Color color, const char *text) = 0;
virtual void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) = 0;
};
class DisplayBuffer {
public:
/// Fill the entire screen with the given color.
virtual void fill(Color color);
/// Clear the entire screen by filling it with OFF pixels.
void clear();
/// Get the width of the image in pixels with rotation applied.
int get_width();
int get_width() override;
/// Get the height of the image in pixels with rotation applied.
int get_height();
int get_height() override;
/// Set a single pixel at the specified coordinates to the given color.
void draw_pixel_at(int x, int y, Color color = COLOR_ON);
/// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON);
/// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
void horizontal_line(int x, int y, int width, Color color = COLOR_ON);
/// Draw a vertical line from the point [x,y] to [x,y+width] with the given color.
void vertical_line(int x, int y, int height, Color color = COLOR_ON);
/// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at
/// [x1+width,y1+height].
void rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
/// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height].
void filled_rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
/// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color.
void circle(int center_x, int center_xy, int radius, Color color = COLOR_ON);
/// Fill a circle centered around [center_x,center_y] with the radius radius with the given color.
void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON);
/** Print `text` with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param align The alignment of the text.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text);
/** Print `text` with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, Color color, const char *text);
/** Print `text` with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param align The alignment of the text.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, TextAlign align, const char *text);
/** Print `text` with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, const char *text);
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param align The alignment of the text.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...)
__attribute__((format(printf, 7, 8)));
/** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7)));
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param align The alignment of the text.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...)
__attribute__((format(printf, 6, 7)));
/** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param align The alignment of the text.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time)
__attribute__((format(strftime, 7, 0)));
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time)
__attribute__((format(strftime, 6, 0)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param align The alignment of the text.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time)
__attribute__((format(strftime, 6, 0)));
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) __attribute__((format(strftime, 5, 0)));
/** Draw the `image` with the top-left corner at [x,y] to the screen.
*
* @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, BaseImage *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
/** Draw the `image` at [x,y] to the screen.
*
* @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 align The alignment of the image.
* @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, BaseImage *image, ImageAlign align, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
#ifdef USE_GRAPH
/** Draw the `graph` with the top-left corner at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param graph The graph id to draw
* @param color_on The color to replace in binary images for the on bits.
*/
void graph(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
/** Draw the `legend` for graph with the top-left corner at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param graph The graph id for which the legend applies to
* @param graph The graph id for which the legend applies to
* @param graph The graph id for which the legend applies to
* @param name_font The font used for the trace name
* @param value_font The font used for the trace value and units
* @param color_on The color of the border
*/
void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
#endif // USE_GRAPH
#ifdef USE_QR_CODE
/** Draw the `qr_code` with the top-left corner at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param qr_code The qr_code to draw
* @param color_on The color to replace in binary images for the on bits.
*/
void qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on = COLOR_ON, int scale = 1);
#endif
/** Get the text bounds of the given string.
*
* @param x The x coordinate to place the string at, can be 0 if only interested in dimensions.
* @param y The y coordinate to place the string at, can be 0 if only interested in dimensions.
* @param text The text to measure.
* @param font The font to measure the text bounds with.
* @param align The alignment of the text. Set to TextAlign::TOP_LEFT if only interested in dimensions.
* @param x1 A pointer to store the returned x coordinate of the upper left corner in.
* @param y1 A pointer to store the returned y coordinate of the upper left corner in.
* @param width A pointer to store the returned text width in.
* @param height A pointer to store the returned text height in.
*/
void get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1, int *width,
int *height);
/// Internal method to set the display writer lambda.
void set_writer(display_writer_t &&writer);
void show_page(DisplayPage *page);
void show_next_page();
void show_prev_page();
void set_pages(std::vector<DisplayPage *> pages);
const DisplayPage *get_active_page() const { return this->page_; }
void add_on_page_change_trigger(DisplayOnPageChangeTrigger *t) { this->on_page_change_triggers_.push_back(t); }
/// Internal method to set the display rotation with.
void set_rotation(DisplayRotation rotation);
// Internal method to set display auto clearing.
void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; }
void draw_pixel_at(int x, int y, Color color) override;
virtual int get_height_internal() = 0;
virtual int get_width_internal() = 0;
DisplayRotation get_rotation() const { return this->rotation_; }
/** Get the type of display that the buffer corresponds to. In case of dynamically configurable displays,
* returns the type the display is currently configured to.
*/
virtual DisplayType get_display_type() = 0;
/** Set the clipping rectangle for further drawing
*
* @param[in] rect: Pointer to Rect for clipping (or NULL for entire screen)
*
* return true if success, false if error
*/
void start_clipping(Rect rect);
void start_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
start_clipping(Rect(left, top, right - left, bottom - top));
};
/** Add a rectangular region to the invalidation region
* - This is usually called when an element has been modified
*
* @param[in] rect: Rectangle to add to the invalidation region
*/
void extend_clipping(Rect rect);
void extend_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
this->extend_clipping(Rect(left, top, right - left, bottom - top));
};
/** substract a rectangular region to the invalidation region
* - This is usually called when an element has been modified
*
* @param[in] rect: Rectangle to add to the invalidation region
*/
void shrink_clipping(Rect rect);
void shrink_clipping(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
this->shrink_clipping(Rect(left, top, right - left, bottom - top));
};
/** Reset the invalidation region
*/
void end_clipping();
/** Get the current the clipping rectangle
*
* return rect for active clipping region
*/
Rect get_clipping();
bool is_clipping() const { return !this->clipping_rectangle_.empty(); }
protected:
void vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg);
virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0;
void init_internal_(uint32_t buffer_length);
void do_update_();
uint8_t *buffer_{nullptr};
DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
optional<display_writer_t> writer_{};
DisplayPage *page_{nullptr};
DisplayPage *previous_page_{nullptr};
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
bool auto_clear_enabled_{true};
std::vector<Rect> clipping_rectangle_;
};
class DisplayPage {
public:
DisplayPage(display_writer_t writer);
void show();
void show_next();
void show_prev();
void set_parent(DisplayBuffer *parent);
void set_prev(DisplayPage *prev);
void set_next(DisplayPage *next);
const display_writer_t &get_writer() const;
protected:
DisplayBuffer *parent_;
display_writer_t writer_;
DisplayPage *prev_{nullptr};
DisplayPage *next_{nullptr};
};
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
public:
TEMPLATABLE_VALUE(DisplayPage *, page)
void play(Ts... x) override {
auto *page = this->page_.value(x...);
if (page != nullptr) {
page->show();
}
}
};
template<typename... Ts> class DisplayPageShowNextAction : public Action<Ts...> {
public:
DisplayPageShowNextAction(DisplayBuffer *buffer) : buffer_(buffer) {}
void play(Ts... x) override { this->buffer_->show_next_page(); }
DisplayBuffer *buffer_;
};
template<typename... Ts> class DisplayPageShowPrevAction : public Action<Ts...> {
public:
DisplayPageShowPrevAction(DisplayBuffer *buffer) : buffer_(buffer) {}
void play(Ts... x) override { this->buffer_->show_prev_page(); }
DisplayBuffer *buffer_;
};
template<typename... Ts> class DisplayIsDisplayingPageCondition : public Condition<Ts...> {
public:
DisplayIsDisplayingPageCondition(DisplayBuffer *parent) : parent_(parent) {}
void set_page(DisplayPage *page) { this->page_ = page; }
bool check(Ts... x) override { return this->parent_->get_active_page() == this->page_; }
protected:
DisplayBuffer *parent_;
DisplayPage *page_;
};
class DisplayOnPageChangeTrigger : public Trigger<DisplayPage *, DisplayPage *> {
public:
explicit DisplayOnPageChangeTrigger(DisplayBuffer *parent) { parent->add_on_page_change_trigger(this); }
void process(DisplayPage *from, DisplayPage *to);
void set_from(DisplayPage *p) { this->from_ = p; }
void set_to(DisplayPage *p) { this->to_ = p; }
protected:
DisplayPage *from_{nullptr};
DisplayPage *to_{nullptr};
};
} // namespace display

View file

@ -19,6 +19,7 @@ CONF_CRC_CHECK = "crc_check"
CONF_DECRYPTION_KEY = "decryption_key"
CONF_DSMR_ID = "dsmr_id"
CONF_GAS_MBUS_ID = "gas_mbus_id"
CONF_WATER_MBUS_ID = "water_mbus_id"
CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length"
CONF_REQUEST_INTERVAL = "request_interval"
CONF_REQUEST_PIN = "request_pin"
@ -53,6 +54,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_DECRYPTION_KEY): _validate_key,
cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
cv.Optional(CONF_WATER_MBUS_ID, default=2): cv.int_,
cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_,
cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema,
cv.Optional(
@ -82,9 +84,10 @@ async def to_code(config):
cg.add(var.set_receive_timeout(config[CONF_RECEIVE_TIMEOUT].total_milliseconds))
cg.add_build_flag("-DDSMR_GAS_MBUS_ID=" + str(config[CONF_GAS_MBUS_ID]))
cg.add_build_flag("-DDSMR_WATER_MBUS_ID=" + str(config[CONF_WATER_MBUS_ID]))
# DSMR Parser
cg.add_library("glmnet/Dsmr", "0.5")
cg.add_library("glmnet/Dsmr", "0.7")
# Crypto
cg.add_library("rweather/Crypto", "0.4.0")

View file

@ -8,6 +8,7 @@ from esphome.const import (
DEVICE_CLASS_GAS,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_WATER,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
@ -236,6 +237,12 @@ CONFIG_SCHEMA = cv.Schema(
device_class=DEVICE_CLASS_GAS,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("water_delivered"): sensor.sensor_schema(
unit_of_measurement=UNIT_CUBIC_METER,
accuracy_decimals=3,
device_class=DEVICE_CLASS_WATER,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
}
).extend(cv.COMPONENT_SCHEMA)

View file

@ -547,6 +547,8 @@ def copy_files():
CORE.relative_build_path(f"components/{name}"),
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git", ".github"),
symlinks=True,
ignore_dangling_symlinks=True,
)
dir = os.path.dirname(__file__)

View file

@ -55,3 +55,4 @@ async def to_code(config):
if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)

View file

@ -19,7 +19,11 @@
#include <sys/cdefs.h>
#include "esp_log.h"
#include "esp_eth.h"
#if ESP_IDF_VERSION_MAJOR >= 5
#include "esp_eth_phy_802_3.h"
#else
#include "eth_phy_regs_struct.h"
#endif
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
@ -170,7 +174,11 @@ static esp_err_t jl1101_reset_hw(esp_eth_phy_t *phy) {
return ESP_OK;
}
#if ESP_IDF_VERSION_MAJOR >= 5
static esp_err_t jl1101_negotiate(esp_eth_phy_t *phy, eth_phy_autoneg_cmd_t cmd, bool *nego_state) {
#else
static esp_err_t jl1101_negotiate(esp_eth_phy_t *phy) {
#endif
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
esp_eth_mediator_t *eth = jl1101->eth;
/* in case any link status has changed, let's assume we're in link down status */
@ -285,7 +293,11 @@ static esp_err_t jl1101_init(esp_eth_phy_t *phy) {
esp_eth_mediator_t *eth = jl1101->eth;
// Detect PHY address
if (jl1101->addr == ESP_ETH_PHY_ADDR_AUTO) {
#if ESP_IDF_VERSION_MAJOR >= 5
PHY_CHECK(esp_eth_phy_802_3_detect_phy_addr(eth, &jl1101->addr) == ESP_OK, "Detect PHY address failed", err);
#else
PHY_CHECK(esp_eth_detect_phy_addr(eth, &jl1101->addr) == ESP_OK, "Detect PHY address failed", err);
#endif
}
/* Power on Ethernet PHY */
PHY_CHECK(jl1101_pwrctl(phy, true) == ESP_OK, "power control failed", err);
@ -324,7 +336,11 @@ esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config) {
jl1101->parent.init = jl1101_init;
jl1101->parent.deinit = jl1101_deinit;
jl1101->parent.set_mediator = jl1101_set_mediator;
#if ESP_IDF_VERSION_MAJOR >= 5
jl1101->parent.autonego_ctrl = jl1101_negotiate;
#else
jl1101->parent.negotiate = jl1101_negotiate;
#endif
jl1101->parent.get_link = jl1101_get_link;
jl1101->parent.pwrctl = jl1101_pwrctl;
jl1101->parent.get_addr = jl1101_get_addr;

View file

@ -41,18 +41,27 @@ void EthernetComponent::setup() {
this->eth_netif_ = esp_netif_new(&cfg);
// Init MAC and PHY configs to default
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
phy_config.phy_addr = this->phy_addr_;
phy_config.reset_gpio_num = this->power_pin_;
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
#if ESP_IDF_VERSION_MAJOR >= 5
eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_;
esp32_emac_config.smi_mdio_gpio_num = this->mdio_pin_;
esp32_emac_config.clock_config.rmii.clock_mode = this->clk_mode_;
esp32_emac_config.clock_config.rmii.clock_gpio = this->clk_gpio_;
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
#else
mac_config.smi_mdc_gpio_num = this->mdc_pin_;
mac_config.smi_mdio_gpio_num = this->mdio_pin_;
mac_config.clock_config.rmii.clock_mode = this->clk_mode_;
mac_config.clock_config.rmii.clock_gpio = this->clk_gpio_;
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
#endif
switch (this->type_) {
case ETHERNET_TYPE_LAN8720: {
@ -76,7 +85,11 @@ void EthernetComponent::setup() {
break;
}
case ETHERNET_TYPE_KSZ8081: {
#if ESP_IDF_VERSION_MAJOR >= 5
this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config);
#else
this->phy_ = esp_eth_phy_new_ksz8081(&phy_config);
#endif
break;
}
default: {
@ -221,13 +234,13 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base
return;
}
ESP_LOGV(TAG, "[Ethernet event] %s (num=%d)", event_name, event);
ESP_LOGV(TAG, "[Ethernet event] %s (num=%" PRId32 ")", event_name, event);
}
void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
void *event_data) {
global_eth_component->connected_ = true;
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP (num=%d)", event_id);
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP (num=%" PRId32 ")", event_id);
}
void EthernetComponent::start_connect_() {

View file

@ -7,7 +7,6 @@ import re
import requests
from esphome import core
from esphome.components import display
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.helpers import copy_file_if_changed
@ -29,9 +28,11 @@ DOMAIN = "font"
DEPENDENCIES = ["display"]
MULTI_CONF = True
Font = display.display_ns.class_("Font")
Glyph = display.display_ns.class_("Glyph")
GlyphData = display.display_ns.struct("GlyphData")
font_ns = cg.esphome_ns.namespace("font")
Font = font_ns.class_("Font")
Glyph = font_ns.class_("Glyph")
GlyphData = font_ns.struct("GlyphData")
def validate_glyphs(value):

View file

@ -2,13 +2,15 @@
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/color.h"
#include "esphome/components/display/display_buffer.h"
namespace esphome {
namespace display {
namespace font {
static const char *const TAG = "display";
static const char *const TAG = "font";
void Glyph::draw(int x_at, int y_start, DisplayBuffer *display, Color color) const {
void Glyph::draw(int x_at, int y_start, display::Display *display, Color color) const {
int scan_x1, scan_y1, scan_width, scan_height;
this->scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height);
@ -116,7 +118,7 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in
*x_offset = min_x;
*width = x - min_x;
}
void Font::print(int x_start, int y_start, DisplayBuffer *display, Color color, const char *text) {
void Font::print(int x_start, int y_start, display::Display *display, Color color, const char *text) {
int i = 0;
int x_at = x_start;
while (text[i] != '\0') {
@ -143,5 +145,5 @@ void Font::print(int x_start, int y_start, DisplayBuffer *display, Color color,
}
}
} // namespace display
} // namespace font
} // namespace esphome

View file

@ -1,12 +1,12 @@
#pragma once
#include "esphome/core/datatypes.h"
#include "display_buffer.h"
#include "esphome/core/color.h"
#include "esphome/components/display/display_buffer.h"
namespace esphome {
namespace display {
namespace font {
class DisplayBuffer;
class Font;
struct GlyphData {
@ -22,7 +22,7 @@ class Glyph {
public:
Glyph(const GlyphData *data) : glyph_data_(data) {}
void draw(int x, int y, DisplayBuffer *display, Color color) const;
void draw(int x, int y, display::Display *display, Color color) const;
const char *get_char() const;
@ -38,7 +38,7 @@ class Glyph {
const GlyphData *glyph_data_;
};
class Font : public BaseFont {
class Font : public display::BaseFont {
public:
/** Construct the font with the given glyphs.
*
@ -50,7 +50,7 @@ class Font : public BaseFont {
int match_next_glyph(const char *str, int *match_length);
void print(int x_start, int y_start, DisplayBuffer *display, Color color, const char *text) override;
void print(int x_start, int y_start, display::Display *display, Color color, const char *text) override;
void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override;
inline int get_baseline() { return this->baseline_; }
inline int get_height() { return this->height_; }
@ -63,5 +63,5 @@ class Font : public BaseFont {
int height_;
};
} // namespace display
} // namespace font
} // namespace esphome

View file

@ -1,6 +1,5 @@
#include "graph.h"
#include "esphome/components/display/display_buffer.h"
#include "esphome/components/display/font.h"
#include "esphome/components/display/display.h"
#include "esphome/core/color.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
@ -57,7 +56,7 @@ void GraphTrace::init(Graph *g) {
this->data_.set_update_time_ms(g->get_duration() * 1000 / g->get_width());
}
void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color) {
void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color color) {
/// Plot border
if (this->border_) {
buff->horizontal_line(x_offset, y_offset, this->width_, color);
@ -304,7 +303,7 @@ void GraphLegend::init(Graph *g) {
}
}
void Graph::draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color) {
void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color) {
if (!legend_)
return;

View file

@ -8,10 +8,10 @@
namespace esphome {
// forward declare DisplayBuffer
// forward declare Display
namespace display {
class DisplayBuffer;
class Font;
class Display;
class BaseFont;
} // namespace display
namespace graph {
@ -45,8 +45,8 @@ enum ValuePositionType {
class GraphLegend {
public:
void init(Graph *g);
void set_name_font(display::Font *font) { this->font_label_ = font; }
void set_value_font(display::Font *font) { this->font_value_ = font; }
void set_name_font(display::BaseFont *font) { this->font_label_ = font; }
void set_value_font(display::BaseFont *font) { this->font_value_ = font; }
void set_width(uint32_t width) { this->width_ = width; }
void set_height(uint32_t height) { this->height_ = height; }
void set_border(bool val) { this->border_ = val; }
@ -63,8 +63,8 @@ class GraphLegend {
ValuePositionType values_{VALUE_POSITION_TYPE_AUTO};
bool units_{true};
DirectionType direction_{DIRECTION_TYPE_AUTO};
display::Font *font_label_{nullptr};
display::Font *font_value_{nullptr};
display::BaseFont *font_label_{nullptr};
display::BaseFont *font_value_{nullptr};
// Calculated values
Graph *parent_{nullptr};
// (x0) (xs,ys) (xs,ys)
@ -133,8 +133,8 @@ class GraphTrace {
class Graph : public Component {
public:
void draw(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color);
void draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color);
void draw(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color);
void draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color);
void setup() override;
float get_setup_priority() const override { return setup_priority::PROCESSOR; }

View file

@ -14,6 +14,7 @@ namespace i2c {
static const char *const TAG = "i2c.idf";
void IDFI2CBus::setup() {
ESP_LOGCONFIG(TAG, "Setting up I2C bus...");
static i2c_port_t next_port = 0;
port_ = next_port++;

View file

@ -13,6 +13,7 @@ from esphome.const import (
CONF_PAGES,
CONF_RESET_PIN,
CONF_DIMENSIONS,
CONF_DATA_RATE,
)
DEPENDENCIES = ["spi"]
@ -43,6 +44,7 @@ MODELS = {
"ILI9481": ili9XXX_ns.class_("ILI9XXXILI9481", ili9XXXSPI),
"ILI9486": ili9XXX_ns.class_("ILI9XXXILI9486", ili9XXXSPI),
"ILI9488": ili9XXX_ns.class_("ILI9XXXILI9488", ili9XXXSPI),
"ILI9488_A": ili9XXX_ns.class_("ILI9XXXILI9488A", ili9XXXSPI),
"ST7796": ili9XXX_ns.class_("ILI9XXXST7796", ili9XXXSPI),
"S3BOX": ili9XXX_ns.class_("ILI9XXXS3Box", ili9XXXSPI),
"S3BOX_LITE": ili9XXX_ns.class_("ILI9XXXS3BoxLite", ili9XXXSPI),
@ -97,6 +99,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list(
cv.file_
),
cv.Optional(CONF_DATA_RATE, default="40MHz"): spi.SPI_DATA_RATE_SCHEMA,
}
)
.extend(cv.polling_component_schema("1s"))
@ -175,3 +178,6 @@ async def to_code(config):
if rhs is not None:
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
cg.add(var.set_palette(prog_arr))
spi_data_rate = str(spi.SPI_DATA_RATE_OPTIONS[config[CONF_DATA_RATE]])
cg.add_define("ILI9XXXDisplay_DATA_RATE", cg.RawExpression(spi_data_rate))

View file

@ -152,12 +152,10 @@ void ILI9XXXDisplay::update() {
this->need_update_ = true;
return;
}
this->prossing_update_ = true;
do {
this->prossing_update_ = true;
this->need_update_ = false;
if (!this->need_update_) {
this->do_update_();
}
this->do_update_();
} while (this->need_update_);
this->prossing_update_ = false;
this->display_();
@ -411,6 +409,17 @@ void ILI9XXXILI9488::initialize() {
this->is_18bitdisplay_ = true;
}
// 40_TFT display
void ILI9XXXILI9488A::initialize() {
this->init_lcd_(INITCMD_ILI9488_A);
if (this->width_ == 0) {
this->width_ = 480;
}
if (this->height_ == 0) {
this->height_ = 320;
}
this->is_18bitdisplay_ = true;
}
// 40_TFT display
void ILI9XXXST7796::initialize() {
this->init_lcd_(INITCMD_ST7796);
if (this->width_ == 0) {

View file

@ -15,10 +15,14 @@ enum ILI9XXXColorMode {
BITS_16 = 0x10,
};
#ifndef ILI9XXXDisplay_DATA_RATE
#define ILI9XXXDisplay_DATA_RATE spi::DATA_RATE_40MHZ
#endif // ILI9XXXDisplay_DATA_RATE
class ILI9XXXDisplay : public PollingComponent,
public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_40MHZ> {
spi::CLOCK_PHASE_LEADING, ILI9XXXDisplay_DATA_RATE> {
public:
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
float get_setup_priority() const override;
@ -128,6 +132,12 @@ class ILI9XXXILI9488 : public ILI9XXXDisplay {
void initialize() override;
};
//----------- ILI9XXX_35_TFT origin colors rotated display --------------
class ILI9XXXILI9488A : public ILI9XXXDisplay {
protected:
void initialize() override;
};
//----------- ILI9XXX_35_TFT rotated display --------------
class ILI9XXXST7796 : public ILI9XXXDisplay {
protected:

View file

@ -139,6 +139,40 @@ static const uint8_t PROGMEM INITCMD_ILI9488[] = {
// 5 frames
//ILI9XXX_ETMOD, 1, 0xC6, //
ILI9XXX_SLPOUT, 0x80, // Exit sleep mode
//ILI9XXX_INVON , 0,
ILI9XXX_DISPON, 0x80, // Set display on
0x00 // end
};
static const uint8_t PROGMEM INITCMD_ILI9488_A[] = {
ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F,
ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F,
ILI9XXX_PWCTR1, 2, 0x17, 0x15, // VRH1 VRH2
ILI9XXX_PWCTR2, 1, 0x41, // VGH, VGL
ILI9XXX_VMCTR1, 3, 0x00, 0x12, 0x80, // nVM VCM_REG VCM_REG_EN
ILI9XXX_IFMODE, 1, 0x00,
ILI9XXX_FRMCTR1, 1, 0xA0, // Frame rate = 60Hz
ILI9XXX_INVCTR, 1, 0x02, // Display Inversion Control = 2dot
ILI9XXX_DFUNCTR, 2, 0x02, 0x02, // Nomal scan
0xE9, 1, 0x00, // Set Image Functio. Disable 24 bit data
ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82, // Adjust Control 3
ILI9XXX_MADCTL, 1, 0x28,
//ILI9XXX_PIXFMT, 1, 0x55, // Interface Pixel Format = 16bit
ILI9XXX_PIXFMT, 1, 0x66, //ILI9488 only supports 18-bit pixel format in 4/3 wire SPI mode
// 5 frames
//ILI9XXX_ETMOD, 1, 0xC6, //
@ -218,12 +252,12 @@ static const uint8_t PROGMEM INITCMD_S3BOXLITE[] = {
ILI9XXX_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control
0xF2, 1, 0x00, // 3Gamma Function Disable
ILI9XXX_GAMMASET , 1, 0x01, // Gamma curve selected
ILI9XXX_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03,
0x0E, 0x09, 0x00,
ILI9XXX_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C,
0x31, 0x36, 0x0F,
ILI9XXX_GMCTRP1 , 14, 0xF0, 0x09, 0x0B, 0x06, 0x04, 0x15, // Set Gamma
0x2F, 0x54, 0x42, 0x3C, 0x17, 0x14,
0x18, 0x1B,
ILI9XXX_GMCTRN1 , 14, 0xE0, 0x09, 0x0B, 0x06, 0x04, 0x03, // Set Gamma
0x2B, 0x43, 0x42, 0x3B, 0x16, 0x14,
0x17, 0x1B,
ILI9XXX_SLPOUT , 0x80, // Exit Sleep
ILI9XXX_DISPON , 0x80, // Display on
0x00 // End of list

View file

@ -6,7 +6,7 @@ import re
import requests
from esphome import core
from esphome.components import display, font
from esphome.components import font
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import (
@ -28,7 +28,9 @@ DOMAIN = "image"
DEPENDENCIES = ["display"]
MULTI_CONF = True
ImageType = display.display_ns.enum("ImageType")
image_ns = cg.esphome_ns.namespace("image")
ImageType = image_ns.enum("ImageType")
IMAGE_TYPE = {
"BINARY": ImageType.IMAGE_TYPE_BINARY,
"TRANSPARENT_BINARY": ImageType.IMAGE_TYPE_BINARY,
@ -46,7 +48,7 @@ MDI_DOWNLOAD_TIMEOUT = 30 # seconds
SOURCE_LOCAL = "local"
SOURCE_MDI = "mdi"
Image_ = display.display_ns.class_("Image")
Image_ = image_ns.class_("Image")
def _compute_local_icon_path(value) -> Path:

View file

@ -3,9 +3,9 @@
#include "esphome/core/hal.h"
namespace esphome {
namespace display {
namespace image {
void Image::draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) {
void Image::draw(int x, int y, display::Display *display, Color color_on, Color color_off) {
switch (type_) {
case IMAGE_TYPE_BINARY: {
for (int img_x = 0; img_x < width_; img_x++) {
@ -130,5 +130,5 @@ ImageType Image::get_type() const { return this->type_; }
Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
: width_(width), height_(height), type_(type), data_start_(data_start) {}
} // namespace display
} // namespace image
} // namespace esphome

View file

@ -1,9 +1,9 @@
#pragma once
#include "esphome/core/color.h"
#include "display_buffer.h"
#include "esphome/components/display/display_buffer.h"
namespace esphome {
namespace display {
namespace image {
enum ImageType {
IMAGE_TYPE_BINARY = 0,
@ -31,15 +31,15 @@ inline int image_type_to_bpp(ImageType type) {
inline int image_type_to_width_stride(int width, ImageType type) { return (width * image_type_to_bpp(type) + 7u) / 8u; }
class Image : public BaseImage {
class Image : public display::BaseImage {
public:
Image(const uint8_t *data_start, int width, int height, ImageType type);
Color get_pixel(int x, int y, Color color_on = COLOR_ON, Color color_off = COLOR_OFF) const;
Color get_pixel(int x, int y, Color color_on = display::COLOR_ON, Color color_off = display::COLOR_OFF) const;
int get_width() const override;
int get_height() const override;
ImageType get_type() const;
void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) override;
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override;
void set_transparency(bool transparent) { transparent_ = transparent; }
bool has_transparency() const { return transparent_; }
@ -58,5 +58,5 @@ class Image : public BaseImage {
bool transparent_;
};
} // namespace display
} // namespace image
} // namespace esphome

View file

@ -57,6 +57,10 @@ void MDNSComponent::compile_records_() {
service.txt_records.push_back({"network", "ethernet"});
#endif
#ifdef USE_API_NOISE
service.txt_records.push_back({"api_encryption", "Noise_NNpsk0_25519_ChaChaPoly_SHA256"});
#endif
#ifdef ESPHOME_PROJECT_NAME
service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME});
service.txt_records.push_back({"project_version", ESPHOME_PROJECT_VERSION});

View file

@ -54,16 +54,16 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
const auto &manu_datas = device.get_manufacturer_datas();
if (manu_datas.size() != 1) {
ESP_LOGE(TAG, "%s: Unexpected manu_datas size (%d)", device.address_str().c_str(), manu_datas.size());
ESP_LOGE(TAG, "[%s] Unexpected manu_datas size (%d)", device.address_str().c_str(), manu_datas.size());
return false;
}
const auto &manu_data = manu_datas[0];
ESP_LOGVV(TAG, "%s: Manufacturer data: %s", device.address_str().c_str(), format_hex_pretty(manu_data.data).c_str());
ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", device.address_str().c_str(), format_hex_pretty(manu_data.data).c_str());
if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) {
ESP_LOGE(TAG, "%s: Unexpected manu_data size (%d)", device.address_str().c_str(), manu_data.data.size());
ESP_LOGE(TAG, "[%s] Unexpected manu_data size (%d)", device.address_str().c_str(), manu_data.data.size());
return false;
}
@ -72,20 +72,20 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
const u_int8_t hardware_id = mopeka_data->data_1 & 0xCF;
if (static_cast<SensorType>(hardware_id) != STANDARD && static_cast<SensorType>(hardware_id) != XL) {
ESP_LOGE(TAG, "%s: Unsupported Sensor Type (0x%X)", device.address_str().c_str(), hardware_id);
ESP_LOGE(TAG, "[%s] Unsupported Sensor Type (0x%X)", device.address_str().c_str(), hardware_id);
return false;
}
ESP_LOGVV(TAG, "%s: Sensor slow update rate: %d", device.address_str().c_str(), mopeka_data->slow_update_rate);
ESP_LOGVV(TAG, "%s: Sensor sync pressed: %d", device.address_str().c_str(), mopeka_data->sync_pressed);
ESP_LOGVV(TAG, "[%s] Sensor slow update rate: %d", device.address_str().c_str(), mopeka_data->slow_update_rate);
ESP_LOGVV(TAG, "[%s] Sensor sync pressed: %d", device.address_str().c_str(), mopeka_data->sync_pressed);
for (u_int8_t i = 0; i < 3; i++) {
ESP_LOGVV(TAG, "%s: %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 1,
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 1,
mopeka_data->val[i].value_0, mopeka_data->val[i].time_0);
ESP_LOGVV(TAG, "%s: %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 2,
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 2,
mopeka_data->val[i].value_1, mopeka_data->val[i].time_1);
ESP_LOGVV(TAG, "%s: %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 3,
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 3,
mopeka_data->val[i].value_2, mopeka_data->val[i].time_2);
ESP_LOGVV(TAG, "%s: %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 4,
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 4,
mopeka_data->val[i].value_3, mopeka_data->val[i].time_3);
}
@ -146,19 +146,19 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
// This value is better than a previous one.
best_value = measurements_value[i];
best_time = measurement_time;
// Reset measurement_time or next values.
measurement_time = 0;
}
// Reset measurement_time or next values.
measurement_time = 0;
}
}
}
ESP_LOGV(TAG, "%s: Found %u values with best data %u time %u.", device.address_str().c_str(),
ESP_LOGV(TAG, "[%s] Found %u values with best data %u time %u.", device.address_str().c_str(),
number_of_usable_values, best_value, best_time);
if (number_of_usable_values < 2 || best_value < 2 || best_time < 2) {
if (number_of_usable_values < 1 || best_value < 2 || best_time < 2) {
// At least two measurement values must be present.
ESP_LOGW(TAG, "%s: Poor read quality. Setting distance to 0.", device.address_str().c_str());
ESP_LOGW(TAG, "[%s] Poor read quality. Setting distance to 0.", device.address_str().c_str());
if (this->distance_ != nullptr) {
this->distance_->publish_state(0);
}
@ -167,7 +167,7 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
}
} else {
float lpg_speed_of_sound = this->get_lpg_speed_of_sound_(temp_in_c);
ESP_LOGV(TAG, "%s: Speed of sound in current fluid %f m/s", device.address_str().c_str(), lpg_speed_of_sound);
ESP_LOGV(TAG, "[%s] Speed of sound in current fluid %f m/s", device.address_str().c_str(), lpg_speed_of_sound);
uint32_t distance_value = lpg_speed_of_sound * best_time / 100.0f;

View file

@ -1,5 +1,5 @@
#include "qr_code.h"
#include "esphome/components/display/display_buffer.h"
#include "esphome/components/display/display.h"
#include "esphome/core/color.h"
#include "esphome/core/log.h"
@ -33,7 +33,7 @@ void QrCode::generate_qr_code() {
}
}
void QrCode::draw(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color, int scale) {
void QrCode::draw(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color, int scale) {
ESP_LOGV(TAG, "Drawing QR code at (%d, %d)", x_offset, y_offset);
if (this->needs_update_) {

View file

@ -9,13 +9,13 @@
namespace esphome {
// forward declare DisplayBuffer
namespace display {
class DisplayBuffer;
class Display;
} // namespace display
namespace qr_code {
class QrCode : public Component {
public:
void draw(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color, int scale);
void draw(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color, int scale);
void dump_config() override;

View file

@ -42,13 +42,18 @@ void SCD30Component::setup() {
ESP_LOGD(TAG, "SCD30 Firmware v%0d.%02d", (uint16_t(raw_firmware_version[0]) >> 8),
uint16_t(raw_firmware_version[0] & 0xFF));
if (this->temperature_offset_ != 0) {
if (!this->write_command(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t) (temperature_offset_ * 100.0))) {
ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
return;
}
uint16_t temp_offset;
if (this->temperature_offset_ > 0) {
temp_offset = (this->temperature_offset_ * 100);
} else {
temp_offset = 0;
}
if (!this->write_command(SCD30_CMD_TEMPERATURE_OFFSET, temp_offset)) {
ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
return;
}
#ifdef USE_ESP32
// According ESP32 clock stretching is typically 30ms and up to 150ms "due to

View file

@ -68,7 +68,10 @@ CONFIG_SCHEMA = (
cv.int_range(min=0, max=0xFFFF, max_included=False),
),
cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION, default=0): cv.pressure,
cv.Optional(CONF_TEMPERATURE_OFFSET): cv.temperature,
cv.Optional(CONF_TEMPERATURE_OFFSET): cv.All(
cv.temperature,
cv.float_range(min=0, max=655.35),
),
cv.Optional(CONF_UPDATE_INTERVAL, default="60s"): cv.All(
cv.positive_time_period_seconds,
cv.Range(

View file

@ -33,6 +33,7 @@ SCRIPT_MODES = {
PARAMETER_TYPE_TRANSLATIONS = {
"string": "std::string",
"boolean": "bool",
}
@ -149,6 +150,16 @@ async def to_code(config):
),
)
async def script_execute_action_to_code(config, action_id, template_arg, args):
def convert(type: str):
def converter(value):
if type == "std::string":
return value
if type == "bool":
return cg.RawExpression(str(value).lower())
return cg.RawExpression(str(value))
return converter
async def get_ordered_args(config, script_params):
config_args = config.copy()
config_args.pop(CONF_ID)
@ -160,7 +171,9 @@ async def script_execute_action_to_code(config, action_id, template_arg, args):
raise EsphomeError(
f"Missing parameter: '{name}' in script.execute {config[CONF_ID]}"
)
arg = await cg.templatable(config_args[name], args, type)
arg = await cg.templatable(
config_args[name], args, type, convert(str(type))
)
script_args.append(arg)
return script_args

View file

@ -16,6 +16,22 @@ CODEOWNERS = ["@esphome/core"]
spi_ns = cg.esphome_ns.namespace("spi")
SPIComponent = spi_ns.class_("SPIComponent", cg.Component)
SPIDevice = spi_ns.class_("SPIDevice")
SPIDataRate = spi_ns.enum("SPIDataRate")
SPI_DATA_RATE_OPTIONS = {
80e6: SPIDataRate.DATA_RATE_80MHZ,
40e6: SPIDataRate.DATA_RATE_40MHZ,
20e6: SPIDataRate.DATA_RATE_20MHZ,
10e6: SPIDataRate.DATA_RATE_10MHZ,
5e6: SPIDataRate.DATA_RATE_5MHZ,
2e6: SPIDataRate.DATA_RATE_2MHZ,
1e6: SPIDataRate.DATA_RATE_1MHZ,
2e5: SPIDataRate.DATA_RATE_200KHZ,
75e3: SPIDataRate.DATA_RATE_75KHZ,
1e3: SPIDataRate.DATA_RATE_1KHZ,
}
SPI_DATA_RATE_SCHEMA = cv.All(cv.frequency, cv.enum(SPI_DATA_RATE_OPTIONS))
MULTI_CONF = True
CONF_FORCE_SW = "force_sw"

View file

@ -67,6 +67,7 @@ enum SPIDataRate : uint32_t {
DATA_RATE_10MHZ = 10000000,
DATA_RATE_20MHZ = 20000000,
DATA_RATE_40MHZ = 40000000,
DATA_RATE_80MHZ = 80000000,
};
class SPIComponent : public Component {

View file

@ -6,11 +6,21 @@ namespace template_ {
static const char *const TAG = "template.binary_sensor";
void TemplateBinarySensor::loop() {
if (!this->f_.has_value())
void TemplateBinarySensor::setup() {
if (!this->publish_initial_state_)
return;
auto s = (*this->f_)();
if (this->f_ != nullptr) {
this->publish_initial_state(*this->f_());
} else {
this->publish_initial_state(false);
}
}
void TemplateBinarySensor::loop() {
if (this->f_ == nullptr)
return;
auto s = this->f_();
if (s.has_value()) {
this->publish_state(*s);
}

View file

@ -10,13 +10,14 @@ class TemplateBinarySensor : public Component, public binary_sensor::BinarySenso
public:
void set_template(std::function<optional<bool>()> &&f) { this->f_ = f; }
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
protected:
optional<std::function<optional<bool>()>> f_{};
std::function<optional<bool>()> f_{nullptr};
};
} // namespace template_

View file

@ -7,6 +7,17 @@ namespace touchscreen {
static const char *const TAG = "touchscreen";
void Touchscreen::set_display(display::Display *display) {
this->display_ = display;
this->display_width_ = display->get_width();
this->display_height_ = display->get_height();
this->rotation_ = static_cast<TouchRotation>(display->get_rotation());
if (this->rotation_ == ROTATE_90_DEGREES || this->rotation_ == ROTATE_270_DEGREES) {
std::swap(this->display_width_, this->display_height_);
}
}
void Touchscreen::send_touch_(TouchPoint tp) {
ESP_LOGV(TAG, "Touch (x=%d, y=%d)", tp.x, tp.y);
this->touch_trigger_.trigger(tp);

View file

@ -31,13 +31,8 @@ enum TouchRotation {
class Touchscreen {
public:
void set_display(display::DisplayBuffer *display) {
this->display_ = display;
this->display_width_ = display->get_width_internal();
this->display_height_ = display->get_height_internal();
this->rotation_ = static_cast<TouchRotation>(display->get_rotation());
}
display::DisplayBuffer *get_display() const { return this->display_; }
void set_display(display::Display *display);
display::Display *get_display() const { return this->display_; }
Trigger<TouchPoint> *get_touch_trigger() { return &this->touch_trigger_; }
@ -49,7 +44,7 @@ class Touchscreen {
uint16_t display_width_;
uint16_t display_height_;
display::DisplayBuffer *display_;
display::Display *display_;
TouchRotation rotation_;
Trigger<TouchPoint> touch_trigger_;
std::vector<TouchListener *> touch_listeners_;

View file

@ -63,6 +63,7 @@ WaveshareEPaper7P5InHDB = waveshare_epaper_ns.class_(
WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_(
"WaveshareEPaper2P13InDKE", WaveshareEPaper
)
GDEW0154M09 = waveshare_epaper_ns.class_("GDEW0154M09", WaveshareEPaper)
WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel")
WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeBModel")
@ -91,6 +92,7 @@ MODELS = {
"7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt),
"7.50in-hd-b": ("b", WaveshareEPaper7P5InHDB),
"2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE),
"1.54in-m5coreink-m09": ("c", GDEW0154M09),
}

View file

@ -763,6 +763,146 @@ void GDEY029T94::dump_config() {
LOG_UPDATE_INTERVAL(this);
}
// ========================================================
// Good Display 1.54in black/white/grey GDEW0154M09
// As used in M5Stack Core Ink
// Datasheet:
// - https://v4.cecdn.yun300.cn/100001_1909185148/GDEW0154M09-200709.pdf
// - https://github.com/m5stack/M5Core-Ink
// Reference code from GoodDisplay:
// - https://github.com/GoodDisplay/E-paper-Display-Library-of-GoodDisplay/
// -> /Monochrome_E-paper-Display/1.54inch_JD79653_GDEW0154M09_200x200/ESP32-Arduino%20IDE/GDEW0154M09_Arduino.ino
// M5Stack Core Ink spec:
// - https://docs.m5stack.com/en/core/coreink
// ========================================================
void GDEW0154M09::initialize() {
this->init_internal_();
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
this->lastbuff_ = allocator.allocate(this->get_buffer_length_());
if (this->lastbuff_ != nullptr) {
memset(this->lastbuff_, 0xff, sizeof(uint8_t) * this->get_buffer_length_());
}
this->clear_();
}
void GDEW0154M09::reset_() {
// RST is inverse from other einks in this project
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(false);
delay(10);
this->reset_pin_->digital_write(true);
delay(10);
}
}
void GDEW0154M09::init_internal_() {
this->reset_();
// clang-format off
// 200x200 resolution: 11
// LUT from OTP: 0
// B/W mode (doesn't work): 1
// scan-up: 1
// shift-right: 1
// booster ON: 1
// no soft reset: 1
const uint8_t panel_setting_1 = 0b11011111;
// VCOM status off 0
// Temp sensing default 1
// VGL Power Off Floating 1
// NORG expect refresh 1
// VCOM Off on displ off 0
const uint8_t panel_setting_2 = 0b01110;
const uint8_t wf_t0154_cz_b3_list[] = {
11, // 11 commands in list
CMD_PSR_PANEL_SETTING, 2, panel_setting_1, panel_setting_2,
CMD_UNDOCUMENTED_0x4D, 1, 0x55,
CMD_UNDOCUMENTED_0xAA, 1, 0x0f,
CMD_UNDOCUMENTED_0xE9, 1, 0x02,
CMD_UNDOCUMENTED_0xB6, 1, 0x11,
CMD_UNDOCUMENTED_0xF3, 1, 0x0a,
CMD_TRES_RESOLUTION_SETTING, 3, 0xc8, 0x00, 0xc8,
CMD_TCON_TCONSETTING, 1, 0x00,
CMD_CDI_VCOM_DATA_INTERVAL, 1, 0xd7,
CMD_PWS_POWER_SAVING, 1, 0x00,
CMD_PON_POWER_ON, 0
};
// clang-format on
this->write_init_list_(wf_t0154_cz_b3_list);
delay(100); // NOLINT
this->wait_until_idle_();
}
void GDEW0154M09::write_init_list_(const uint8_t *list) {
uint8_t list_limit = list[0];
uint8_t *start_ptr = ((uint8_t *) list + 1);
for (uint8_t i = 0; i < list_limit; i++) {
this->command(*(start_ptr + 0));
for (uint8_t dnum = 0; dnum < *(start_ptr + 1); dnum++) {
this->data(*(start_ptr + 2 + dnum));
}
start_ptr += (*(start_ptr + 1) + 2);
}
}
void GDEW0154M09::clear_() {
uint32_t pixsize = this->get_buffer_length_();
for (uint8_t j = 0; j < 2; j++) {
this->command(CMD_DTM1_DATA_START_TRANS);
for (int count = 0; count < pixsize; count++) {
this->data(0x00);
}
this->command(CMD_DTM2_DATA_START_TRANS2);
for (int count = 0; count < pixsize; count++) {
this->data(0xff);
}
this->command(CMD_DISPLAY_REFRESH);
delay(10);
this->wait_until_idle_();
}
}
void HOT GDEW0154M09::display() {
this->init_internal_();
// "Mode 0 display" for now
this->command(CMD_DTM1_DATA_START_TRANS);
for (int i = 0; i < this->get_buffer_length_(); i++) {
this->data(0xff);
}
this->command(CMD_DTM2_DATA_START_TRANS2); // write 'new' data to SRAM
for (int i = 0; i < this->get_buffer_length_(); i++) {
this->data(this->buffer_[i]);
}
this->command(CMD_DISPLAY_REFRESH);
delay(10);
this->wait_until_idle_();
this->deep_sleep();
}
void GDEW0154M09::deep_sleep() {
// COMMAND DEEP SLEEP
this->command(CMD_POF_POWER_OFF);
this->wait_until_idle_();
delay(1000); // NOLINT
this->command(CMD_DSLP_DEEP_SLEEP);
this->data(DATA_DSLP_DEEP_SLEEP);
}
int GDEW0154M09::get_width_internal() { return 200; }
int GDEW0154M09::get_height_internal() { return 200; }
void GDEW0154M09::dump_config() {
LOG_DISPLAY("", "M5Stack CoreInk E-Paper (Good Display)", this);
ESP_LOGCONFIG(TAG, " Model: 1.54in Greyscale GDEW0154M09");
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Busy Pin: ", this->busy_pin_);
LOG_UPDATE_INTERVAL(this);
}
static const uint8_t LUT_VCOM_DC_4_2[] = {
0x00, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00, 0x17, 0x17, 0x00, 0x00, 0x02, 0x00, 0x0A, 0x01,
0x00, 0x00, 0x01, 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

View file

@ -170,6 +170,46 @@ class GDEY029T94 : public WaveshareEPaper {
int get_height_internal() override;
};
class GDEW0154M09 : public WaveshareEPaper {
public:
void initialize() override;
void display() override;
void dump_config() override;
void deep_sleep() override;
protected:
int get_width_internal() override;
int get_height_internal() override;
private:
static const uint8_t CMD_DTM1_DATA_START_TRANS = 0x10;
static const uint8_t CMD_DTM2_DATA_START_TRANS2 = 0x13;
static const uint8_t CMD_DISPLAY_REFRESH = 0x12;
static const uint8_t CMD_AUTO_SEQ = 0x17;
static const uint8_t DATA_AUTO_PON_DSR_POF_DSLP = 0xA7;
static const uint8_t CMD_PSR_PANEL_SETTING = 0x00;
static const uint8_t CMD_UNDOCUMENTED_0x4D = 0x4D; // NOLINT
static const uint8_t CMD_UNDOCUMENTED_0xAA = 0xaa; // NOLINT
static const uint8_t CMD_UNDOCUMENTED_0xE9 = 0xe9; // NOLINT
static const uint8_t CMD_UNDOCUMENTED_0xB6 = 0xb6; // NOLINT
static const uint8_t CMD_UNDOCUMENTED_0xF3 = 0xf3; // NOLINT
static const uint8_t CMD_TRES_RESOLUTION_SETTING = 0x61;
static const uint8_t CMD_TCON_TCONSETTING = 0x60;
static const uint8_t CMD_CDI_VCOM_DATA_INTERVAL = 0x50;
static const uint8_t CMD_POF_POWER_OFF = 0x02;
static const uint8_t CMD_DSLP_DEEP_SLEEP = 0x07;
static const uint8_t DATA_DSLP_DEEP_SLEEP = 0xA5;
static const uint8_t CMD_PWS_POWER_SAVING = 0xe3;
static const uint8_t CMD_PON_POWER_ON = 0x04;
static const uint8_t CMD_PTL_PARTIAL_WINDOW = 0x90;
uint8_t *lastbuff_ = nullptr;
void reset_();
void clear_();
void write_init_list_(const uint8_t *list);
void init_internal_();
};
class WaveshareEPaper2P9InB : public WaveshareEPaper {
public:
void initialize() override;

File diff suppressed because it is too large Load diff

View file

@ -108,6 +108,7 @@ ROOT_CONFIG_PATH = object()
RESERVED_IDS = [
# C++ keywords http://en.cppreference.com/w/cpp/keyword
"alarm",
"alignas",
"alignof",
"and",

View file

@ -968,6 +968,7 @@ DEVICE_CLASS_GAS = "gas"
DEVICE_CLASS_GATE = "gate"
DEVICE_CLASS_HEAT = "heat"
DEVICE_CLASS_HUMIDITY = "humidity"
DEVICE_CLASS_IDENTIFY = "identify"
DEVICE_CLASS_ILLUMINANCE = "illuminance"
DEVICE_CLASS_IRRADIANCE = "irradiance"
DEVICE_CLASS_LIGHT = "light"

View file

@ -201,8 +201,8 @@ WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {
uint32_t now = millis();
if (now - started_ > 50) {
const char *src = component_ == nullptr ? "<null>" : component_->get_component_source();
ESP_LOGV(TAG, "Component %s took a long time for an operation (%.2f s).", src, (now - started_) / 1e3f);
ESP_LOGV(TAG, "Components should block for at most 20-30ms.");
ESP_LOGW(TAG, "Component %s took a long time for an operation (%.2f s).", src, (now - started_) / 1e3f);
ESP_LOGW(TAG, "Components should block for at most 20-30ms.");
;
}
}

View file

@ -29,7 +29,7 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u
if (timeout == SCHEDULER_DONT_RUN)
return;
ESP_LOGVV(TAG, "set_timeout(name='%s', timeout=%u)", name.c_str(), timeout);
ESP_LOGVV(TAG, "set_timeout(name='%s', timeout=%" PRIu32 ")", name.c_str(), timeout);
auto item = make_unique<SchedulerItem>();
item->component = component;
@ -60,7 +60,7 @@ void HOT Scheduler::set_interval(Component *component, const std::string &name,
if (interval != 0)
offset = (random_uint32() % interval) / 2;
ESP_LOGVV(TAG, "set_interval(name='%s', interval=%u, offset=%u)", name.c_str(), interval, offset);
ESP_LOGVV(TAG, "set_interval(name='%s', interval=%" PRIu32 ", offset=%" PRIu32 ")", name.c_str(), interval, offset);
auto item = make_unique<SchedulerItem>();
item->component = component;
@ -108,8 +108,8 @@ void HOT Scheduler::set_retry(Component *component, const std::string &name, uin
if (initial_wait_time == SCHEDULER_DONT_RUN)
return;
ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%u, max_attempts=%u, backoff_factor=%0.1f)", name.c_str(),
initial_wait_time, max_attempts, backoff_increase_factor);
ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%" PRIu32 ", max_attempts=%u, backoff_factor=%0.1f)",
name.c_str(), initial_wait_time, max_attempts, backoff_increase_factor);
if (backoff_increase_factor < 0.0001) {
ESP_LOGE(TAG,
@ -154,16 +154,16 @@ void HOT Scheduler::call() {
if (now - last_print > 2000) {
last_print = now;
std::vector<std::unique_ptr<SchedulerItem>> old_items;
ESP_LOGVV(TAG, "Items: count=%u, now=%u", this->items_.size(), now);
ESP_LOGVV(TAG, "Items: count=%u, now=%" PRIu32, this->items_.size(), now);
while (!this->empty_()) {
this->lock_.lock();
auto item = std::move(this->items_[0]);
this->pop_raw_();
this->lock_.unlock();
ESP_LOGVV(TAG, " %s '%s' interval=%u last_execution=%u (%u) next=%u (%u)", item->get_type_str(),
item->name.c_str(), item->interval, item->last_execution, item->last_execution_major,
item->next_execution(), item->next_execution_major());
ESP_LOGVV(TAG, " %s '%s' interval=%" PRIu32 " last_execution=%" PRIu32 " (%u) next=%" PRIu32 " (%u)",
item->get_type_str(), item->name.c_str(), item->interval, item->last_execution,
item->last_execution_major, item->next_execution(), item->next_execution_major());
old_items.push_back(std::move(item));
}
@ -222,8 +222,8 @@ void HOT Scheduler::call() {
}
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", item->get_type_str(),
item->name.c_str(), item->interval, item->last_execution, now);
ESP_LOGVV(TAG, "Running %s '%s' with interval=%" PRIu32 " last_execution=%" PRIu32 " (now=%" PRIu32 ")",
item->get_type_str(), item->name.c_str(), item->interval, item->last_execution, now);
#endif
// Warning: During callback(), a lot of stuff can happen, including:

View file

@ -546,22 +546,11 @@ class DownloadBinaryRequestHandler(BaseHandler):
return
with open(path, "rb") as f:
while True:
# For a 528KB image used as benchmark:
# - using 256KB blocks resulted in the smallest file size.
# - blocks larger than 256KB didn't improve the size of compressed file.
# - blocks smaller than 256KB hindered compression, making the output file larger.
data = f.read()
if compressed:
data = gzip.compress(data, 9)
self.write(data)
# Read file in blocks of 256KB.
data = f.read(256 * 1024)
if not data:
break
if compressed:
data = gzip.compress(data, 9)
self.write(data)
self.finish()

View file

@ -62,7 +62,7 @@ lib_deps =
fastled/FastLED@3.3.2 ; fastled_base
mikalhart/TinyGPSPlus@1.0.2 ; gps
freekode/TM1651@1.0.1 ; tm1651
glmnet/Dsmr@0.5 ; dsmr
glmnet/Dsmr@0.7 ; dsmr
rweather/Crypto@0.4.0 ; dsmr
dudanov/MideaUART@1.1.8 ; midea
tonia/HeatpumpIR@1.0.20 ; heatpumpir

View file

@ -7,10 +7,10 @@ tzlocal==5.0.1 # from time
tzdata>=2021.1 # from time
pyserial==3.5
platformio==6.1.7 # When updating platformio, also update Dockerfile
esptool==4.6
esptool==4.6.2
click==8.1.3
esphome-dashboard==20230621.0
aioesphomeapi==14.1.0
aioesphomeapi==15.0.0
zeroconf==0.69.0
# esp-idf requires this, but doesn't bundle it by default

View file

@ -1,13 +1,13 @@
pylint==2.17.4
flake8==6.0.0 # also change in .pre-commit-config.yaml when updating
black==23.3.0 # also change in .pre-commit-config.yaml when updating
pyupgrade==3.4.0 # also change in .pre-commit-config.yaml when updating
pyupgrade==3.7.0 # also change in .pre-commit-config.yaml when updating
pre-commit
# Unit tests
pytest==7.3.2
pytest==7.4.0
pytest-cov==4.1.0
pytest-mock==3.10.0
pytest-mock==3.11.1
pytest-asyncio==0.21.0
asyncmock==0.4.2
hypothesis==5.49.0

View file

@ -461,8 +461,10 @@ def merge(source, destination):
def is_platform_schema(schema_name):
# added mostly because of schema_name == "microphone.MICROPHONE_SCHEMA"
# and "alarm_control_panel"
# which is shrunk because there is only one component of the schema (i2s_audio)
return schema_name == "microphone.MICROPHONE_SCHEMA"
component = schema_name.split(".")[0]
return component in components and components[component].is_platform_component
def shrink():
@ -530,6 +532,10 @@ def shrink():
elif not key_s:
for target in paths:
target_s = get_arr_path_schema(target)
if S_SCHEMA not in target_s:
# an empty schema like speaker.SPEAKER_SCHEMA
target_s[S_EXTENDS].remove(x)
continue
assert target_s[S_SCHEMA][S_EXTENDS] == [x]
target_s.pop(S_SCHEMA)
target_s.pop(S_TYPE) # undefined

View file

@ -607,7 +607,7 @@ def lint_trailing_whitespace(fname, match):
"esphome/components/button/button.h",
"esphome/components/climate/climate.h",
"esphome/components/cover/cover.h",
"esphome/components/display/display_buffer.h",
"esphome/components/display/display.h",
"esphome/components/fan/fan.h",
"esphome/components/i2c/i2c.h",
"esphome/components/lock/lock.h",

View file

@ -9,6 +9,9 @@ set -x
esphome compile tests/test1.yaml
esphome compile tests/test2.yaml
esphome compile tests/test3.yaml
esphome compile tests/test3.1.yaml
esphome compile tests/test4.yaml
esphome compile tests/test5.yaml
esphome compile tests/test6.yaml
esphome compile tests/test7.yaml
esphome compile tests/test8.yaml

View file

@ -1370,8 +1370,15 @@ binary_sensor:
device_class: window
filters:
- invert:
- delayed_on_off: 40ms
- delayed_on_off:
time_on: 10s
time_off: !lambda "return 1000;"
- delayed_on: 40ms
- delayed_off: 40ms
- delayed_on_off: !lambda "return 10;"
- delayed_on: !lambda "return 1000;"
- delayed_off: !lambda "return 0;"
on_press:
then:
- lambda: >-

View file

@ -397,6 +397,19 @@ sensor:
name: MICS-4514 C2H5OH
ammonia:
name: MICS-4514 NH3
- platform: mopeka_std_check
mac_address: D3:75:F2:DC:16:91
tank_type: CUSTOM
custom_distance_full: 40cm
custom_distance_empty: 10mm
temperature:
name: Propane test temp
level:
name: Propane test level
distance:
name: Propane test distance
battery_level:
name: Propane test battery level
time:
- platform: homeassistant

View file

@ -504,6 +504,14 @@ display:
full_update_every: 30
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
- platform: waveshare_epaper
cs_pin: GPIO23
dc_pin: GPIO23
busy_pin: GPIO23
reset_pin: GPIO23
model: 1.54in-m5coreink-m09
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
- platform: inkplate6
id: inkplate_display
greyscale: false