Skip to main content

在 Home Assistant 中与 ESPHome 配合使用


Home Assistant 简介

Home Assistant 是一个功能强大的开源家庭自动化平台,允许您从一个统一的界面控制和监控智能家居设备。它充当智能家居的中央枢纽,使您能够自动化例程、监控传感器,并创建更智能的生活空间。

为什么选择 Home Assistant?

  • 本地控制:与许多基于云的解决方案不同,Home Assistant 在您的网络上本地运行,确保您的数据保持私密,即使没有互联网访问,您的自动化也能正常工作。

  • 广泛的设备支持:Home Assistant 集成了数千种不同的智能家居设备和服务,使其具有高度的通用性和面向未来的特性。

  • 强大的自动化:创建复杂的自动化规则,可以响应各种触发器,如时间、设备状态、传感器读数等。

  • 可定制的仪表板:设计您自己的用户界面,显示对您最重要的信息。

为什么将电子纸显示屏与 Home Assistant 结合使用?

XIAO 7.5" ePaper Panel 是 Home Assistant 的绝佳伴侣,原因如下:

  1. 节能高效:电子纸显示屏仅在更新内容时消耗电力,非常适合显示持久信息,如天气预报、日历事件或系统状态。

  2. 清晰可见:与 LCD 屏幕不同,电子纸显示屏在任何光照条件下都易于阅读,包括直射阳光,使其成为壁挂式家庭控制面板的理想选择。

  3. 长电池续航:结合深度睡眠模式,显示屏可以在单次电池充电下运行数月,同时仍能提供一目了然的有价值信息。

  4. 灵活集成:通过 ESPHome,显示屏与 Home Assistant 无缝集成,允许您以优雅、始终可见的格式显示智能家居系统中的任何数据。

这些优势使 XIAO 7.5" ePaper Panel 成为为您的 Home Assistant 设置创建节能、始终在线信息显示屏的理想选择。

ESPHome 集成

ESPHome 是一个专为 ESP8266/ESP32 设备设计的开源固件创建工具。它允许您使用简单的 YAML 配置文件创建自定义固件,然后可以将其刷写到您的设备上。对于 XIAO 7.5" ePaper Panel,ESPHome 作为重要的中间件,实现设备与 Home Assistant 之间的通信。

该系统通过将您的 YAML 配置转换为在 ESP 设备上运行的功能齐全的固件来工作。该固件处理连接到网络、与 Home Assistant 通信以及控制 ePaper 显示屏的所有复杂任务。当与 Home Assistant 结合使用时,ESPHome 为创建复杂的家庭自动化显示屏和控制器提供了强大的平台。

让我们探索如何设置并充分利用这个多功能显示屏。

开始使用

在本文教程内容开始之前,您可能需要准备以下硬件。

所需材料

XIAO 7.5" ePaper PanelHome Assistant Green

Home Assistant Green 是自动化您家庭的最简单且最注重隐私的方式。它提供轻松的设置,让您只需一个系统就能控制所有智能设备,默认情况下所有数据都存储在本地。这款主板受益于蓬勃发展的 Home Assistant 生态系统,并将通过开源每月得到改进。

我们建议在本教程中使用 Home Assistant Green 作为 Home Assistant 主机,或者您可以使用任何带有 Supervisor 的 Home Assistant 主机。

安装 Home Assistant

我们还为一些 Seeed Studio 产品编写了如何安装 Home Assistant 的教程,请参考它们。

如果您没有使用 Seeed Studio 产品,您也可以在官方 Home Assistant 网站上查看并学习如何为其他产品安装 Home Assistant。

步骤 1. 安装 ESPHome

如果您已经安装了 ESPHome,可以跳过此步骤。

转到 Settings -> Add-ons -> ADD-ON STORE

搜索 ESPHome 并点击它。点击 INSTALLSTART

tip

如果您在附加组件商店中找不到 ESPHome,请确保您使用的是支持附加组件的 Home Assistant 安装(如 Home Assistant OS 或监督安装)。对于其他安装类型(如 Home Assistant Container),您可能需要使用 Docker 独立运行 ESPHome Device Builder。有关更多详细信息,请参阅官方 ESPHome 文档

然后,ESPHome Builder 将出现在侧边栏中。

步骤 2. 添加新设备

