├── fonts
├── README.md
├── TT0246M.ttf
├── GothamRnd-Bold.ttf
├── GothamRnd-Book.ttf
└── materialdesignicons.ttf
├── eink_clock.yaml
├── eink_clock_with_temp.yaml
├── README.md
├── eink_clock_with_forecast.yaml
├── eink_clock_with_pages.yaml
├── README_en.md
└── eink_clock_with_purifier_and_motion.yaml
/fonts/README.md:
--------------------------------------------------------------------------------
1 | put these files in esphome/fonts
2 |
--------------------------------------------------------------------------------
/fonts/TT0246M.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xangin/esphome_eink2.9_series/HEAD/fonts/TT0246M.ttf
--------------------------------------------------------------------------------
/fonts/GothamRnd-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xangin/esphome_eink2.9_series/HEAD/fonts/GothamRnd-Bold.ttf
--------------------------------------------------------------------------------
/fonts/GothamRnd-Book.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xangin/esphome_eink2.9_series/HEAD/fonts/GothamRnd-Book.ttf
--------------------------------------------------------------------------------
/fonts/materialdesignicons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xangin/esphome_eink2.9_series/HEAD/fonts/materialdesignicons.ttf
--------------------------------------------------------------------------------
/eink_clock.yaml:
--------------------------------------------------------------------------------
1 | substitutions:
2 | device_name: eink-clock
3 |
4 | esphome:
5 | name: eink-clock
6 |
7 | esp8266:
8 | board: d1_mini
9 |
10 | # Enable logging
11 | logger:
12 |
13 | # Enable Home Assistant API
14 | api:
15 |
16 | ota:
17 |
18 | wifi:
19 | ssid: !secret my_ap_ssid
20 | password: !secret my_ap_password
21 |
22 | # Enable fallback hotspot (captive portal) in case wifi connection fails
23 | ap:
24 | ssid: "eink-clock"
25 | password: "12345678"
26 |
27 | time:
28 | - platform: sntp
29 | id: sntp_time
30 | timezone: Asia/Taipei
31 |
32 | sensor:
33 | - platform: wifi_signal
34 | id: wifisignal
35 | update_interval: 60s
36 |
37 | font:
38 | - file: "fonts/GothamRnd-Bold.ttf"
39 | id: time_font
40 | size: 95
41 | glyphs: [0, 1, 2, 3, 4, 5, 6, 7 ,8, 9, ':']
42 |
43 | - file: "fonts/GothamRnd-Book.ttf"
44 | id: time_font_book
45 | size: 95
46 | glyphs: [0, 1, 2, 3, 4, 5, 6, 7 ,8, 9, ':']
47 |
48 | - file: "fonts/GothamRnd-Bold.ttf"
49 | id: weekday_font
50 | size: 35
51 | glyphs: &font-glyphs
52 | ['!', ',', '.', '"', '%', '-', '_', ':', '°', '/',
53 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ',
54 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
55 | 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
56 | 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
57 | 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
58 | 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
59 |
60 | - file: "fonts/GothamRnd-Book.ttf"
61 | id: date_font
62 | size: 32
63 | glyphs: *font-glyphs
64 |
65 | - file: 'fonts/materialdesignicons.ttf'
66 | id: icon_font
67 | size: 20
68 | glyphs: &mdi-weather-glyphs
69 | - "\U000F0783" # mdi-signal-off
70 | - "\U000F08BF" # mdi-signal-cellular-outline bad
71 | - "\U000F08BC" # mdi-signal-cellular-1
72 | - "\U000F08BD" # mdi-signal-cellular-2
73 | - "\U000F08BE" # mdi-signal-cellular-3 good
74 |
75 | spi:
76 | clk_pin: D5
77 | mosi_pin: D7
78 |
79 | display:
80 | - platform: waveshare_epaper
81 | cs_pin: D8
82 | dc_pin: D2
83 | busy_pin: D1
84 | reset_pin: D4
85 | model: 2.90in
86 | rotation: 270
87 | update_interval: 10s
88 | full_update_every: 600
89 | lambda: |-
90 |
91 | //wifi signal
92 | if (id(wifisignal).state >= -60) {
93 | //Excellent
94 | it.print(0, 0, id(icon_font), "\U000F08BE");
95 | } else if (id(wifisignal).state >= -70) {
96 | //Good
97 | it.print(0, 0, id(icon_font), "\U000F08BD");
98 | } else if (id(wifisignal).state >= -75) {
99 | //Fair
100 | it.print(0, 0, id(icon_font),"\U000F08BC");
101 | } else if (id(wifisignal).state >= -85) {
102 | //Weak
103 | it.print(0, 0, id(icon_font),"\U000F08BF");
104 | } else {
105 | //Unlikely working signal
106 | it.print(0, 0, id(icon_font),"\U000F0783");
107 | }
108 | //Time: hour
109 | it.strftime(75,1, id(time_font_book), TextAlign::TOP_CENTER, "%H",id(sntp_time).now());
110 |
111 | //Time: ":"
112 | it.printf(150,-8, id(time_font_book), TextAlign::TOP_CENTER, ":");
113 |
114 | //Time: minutes
115 | it.strftime(225,1, id(time_font_book), TextAlign::TOP_CENTER, "%M",id(sntp_time).now());
116 |
117 | //Time: date
118 | it.strftime(104,93, id(date_font), TextAlign::TOP_CENTER, "%Y/%m/%d",id(sntp_time).now());
119 |
120 | //Weekday
121 | it.strftime(250,90, id(weekday_font), TextAlign::TOP_CENTER, "%a",id(sntp_time).now());
122 |
123 |
--------------------------------------------------------------------------------
/eink_clock_with_temp.yaml:
--------------------------------------------------------------------------------
1 | substitutions:
2 | device_name: eink-clock-temp
3 | temperature_entity_id: sensor.kids_room_temperature #replace with your temperature entity_id
4 | humidity_entity_id: sensor.kids_room_humidity #replace with your humidity entity_id
5 |
6 | esphome:
7 | name: eink-clock-temp
8 |
9 | esp8266:
10 | board: d1_mini
11 |
12 | # Enable logging
13 | logger:
14 |
15 | # Enable Home Assistant API
16 | api:
17 |
18 | ota:
19 | password: !secret my_ap_password
20 |
21 | wifi:
22 | ssid: !secret my_ap_ssid
23 | password: !secret my_ap_password
24 |
25 | # Enable fallback hotspot (captive portal) in case wifi connection fails
26 | ap:
27 | ssid: "eink-clock-temp"
28 | password: "12345678"
29 |
30 | captive_portal:
31 |
32 | time:
33 | - platform: homeassistant
34 | id: ha_time
35 |
36 | binary_sensor:
37 | - platform: status
38 | name: "${device_name} Status"
39 | id: system_status
40 |
41 | sensor:
42 | - platform: wifi_signal
43 | id: wifisignal
44 | name: "${device_name} WiFi Signal"
45 | update_interval: 60s
46 |
47 | - platform: homeassistant
48 | entity_id: ${temperature_entity_id}
49 | id: temp_data
50 |
51 | - platform: homeassistant
52 | entity_id: ${humidity_entity_id}
53 | id: hum_data
54 |
55 |
56 | font:
57 | - file: "fonts/GothamRnd-Book.ttf"
58 | id: time_font
59 | size: 80
60 | glyphs: [0, 1, 2, 3, 4, 5, 6, 7 ,8, 9, ':']
61 |
62 | - file: "fonts/GothamRnd-Bold.ttf"
63 | id: temp_font
64 | size: 30
65 | glyphs: &font-glyphs
66 | ['!', ',', '.', '"', '%', '-', '_', ':', '°', '/',
67 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ',
68 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
69 | 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
70 | 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
71 | 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
72 | 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
73 |
74 | - file: "fonts/GothamRnd-Book.ttf"
75 | id: book_40
76 | size: 40
77 | glyphs: *font-glyphs
78 |
79 | - file: "fonts/GothamRnd-Bold.ttf"
80 | id: bold_40
81 | size: 40
82 | glyphs: *font-glyphs
83 |
84 | - file: "fonts/GothamRnd-Bold.ttf"
85 | id: bold_25
86 | size: 25
87 | glyphs: *font-glyphs
88 |
89 | - file: "fonts/GothamRnd-Book.ttf"
90 | id: date_font
91 | size: 30
92 | glyphs: *font-glyphs
93 |
94 | - file: "fonts/materialdesignicons.ttf"
95 | id: icon_font
96 | size: 30
97 | glyphs:
98 | - "\U000F058E" # mdi-water-percent
99 | - "\U000F050F" # mdi-thermometer
100 |
101 |
102 | spi:
103 | clk_pin: D5
104 | mosi_pin: D7
105 |
106 | display:
107 | - platform: waveshare_epaper
108 | id: my_display
109 | cs_pin: D8
110 | dc_pin: D2
111 | busy_pin: D1
112 | reset_pin: D4
113 | model: 2.90in
114 | rotation: 270
115 | update_interval: 10s
116 | full_update_every: 60
117 | lambda: |-
118 | //time
119 | it.strftime(75,1, id(time_font), TextAlign::TOP_CENTER, "%H",id(ha_time).now());
120 |
121 | it.printf(150,-6, id(time_font), TextAlign::TOP_CENTER, ":");
122 |
123 | it.strftime(225,1, id(time_font), TextAlign::TOP_CENTER, "%M",id(ha_time).now());
124 |
125 | it.strftime(150, 70, id(date_font), TextAlign::TOP_CENTER, "%Y/%m/%d %a", id(ha_time).now());
126 |
127 | //temp
128 | it.print(25, 97, id(icon_font),TextAlign::TOP_CENTER, "\U000F050F");
129 | if(id(temp_data).has_state()){
130 | it.printf(90,100, id(temp_font), TextAlign::TOP_CENTER, "%.1f°C", id(temp_data).state);
131 | }
132 | //hum
133 | it.print(185, 100, id(icon_font),TextAlign::TOP_CENTER, "\U000F058E");
134 |
135 | if(id(hum_data).has_state()){
136 | it.printf(240, 100, id(temp_font), TextAlign::TOP_CENTER, "%.1f%%", id(hum_data).state);
137 | }
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ESPhome E-ink 2.9" series sample
2 |
3 |
4 |
5 | 正體中文 | [English](https://github.com/xangin/esphome_eink2.9_series/blob/main/README_en.md)
6 |
7 | 本專案為利用2.9"電子紙搭配ESPhome與Home Assistant,可自由調整喜歡的版面
8 |
9 | 或是加上更多頁面並透過HA控制換頁,例如當衣服洗好時,會自動將畫面切為訊息提示
10 |
11 | 要有多少頁或是顯示什麼都能自由設計
12 |
13 | - 成品尺寸: 寬 88mm x 高 47mm
14 |
15 |
16 |
17 | - 成品背面
18 |
19 |
20 |
21 |
22 | # 範例參考:
23 |
24 | ## 範例1. eink_clock.yaml
25 |
26 | 單純時鐘僅顯示日期與時間
27 |
28 |
29 |
30 |
31 | ## 範例2. eink_clock_with_forecast.yaml
32 |
33 | 除了日期與時間外,再加上讀取來自HA的天氣預報圖示與氣溫
34 |
35 |
36 |
37 |
38 | ## 範例3. eink_clock_with_temp.yaml
39 |
40 | 除了日期與時間外,再加上讀取來自HA的某個溫度與濕度實體
41 |
42 |
43 |
44 | ## 範例4. eink_clock_with_pages.yaml
45 |
46 | 除了日期與時間外,還多了訊息頁面
47 |
48 |
49 |
50 | ## Hardware 硬體架構
51 |
52 | - [微雪 2.9吋黑白墨水屏裸屏](https://detail.tmall.com/item.htm?id=605757420567) - 不帶外殼
53 | - [墨水屏驅動板 ESP8266](https://oshwhub.com/lingdy2012/mo-shui-ping-_esp8266-qu-dong-ban-_0603_wos_v0-1) - 閑魚有售
54 | - [电子墨水屏外壳(EW029F2(2.9寸单电池))](https://item.taobao.com/item.htm?id=601700008521)- 外殼
55 |
56 |
57 | ## Installation 安裝方式
58 |
59 | 1. 將`/fonts`資料夾內的檔案放到HA/config/esphome的資料夾內
60 | 2. 將任一YAML放到HA/config/esphome,並將內容修改成自己想要的,編譯後插上USB即可燒錄至ESP模組
61 | 3. 有讀取HA資訊的必須要將ESPhome裝置加入HA後才正確顯示資訊
62 |
63 | ## ESPHome yaml 說明
64 |
65 | ### 在HA控制換頁
66 |
67 | 有2個按鈕,按下去分別會去顯示p1(Time Page)與p2(Message Page),如果有要再新增更多頁可以再仿照程式碼再新增
68 |
69 | ```YAML
70 | button:
71 | - platform: template
72 | name: "Show Time Page"
73 | icon: 'mdi:clock'
74 | on_press:
75 | then:
76 | - display.page.show: p1
77 | - component.update: my_display
78 |
79 | - platform: template
80 | name: "Show Message Page"
81 | icon: 'mdi:update'
82 | on_press:
83 | then:
84 | - display.page.show: p2
85 | - component.update: my_display
86 | ```
87 |
88 | ### 根據Wi-Fi強度顯示圖示
89 |
90 | 說明:
91 | - 大於等於-60顯示三格
92 | - -60~-70顯示兩格
93 | - -70~-75顯示一格
94 | - -75~-85顯示零格
95 | - 小於-85顯示中斷
96 |
97 | 可自由變更強度範圍要顯示的格數
98 |
99 | ```YAML
100 | //wifi signal
101 | if (id(wifisignal).state >= -60) {
102 | //Excellent
103 | it.print(0, 0, id(wifi_font), "\U000F08BE");
104 | } else if (id(wifisignal).state >= -70) {
105 | //Good
106 | it.print(0, 0, id(wifi_font), "\U000F08BD");
107 | } else if (id(wifisignal).state >= -75) {
108 | //Fair
109 | it.print(0, 0, id(wifi_font),"\U000F08BC");
110 | } else if (id(wifisignal).state >= -85) {
111 | //Weak
112 | it.print(0, 0, id(wifi_font),"\U000F08BF");
113 | } else {
114 | //Unlikely working signal
115 | it.print(0, 0, id(wifi_font),"\U000F0783");
116 | }
117 | ```
118 |
119 | ### 修改自訂訊息內容
120 |
121 | 作法:
122 |
123 | 1. 在字型宣告處的`msg_font`將要顯示的中文字**先全部寫出來**這樣才能正常顯示唷!!
124 | ```YAML
125 | font:
126 | - file: "fonts/NotoSansTC-Medium.ttf"
127 | id: msg_font
128 | size: 40
129 | glyphs: 衣服已經洗拿去烘好囉!趕快收起來ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz,."%-~_:°
130 | ```
131 | 2. 在`display`的p2,置換想要顯示的文字,依目前設定的大小,一行就是顯示`7個中文字`,超過將無法顯示唷!
132 | ```YAML
133 | display:
134 | ...
135 | - id: p2
136 | lambda: |-
137 | it.printf(150,15, id(msg_font), TextAlign::TOP_CENTER, "衣服已經洗好囉!");
138 | it.printf(150,70, id(msg_font), TextAlign::TOP_CENTER, "趕快拿去烘~");
139 | ```
140 |
141 | ### 增加更多頁面
142 |
143 | 作法: 仿照按鈕及顯示的程式碼,可再新增多組,例如下面是第三頁,按下去顯示衣服烘好了
144 |
145 | 有不在`msg_font`內的中文字要記得新增,這樣才能正常顯示唷!
146 |
147 | ```YAML
148 |
149 | button: #複製在button程式碼的最下面,不可重複寫button唷!
150 | ...
151 | - platform: template
152 | name: "Show Dryer Done Page"
153 | icon: 'mdi:update'
154 | on_press:
155 | then:
156 | - display.page.show: p3
157 | - component.update: my_display
158 |
159 |
160 | display: #複製在display程式碼的最下面,不可重複寫display唷!
161 | ...
162 | - id: p3
163 | lambda: |-
164 | it.printf(150,15, id(msg_font), TextAlign::TOP_CENTER, "衣服已經烘好囉!");
165 | it.printf(150,70, id(msg_font), TextAlign::TOP_CENTER, "趕快收起來!!");
166 | ```
167 |
168 |
--------------------------------------------------------------------------------
/eink_clock_with_forecast.yaml:
--------------------------------------------------------------------------------
1 | substitutions:
2 | device_name: eink-clock-forecast
3 |
4 | esphome:
5 | name: eink-clock-forecast
6 |
7 | esp8266:
8 | board: d1_mini
9 |
10 | # Enable logging
11 | logger:
12 |
13 | # Enable Home Assistant API
14 | api:
15 |
16 | ota:
17 | password: !secret my_ap_password
18 |
19 | wifi:
20 | ssid: !secret my_ap_ssid
21 | password: !secret my_ap_password
22 |
23 | # Enable fallback hotspot (captive portal) in case wifi connection fails
24 | ap:
25 | ssid: "eink-clock-forecast"
26 | password: "12345678"
27 |
28 | captive_portal:
29 |
30 | time:
31 | - platform: homeassistant
32 | id: ha_time
33 |
34 | text_sensor:
35 | #weather icon
36 | - platform: homeassistant
37 | entity_id: sensor.openweathermap_condition
38 | id: today_weather
39 |
40 | #weather temperature
41 | - platform: homeassistant
42 | entity_id: sensor.openweathermap_temperature
43 | id: today_temperature
44 |
45 |
46 | binary_sensor:
47 | - platform: status
48 | name: "${device_name} WiFi Status"
49 |
50 | font:
51 | - file: "fonts/TT0246M.ttf"
52 | id: time_font
53 | size: 72
54 | glyphs: [0, 1, 2, 3, 4, 5, 6, 7 ,8, 9, ':']
55 |
56 | - file: "fonts/GothamRnd-Book.ttf"
57 | id: date_font
58 | size: 25
59 | glyphs: &font-glyphs
60 | ['!', ',', '.', '"', '%', '-', '_', ':', '°', '/',
61 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ',
62 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
63 | 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
64 | 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
65 | 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
66 | 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
67 |
68 | - file: "fonts/GothamRnd-Bold.ttf"
69 | id: weekday_font
70 | size: 25
71 | glyphs: *font-glyphs
72 |
73 | - file: "fonts/GothamRnd-Bold.ttf"
74 | id: temp_font
75 | size: 30
76 | glyphs: *font-glyphs
77 |
78 | - file: 'fonts/materialdesignicons.ttf'
79 | id: icon_weather
80 | size: 80
81 | glyphs: &mdi-weather-glyphs
82 | - "\U000F0590" # mdi-weather-cloudy
83 | - "\U000F0F2F" # mdi-weather-cloudy-alert
84 | - "\U000F0E6E" # mdi-weather-cloudy-arrow-right
85 | - "\U000F0593" # mdi-weather-lightning
86 | - "\U000F067E" # mdi-weather-lightning-rainy
87 | - "\U000F0594" # mdi-weather-night
88 | - "\U000F0F31" # mdi-weather-night-partly-cloudy
89 | - "\U000F0595" # mdi-weather-partly-cloudy
90 | - "\U000F0F32" # mdi-weather-partly-lightning
91 | - "\U000F0F33" # mdi-weather-partly-rainy
92 | - "\U000F0596" # mdi-weather-pouring
93 | - "\U000F0597" # mdi-weather-rainy
94 | - "\U000F0599" # mdi-weather-sunny
95 | - "\U000F0F37" # mdi-weather-sunny-alert
96 | - "\U000F14E4" # mdi-weather-sunny-off
97 | - "\U000F059A" # mdi-weather-sunset
98 | - "\U000F059B" # mdi-weather-sunset-down
99 | - "\U000F059C" # mdi-weather-sunset-up
100 | - "\U000F059D" # mdi-weather-windy
101 | - "\U000F059E" # mdi-weather-windy-variant
102 |
103 | spi:
104 | clk_pin: D5
105 | mosi_pin: D7
106 |
107 | display:
108 | - platform: waveshare_epaper
109 | cs_pin: D8
110 | dc_pin: D2
111 | busy_pin: D1
112 | reset_pin: D4
113 | model: 2.90in
114 | rotation: 270
115 | update_interval: 10s
116 | id: my_display
117 | full_update_every: 30
118 | lambda: |-
119 | // Map weather states to MDI characters.
120 | std::map weather_icon_map
121 | {
122 | {"cloudy", "\U000F0590"},
123 | {"cloudy-alert", "\U000F0F2F"},
124 | {"fog", "\U000F0591"},
125 | {"hail", "\U000F0592"},
126 | {"hazy", "\U000F0F30"},
127 | {"lightning", "\U000F0593"},
128 | {"lightning-rainy", "\U000F067E"},
129 | {"clear-night", "\U000F0594"},
130 | {"night-partly-cloudy", "\U000F0F31"},
131 | {"partlycloudy", "\U000F0595"},
132 | {"partly-lightning", "\U000F0F32"},
133 | {"partly-rainy", "\U000F0F33"},
134 | {"pouring", "\U000F0596"},
135 | {"rainy", "\U000F0597"},
136 | {"sunny", "\U000F0599"},
137 | {"sunny-alert", "\U000F0F37"},
138 | {"sunny-off", "\U000F14E4"},
139 | {"sunset", "\U000F059A"},
140 | {"sunset-down", "\U000F059B"},
141 | {"sunset-up", "\U000F059C"},
142 | {"windy", "\U000F059D"},
143 | {"windy-variant", "\U000F059E"},
144 | };
145 |
146 | //time
147 | it.strftime(110, -10, id(time_font), TextAlign::TOP_CENTER, "%H:%M", id(ha_time).now());
148 |
149 | //date
150 | it.strftime(110, 75, id(weekday_font), TextAlign::TOP_CENTER, "%Y/%m/%d", id(ha_time).now());
151 |
152 | //weekday
153 | it.strftime(110, 100, id(weekday_font), TextAlign::TOP_CENTER, "%A", id(ha_time).now());
154 |
155 | //weather icon
156 | it.printf(255, 10, id(icon_weather), TextAlign::TOP_CENTER, "%s", weather_icon_map[id(today_weather).state.c_str()].c_str());
157 |
158 | //temp
159 | it.printf(255, 95, id(temp_font), TextAlign::TOP_CENTER, "%s°C", id(today_temperature).state.c_str());
160 |
161 |
--------------------------------------------------------------------------------
/eink_clock_with_pages.yaml:
--------------------------------------------------------------------------------
1 | substitutions:
2 | device_name: d1mini-eink-clock
3 |
4 | esphome:
5 | name: d1mini-eink-clock
6 | comment: D1mini
7 |
8 | esp8266:
9 | board: d1_mini
10 |
11 | # Enable logging
12 | logger:
13 |
14 | # Enable Home Assistant API
15 | api:
16 |
17 | ota:
18 |
19 | wifi:
20 | ssid: !secret my_ap_ssid
21 | password: !secret my_ap_password
22 |
23 | # Enable fallback hotspot (captive portal) in case wifi connection fails
24 | ap:
25 | ssid: "d1mini-eink-clock"
26 | password: "12345678"
27 |
28 | captive_portal:
29 |
30 | time:
31 | - platform: sntp
32 | id: sntp_time
33 | timezone: Asia/Taipei
34 |
35 | sensor:
36 | - platform: wifi_signal
37 | id: wifisignal
38 | update_interval: 60s
39 |
40 | button:
41 | - platform: template
42 | name: "Show Time Page"
43 | icon: 'mdi:clock'
44 | on_press:
45 | then:
46 | - display.page.show: p1
47 | - component.update: my_display
48 |
49 | - platform: template
50 | name: "Show Message Page"
51 | icon: 'mdi:update'
52 | on_press:
53 | then:
54 | - display.page.show: p2
55 | - component.update: my_display
56 |
57 | font:
58 | - file: "fonts/GothamRnd-Bold.ttf"
59 | id: time_font
60 | size: 95
61 | glyphs: [0, 1, 2, 3, 4, 5, 6, 7 ,8, 9, ':']
62 |
63 | - file: "fonts/GothamRnd-Book.ttf"
64 | id: time_font_book
65 | size: 95
66 | glyphs: [0, 1, 2, 3, 4, 5, 6, 7 ,8, 9, ':']
67 |
68 | - file: "fonts/GothamRnd-Bold.ttf"
69 | id: weekday_font
70 | size: 35
71 | glyphs: &font-glyphs
72 | ['!', ',', '.', '"', '%', '-', '_', ':', '°', '/',
73 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ',
74 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
75 | 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
76 | 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
77 | 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
78 | 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
79 |
80 | - file: "fonts/GothamRnd-Book.ttf"
81 | id: date_font
82 | size: 32
83 | glyphs: *font-glyphs
84 |
85 | - file: "fonts/NotoSansTC-Medium.ttf"
86 | id: msg_font
87 | size: 40
88 | glyphs: 衣服已經洗拿去烘好囉!趕快收起來ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz,."%-~_:°
89 |
90 | - file: 'fonts/materialdesignicons-7296.ttf'
91 | id: icon_font
92 | size: 20
93 | glyphs: &mdi-weather-glyphs
94 | - "\U000F0783" # mdi-signal-off
95 | - "\U000F08BF" # mdi-signal-cellular-outline bad
96 | - "\U000F08BC" # mdi-signal-cellular-1
97 | - "\U000F08BD" # mdi-signal-cellular-2
98 | - "\U000F08BE" # mdi-signal-cellular-3 good
99 |
100 | spi:
101 | clk_pin: D5
102 | mosi_pin: D7
103 |
104 | display:
105 | - platform: waveshare_epaper
106 | cs_pin: D8
107 | dc_pin: D2
108 | busy_pin: D1
109 | reset_pin: D4
110 | model: 2.90in
111 | rotation: 270
112 | update_interval: 10s
113 | full_update_every: 60
114 | id: my_display
115 | pages:
116 | - id: p1
117 | lambda: |-
118 | //wifi signal
119 | if (id(wifisignal).state >= -60) {
120 | //Excellent
121 | it.print(0, 0, id(icon_font), "\U000F08BE");
122 | } else if (id(wifisignal).state >= -70) {
123 | //Good
124 | it.print(0, 0, id(icon_font), "\U000F08BD");
125 | } else if (id(wifisignal).state >= -75) {
126 | //Fair
127 | it.print(0, 0, id(icon_font),"\U000F08BC");
128 | } else if (id(wifisignal).state >= -85) {
129 | //Weak
130 | it.print(0, 0, id(icon_font),"\U000F08BF");
131 | } else {
132 | //Unlikely working signal
133 | it.print(0, 0, id(icon_font),"\U000F0783");
134 | }
135 | //Time: hour
136 | it.strftime(75,1, id(time_font_book), TextAlign::TOP_CENTER, "%H",id(sntp_time).now());
137 |
138 | //Time: ":"
139 | it.printf(150,-8, id(time_font_book), TextAlign::TOP_CENTER, ":");
140 |
141 | //Time: minutes
142 | it.strftime(225,1, id(time_font_book), TextAlign::TOP_CENTER, "%M",id(sntp_time).now());
143 |
144 | //Time: date
145 | it.strftime(104,93, id(date_font), TextAlign::TOP_CENTER, "%Y/%m/%d",id(sntp_time).now());
146 |
147 | //Weekday
148 | it.strftime(250,90, id(weekday_font), TextAlign::TOP_CENTER, "%a",id(sntp_time).now());
149 |
150 | - id: p2
151 | lambda: |-
152 | //wifi signal
153 | if (id(wifisignal).state >= -60) {
154 | //Excellent
155 | it.print(0, 0, id(icon_font), "\U000F08BE");
156 | } else if (id(wifisignal).state >= -70) {
157 | //Good
158 | it.print(0, 0, id(icon_font), "\U000F08BD");
159 | } else if (id(wifisignal).state >= -75) {
160 | //Fair
161 | it.print(0, 0, id(icon_font),"\U000F08BC");
162 | } else if (id(wifisignal).state >= -85) {
163 | //Weak
164 | it.print(0, 0, id(icon_font),"\U000F08BF");
165 | } else {
166 | //Unlikely working signal
167 | it.print(0, 0, id(icon_font),"\U000F0783");
168 | }
169 |
170 | it.printf(150,15, id(msg_font), TextAlign::TOP_CENTER, "衣服已經洗好囉!");
171 | it.printf(150,70, id(msg_font), TextAlign::TOP_CENTER, "趕快拿去烘~");
172 |
--------------------------------------------------------------------------------
/README_en.md:
--------------------------------------------------------------------------------
1 | # ESPhome E-ink 2.9" series sample
2 |
3 |
4 |
5 | [正體中文](https://github.com/xangin/esphome_eink2.9_series/blob/main/README.md) | English
6 |
7 | This project uses a 2.9" e-ink display with ESPhome and Home Assistant, allowing for customizable layouts.
8 |
9 | You can add more pages and control page switching through HA, for example, when the clothes are done washing, the screen will automatically switch to a message prompt.
10 |
11 | You can design how many pages or what to display freely.
12 |
13 | - Product size: Width 88mm x Height 47mm
14 |
15 |
16 |
17 | - Back of the product
18 |
19 |
20 |
21 |
22 | # Sample references:
23 |
24 | ## Sample 1. eink_clock.yaml
25 |
26 | A simple clock that only displays the date and time.
27 |
28 |
29 |
30 |
31 | ## Sample 2. eink_clock_with_forecast.yaml
32 |
33 | In addition to the date and time, it also reads the weather forecast icon and temperature from HA.
34 |
35 |
36 |
37 |
38 | ## Sample 3. eink_clock_with_temp.yaml
39 |
40 | In addition to the date and time, it also reads a temperature and humidity entity from HA.
41 |
42 |
43 |
44 | ## Sample 4. eink_clock_with_pages.yaml
45 |
46 | In addition to the date and time, it also includes a message page.
47 |
48 |
49 |
50 | ## Hardware
51 |
52 | - [Waveshare 2.9 inch Black and White E-ink Screen](https://detail.tmall.com/item.htm?id=605757420567) - Without casing
53 | - [E-ink Screen Driver Board ESP8266](https://oshwhub.com/lingdy2012/mo-shui-ping-_esp8266-qu-dong-ban-_0603_wos_v0-1) - Available on Xianyu
54 | - [E-ink Screen Casing (EW029F2(2.9 inch single battery))](https://item.taobao.com/item.htm?id=601700008521)- Case
55 |
56 |
57 | ## Installation
58 |
59 | 1. Place the files in the `/fonts` folder into the HA/config/esphome folder.
60 | 2. Place any YAML into HA/config/esphome, modify the content as desired, compile, and flash it to the ESP module via USB.
61 | 3. For reading information from HA, the ESPhome device must be added to HA for accurate display of information.
62 |
63 | ## ESPHome yaml
64 |
65 | ### Page switching control in HA
66 |
67 | There are 2 buttons that display p1 (Time Page) and p2 (Message Page) when pressed. If you want to add more pages, you can follow the code to add more.
68 |
69 | ```YAML
70 | button:
71 | - platform: template
72 | name: "Show Time Page"
73 | icon: 'mdi:clock'
74 | on_press:
75 | then:
76 | - display.page.show: p1
77 | - component.update: my_display
78 |
79 | - platform: template
80 | name: "Show Message Page"
81 | icon: 'mdi:update'
82 | on_press:
83 | then:
84 | - display.page.show: p2
85 | - component.update: my_display
86 | ```
87 |
88 | ### Display icons based on Wi-Fi strength
89 |
90 | 說明:
91 |
92 | - Greater than or equal to -60 shows three bars
93 | - -60~-70 shows two bars
94 | - -70~-75 shows one bar
95 | - -75~-85 shows zero bars
96 | - Less than -85 shows disconnected
97 |
98 | You can freely change the strength range to display the number of bars.
99 |
100 | ```YAML
101 | //wifi signal
102 | if (id(wifisignal).state >= -60) {
103 | //Excellent
104 | it.print(0, 0, id(wifi_font), "\U000F08BE");
105 | } else if (id(wifisignal).state >= -70) {
106 | //Good
107 | it.print(0, 0, id(wifi_font), "\U000F08BD");
108 | } else if (id(wifisignal).state >= -75) {
109 | //Fair
110 | it.print(0, 0, id(wifi_font),"\U000F08BC");
111 | } else if (id(wifisignal).state >= -85) {
112 | //Weak
113 | it.print(0, 0, id(wifi_font),"\U000F08BF");
114 | } else {
115 | //Unlikely working signal
116 | it.print(0, 0, id(wifi_font),"\U000F0783");
117 | }
118 | ```
119 |
120 | ### Modify custom message content
121 |
122 | Steps:
123 |
124 | 1. In the font declaration `msg_font`, write out **all** the Chinese characters to be displayed first for proper display.
125 |
126 | ```YAML
127 | font:
128 | - file: "fonts/NotoSansTC-Medium.ttf"
129 | id: msg_font
130 | size: 40
131 | glyphs: 衣服已經洗拿去烘好囉!趕快收起來ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz,."%-~_:°
132 | ```
133 |
134 | 2. In the `display` p2, replace the text you want to show. Based on the current size setting, one line displays `7 Chinese characters`. Exceeding this limit will not display correctly.
135 |
136 | ```YAML
137 | display:
138 | ...
139 | - id: p2
140 | lambda: |-
141 | it.printf(150,15, id(msg_font), TextAlign::TOP_CENTER, "衣服已經洗好囉!");
142 | it.printf(150,70, id(msg_font), TextAlign::TOP_CENTER, "趕快拿去烘~");
143 | ```
144 |
145 | ### Add more pages
146 |
147 | Steps: Follow the button and display code to add more groups. For example, below is a third page that shows the clothes are done drying when pressed.
148 |
149 | Remember to add any Chinese characters not in `msg_font` to ensure proper display.
150 |
151 | ```YAML
152 |
153 | button: #Copy to the very bottom of the button code, do not write button repeatedly!
154 | ...
155 | - platform: template
156 | name: "Show Dryer Done Page"
157 | icon: 'mdi:update'
158 | on_press:
159 | then:
160 | - display.page.show: p3
161 | - component.update: my_display
162 |
163 |
164 | display: #Copy to the very bottom of the display code, do not write display repeatedly!
165 | ...
166 | - id: p3
167 | lambda: |-
168 | it.printf(150,15, id(msg_font), TextAlign::TOP_CENTER, "衣服已經烘好囉!");
169 | it.printf(150,70, id(msg_font), TextAlign::TOP_CENTER, "趕快收起來!!");
170 | ```
171 |
172 |
--------------------------------------------------------------------------------
/eink_clock_with_purifier_and_motion.yaml:
--------------------------------------------------------------------------------
1 | esphome:
2 | name: eink-sht-motion
3 |
4 | substitutions:
5 | device_name: kitchen
6 |
7 | esp8266:
8 | board: d1_mini
9 |
10 | # Enable logging
11 | logger:
12 |
13 | # Enable Home Assistant API
14 | api:
15 |
16 | ota:
17 | password: !secret my_ap_password
18 |
19 | wifi:
20 | ssid: !secret my_ap_ssid
21 | password: !secret my_ap_password
22 |
23 | # Enable fallback hotspot (captive portal) in case wifi connection fails
24 | ap:
25 | ssid: "eink-sht-motion"
26 | password: "12345678"
27 |
28 | time:
29 | - platform: homeassistant
30 | id: ha_time
31 |
32 | i2c:
33 | sda: D2
34 | scl: D1
35 | scan: true
36 |
37 | binary_sensor:
38 | - platform: status
39 | name: "${device_name} Status"
40 | id: system_status
41 |
42 | - platform: gpio
43 | id: motion
44 | pin: D6
45 | name: "${device_name} Motion"
46 | device_class: motion
47 | filters:
48 | - delayed_off: 1s
49 |
50 | sensor:
51 | - platform: wifi_signal
52 | id: wifisignal
53 | name: "${device_name} WiFi Signal"
54 | update_interval: 60s
55 |
56 | - platform: sht3xd
57 | address: 0x44
58 | update_interval: 30s
59 | temperature:
60 | name: "${device_name} Temperature"
61 | id: temp_data
62 | filters:
63 | - calibrate_polynomial:
64 | degree: 2
65 | datapoints:
66 | - 0.0 -> 0.0
67 | - 34.6 -> 28.6
68 | - 36.2 -> 29.6
69 |
70 | humidity:
71 | name: "${device_name} Humidity"
72 | id: hum_data
73 | filters:
74 | - calibrate_polynomial:
75 | degree: 2
76 | datapoints:
77 | - 0.0 -> 0.0
78 | - 50.2 -> 65
79 | - 55.5 -> 72
80 |
81 |
82 |
83 | - platform: homeassistant
84 | entity_id: sensor.water_purifier_pp_filter_level
85 | id: pp_filter
86 |
87 | - platform: homeassistant
88 | entity_id: sensor.water_purifier_ro_filter_level
89 | id: ro_filter
90 |
91 | - platform: homeassistant
92 | entity_id: sensor.water_purifier_input_water_tds
93 | id: tds_in
94 |
95 | - platform: homeassistant
96 | entity_id: sensor.water_purifier_output_water_tds
97 | id: tds_out
98 |
99 | button:
100 | - platform: template
101 | name: '${device_name} Show Time Page'
102 | icon: 'mdi:update'
103 | on_press:
104 | then:
105 | - display.page.show: p1
106 | - component.update: my_display
107 | internal: false
108 |
109 | - platform: template
110 | name: '${device_name} Show Water Page'
111 | icon: 'mdi:update'
112 | on_press:
113 | then:
114 | - display.page.show: p2
115 | - component.update: my_display
116 | internal: false
117 |
118 | font:
119 | - file: "fonts/GothamRnd-Book.ttf"
120 | id: time_font
121 | size: 80
122 | glyphs: [0, 1, 2, 3, 4, 5, 6, 7 ,8, 9, ':']
123 |
124 | - file: "fonts/GothamRnd-Bold.ttf"
125 | id: temp_font
126 | size: 30
127 | glyphs: &font-glyphs
128 | ['!', ',', '.', '"', '%', '-', '_', ':', '°', '/',
129 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ',
130 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
131 | 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
132 | 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
133 | 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
134 | 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
135 |
136 | - file: "fonts/GothamRnd-Book.ttf"
137 | id: book_40
138 | size: 40
139 | glyphs: *font-glyphs
140 |
141 | - file: "fonts/GothamRnd-Bold.ttf"
142 | id: bold_40
143 | size: 40
144 | glyphs: *font-glyphs
145 |
146 | - file: "fonts/GothamRnd-Bold.ttf"
147 | id: bold_25
148 | size: 25
149 | glyphs: *font-glyphs
150 |
151 | - file: "fonts/GothamRnd-Book.ttf"
152 | id: date_font
153 | size: 30
154 | glyphs: *font-glyphs
155 |
156 | - file: "fonts/materialdesignicons-7296.ttf"
157 | id: icon_font
158 | size: 30
159 | glyphs:
160 | - "\U000F058E" # mdi-water-percent
161 | - "\U000F050F" # mdi-thermometer
162 |
163 | color:
164 | - id: color_black
165 | red: 0%
166 | green: 0%
167 | blue: 0%
168 | white: 50%
169 |
170 | - id: color_white
171 | red: 0%
172 | green: 0%
173 | blue: 0%
174 | white: 0%
175 |
176 | spi:
177 | clk_pin: D5
178 | mosi_pin: D7
179 |
180 | display:
181 | - platform: waveshare_epaper
182 | id: my_display
183 | cs_pin: D0
184 | dc_pin: D8
185 | model: 2.90in
186 | rotation: 270
187 | update_interval: 10s
188 | full_update_every: 30
189 | pages:
190 | - id: p1
191 | lambda: |-
192 | //time
193 | it.strftime(75,1, id(time_font), TextAlign::TOP_CENTER, "%H",id(ha_time).now());
194 |
195 | it.printf(150,-6, id(time_font), TextAlign::TOP_CENTER, ":");
196 |
197 | it.strftime(225,1, id(time_font), TextAlign::TOP_CENTER, "%M",id(ha_time).now());
198 |
199 | it.strftime(150, 70, id(date_font), TextAlign::TOP_CENTER, "%Y/%m/%d %a", id(ha_time).now());
200 |
201 | //temp
202 | it.print(25, 97, id(icon_font),TextAlign::TOP_CENTER, "\U000F050F");
203 | if(id(temp_data).has_state()){
204 | it.printf(90,100, id(temp_font), TextAlign::TOP_CENTER, "%.1f°C", id(temp_data).state);
205 | }
206 | //hum
207 | it.print(185, 100, id(icon_font),TextAlign::TOP_CENTER, "\U000F058E");
208 | if(id(hum_data).has_state()){
209 | it.printf(240, 100, id(temp_font), TextAlign::TOP_CENTER, "%.1f%%", id(hum_data).state);
210 | }
211 |
212 | - id: p2
213 | lambda: |-
214 | it.printf(1,-1, id(book_40), TextAlign::TOP_LEFT, "PP:");
215 | it.printf(65,10, id(bold_25), TextAlign::TOP_LEFT, "Filter");
216 | it.printf(30,33, id(bold_40), TextAlign::TOP_LEFT, "%.0f%%",id(pp_filter).state);
217 |
218 | it.printf(1,63, id(book_40), TextAlign::TOP_LEFT, "RO");
219 | it.printf(30,95, id(bold_40), TextAlign::TOP_LEFT, "%.0f%%",id(ro_filter).state);
220 |
221 | it.printf(160,-1, id(book_40), TextAlign::TOP_LEFT, "IN:");
222 | it.printf(215,10, id(bold_25), TextAlign::TOP_LEFT, "TDS");
223 | it.printf(200,33, id(bold_40), TextAlign::TOP_LEFT, "%.0f",id(tds_in).state);
224 |
225 | it.printf(160,63, id(book_40), TextAlign::TOP_LEFT, "OUT:");
226 | it.printf(200,95, id(bold_40), TextAlign::TOP_LEFT, "%.0f",id(tds_out).state);
227 |
--------------------------------------------------------------------------------