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: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }} 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 - name: Run esphome compile tests/test${{ matrix.file }}.yaml
run: | run: |
. venv/bin/activate . venv/bin/activate

View file

@ -6,14 +6,12 @@ on:
schedule: schedule:
- cron: '45 6 * * *' - cron: '45 6 * * *'
permissions:
contents: write
pull-requests: write
jobs: jobs:
sync: sync:
name: Sync Device Classes name: Sync Device Classes
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'esphome/esphome'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@ -38,15 +36,6 @@ jobs:
run: | run: |
python ./script/sync-device_class.py 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 - name: Commit changes
uses: peter-evans/create-pull-request@v5 uses: peter-evans/create-pull-request@v5
with: with:
@ -56,5 +45,5 @@ jobs:
branch: sync/device-classes branch: sync/device-classes
delete-branch: true delete-branch: true
title: "Synchronise Device Classes from Home Assistant" 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 }} token: ${{ secrets.DEVICE_CLASS_SYNC_TOKEN }}

View file

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

View file

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

View file

@ -3,9 +3,10 @@
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
namespace esphome { 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), : Image(data_start, width, height, type),
animation_data_start_(data_start), animation_data_start_(data_start),
current_frame_(0), current_frame_(0),
@ -65,5 +66,5 @@ void Animation::update_data_start_() {
this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_; this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_;
} }
} // namespace display } // namespace animation
} // namespace esphome } // namespace esphome

View file

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

View file

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

View file