转到 ESPHome 并点击 NEW DEVICE

给设备起一个您喜欢的名称,然后点击 NEXT

创建新设备后,点击 EDIT

步骤 3. 安装固件

这是一个非常基本的示例,将在显示屏上显示"Hello World!"。

主要目的是向您展示将固件安装到设备的不同方法。

安装 ESPHome 并添加新设备后,您可以复制下面的代码并将其粘贴到 captive_portal 之后,如下所示。

点击此处预览完整代码

# define font to display words
font:
- file: "gfonts://Inter@700"
id: font1
size: 24

# define SPI interface
spi:
clk_pin: GPIO8
mosi_pin: GPIO10

display:
- platform: waveshare_epaper
cs_pin: GPIO3
dc_pin: GPIO5
busy_pin:
number: GPIO4
inverted: true
reset_pin: GPIO2
model: 7.50inv2
update_interval: 30s
lambda: |-
it.print(0, 0, id(font1), "Hello World!");

点击 INSTALL 将代码安装到设备上,您将看到以下图像。

tip

如果您的 Home Assistant 主机(Raspberry PI/Green/Yellow 等)距离您较远,我们建议使用此方法。您可以使用手边的计算机进行安装。

首先,您需要点击 Manual download 下载编译好的固件。

打开这个网站,我们将在这里上传固件到电子纸面板。

返回 ESPHome 下载固件。

选择 Factory 格式。

使用 USB 线缆 将电子纸面板连接到您的计算机 并点击 CONNECT

选择 usbmodemxxx(Windows 是 COMxxx)并点击连接。遇到问题?点击这里。

点击 INSTALL 并选择您刚才下载的固件。

稍等片刻,您将在显示屏上看到 'Hello world!' ~

基本用法

1. 显示形状

此示例将在显示屏上显示形状。

安装 ESPHome 并添加新设备后,您可以复制下面的代码并将其粘贴到 captive_portal 部分,如下图所示。

点击这里复制代码。
spi:
clk_pin: GPIO8
mosi_pin: GPIO10

display:
- platform: waveshare_epaper
model: 7.50inv2
cs_pin: GPIO3
dc_pin: GPIO5
reset_pin: GPIO2
busy_pin:
number: GPIO4
inverted: true
update_interval: 5min
lambda: |-
it.rectangle(10, 10, 100, 50);
it.rectangle(150, 10, 50, 50);
it.circle(250, 35, 25);

it.filled_rectangle(10, 80, 100, 50);
it.filled_rectangle(150, 80, 50, 50);
it.filled_circle(250, 105, 25);

当您看到如下图所示的反馈时,说明代码运行成功。

您也可以点击这里查看更多用法。

2. 在 HA 中显示信息

此示例将在显示屏上显示 HA 中的信息。

首先,您需要将此设备添加到 HA 中。否则,您无法从 HA 获取信息。

如果 HA 没有显示设备,您应该先运行上面的演示。运行上面的演示后,您可以在 HA 中看到设备。

然后,点击 SUBMITFINISH

安装 ESPHome 并添加新设备后,您可以复制下面的代码并将其粘贴到 captive_portal 之后,如下所示。

点击这里预览完整代码

# Define font to show info
font:
- file: "gfonts://Inter@700"
id: myFont
size: 24

# Get info from HA, as string format
text_sensor:
- platform: homeassistant
entity_id: weather.forecast_home
id: myWeather
internal: true
- platform: homeassistant
entity_id: weather.forecast_home
id: myTemperature
attribute: "temperature"
internal: true

# Get info from HA, as float format
sensor:
- platform: homeassistant
entity_id: weather.forecast_home
id: myPressure
attribute: "pressure"
internal: true

# Display info via SPI
spi:
clk_pin: GPIO8
mosi_pin: GPIO10

display:
- platform: waveshare_epaper
cs_pin: GPIO3
dc_pin: GPIO5
busy_pin:
number: GPIO4
inverted: true
reset_pin: GPIO2
model: 7.50inv2
update_interval: 30s
lambda: |-
//print info in log
ESP_LOGD("epaper", "weather: %s", id(myWeather).state.c_str());
ESP_LOGD("epaper", "temperature: %s", id(myTemperature).state.c_str());
ESP_LOGD("epaper", "pressure: %.1f", id(myPressure).state);
//display info in epaper screen
it.printf(100, 100, id(myFont), "%s", id(myWeather).state.c_str());
it.printf(100, 150, id(myFont), "%s", id(myTemperature).state.c_str());
it.printf(100, 200, id(myFont), "%.1f", id(myPressure).state);

将这些代码安装到您的设备上。

代码的功能是从 HA 获取天气温度压力信息,并在显示屏上显示它们。

当您看到如下图所示的反馈时,说明代码运行成功。

3. 显示图标

此示例将在显示屏上显示图标。

首先,我们需要安装一个文件编辑器插件。搜索 Studio Code Server 并点击它。点击 INSTALLSTART

然后,创建一个名为 fonts 的新文件夹,下载此文件并将其放入 fonts 文件夹

安装 ESPHome 并添加新设备后,您可以复制下面的代码并将其粘贴到 captive_portal 之后,如下所示。

点击此处预览完整代码
font:
- file: 'fonts/materialdesignicons-webfont.ttf' #here is the directory to save ttf file
id: font_mdi_large
size: 200 # big size icon
glyphs: &mdi-weather-glyphs
- "\U000F0595" # weather cloudy
- "\U000F0592" # weather hail
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font_mdi_medium # small size icon
size: 40
glyphs: *mdi-weather-glyphs

spi:
clk_pin: GPIO8
mosi_pin: GPIO10

display:
- platform: waveshare_epaper
cs_pin: GPIO3
dc_pin: GPIO5
busy_pin:
number: GPIO4
inverted: true
reset_pin: GPIO2
model: 7.50inv2
update_interval: 30s
lambda: |-
it.printf(100, 200, id(font_mdi_medium), TextAlign::CENTER, "\U000F0595");
it.printf(400, 200, id(font_mdi_large), TextAlign::CENTER, "\U000F0592");

当您看到如下图所示的反馈时,说明代码运行成功。

如果您想使用其他图标,可以点击下面的按钮探索更多选项。

选择您想要的图标。

复制代码并将其粘贴到 captive_portal 部分,如下图所示。

4. 显示图像

此示例将在显示屏上显示您喜欢的任何图像。

与前面的示例一样,我们需要安装 Studio Code Server 并创建一个名为 image 的新文件夹来保存图像。

然后将图像放入 image 文件夹中。您可以点击下面的按钮下载一张图像进行尝试。

安装 ESPHome 并添加新设备后,您可以复制下面的代码并将其粘贴到 captive_portal 之后,如下所示。

点击这里预览完整代码

image:
- file: /config/esphome/image/wifi.jpg # the path where you save the image, png or jpg format
id: myImage
type: BINARY
resize: 800x480 # how big you want to show, the biggest size should be as same as ePaper Penal pixel(800x480)
invert_alpha: true # invert color

spi:
clk_pin: GPIO8
mosi_pin: GPIO10

display:
- platform: waveshare_epaper
cs_pin: GPIO3
dc_pin: GPIO5
busy_pin:
number: GPIO4
inverted: true
reset_pin: GPIO2
model: 7.50inv2
update_interval: 30s
lambda: |-
it.image(0, 0, id(myImage));

当您看到如下图所示的反馈时,说明代码运行成功。

演示 1. 将 Home Assistant 仪表板截图

此示例将在显示屏上显示 HA 的截图。

首先,您需要安装一个截图插件 Puppet点击这里安装。

请注意版本应该高于或等于 1.11.4。安装后,进入配置页面。我们需要为此插件创建一个 access_token。

请参阅下一步创建令牌并粘贴到这里。

转到安全页面的底部并创建一个令牌,然后复制并粘贴到 Puppet 插件中。

记住要重启 Puppet 插件。

启动插件将在端口 10000 上启动一个新服务器。您请求的任何路径都将返回该页面的截图。您需要指定所需的视口大小。

例如,要获取默认仪表板的 1000px x 1000px 截图,请获取:

# http://192.168.1.191:10000/lovelace/0?viewport=1000x1000(我的地址)

http://homeassistant.local:10000/lovelace/0?viewport=1000x1000

为了减少 E Ink® 显示器的调色板,您可以添加 eink 参数。该值表示要使用的颜色数量(包括黑色)。例如,对于 2 色 E Ink® 显示器:

http://homeassistant.local:10000/lovelace/0?viewport=1000x1000&eink=2