@ -95,6 +95,14 @@ DEVICE_CLASSES = [
IS_PLATFORM_COMPONENT = True 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") binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor")
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase) BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase)
BinarySensorInitiallyOff = binary_sensor_ns.class_( BinarySensorInitiallyOff = binary_sensor_ns.class_(
@ -138,47 +146,75 @@ FILTER_REGISTRY = Registry()
validate_filters = cv.validate_registry("filter", FILTER_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): async def invert_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id) return cg.new_Pvariable(filter_id)
@FILTER_REGISTRY.register( @register_filter(
"delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds "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): 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, {}) 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 return var
@FILTER_REGISTRY.register( @register_filter(
"delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds "delayed_on", DelayedOnFilter, cv.templatable(cv.positive_time_period_milliseconds)
) )
async def delayed_on_filter_to_code(config, filter_id): 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, {}) await cg.register_component(var, {})
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_delay(template_))
return var return var
@FILTER_REGISTRY.register( @register_filter(
"delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds "delayed_off",
DelayedOffFilter,
cv.templatable(cv.positive_time_period_milliseconds),
) )
async def delayed_off_filter_to_code(config, filter_id): 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, {}) await cg.register_component(var, {})
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_delay(template_))
return var return var
CONF_TIME_OFF = "time_off" @register_filter(
CONF_TIME_ON = "time_on"
DEFAULT_DELAY = "1s"
DEFAULT_TIME_OFF = "100ms"
DEFAULT_TIME_ON = "900ms"
@FILTER_REGISTRY.register(
"autorepeat", "autorepeat",
AutorepeatFilter, AutorepeatFilter,
cv.All( cv.All(
@ -215,7 +251,7 @@ async def autorepeat_filter_to_code(config, filter_id):
return var 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): async def lambda_filter_to_code(config, filter_id):
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config, [(bool, "x")], return_type=cg.optional.template(bool) 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) { optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) {
if (value) { 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 { } 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 {}; return {};
} }
float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; } 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) { optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
if (value) { 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 {}; return {};
} else { } else {
this->cancel_timeout("ON"); 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; } 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) { optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
if (!value) { 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 {}; return {};
} else { } else {
this->cancel_timeout("OFF"); 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> 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 binary_sensor
} // namespace esphome } // namespace esphome

View file

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

View file

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

View file

@ -38,7 +38,6 @@ CONFIG_SCHEMA = cv.All(
), ),
} }
).extend(cv.polling_component_schema("60s")), ).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/hal.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/version.h" #include "esphome/core/version.h"
#include <cinttypes>
#ifdef USE_ESP32 #ifdef USE_ESP32
@ -13,6 +14,7 @@
#if ESP_IDF_VERSION_MAJOR >= 4 #if ESP_IDF_VERSION_MAJOR >= 4
#include <esp32/rom/rtc.h> #include <esp32/rom/rtc.h>
#include <esp_chip_info.h>
#else #else
#include <rom/rtc.h> #include <rom/rtc.h>
#endif #endif
@ -20,8 +22,12 @@
#endif // USE_ESP32 #endif // USE_ESP32
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#ifdef USE_RP2040
#include <Arduino.h>
#else
#include <Esp.h> #include <Esp.h>
#endif #endif
#endif
namespace esphome { namespace esphome {
namespace debug { namespace debug {
@ -33,6 +39,8 @@ static uint32_t get_free_heap() {
return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32) #elif defined(USE_ESP32)
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
#elif defined(USE_RP2040)
return rp2040.getFreeHeap();
#endif #endif
} }
@ -61,9 +69,9 @@ void DebugComponent::dump_config() {
device_info += ESPHOME_VERSION; device_info += ESPHOME_VERSION;
this->free_heap_ = get_free_heap(); 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; const char *flash_mode;
switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance) switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance)
case FM_QIO: case FM_QIO:
@ -272,6 +280,11 @@ void DebugComponent::dump_config() {
reset_reason = ESP.getResetReason().c_str(); reset_reason = ESP.getResetReason().c_str();
#endif #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 #ifdef USE_TEXT_SENSOR
if (this->device_info_ != nullptr) { if (this->device_info_ != nullptr) {
if (device_info.length() > 255) if (device_info.length() > 255)
@ -289,7 +302,7 @@ void DebugComponent::loop() {
uint32_t new_free_heap = get_free_heap(); uint32_t new_free_heap = get_free_heap();
if (new_free_heap < this->free_heap_ / 2) { if (new_free_heap < this->free_heap_ / 2) {
this->free_heap_ = new_free_heap; 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); 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 "display_buffer.h"
#include <utility> #include <utility>
#include "esphome/core/application.h" #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" #include "esphome/core/log.h"
namespace esphome { namespace esphome {
@ -12,9 +10,6 @@ namespace display {
static const char *const TAG = "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) { void DisplayBuffer::init_internal_(uint32_t buffer_length) {
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
this->buffer_ = allocator.allocate(buffer_length); this->buffer_ = allocator.allocate(buffer_length);
@ -25,8 +20,6 @@ void DisplayBuffer::init_internal_(uint32_t buffer_length) {
this->clear(); 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() { int DisplayBuffer::get_width() {
switch (this->rotation_) { switch (this->rotation_) {
case DISPLAY_ROTATION_90_DEGREES: case DISPLAY_ROTATION_90_DEGREES:
@ -38,6 +31,7 @@ int DisplayBuffer::get_width() {
return this->get_width_internal(); return this->get_width_internal();
} }
} }
int DisplayBuffer::get_height() { int DisplayBuffer::get_height() {
switch (this->rotation_) { switch (this->rotation_) {
case DISPLAY_ROTATION_0_DEGREES: case DISPLAY_ROTATION_0_DEGREES:
@ -49,7 +43,7 @@ int DisplayBuffer::get_height() {
return this->get_width_internal(); 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) { void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
if (!this->get_clipping().inside(x, y)) if (!this->get_clipping().inside(x, y))
return; // NOLINT 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); this->draw_absolute_pixel_internal(x, y, color);
App.feed_wdt(); 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 display
} // namespace esphome } // namespace esphome

View file

@ -2,568 +2,35 @@
#include <cstdarg> #include <cstdarg>
#include <vector> #include <vector>
#include "rect.h"
#include "display.h"
#include "display_color_utils.h" #include "display_color_utils.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.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 esphome {
namespace display { namespace display {
/** TextAlign is used to tell the display class how to position a piece of text. By default class DisplayBuffer : public Display {
* 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 {
public: 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. /// 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. /// 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. /// Set a single pixel at the specified coordinates to the given color.
void draw_pixel_at(int x, int y, Color color = COLOR_ON); void draw_pixel_at(int x, int y, Color color) override;
/// 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; }
virtual int get_height_internal() = 0; virtual int get_height_internal() = 0;
virtual int get_width_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: 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; virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0;
void init_internal_(uint32_t buffer_length); void init_internal_(uint32_t buffer_length);
void do_update_();
uint8_t *buffer_{nullptr}; 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 } // namespace display

View file

@ -19,6 +19,7 @@ CONF_CRC_CHECK = "crc_check"
CONF_DECRYPTION_KEY = "decryption_key" CONF_DECRYPTION_KEY = "decryption_key"
CONF_DSMR_ID = "dsmr_id" CONF_DSMR_ID = "dsmr_id"
CONF_GAS_MBUS_ID = "gas_mbus_id" CONF_GAS_MBUS_ID = "gas_mbus_id"
CONF_WATER_MBUS_ID = "water_mbus_id"
CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length" CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length"
CONF_REQUEST_INTERVAL = "request_interval" CONF_REQUEST_INTERVAL = "request_interval"
CONF_REQUEST_PIN = "request_pin" CONF_REQUEST_PIN = "request_pin"
@ -53,6 +54,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_DECRYPTION_KEY): _validate_key, cv.Optional(CONF_DECRYPTION_KEY): _validate_key,
cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, 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_MAX_TELEGRAM_LENGTH, default=1500): cv.int_,
cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema,
cv.Optional( 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(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_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 # DSMR Parser
cg.add_library("glmnet/Dsmr", "0.5") cg.add_library("glmnet/Dsmr", "0.7")
# Crypto # Crypto
cg.add_library("rweather/Crypto", "0.4.0") cg.add_library("rweather/Crypto", "0.4.0")

View file

@ -8,6 +8,7 @@ from esphome.const import (
DEVICE_CLASS_GAS, DEVICE_CLASS_GAS,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_WATER,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING, STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE, UNIT_AMPERE,
@ -236,6 +237,12 @@ CONFIG_SCHEMA = cv.Schema(
device_class=DEVICE_CLASS_GAS, device_class=DEVICE_CLASS_GAS,
state_class=STATE_CLASS_TOTAL_INCREASING, 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) ).extend(cv.COMPONENT_SCHEMA)

View file

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

View file

@ -55,3 +55,4 @@ async def to_code(config):
if CORE.using_esp_idf: if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) 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 <sys/cdefs.h>
#include "esp_log.h" #include "esp_log.h"
#include "esp_eth.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" #include "eth_phy_regs_struct.h"
#endif
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "driver/gpio.h" #include "driver/gpio.h"
@ -170,7 +174,11 @@ static esp_err_t jl1101_reset_hw(esp_eth_phy_t *phy) {
return ESP_OK; 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) { static esp_err_t jl1101_negotiate(esp_eth_phy_t *phy) {
#endif
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent); phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
esp_eth_mediator_t *eth = jl1101->eth; esp_eth_mediator_t *eth = jl1101->eth;
/* in case any link status has changed, let's assume we're in link down status */ /* 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; esp_eth_mediator_t *eth = jl1101->eth;
// Detect PHY address // Detect PHY address
if (jl1101->addr == ESP_ETH_PHY_ADDR_AUTO) { 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); PHY_CHECK(esp_eth_detect_phy_addr(eth, &jl1101->addr) == ESP_OK, "Detect PHY address failed", err);
#endif
} }
/* Power on Ethernet PHY */ /* Power on Ethernet PHY */
PHY_CHECK(jl1101_pwrctl(phy, true) == ESP_OK, "power control failed", err); 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.init = jl1101_init;
jl1101->parent.deinit = jl1101_deinit; jl1101->parent.deinit = jl1101_deinit;
jl1101->parent.set_mediator = jl1101_set_mediator; 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; jl1101->parent.negotiate = jl1101_negotiate;
#endif
jl1101->parent.get_link = jl1101_get_link; jl1101->parent.get_link = jl1101_get_link;
jl1101->parent.pwrctl = jl1101_pwrctl; jl1101->parent.pwrctl = jl1101_pwrctl;
jl1101->parent.get_addr = jl1101_get_addr; jl1101->parent.get_addr = jl1101_get_addr;

View file

@ -41,18 +41,27 @@ void EthernetComponent::setup() {
this->eth_netif_ = esp_netif_new(&cfg); this->eth_netif_ = esp_netif_new(&cfg);
// Init MAC and PHY configs to default // 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(); eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
phy_config.phy_addr = this->phy_addr_; phy_config.phy_addr = this->phy_addr_;
phy_config.reset_gpio_num = this->power_pin_; 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_mdc_gpio_num = this->mdc_pin_;
mac_config.smi_mdio_gpio_num = this->mdio_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_mode = this->clk_mode_;
mac_config.clock_config.rmii.clock_gpio = this->clk_gpio_; mac_config.clock_config.rmii.clock_gpio = this->clk_gpio_;
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
#endif
switch (this->type_) { switch (this->type_) {
case ETHERNET_TYPE_LAN8720: { case ETHERNET_TYPE_LAN8720: {
@ -76,7 +85,11 @@ void EthernetComponent::setup() {
break; break;
} }
case ETHERNET_TYPE_KSZ8081: { 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); this->phy_ = esp_eth_phy_new_ksz8081(&phy_config);
#endif
break; break;
} }
default: { default: {
@ -221,13 +234,13 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base
return; 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 EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
void *event_data) { void *event_data) {
global_eth_component->connected_ = true; 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_() { void EthernetComponent::start_connect_() {

View file

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

View file

@ -2,13 +2,15 @@
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/color.h"
#include "esphome/components/display/display_buffer.h"
namespace esphome { 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; int scan_x1, scan_y1, scan_width, scan_height;
this->scan_area(&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; *x_offset = min_x;
*width = x - 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 i = 0;
int x_at = x_start; int x_at = x_start;
while (text[i] != '\0') { 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 } // namespace esphome

View file

@ -1,12 +1,12 @@
#pragma once #pragma once
#include "esphome/core/datatypes.h" #include "esphome/core/datatypes.h"
#include "display_buffer.h" #include "esphome/core/color.h"
#include "esphome/components/display/display_buffer.h"
namespace esphome { namespace esphome {
namespace display { namespace font {
class DisplayBuffer;
class Font; class Font;
struct GlyphData { struct GlyphData {
@ -22,7 +22,7 @@ class Glyph {
public: public:
Glyph(const GlyphData *data) : glyph_data_(data) {} 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; const char *get_char() const;
@ -38,7 +38,7 @@ class Glyph {
const GlyphData *glyph_data_; const GlyphData *glyph_data_;
}; };
class Font : public BaseFont { class Font : public display::BaseFont {
public: public:
/** Construct the font with the given glyphs. /** 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); 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; 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_baseline() { return this->baseline_; }
inline int get_height() { return this->height_; } inline int get_height() { return this->height_; }
@ -63,5 +63,5 @@ class Font : public BaseFont {
int height_; int height_;
}; };
} // namespace display } // namespace font
} // namespace esphome } // namespace esphome

View file

@ -1,6 +1,5 @@
#include "graph.h" #include "graph.h"
#include "esphome/components/display/display_buffer.h" #include "esphome/components/display/display.h"
#include "esphome/components/display/font.h"
#include "esphome/core/color.h" #include "esphome/core/color.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/hal.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()); 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 /// Plot border
if (this->border_) { if (this->border_) {
buff->horizontal_line(x_offset, y_offset, this->width_, color); 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_) if (!legend_)
return; return;

View file

@ -8,10 +8,10 @@
namespace esphome { namespace esphome {
// forward declare DisplayBuffer // forward declare Display
namespace display { namespace display {
class DisplayBuffer; class Display;
class Font; class BaseFont;
} // namespace display } // namespace display
namespace graph { namespace graph {
@ -45,8 +45,8 @@ enum ValuePositionType {
class GraphLegend { class GraphLegend {
public: public:
void init(Graph *g); void init(Graph *g);
void set_name_font(display::Font *font) { this->font_label_ = font; } void set_name_font(display::BaseFont *font) { this->font_label_ = font; }
void set_value_font(display::Font *font) { this->font_value_ = font; } void set_value_font(display::BaseFont *font) { this->font_value_ = font; }
void set_width(uint32_t width) { this->width_ = width; } void set_width(uint32_t width) { this->width_ = width; }
void set_height(uint32_t height) { this->height_ = height; } void set_height(uint32_t height) { this->height_ = height; }
void set_border(bool val) { this->border_ = val; } void set_border(bool val) { this->border_ = val; }
@ -63,8 +63,8 @@ class GraphLegend {
ValuePositionType values_{VALUE_POSITION_TYPE_AUTO}; ValuePositionType values_{VALUE_POSITION_TYPE_AUTO};
bool units_{true}; bool units_{true};
DirectionType direction_{DIRECTION_TYPE_AUTO}; DirectionType direction_{DIRECTION_TYPE_AUTO};
display::Font *font_label_{nullptr}; display::BaseFont *font_label_{nullptr};
display::Font *font_value_{nullptr}; display::BaseFont *font_value_{nullptr};
// Calculated values // Calculated values
Graph *parent_{nullptr}; Graph *parent_{nullptr};
// (x0) (xs,ys) (xs,ys) // (x0) (xs,ys) (xs,ys)
@ -133,8 +133,8 @@ class GraphTrace {
class Graph : public Component { class Graph : public Component {
public: public:
void draw(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::DisplayBuffer *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; void setup() override;
float get_setup_priority() const override { return setup_priority::PROCESSOR; } 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"; static const char *const TAG = "i2c.idf";
void IDFI2CBus::setup() { void IDFI2CBus::setup() {
ESP_LOGCONFIG(TAG, "Setting up I2C bus...");
static i2c_port_t next_port = 0; static i2c_port_t next_port = 0;
port_ = next_port++; port_ = next_port++;

View file

@ -13,6 +13,7 @@ from esphome.const import (
CONF_PAGES, CONF_PAGES,
CONF_RESET_PIN, CONF_RESET_PIN,
CONF_DIMENSIONS, CONF_DIMENSIONS,
CONF_DATA_RATE,
) )
DEPENDENCIES = ["spi"] DEPENDENCIES = ["spi"]
@ -43,6 +44,7 @@ MODELS = {
"ILI9481": ili9XXX_ns.class_("ILI9XXXILI9481", ili9XXXSPI), "ILI9481": ili9XXX_ns.class_("ILI9XXXILI9481", ili9XXXSPI),
"ILI9486": ili9XXX_ns.class_("ILI9XXXILI9486", ili9XXXSPI), "ILI9486": ili9XXX_ns.class_("ILI9XXXILI9486", ili9XXXSPI),
"ILI9488": ili9XXX_ns.class_("ILI9XXXILI9488", ili9XXXSPI), "ILI9488": ili9XXX_ns.class_("ILI9XXXILI9488", ili9XXXSPI),
"ILI9488_A": ili9XXX_ns.class_("ILI9XXXILI9488A", ili9XXXSPI),
"ST7796": ili9XXX_ns.class_("ILI9XXXST7796", ili9XXXSPI), "ST7796": ili9XXX_ns.class_("ILI9XXXST7796", ili9XXXSPI),
"S3BOX": ili9XXX_ns.class_("ILI9XXXS3Box", ili9XXXSPI), "S3BOX": ili9XXX_ns.class_("ILI9XXXS3Box", ili9XXXSPI),
"S3BOX_LITE": ili9XXX_ns.class_("ILI9XXXS3BoxLite", 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.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list(
cv.file_ cv.file_
), ),
cv.Optional(CONF_DATA_RATE, default="40MHz"): spi.SPI_DATA_RATE_SCHEMA,
} }
) )
.extend(cv.polling_component_schema("1s")) .extend(cv.polling_component_schema("1s"))
@ -175,3 +178,6 @@ async def to_code(config):
if rhs is not None: if rhs is not None:
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
cg.add(var.set_palette(prog_arr)) 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; this->need_update_ = true;
return; return;
} }
do {
this->prossing_update_ = true; this->prossing_update_ = true;
do {
this->need_update_ = false; this->need_update_ = false;
if (!this->need_update_) {
this->do_update_(); this->do_update_();
}
} while (this->need_update_); } while (this->need_update_);
this->prossing_update_ = false; this->prossing_update_ = false;
this->display_(); this->display_();
@ -411,6 +409,17 @@ void ILI9XXXILI9488::initialize() {
this->is_18bitdisplay_ = true; this->is_18bitdisplay_ = true;
} }
// 40_TFT display // 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() { void ILI9XXXST7796::initialize() {
this->init_lcd_(INITCMD_ST7796); this->init_lcd_(INITCMD_ST7796);
if (this->width_ == 0) { if (this->width_ == 0) {

View file

@ -15,10 +15,14 @@ enum ILI9XXXColorMode {
BITS_16 = 0x10, BITS_16 = 0x10,
}; };
#ifndef ILI9XXXDisplay_DATA_RATE
#define ILI9XXXDisplay_DATA_RATE spi::DATA_RATE_40MHZ
#endif // ILI9XXXDisplay_DATA_RATE
class ILI9XXXDisplay : public PollingComponent, class ILI9XXXDisplay : public PollingComponent,
public display::DisplayBuffer, public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, 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: public:
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
float get_setup_priority() const override; float get_setup_priority() const override;
@ -128,6 +132,12 @@ class ILI9XXXILI9488 : public ILI9XXXDisplay {
void initialize() override; void initialize() override;
}; };
//----------- ILI9XXX_35_TFT origin colors rotated display --------------
class ILI9XXXILI9488A : public ILI9XXXDisplay {
protected:
void initialize() override;
};
//----------- ILI9XXX_35_TFT rotated display -------------- //----------- ILI9XXX_35_TFT rotated display --------------
class ILI9XXXST7796 : public ILI9XXXDisplay { class ILI9XXXST7796 : public ILI9XXXDisplay {
protected: 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 // 5 frames
//ILI9XXX_ETMOD, 1, 0xC6, // //ILI9XXX_ETMOD, 1, 0xC6, //
@ -218,12 +252,12 @@ static const uint8_t PROGMEM INITCMD_S3BOXLITE[] = {
ILI9XXX_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control ILI9XXX_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control
0xF2, 1, 0x00, // 3Gamma Function Disable 0xF2, 1, 0x00, // 3Gamma Function Disable
ILI9XXX_GAMMASET , 1, 0x01, // Gamma curve selected ILI9XXX_GAMMASET , 1, 0x01, // Gamma curve selected
ILI9XXX_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma ILI9XXX_GMCTRP1 , 14, 0xF0, 0x09, 0x0B, 0x06, 0x04, 0x15, // Set Gamma
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x2F, 0x54, 0x42, 0x3C, 0x17, 0x14,
0x0E, 0x09, 0x00, 0x18, 0x1B,
ILI9XXX_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma ILI9XXX_GMCTRN1 , 14, 0xE0, 0x09, 0x0B, 0x06, 0x04, 0x03, // Set Gamma
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x2B, 0x43, 0x42, 0x3B, 0x16, 0x14,
0x31, 0x36, 0x0F, 0x17, 0x1B,
ILI9XXX_SLPOUT , 0x80, // Exit Sleep ILI9XXX_SLPOUT , 0x80, // Exit Sleep
ILI9XXX_DISPON , 0x80, // Display on ILI9XXX_DISPON , 0x80, // Display on
0x00 // End of list 0x00 // End of list

View file

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

View file

@ -3,9 +3,9 @@
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
namespace esphome { 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_) { switch (type_) {
case IMAGE_TYPE_BINARY: { case IMAGE_TYPE_BINARY: {
for (int img_x = 0; img_x < width_; img_x++) { 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) Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
: width_(width), height_(height), type_(type), data_start_(data_start) {} : width_(width), height_(height), type_(type), data_start_(data_start) {}
} // namespace display } // namespace image
} // namespace esphome } // namespace esphome

View file

@ -1,9 +1,9 @@
#pragma once #pragma once
#include "esphome/core/color.h" #include "esphome/core/color.h"
#include "display_buffer.h" #include "esphome/components/display/display_buffer.h"
namespace esphome { namespace esphome {
namespace display { namespace image {
enum ImageType { enum ImageType {
IMAGE_TYPE_BINARY = 0, 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; } 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: public:
Image(const uint8_t *data_start, int width, int height, ImageType type); 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_width() const override;
int get_height() const override; int get_height() const override;
ImageType get_type() const; 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; } void set_transparency(bool transparent) { transparent_ = transparent; }
bool has_transparency() const { return transparent_; } bool has_transparency() const { return transparent_; }
@ -58,5 +58,5 @@ class Image : public BaseImage {
bool transparent_; bool transparent_;
}; };
} // namespace display } // namespace image
} // namespace esphome } // namespace esphome

View file

@ -57,6 +57,10 @@ void MDNSComponent::compile_records_() {
service.txt_records.push_back({"network", "ethernet"}); service.txt_records.push_back({"network", "ethernet"});
#endif #endif
#ifdef USE_API_NOISE
service.txt_records.push_back({"api_encryption", "Noise_NNpsk0_25519_ChaChaPoly_SHA256"});
#endif
#ifdef ESPHOME_PROJECT_NAME #ifdef ESPHOME_PROJECT_NAME
service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME}); service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME});
service.txt_records.push_back({"project_version", ESPHOME_PROJECT_VERSION}); 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(); const auto &manu_datas = device.get_manufacturer_datas();
if (manu_datas.size() != 1) { 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; return false;
} }
const auto &manu_data = manu_datas[0]; 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) { 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; 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; const u_int8_t hardware_id = mopeka_data->data_1 & 0xCF;
if (static_cast<SensorType>(hardware_id) != STANDARD && static_cast<SensorType>(hardware_id) != XL) { if (static_cast<SensorType>(hardware_id) != STANDARD && static_cast<SensorType>(hardware_id) != XL) {
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; 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 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 sync pressed: %d", device.address_str().c_str(), mopeka_data->sync_pressed);
for (u_int8_t i = 0; i < 3; i++) { 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); 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); 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); 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); 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. // This value is better than a previous one.
best_value = measurements_value[i]; best_value = measurements_value[i];
best_time = measurement_time; best_time = measurement_time;
}
// Reset measurement_time or next values. // Reset measurement_time or next values.
measurement_time = 0; 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); 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. // 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) { if (this->distance_ != nullptr) {
this->distance_->publish_state(0); this->distance_->publish_state(0);
} }
@ -167,7 +167,7 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
} }
} else { } else {
float lpg_speed_of_sound = this->get_lpg_speed_of_sound_(temp_in_c); 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; uint32_t distance_value = lpg_speed_of_sound * best_time / 100.0f;

View file

@ -1,5 +1,5 @@
#include "qr_code.h" #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/color.h"
#include "esphome/core/log.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); ESP_LOGV(TAG, "Drawing QR code at (%d, %d)", x_offset, y_offset);
if (this->needs_update_) { if (this->needs_update_) {

View file

@ -9,13 +9,13 @@
namespace esphome { namespace esphome {
// forward declare DisplayBuffer // forward declare DisplayBuffer
namespace display { namespace display {
class DisplayBuffer; class Display;
} // namespace display } // namespace display
namespace qr_code { namespace qr_code {
class QrCode : public Component { class QrCode : public Component {
public: 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; void dump_config() override;

View file

@ -42,14 +42,19 @@ void SCD30Component::setup() {
ESP_LOGD(TAG, "SCD30 Firmware v%0d.%02d", (uint16_t(raw_firmware_version[0]) >> 8), ESP_LOGD(TAG, "SCD30 Firmware v%0d.%02d", (uint16_t(raw_firmware_version[0]) >> 8),
uint16_t(raw_firmware_version[0] & 0xFF)); uint16_t(raw_firmware_version[0] & 0xFF));
if (this->temperature_offset_ != 0) { uint16_t temp_offset;
if (!this->write_command(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t) (temperature_offset_ * 100.0))) { 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."); ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset.");
this->error_code_ = MEASUREMENT_INIT_FAILED; this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed(); this->mark_failed();
return; return;
} }
}
#ifdef USE_ESP32 #ifdef USE_ESP32
// According ESP32 clock stretching is typically 30ms and up to 150ms "due to // According ESP32 clock stretching is typically 30ms and up to 150ms "due to
// internal calibration processes". The I2C peripheral only supports 13ms (at // internal calibration processes". The I2C peripheral only supports 13ms (at

View file

@ -68,7 +68,10 @@ CONFIG_SCHEMA = (
cv.int_range(min=0, max=0xFFFF, max_included=False), cv.int_range(min=0, max=0xFFFF, max_included=False),
), ),
cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION, default=0): cv.pressure, 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.Optional(CONF_UPDATE_INTERVAL, default="60s"): cv.All(
cv.positive_time_period_seconds, cv.positive_time_period_seconds,
cv.Range( cv.Range(

View file

@ -33,6 +33,7 @@ SCRIPT_MODES = {
PARAMETER_TYPE_TRANSLATIONS = { PARAMETER_TYPE_TRANSLATIONS = {
"string": "std::string", "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): 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): async def get_ordered_args(config, script_params):
config_args = config.copy() config_args = config.copy()
config_args.pop(CONF_ID) config_args.pop(CONF_ID)
@ -160,7 +171,9 @@ async def script_execute_action_to_code(config, action_id, template_arg, args):
raise EsphomeError( raise EsphomeError(
f"Missing parameter: '{name}' in script.execute {config[CONF_ID]}" 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) script_args.append(arg)
return script_args return script_args

View file

@ -16,6 +16,22 @@ CODEOWNERS = ["@esphome/core"]
spi_ns = cg.esphome_ns.namespace("spi") spi_ns = cg.esphome_ns.namespace("spi")
SPIComponent = spi_ns.class_("SPIComponent", cg.Component) SPIComponent = spi_ns.class_("SPIComponent", cg.Component)
SPIDevice = spi_ns.class_("SPIDevice") 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 MULTI_CONF = True
CONF_FORCE_SW = "force_sw" CONF_FORCE_SW = "force_sw"

View file

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

View file

@ -6,11 +6,21 @@ namespace template_ {
static const char *const TAG = "template.binary_sensor"; static const char *const TAG = "template.binary_sensor";
void TemplateBinarySensor::loop() { void TemplateBinarySensor::setup() {
if (!this->f_.has_value()) if (!this->publish_initial_state_)
return; 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()) { if (s.has_value()) {
this->publish_state(*s); this->publish_state(*s);
} }

View file

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

View file

@ -7,6 +7,17 @@ namespace touchscreen {
static const char *const TAG = "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) { void Touchscreen::send_touch_(TouchPoint tp) {
ESP_LOGV(TAG, "Touch (x=%d, y=%d)", tp.x, tp.y); ESP_LOGV(TAG, "Touch (x=%d, y=%d)", tp.x, tp.y);
this->touch_trigger_.trigger(tp); this->touch_trigger_.trigger(tp);

View file

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

View file

@ -63,6 +63,7 @@ WaveshareEPaper7P5InHDB = waveshare_epaper_ns.class_(
WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_( WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_(
"WaveshareEPaper2P13InDKE", WaveshareEPaper "WaveshareEPaper2P13InDKE", WaveshareEPaper
) )
GDEW0154M09 = waveshare_epaper_ns.class_("GDEW0154M09", WaveshareEPaper)
WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel") WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel")
WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeBModel") WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeBModel")
@ -91,6 +92,7 @@ MODELS = {
"7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt), "7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt),
"7.50in-hd-b": ("b", WaveshareEPaper7P5InHDB), "7.50in-hd-b": ("b", WaveshareEPaper7P5InHDB),
"2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE), "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); 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[] = { 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, 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, 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; 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 { class WaveshareEPaper2P9InB : public WaveshareEPaper {
public: public:
void initialize() override; 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 = [ RESERVED_IDS = [
# C++ keywords http://en.cppreference.com/w/cpp/keyword # C++ keywords http://en.cppreference.com/w/cpp/keyword
"alarm",
"alignas", "alignas",
"alignof", "alignof",
"and", "and",

View file

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

View file

@ -201,8 +201,8 @@ WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {
uint32_t now = millis(); uint32_t now = millis();
if (now - started_ > 50) { if (now - started_ > 50) {
const char *src = component_ == nullptr ? "<null>" : component_->get_component_source(); 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_LOGW(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, "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) if (timeout == SCHEDULER_DONT_RUN)
return; 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>(); auto item = make_unique<SchedulerItem>();
item->component = component; item->component = component;
@ -60,7 +60,7 @@ void HOT Scheduler::set_interval(Component *component, const std::string &name,
if (interval != 0) if (interval != 0)
offset = (random_uint32() % interval) / 2; 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>(); auto item = make_unique<SchedulerItem>();
item->component = component; 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) if (initial_wait_time == SCHEDULER_DONT_RUN)
return; return;
ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%u, max_attempts=%u, backoff_factor=%0.1f)", name.c_str(), ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%" PRIu32 ", max_attempts=%u, backoff_factor=%0.1f)",
initial_wait_time, max_attempts, backoff_increase_factor); name.c_str(), initial_wait_time, max_attempts, backoff_increase_factor);
if (backoff_increase_factor < 0.0001) { if (backoff_increase_factor < 0.0001) {
ESP_LOGE(TAG, ESP_LOGE(TAG,
@ -154,16 +154,16 @@ void HOT Scheduler::call() {
if (now - last_print > 2000) { if (now - last_print > 2000) {
last_print = now; last_print = now;
std::vector<std::unique_ptr<SchedulerItem>> old_items; 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_()) { while (!this->empty_()) {
this->lock_.lock(); this->lock_.lock();
auto item = std::move(this->items_[0]); auto item = std::move(this->items_[0]);
this->pop_raw_(); this->pop_raw_();
this->lock_.unlock(); this->lock_.unlock();
ESP_LOGVV(TAG, " %s '%s' interval=%u last_execution=%u (%u) next=%u (%u)", item->get_type_str(), ESP_LOGVV(TAG, " %s '%s' interval=%" PRIu32 " last_execution=%" PRIu32 " (%u) next=%" PRIu32 " (%u)",
item->name.c_str(), item->interval, item->last_execution, item->last_execution_major, item->get_type_str(), item->name.c_str(), item->interval, item->last_execution,
item->next_execution(), item->next_execution_major()); item->last_execution_major, item->next_execution(), item->next_execution_major());
old_items.push_back(std::move(item)); old_items.push_back(std::move(item));
} }
@ -222,8 +222,8 @@ void HOT Scheduler::call() {
} }
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", item->get_type_str(), ESP_LOGVV(TAG, "Running %s '%s' with interval=%" PRIu32 " last_execution=%" PRIu32 " (now=%" PRIu32 ")",
item->name.c_str(), item->interval, item->last_execution, now); item->get_type_str(), item->name.c_str(), item->interval, item->last_execution, now);
#endif #endif
// Warning: During callback(), a lot of stuff can happen, including: // Warning: During callback(), a lot of stuff can happen, including:

View file

@ -546,22 +546,11 @@ class DownloadBinaryRequestHandler(BaseHandler):
return return
with open(path, "rb") as f: with open(path, "rb") as f:
while True: data = f.read()
# 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.
# Read file in blocks of 256KB.
data = f.read(256 * 1024)
if not data:
break
if compressed: if compressed:
data = gzip.compress(data, 9) data = gzip.compress(data, 9)
self.write(data) self.write(data)
self.finish() self.finish()

View file

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

View file

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

View file

@ -1,13 +1,13 @@
pylint==2.17.4 pylint==2.17.4
flake8==6.0.0 # also change in .pre-commit-config.yaml when updating 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 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 pre-commit
# Unit tests # Unit tests
pytest==7.3.2 pytest==7.4.0
pytest-cov==4.1.0 pytest-cov==4.1.0
pytest-mock==3.10.0 pytest-mock==3.11.1
pytest-asyncio==0.21.0 pytest-asyncio==0.21.0
asyncmock==0.4.2 asyncmock==0.4.2
hypothesis==5.49.0 hypothesis==5.49.0

View file

@ -461,8 +461,10 @@ def merge(source, destination):
def is_platform_schema(schema_name): def is_platform_schema(schema_name):
# added mostly because of schema_name == "microphone.MICROPHONE_SCHEMA" # 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) # 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(): def shrink():
@ -530,6 +532,10 @@ def shrink():
elif not key_s: elif not key_s:
for target in paths: for target in paths:
target_s = get_arr_path_schema(target) 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] assert target_s[S_SCHEMA][S_EXTENDS] == [x]
target_s.pop(S_SCHEMA) target_s.pop(S_SCHEMA)
target_s.pop(S_TYPE) # undefined 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/button/button.h",
"esphome/components/climate/climate.h", "esphome/components/climate/climate.h",
"esphome/components/cover/cover.h", "esphome/components/cover/cover.h",
"esphome/components/display/display_buffer.h", "esphome/components/display/display.h",
"esphome/components/fan/fan.h", "esphome/components/fan/fan.h",
"esphome/components/i2c/i2c.h", "esphome/components/i2c/i2c.h",
"esphome/components/lock/lock.h", "esphome/components/lock/lock.h",

View file

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

View file

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

View file

@ -397,6 +397,19 @@ sensor:
name: MICS-4514 C2H5OH name: MICS-4514 C2H5OH
ammonia: ammonia:
name: MICS-4514 NH3 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: time:
- platform: homeassistant - platform: homeassistant

View file

@ -504,6 +504,14 @@ display:
full_update_every: 30 full_update_every: 30
lambda: |- lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height()); 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 - platform: inkplate6
id: inkplate_display id: inkplate_display
greyscale: false greyscale: false