如果您使用 eink=2,您还可以通过添加 invert 参数来反转颜色:

http://homeassistant.local:10000/lovelace/0?viewport=1000x1000&eink=2&invert

此外,您还可以截取其他页面的屏幕截图,例如 HA 中的待办事项列表页面:

http://192.168.1.191:10000/todo?viewport=800x480&eink=2&invert

您可以通过在浏览器中输入此链接来查看截图效果。

安装 ESPHome 并添加新设备后,您可以复制下面的代码并将其粘贴到 captive_portal 之后,如下所示。

点击此处预览完整代码

http_request:
verify_ssl: false
timeout: 10s
watchdog_timeout: 15s

online_image:
- id: dashboard_image
format: PNG
type: BINARY
buffer_size: 30000
url: http://192.168.1.191:10000/todo?viewport=800x480&eink=2&invert #将此链接更改为您的截图链接
update_interval: 30s
on_download_finished:
- delay: 0ms
- component.update: main_display

spi:
clk_pin: GPIO8
mosi_pin: GPIO10

display:
- platform: waveshare_epaper
id: main_display
cs_pin: GPIO3
dc_pin: GPIO5
busy_pin:
number: GPIO4
inverted: true
reset_pin: GPIO2
model: 7.50inv2
update_interval: never
lambda: |-
it.image(0, 0, id(dashboard_image));

当您看到如下图所示的反馈时,说明代码运行成功。

演示2. 深度睡眠模式

tip

在深度睡眠模式下,您无法直接向设备上传代码。您需要进入下载模式。点击这里跳转到Q3。

此示例将展示如何使用深度睡眠模式来节省电力。每6小时更新一次信息。2000mAh电池可以持续使用约3个月。

安装ESPHome并添加新设备后,您可以复制下面的代码并将其粘贴到captive_portal之后,如下所示。

点击这里预览完整代码
globals:
- id: sleep_counter
type: int
restore_value: yes # 关键参数,使用RTC存储
initial_value: '0'

# 这里是深度睡眠部分
deep_sleep:
id: deep_sleep_1
run_duration: 30s # 设备唤醒并运行30秒(足够显示)
sleep_duration: 3min # 深度睡眠3分钟

interval:
- interval: 29s # 在run_duration结束前运行此命令
then:
- logger.log: "正在进入深度睡眠..."

font:
- file: "gfonts://Inter@700"
id: font1
size: 24

spi:
clk_pin: GPIO8
mosi_pin: GPIO10

display:
- platform: waveshare_epaper
cs_pin: GPIO3
dc_pin: GPIO5
busy_pin:
number: GPIO4
inverted: true
reset_pin: GPIO2
model: 7.50inv2
update_interval: 3min
lambda: |-
id(sleep_counter) += 1;
ESP_LOGD("main", "唤醒次数: %d", id(sleep_counter));
it.printf(100, 100, id(font1), "唤醒次数: %d", id(sleep_counter));

你会看到一个计数器。每次唤醒时它都会增加一。

演示 3. 综合示例

tip

为了让您更好地理解,我们强烈建议您首先运行上面的基本用法。

这个示例将展示如何从 HA 获取天气信息和日历信息并在显示屏上显示它们。此外,它将使用深度睡眠模式来节省电力。每 6 小时更新一次信息。一个 2000mAh 电池可以持续约 3 个月。

首先,您需要检查 HA 中是否有天气组件。通常,在安装 HA 时您会有一个。

您也可以转到开发者工具 -> 状态来检查 HA 中是否有天气信息。这是您稍后将获得的信息。

其次,您需要在 HA 中安装日历组件。

转到设置 -> 设备和服务 -> 集成 -> 添加集成

选择本地日历并点击提交按钮。

之后,您将在已配置部分和侧边栏中看到本地日历。

点击侧边栏中的日历并创建 3 个新日历,名称为calendarepaper_eventnew_calendar。您也可以使用其他名称,但请在稍后的代码中保持相同的名称。

tip

在复制代码之前,请将 wifi.jpg图标 ttf 文件和字体 ttf 文件 放入image文件夹和fonts文件夹中。

点击这里预览完整代码

esphome:
name: dashboard
friendly_name: dashboard

esp32:
board: esp32-c3-devkitm-1
framework:
type: arduino

# 启用日志记录
logger:

# 启用 Home Assistant API
api:
encryption:
key: "jBgx0v+Y9eKiQmYTk0SCnHgtDowNDZqgFU26Z2VTYzM="

ota:
- platform: esphome
password: "9f78b53ef216c5d689f7408bb1ebe728"

# -------------------------------------- 保持上面的代码不变,修改下面的代码 --------------------------------------

globals:
- id: wifi_status
type: int
restore_value: no
initial_value: "0"
- id: first_update_done
type: bool
restore_value: no
initial_value: "false"

wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
on_connect:
then:
- lambda: |-
id(wifi_status) = 1;
on_disconnect:
then:
- lambda: |-
id(wifi_status) = 0;


captive_portal:

# 这里是深度睡眠部分
deep_sleep:
id: deep_sleep_1
run_duration: 1min # 设备唤醒并运行60秒(足够拉取数据和更新)
sleep_duration: 60min # 深度睡眠1小时

script:
- id: update_display
then:
- component.update: my_display

interval:
# 条件:wifi已连接 && 数据已获取 && 首次
- interval: 10s # 每秒检查一次
then:
- if:
condition:
and:
- wifi.connected:
- lambda: "return !id(ha_calendar_event_1).state.empty();"
- lambda: "return !id(first_update_done);"
then:
- lambda: |-
ESP_LOGD("Display", "正在更新显示...");
- script.execute: update_display # 立即刷新
- lambda: "id(first_update_done) = true;"
- interval: 59s # 在运行时间结束前1秒运行此命令
then:
- logger.log: "现在进入深度睡眠..."


image:
- file: image/wifi.jpg
type: BINARY
id: esphome_logo
resize: 400x240
invert_alpha: true

# 连接到 Home Assistant 获取时间
time:
- platform: homeassistant
id: homeassistant_time

text_sensor:
- platform: homeassistant
id: ha_calendar_event_1
entity_id: calendar.calendar
attribute: "message"
- platform: homeassistant
id: ha_calendar_start_time_1
entity_id: calendar.calendar
attribute: "start_time"
- platform: homeassistant
id: ha_calendar_end_time_1
entity_id: calendar.calendar
attribute: "end_time"

- platform: homeassistant
id: ha_calendar_event_2
entity_id: calendar.epaper_event
attribute: "message"
- platform: homeassistant
id: ha_calendar_start_time_2
entity_id: calendar.epaper_event
attribute: "start_time"
- platform: homeassistant
id: ha_calendar_end_time_2
entity_id: calendar.epaper_event
attribute: "end_time"

- platform: homeassistant
id: ha_calendar_event_3
entity_id: calendar.new_calendar
attribute: "message"
- platform: homeassistant
id: ha_calendar_start_time_3
entity_id: calendar.new_calendar
attribute: "start_time"
- platform: homeassistant
id: ha_calendar_end_time_3
entity_id: calendar.new_calendar
attribute: "end_time"

- platform: homeassistant
entity_id: weather.forecast_home
id: myWeather
- platform: homeassistant
entity_id: weather.forecast_home
id: temp
attribute: "temperature"
- platform: homeassistant
entity_id: weather.forecast_home
id: humi
attribute: "humidity"
- platform: homeassistant
entity_id: weather.forecast_home
id: press
attribute: "pressure"
- platform: homeassistant
entity_id: weather.forecast_home
id: wind
attribute: "wind_speed"

font:
- file: "fonts/Montserrat-Black.ttf"
id: web_font
size: 20
- file: "fonts/Montserrat-Black.ttf"
id: data_font
size: 30
- file: "fonts/Montserrat-Black.ttf"
id: sensor_font
size: 22

- file: "gfonts://Inter@700" #
id: font1
size: 24

- file: 'fonts/materialdesignicons-webfont.ttf' # 保存ttf文件的目录
id: font_mdi_large
size: 200
glyphs: &mdi-weather-glyphs # https://pictogrammers.com/library/mdi/
- "\U000F050F" # 温度计
- "\U000F058E" # 湿度
- "\U000F059D" # 风速
- "\U000F0D60" # 大气压力
- "\U000F0590" # 多云天气
- "\U000F0596" # 雨天天气
- "\U000F0598" # 雪天天气
- "\U000F0599" # 晴天天气
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font_weather # 复制上面的图标并将大小改为40
size: 200
glyphs: *mdi-weather-glyphs
- file: 'fonts/materialdesignicons-webfont.ttf'
id: img_font_sensor # 复制上面的图标并将大小改为40
size: 70
glyphs: *mdi-weather-glyphs

spi:
clk_pin: GPIO8
mosi_pin: GPIO10

display:
- platform: waveshare_epaper
id: my_display
cs_pin: GPIO3
dc_pin: GPIO5
busy_pin:
number: GPIO4
inverted: true
reset_pin: GPIO2
model: 7.50inv2
update_interval: 50s
lambda: |-
if(id(wifi_status) == 0){
it.image(180, 0, id(esphome_logo));
it.print(230, 300, id(data_font), "WI-FI 连接中");
}else{
// 在这里绘制天气图像
std::string weather_string = id(myWeather).state.c_str();
if(weather_string == "rainy" || weather_string == "lightning" || weather_string == "pouring"){
// 绘制雨天天气图像
it.printf(120, 85, id(font_weather), TextAlign::CENTER, "\U000F0596");
}else if(weather_string == "snowy"){
// 绘制雪天天气图像
it.printf(120, 85, id(font_weather), TextAlign::CENTER, "\U000F0598");
}else if(weather_string == "sunny" || weather_string == "windy"){
// 绘制晴天天气图像
it.printf(120, 85, id(font_weather), TextAlign::CENTER, "\U000F0599");
}else{
// 绘制多云天气图像
it.printf(120, 85, id(font_weather), TextAlign::CENTER, "\U000F0590");
}

auto time_now = id(homeassistant_time).now();
// 月份转换
const char* months[] = {
"一月", "二月", "三月", "四月", "五月", "六月",
"七月", "八月", "九月", "十月", "十一月", "十二月"
};
const char* month_str = months[time_now.month - 1]; // 月份索引从0开始
// 获取日期
int day = time_now.day_of_month;
// 绘制日期
it.printf(250, 110, id(data_font), "%s %d", month_str, day);
// 获取星期几
const char* days[] = {"星期六", "星期日", "星期一", "星期二", "星期三", "星期四", "星期五"};
const char* day_of_week = days[time_now.day_of_week];
it.printf(250, 70, id(data_font), "%s", day_of_week);

int x = 20, y = 180, w = 180, h = 120, r = 10, thickness = 4;
// 绘制四个边框
it.filled_rectangle(x + r, y, w - 2 * r, thickness); // 顶部边框
it.filled_rectangle(x + r, y + h - thickness, w - 2 * r, thickness); // 底部边框
it.filled_rectangle(x, y + r, thickness, h - 2 * r); // 左边框
it.filled_rectangle(x + w - thickness, y + r, thickness, h - 2 * r); // 右边框
// 绘制四个圆角
it.filled_circle(x + r, y + r, r); // 左上角
it.filled_circle(x + w - r, y + r, r); // 右上角
it.filled_circle(x + r, y + h - r, r); // 左下角
it.filled_circle(x + w - r, y + h - r, r); // 右下角
// 用黑色填充内部形成边框
it.filled_rectangle(x + thickness, y + thickness, w - 2 * thickness, h - 2 * thickness, COLOR_OFF);
// 温度
it.printf(x+10, y+10, id(sensor_font), "温度");
it.printf(x+45, y+75, id(img_font_sensor), TextAlign::CENTER, "\U000F050F");
// 获取温度数据
it.printf(x+75,y+65, id(data_font), "%s°F", id(temp).state.c_str());

x = 220;
y = 180;
// 绘制四个边框
it.filled_rectangle(x + r, y, w - 2 * r, thickness); // 顶部边框
it.filled_rectangle(x + r, y + h - thickness, w - 2 * r, thickness); // 底部边框
it.filled_rectangle(x, y + r, thickness, h - 2 * r); // 左边框
it.filled_rectangle(x + w - thickness, y + r, thickness, h - 2 * r); // 右边框
// 绘制四个圆角
it.filled_circle(x + r, y + r, r); // 左上角
it.filled_circle(x + w - r, y + r, r); // 右上角
it.filled_circle(x + r, y + h - r, r); // 左下角
it.filled_circle(x + w - r, y + h - r, r); // 右下角
// 用黑色填充内部形成边框
it.filled_rectangle(x + thickness, y + thickness, w - 2 * thickness, h - 2 * thickness, COLOR_OFF);
// 湿度
it.printf(x+10, y+10, id(sensor_font), "湿度");
it.printf(x+45, y+75, id(img_font_sensor), TextAlign::CENTER, "\U000F058E");
// 获取湿度数据
it.printf(x+75,y+65, id(data_font), "%s%%", id(humi).state.c_str());

x = 20;
y = 320;
// 绘制四个边框
it.filled_rectangle(x + r, y, w - 2 * r, thickness); // 顶部边框
it.filled_rectangle(x + r, y + h - thickness, w - 2 * r, thickness); // 底部边框
it.filled_rectangle(x, y + r, thickness, h - 2 * r); // 左边框
it.filled_rectangle(x + w - thickness, y + r, thickness, h - 2 * r); // 右边框
// 绘制四个圆角
it.filled_circle(x + r, y + r, r); // 左上角
it.filled_circle(x + w - r, y + r, r); // 右上角
it.filled_circle(x + r, y + h - r, r); // 左下角
it.filled_circle(x + w - r, y + h - r, r); // 右下角
// 用黑色填充内部形成边框
it.filled_rectangle(x + thickness, y + thickness, w - 2 * thickness, h - 2 * thickness, COLOR_OFF);
// 气压
it.printf(x+10, y+10, id(sensor_font), "气压");
it.printf(x+45, y+75, id(img_font_sensor), TextAlign::CENTER, "\U000F0D60");
// 获取大气压力数据
it.printf(x+85,y+50, id(data_font), "%s", id(press).state.c_str());
it.printf(x+85,y+78, id(sensor_font), "inHg");

x = 220;
y = 320;
// 绘制四个边框
it.filled_rectangle(x + r, y, w - 2 * r, thickness); // 顶部边框
it.filled_rectangle(x + r, y + h - thickness, w - 2 * r, thickness); // 底部边框
it.filled_rectangle(x, y + r, thickness, h - 2 * r); // 左边框
it.filled_rectangle(x + w - thickness, y + r, thickness, h - 2 * r); // 右边框
// 绘制四个圆角
it.filled_circle(x + r, y + r, r); // 左上角
it.filled_circle(x + w - r, y + r, r); // 右上角
it.filled_circle(x + r, y + h - r, r); // 左下角
it.filled_circle(x + w - r, y + h - r, r); // 右下角
// 用黑色填充内部形成边框
it.filled_rectangle(x + thickness, y + thickness, w - 2 * thickness, h - 2 * thickness, COLOR_OFF);
// 风速
it.printf(x+10, y+10, id(sensor_font), "风速");
it.printf(x+45, y+75, id(img_font_sensor), TextAlign::CENTER, "\U000F059D");
// 获取风速数据
it.printf(x+85,y+50, id(data_font), "%s", id(wind).state.c_str());
it.printf(x+85,y+78, id(sensor_font), "mph");

// 绘制垂直线
it.filled_rectangle(430, 30, 5, 430);
// 右侧部分
it.printf(540, 40, id(data_font), "日历");

// 定义事件结构
struct Event {
std::string message;
std::string start_time;
std::string end_time;
time_t start_timestamp;
};

// 将时间字符串解析为time_t(UNIX时间戳)
auto parse_time = [](const std::string &time_str) -> time_t {
struct tm timeinfo = {};
if (strptime(time_str.c_str(), "%Y-%m-%d %H:%M:%S", &timeinfo) == nullptr) {
return 0; // 无效时间
}
return mktime(&timeinfo);
};

// 创建事件列表
std::vector<Event> events = {
{id(ha_calendar_event_1).state, id(ha_calendar_start_time_1).state, id(ha_calendar_end_time_1).state, parse_time(id(ha_calendar_start_time_1).state)},
{id(ha_calendar_event_2).state, id(ha_calendar_start_time_2).state, id(ha_calendar_end_time_2).state, parse_time(id(ha_calendar_start_time_2).state)},
{id(ha_calendar_event_3).state, id(ha_calendar_start_time_3).state, id(ha_calendar_end_time_3).state, parse_time(id(ha_calendar_start_time_3).state)}
};
ESP_LOGD("myCalendar", "开始时间: %s -> %ld", id(ha_calendar_start_time_1).state.c_str(), parse_time(id(ha_calendar_start_time_1).state));
ESP_LOGD("myCalendar", "开始时间: %s -> %ld", id(ha_calendar_start_time_2).state.c_str(), parse_time(id(ha_calendar_start_time_2).state));
ESP_LOGD("myCalendar", "开始时间: %s -> %ld", id(ha_calendar_start_time_3).state.c_str(), parse_time(id(ha_calendar_start_time_3).state));

// 过滤无效事件(start_timestamp == 0)
events.erase(std::remove_if(events.begin(), events.end(), [](const Event &e) { return e.start_timestamp == 0; }), events.end());

// 按`start_timestamp`排序(从早到晚)
std::sort(events.begin(), events.end(), [](const Event &a, const Event &b) {
return a.start_timestamp < b.start_timestamp;
});

// 定义格式化时间的函数
auto format_time = [](std::string time_str) -> std::string {
struct tm timeinfo;
if (strptime(time_str.c_str(), "%Y-%m-%d %H:%M:%S", &timeinfo) == nullptr) {
return "无效";
}
char buffer[10];
strftime(buffer, sizeof(buffer), "%I:%M%p", &timeinfo); // 转换为12小时格式
return std::string(buffer);
};
// 解析日期
auto format_date = [](const std::string &time_str) -> std::string {
struct tm timeinfo = {};
if (strptime(time_str.c_str(), "%Y-%m-%d %H:%M:%S", &timeinfo) == nullptr) {
return "无效";
}
char buffer[6]; // 需要存储"MM-DD\0"
strftime(buffer, sizeof(buffer), "%m-%d", &timeinfo);
return std::string(buffer);
};

// 绘制事件
int even_x_start_offset = 460;
int even_y_start_offset = 80;
for (const auto &event : events) {
if(even_y_start_offset >= 420){
break;
}

// 格式化时间
std::string formatted_date = format_date(event.start_time);
std::string formatted_start_time = format_time(event.start_time);
std::string formatted_end_time = format_time(event.end_time);

// 组合时间范围字符串
std::string time_range = formatted_start_time + " - " + formatted_end_time;
time_range = formatted_date + " " + time_range;
if(formatted_start_time == "无效" || formatted_end_time == "无效"){
time_range.clear();
}
// 显示时间范围,例如"10:00AM - 11:00AM"
it.printf(even_x_start_offset, even_y_start_offset, id(sensor_font), "%s", time_range.c_str());
even_y_start_offset += 30;
// 显示事件名称
it.printf(even_x_start_offset, even_y_start_offset, id(sensor_font), "%s", event.message.c_str());
even_y_start_offset += 40;
}
}


当您看到如下图所示的反馈时,说明代码运行成功。

常见问题

Q1: 为什么没有数据?

在这种情况下,您应该转到设置 -> 设备和服务 -> 集成来重新配置设备。没有找到您的电子纸面板?尝试重启 HA。

Q2: 为什么我无法在《Home Assistant》中获取这些数据?

在这种情况下,您应该转到设置 -> 设备和服务 -> 集成来添加您的设备到 HA。

Q3: 当设备处于深度睡眠模式时,如何上传新程序?

当设备处于深度睡眠模式时,您无法直接上传新程序。

  1. 首先,确保设备已开启,然后按下板子背面的Boot按钮。

  2. 点击一次Reset按钮并释放Boot按钮。

  3. 之后,关闭电池开关并拔掉电源线。

  4. 最后,重新插入电缆并上传新程序。

Q4: 电池能持续多长时间?

tip

充电时记得打开电池按钮。否则,电池无法充电。

经过我们的测试,每6小时刷新一次屏幕,在深度睡眠模式下电池大约可以持续3个月。

Q5: 电子纸面板无法连接到您的计算机?

尝试多次拔插,或者根据提示安装驱动程序。

Q6: Wi-Fi上传程序失败?

在这种情况下,您的电子纸面板处于离线状态或深度睡眠模式。请让它上线或唤醒它。

资源

技术支持与产品讨论

感谢您选择我们的产品!我们在这里为您提供不同的支持,以确保您使用我们产品的体验尽可能顺畅。我们提供多种沟通渠道,以满足不同的偏好和需求。

Loading Comments...