├── Gimp_Prototype.xcf
├── README.md
├── ble.yaml
├── esp32epaper.yaml
├── fonts
├── Google_Sans_Bold.ttf
├── Google_Sans_Medium.ttf
└── materialdesignicons-webfont_6.5.95.ttf
├── images
├── back.jpg
├── front.jpg
├── inside.jpg
└── side.jpg
├── openhab
└── esp32epaper.rules
├── secrets.yaml.example
└── wifi.yaml
/Gimp_Prototype.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krikk/esp32_E-Paper_Display/c7d93c4f57b3e6243ab41ffbe333eb6467874356/Gimp_Prototype.xcf
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ESP32 based E-Paper Display to display Smart Home Data
2 |
3 |
4 |


5 |

6 |
7 | This is the documentation of my self-build ESP32 based E-Ink Display for displaying Smart Home Data. All of this is heavily inspired on this thread: https://community.home-assistant.io/t/e-paper-display/138625/92
8 |
9 | But as I am a OpenHAB user, it is all based on MQTT, and this is still work in progress...
10 |
11 | Used Hardware:
12 | - Custom build wooden Case (thanks to my father!)
13 | - [Thingpulse ePulse – Low Power ESP32 development board](https://thingpulse.com/product/epulse-low-power-esp32-development-board/) or newer (better) alternative: [ThingPulse ePulse Feather Board](https://thingpulse.com/product/epulse-feather-low-power-esp32-development-board/) (beware this accepts only lower voltage)
14 | - [Waveshare 7.5inch e-Paper HAT](https://www.waveshare.com/wiki/7.5inch_e-Paper_HAT)
15 | - 3 x [18650 Battery Holder](https://www.aliexpress.com/item/4000066839172.html)
16 | - 6 x [Liitokala 18650 3400mAh Li-Ion NCR18650B rechargeable Li-lon](https://www.aliexpress.com/item/32362625564.html)
17 | - 1 x 220 kOhm resistor (for the voltage divider)
18 | - 1 x 100 kOhm resistor (for the voltage divider)
19 |
20 | The battery holders are connected in serial, so that we have an output voltage of 8.4 V max and 5 V min, to monitor battery level i build a voltage divider:
21 | - Input 8.4 V
22 | - R1 = 220kOhm
23 | - R2= 100kOhm
24 | - Out: 2.625V
25 |
26 | *...i need to optimize the power consumption, because this whole setup is only running for about 30 days, with 4x3400mA Batteries!!... still investigating this...*
27 |
28 | Used Software:
29 | - GIMP: to do the layout (see Gimp_Prototype.xcf)
30 | - [ESPHome](https://esphome.io/index.html)
31 | - [OpenHAB](https://www.openhab.org/) you find the needed rules in openhab/esp32epaper.rules (needs JSScripting)
32 | - you find my code in this repository
33 | - the BLE part is disabled, because of memory problems...
34 |
35 | To correctly compile this you have to create a secrets.yaml file within the root directory of the project with the following content:
36 | ```console
37 | wifi_ssid: "yourssid"
38 | wifi_password: "yourpassword"
39 | api_password: "yourpassword"
40 | ota_password: "yourpassword"
41 | mqtt_host: 192.168.1.111
42 | mqtt_user: mqttuser
43 | mqtt_password: mqttpassword
44 | ```
45 |
--------------------------------------------------------------------------------
/ble.yaml:
--------------------------------------------------------------------------------
1 | # Example configuration entry
2 | esp32_ble_tracker:
3 | # Forellenblatt@Wohnzimmer = C4:7C:8D:61:B3:A8
4 | # Gruenlilie@Wohnzimmer = C4:7C:8D:6B:05:AC
5 | #
6 | sensor:
7 | - platform: xiaomi_hhccjcy01
8 | mac_address: 'C4:7C:8D:61:B3:A8'
9 | temperature:
10 | name: "Forellenblatt Temperature"
11 | moisture:
12 | name: "Forellenblatt Moisture"
13 | illuminance:
14 | name: "Forellenblatt Illuminance"
15 | conductivity:
16 | name: "Forellenblatt Soil Conductivity"
17 | battery_level:
18 | name: "Forellenblatt Battery Level"
19 |
20 | - platform: xiaomi_hhccjcy01
21 | mac_address: 'C4:7C:8D:6B:05:AC'
22 | temperature:
23 | name: "Gruenlilie Temperature"
24 | moisture:
25 | name: "Gruenlilie Moisture"
26 | illuminance:
27 | name: "Gruenlilie Illuminance"
28 | conductivity:
29 | name: "Gruenlilie Soil Conductivity"
30 | battery_level:
31 | name: "Gruenlilie Battery Level"
32 |
33 | - platform: ble_rssi
34 | mac_address: C4:7C:8D:61:B3:A8
35 | name: "Forellenblatt RSSI value"
36 |
37 | - platform: ble_rssi
38 | mac_address: C4:7C:8D:6B:05:AC
39 | name: "Gruenlilie RSSI value"
--------------------------------------------------------------------------------
/esp32epaper.yaml:
--------------------------------------------------------------------------------
1 | substitutions:
2 | devicename: esp32epaper
3 |
4 | globals:
5 | - id: prevent_sleep
6 | type: bool
7 | restore_value: no
8 | initial_value: "false"
9 | - id: globalDisplayVal
10 | type: std::vector
11 | restore_value: no
12 | - id: displayWaitTime
13 | type: int
14 | initial_value: '5000'
15 | restore_value: no
16 |
17 |
18 | esphome:
19 | name: $devicename
20 | platform: ESP32
21 | board: esp-wrover-kit
22 | project:
23 | name: "krikk.esp32_E-Paper_Display"
24 | version: "0.0.13"
25 | platformio_options:
26 | upload_speed: 921600
27 | board_build.f_flash: 80000000L
28 | on_boot:
29 | priority: -100
30 | then:
31 | - lambda: "adc_power_acquire();"
32 |
33 | on_shutdown:
34 | then:
35 | # - lambda: "adc_power_off(); my_display->deep_sleep();"
36 | - lambda: "adc_power_release(); my_display->deep_sleep();"
37 |
38 | packages:
39 | wifi: !include wifi.yaml
40 | # ble: !include ble.yaml
41 |
42 |
43 | mqtt:
44 | broker: !secret mqtt_host
45 | username: !secret mqtt_user
46 | password: !secret mqtt_password
47 | discovery: false
48 | client_id: $devicename
49 | birth_message:
50 | topic: tele/$devicename/LWT
51 | payload: Online
52 | will_message:
53 | topic: tele/$devicename/LWT
54 | payload: Offline
55 |
56 | # Enable logging
57 | logger:
58 | level: DEBUG
59 | #level: INFO
60 |
61 |
62 | text_sensor:
63 | - platform: mqtt_subscribe
64 | id: mqttDisplayValues
65 | topic: $devicename/input/DisplayValues
66 | on_value:
67 | then:
68 | - lambda: |-
69 | ESP_LOGD("mqttDisplayValues", "The mqttDisplayValues is: %s", id(mqttDisplayValues).state.c_str());
70 |
71 | std::string str = id(mqttDisplayValues).state;
72 | id(globalDisplayVal).clear();
73 | char * token;
74 | char seps[] = "#";
75 | token = strtok (&str[0],seps);
76 | while (token != NULL)
77 | {
78 | id(globalDisplayVal).push_back(token);
79 | token = strtok (NULL, seps);
80 | }
81 | /*
82 | for ( std::string s : id(globalDisplayVal) ) {
83 | ESP_LOGD("mqttDisplayValues", "String to Vector: %s", s.c_str());
84 | }
85 | ESP_LOGD("mqttDisplayValues", "String time: %s", id(globalDisplayVal).at(1).c_str());
86 | ESP_LOGD("mqttDisplayValues", "String Sleeptime: %s", id(globalDisplayVal).at(22).c_str());
87 | ESP_LOGD("mqttDisplayValues", "String OTA: %s", id(globalDisplayVal).at(23).c_str());
88 | */
89 |
90 | // activate OTA mode if requested
91 | if (id(globalDisplayVal).at(23) == "1") {
92 | id(prevent_sleep) = true;
93 | id(deep_sleep_1).prevent_deep_sleep();
94 | ESP_LOGD("mqttDisplayValues", "OTA: 1");
95 | }
96 | else {
97 | id(prevent_sleep) = false;
98 | ESP_LOGD("mqttDisplayValues", "OTA: 0");
99 | }
100 | // set deep sleep time in seconds
101 | int sleepsec = atoi( id(globalDisplayVal).at(22).c_str());
102 | id(deep_sleep_1).set_sleep_duration(sleepsec*1000);
103 |
104 | // update battery and wifi sensor when requested
105 | if (id(globalDisplayVal).at(24) == "1") {
106 | id(battery).update();
107 | id (wifisignal).update();
108 | ESP_LOGD("mqttDisplayValues", "Sensors Battery Voltage and Wifi signal updated.");
109 | }
110 |
111 | // update display only when presense is set to 1
112 | if (id(globalDisplayVal).at(25) != "0") {
113 | id(my_display).update();
114 | id(displayWaitTime) = atoi( id(globalDisplayVal).at(25).c_str());
115 | ESP_LOGD("mqttDisplayValues", "updating Display, displayWaitTime is: %d ms", id(displayWaitTime));
116 | }
117 | else {
118 | id(displayWaitTime) = 0;
119 | ESP_LOGD("mqttDisplayValues", "displayWaitTime else is: %d", id(displayWaitTime));
120 |
121 | }
122 |
123 |
124 | - delay: !lambda 'return id(displayWaitTime);'
125 | - if:
126 | condition:
127 | lambda: 'return id(prevent_sleep);'
128 | then:
129 | - logger.log:
130 | format: "OTA Mode"
131 | tag: "mqttDisplayValues"
132 | - delay: 54s
133 | else:
134 | - deep_sleep.enter: deep_sleep_1
135 |
136 |
137 | sensor:
138 | # - platform: uptime
139 | # name: "$devicename Uptime Sensor"
140 |
141 | - platform: wifi_signal
142 | name: "WiFi Signal"
143 | id: wifisignal
144 | update_interval: never
145 |
146 | - platform: adc
147 | pin: GPIO36
148 | #attenuation: 11db
149 | attenuation: auto
150 | id: battery
151 | name: "Battery Voltage"
152 | update_interval: never
153 | # filters:
154 | # - calibrate_linear:
155 | # Map 0.0 (from sensor) to 0.0 (true value)
156 | # - 2.62 -> 100
157 | # - 1.56 -> 0
158 |
159 | spi:
160 | clk_pin: GPIO19
161 | mosi_pin: GPIO18
162 | # miso_pin: GPIO5
163 |
164 | font:
165 | - file: 'fonts/Google_Sans_Bold.ttf'
166 | id: clock_font
167 | size: 90
168 | glyphs: [0, 1, 2, 3, 4, 5, 6, 7 ,8, 9, ':']
169 | - file: 'fonts/Google_Sans_Medium.ttf'
170 | id: font_100
171 | size: 100
172 | - file: 'fonts/Google_Sans_Medium.ttf'
173 | id: font_30
174 | size: 30
175 | - file: 'fonts/Google_Sans_Medium.ttf'
176 | id: font_14
177 | size: 14
178 | glyphs: [k, m, /, h, p, 3]
179 | - file: 'fonts/Google_Sans_Medium.ttf'
180 | id: font_40
181 | size: 40
182 | - file: 'fonts/Google_Sans_Medium.ttf'
183 | id: font_55
184 | size: 55
185 | glyphs:
186 | ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
187 | '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
188 | 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
189 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
190 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
191 | 'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/']
192 | - file: 'fonts/Google_Sans_Bold.ttf'
193 | id: status_font
194 | size: 36
195 | glyphs:
196 | ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
197 | '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
198 | 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
199 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
200 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
201 | 'u', 'v', 'w', 'x', 'y', 'z', 'å', 'ä', 'ö', '/']
202 |
203 | # Preview https://pictogrammers.github.io/@mdi/font/6.5.95/
204 | # Download: https://cdnjs.com/libraries/MaterialDesign-Webfont/6.5.95
205 | - file: 'fonts/materialdesignicons-webfont_6.5.95.ttf'
206 | id: weather_font
207 | size: 50
208 | glyphs: [
209 |
210 | # Wifi
211 | '', # F092F mdi-wifi-strength-outline
212 | '', # F091F mdi-wifi-strength-1
213 | '', # F0922 mdi-wifi-strength-2
214 | '', # F0925 mdi-wifi-strength-3
215 | '', # F0928 mdi-wifi-strength-4
216 |
217 | # Battery
218 | '', # F007A mdi-battery-10
219 | '', # F007B mdi-battery-20
220 | '', # F007C mdi-battery-30
221 | '', # F007D mdi-battery-40
222 | '', # F007E mdi-battery-50
223 | '', # F007F mdi-battery-60
224 | '', # F0080 mdi-battery-70
225 | '', # F0081 mdi-battery-80
226 | '', # F0082 mdi-battery-90
227 | '', # F0079 mdi-battery 100
228 | ]
229 |
230 | # Preview https://pictogrammers.github.io/@mdi/font/6.5.95/
231 | # Download: https://cdnjs.com/libraries/MaterialDesign-Webfont/6.5.95
232 | - file: 'fonts/materialdesignicons-webfont_6.5.95.ttf'
233 | id: icon_70
234 | size: 70
235 | glyphs: [
236 | '', # F0899 mdi-account-heart
237 | '', # F050F mdi-thermometer
238 | '', # F0E02 mdi-thermometer-chevron-down Tempmin
239 | '', # F0E03 mdi-thermometer-chevron-up Tempmax
240 | '', # F1137 mdi-fire-hydrant
241 |
242 | '', # F058E mdi-water-percent
243 | '', # F054A mdi-umbrella
244 | '', # F0F55 mdi-home-thermometer-outline
245 | '', # F0F54 mdi-home-thermometer
246 | '', # F07E4 mdi-molecule-co2
247 | '', # F0A72 mdi-solar-power
248 | '', # F06A5 mdi-power-plug
249 | '', # F059D mdi-weather-windy
250 | '', # F0FD3 mdi-bed-double-outline
251 |
252 | '', # F029A mdi-gauge
253 | '', # F15FA mdi-windsock
254 | '', # F058C mdi-water
255 | '', # F0150 mdi-clock-outline Clock
256 |
257 | '', # F11DB mdi-window-closed-variant
258 | '', # F11DC mdi-window-open-variant
259 |
260 | # Sun
261 | '', # F059B mdi-weather-sunset-down
262 | '', # F059C mdi-weather-sunset-up
263 |
264 | # Moon
265 | "", # F0F64 mdi-moon-new new_moon
266 | "", # F0F67 mdi-moon-waxing-crescent waxing_crecent
267 | "", # F0F61 mdi-moon-first-quarter first_quarter
268 | "", # F0F68 mdi-moon-waxing-gibbous waxing_gibbous
269 | "", # F0F62 mdi-moon-full full_moon
270 | "", # F0F66 mdi-moon-waning-gibbous waning_gibbous
271 | "", # F0F63 mdi-moon-last-quarter last_quarter
272 | "", # F0F65 mdi-moon-waning-crescent waning_crescent
273 |
274 |
275 | # room icons
276 | '', # F156D mdi-sofa-outline
277 | '', # F05F2 mdi-food-fork-drink
278 | '', # F0FD1 mdi-bed-king-outline
279 | '', # F064D mdi-human-male
280 | '', # F0649 mdi-human-female
281 | '', # F09A0 mdi-shower
282 | ]
283 |
284 |
285 | - file: 'fonts/materialdesignicons-webfont_6.5.95.ttf'
286 | id: icon_40
287 | size: 40
288 | glyphs: [
289 | '', # F0E02 mdi-thermometer-chevron-down Tempmin
290 | '', # F0E03 mdi-thermometer-chevron-up Tempmax
291 | '', # F058E mdi-water-percent
292 | ]
293 |
294 | - file: 'fonts/materialdesignicons-webfont_6.5.95.ttf'
295 | id: icon_55
296 | size: 55
297 | glyphs: [
298 | '', # F058E mdi-water-percent
299 | ]
300 |
301 |
302 | display:
303 | - platform: waveshare_epaper
304 | cs_pin: GPIO15
305 | dc_pin: GPIO14
306 | busy_pin: GPIO21
307 | reset_pin: GPIO22
308 | model: 7.50inV2
309 | update_interval: never
310 | id: my_display
311 | lambda: |
312 | int x, y;
313 |
314 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
315 | //console.info("out: Date Time PVWatt TotalWatt AussenT AusTMin AusTMax AussenH WohnzT WohnzH BadT BadH SchlT SchlH SZiT SZiH LZiT LZiH CO2 Wind Mond");
316 | // out: 2022-02-28#14:24:57#491#-303#4.5#-1.2#4.5#39#23.5#46#23.1#59#19.9#56#19.8#57#20.5#54#1180#12
317 |
318 | /* Print time in HH:MM format */
319 | it.printf(400, 495, id(clock_font), TextAlign::BOTTOM_CENTER, "%s", id(globalDisplayVal).at(1).c_str());
320 | it.printf(245, 400, id(font_55), TextAlign::BOTTOM_LEFT, "%s", id(globalDisplayVal).at(0).c_str());
321 |
322 |
323 | /* WiFi Signal Strenght
324 | if(id(wifisignal).has_state()) {
325 | x = 545, y = 466;
326 | if (id(wifisignal).state >= -50) {
327 | //Excellent
328 | it.print(x, y, id(weather_font), TextAlign::BOTTOM_CENTER, "");
329 | ESP_LOGI("WiFi", "Exellent");
330 | } else if (id(wifisignal).state >= -60) {
331 | //Good
332 | it.print(x, y, id(weather_font), TextAlign::BOTTOM_CENTER, "");
333 | ESP_LOGI("WiFi", "Good");
334 | } else if (id(wifisignal).state >= -67) {
335 | //Fair
336 | it.print(x, y, id(weather_font), TextAlign::BOTTOM_CENTER, "");
337 | ESP_LOGI("WiFi", "Fair");
338 | } else if (id(wifisignal).state >= -70) {
339 | //Weak
340 | it.print(x, y, id(weather_font), TextAlign::BOTTOM_CENTER, "");
341 | ESP_LOGI("WiFi", "Weak");
342 | } else {
343 | //Unlikely working signal
344 | it.print(x, y, id(weather_font), TextAlign::BOTTOM_CENTER, "");
345 | ESP_LOGI("WiFi", "Unlikely");
346 | }
347 | }
348 | */
349 |
350 | /* Battery Voltage */
351 | if(id(battery).has_state()) {
352 | x = 233, y = 466;
353 | if (id(battery).state >= 2.8) {
354 | // 100 %
355 | it.print(x, y, id(weather_font), TextAlign::BOTTOM_LEFT, "");
356 | } else if (id(battery).state >= 2.7) {
357 | // 90 %
358 | it.print(x, y, id(weather_font), TextAlign::BOTTOM_LEFT, "");
359 | } else if (id(battery).state >= 2.6) {
360 | // 80%
361 | it.print(x, y, id(weather_font), TextAlign::BOTTOM_LEFT, "");
362 | } else if (id(battery).state >= 2.5) {
363 | // 70%
364 | it.print(x, y, id(weather_font), TextAlign::BOTTOM_LEFT, "");
365 | } else if (id(battery).state >= 2.4) {
366 | // 60%
367 | it.print(x, y, id(weather_font), TextAlign::BOTTOM_LEFT, "");
368 | } else if (id(battery).state >= 2.3) {
369 | // 50%
370 | it.print(x, y, id(weather_font), TextAlign::BOTTOM_LEFT, "");
371 | } else if (id(battery).state >= 2.2) {
372 | // 40%
373 | it.print(x, y, id(weather_font), TextAlign::BOTTOM_LEFT, "");
374 | } else if (id(battery).state >= 2.1) {
375 | // 30%
376 | it.print(x, y, id(weather_font), TextAlign::BOTTOM_LEFT, "");
377 | } else if (id(battery).state >= 2.0) {
378 | // 20%
379 | it.print(x, y, id(weather_font), TextAlign::BOTTOM_LEFT, "");
380 | } else {
381 | // 10%
382 | it.print(x, y, id(weather_font), TextAlign::BOTTOM_LEFT, "");
383 | }
384 | }
385 |
386 | // pv power
387 | it.print(645, 480, id(icon_70), TextAlign::BOTTOM_RIGHT, "");
388 | it.printf(800, 480, id(font_55), TextAlign::BOTTOM_RIGHT , "%sW", id(globalDisplayVal).at(2).c_str());
389 |
390 |
391 | // total power
392 | it.print(-13, 480, id(icon_70), TextAlign::BOTTOM_LEFT, "");
393 | it.printf(45, 480, id(font_55), TextAlign::BOTTOM_LEFT , "%sW", id(globalDisplayVal).at(3).c_str());
394 |
395 | // outside temperature
396 | it.print(400, 0, id(font_30), TextAlign::TOP_LEFT, "OUT:");
397 | it.printf(800, -19, id(font_100), TextAlign::TOP_RIGHT, "%s°C", id(globalDisplayVal).at(4).c_str());
398 |
399 | // outside min temperature
400 | it.print(400, 120, id(icon_40), TextAlign::CENTER_LEFT, "");
401 | it.printf(440, 120, id(font_40), TextAlign::CENTER_LEFT, "%s°C", id(globalDisplayVal).at(5).c_str());
402 |
403 | // outside max temperature
404 | it.print(678, 120, id(icon_40), TextAlign::CENTER_RIGHT, "");
405 | it.printf(800, 120, id(font_40), TextAlign::CENTER_RIGHT, "%s°C", id(globalDisplayVal).at(6).c_str());
406 |
407 | // outside humidity
408 | it.printf(812, 200, id(icon_55), TextAlign::CENTER_RIGHT, "");
409 | it.printf(766, 200, id(font_55), TextAlign::CENTER_RIGHT, "%s", id(globalDisplayVal).at(7).c_str());
410 |
411 | // livingroom temperature
412 | it.print(0, 35, id(icon_70), TextAlign::CENTER_LEFT, "");
413 | it.printf(90, 35, id(font_40), TextAlign::CENTER_LEFT , "%s°", id(globalDisplayVal).at(8).c_str());
414 | // livingroom humidity
415 | it.printf(315, 35, id(icon_40), TextAlign::CENTER_RIGHT, "");
416 | it.printf(235, 35, id(font_40), TextAlign::CENTER_LEFT , "%s", id(globalDisplayVal).at(9).c_str());
417 |
418 | // Bath temperature
419 | it.print(0, 105, id(icon_70), TextAlign::CENTER_LEFT, "");
420 | it.printf(90, 105, id(font_40), TextAlign::CENTER_LEFT , "%s°", id(globalDisplayVal).at(10).c_str());
421 | // bath humitidy
422 | it.printf(315, 105, id(icon_40), TextAlign::CENTER_RIGHT, "");
423 | it.printf(235, 105, id(font_40), TextAlign::CENTER_LEFT , "%s", id(globalDisplayVal).at(11).c_str());
424 |
425 | // bedroom temperature
426 | it.print(0, 175, id(icon_70), TextAlign::CENTER_LEFT, "");
427 | it.printf(90, 175, id(font_40), TextAlign::CENTER_LEFT , "%s°", id(globalDisplayVal).at(12).c_str());
428 | // bedroom humidity
429 | it.printf(315, 175, id(icon_40), TextAlign::CENTER_RIGHT, "");
430 | it.printf(235, 175, id(font_40), TextAlign::CENTER_LEFT , "%s", id(globalDisplayVal).at(13).c_str());
431 |
432 | // boys-room temperature
433 | it.print(0, 245, id(icon_70), TextAlign::CENTER_LEFT, "");
434 | it.printf(90, 245, id(font_40), TextAlign::CENTER_LEFT , "%s°", id(globalDisplayVal).at(14).c_str());
435 | // boys-room humidity
436 | it.printf(315, 245, id(icon_40), TextAlign::CENTER_RIGHT, "");
437 | it.printf(235, 245, id(font_40), TextAlign::CENTER_LEFT , "%s", id(globalDisplayVal).at(15).c_str());
438 |
439 | // girl-room temperature
440 | it.print(0, 315, id(icon_70), TextAlign::CENTER_LEFT, "");
441 | it.printf(90, 315, id(font_40), TextAlign::CENTER_LEFT , "%s°", id(globalDisplayVal).at(16).c_str());
442 | // girl-room humidity
443 | it.printf(315, 315, id(icon_40), TextAlign::CENTER_RIGHT, "");
444 | it.printf(235, 315, id(font_40), TextAlign::CENTER_LEFT , "%s", id(globalDisplayVal).at(17).c_str());
445 |
446 |
447 | /* CO2 icon */
448 | it.print(666, 280, id(icon_70), TextAlign::CENTER_RIGHT, "");
449 | it.printf(773, 280, id(font_55), TextAlign::CENTER_RIGHT, "%s", id(globalDisplayVal).at(18).c_str());
450 | it.printf(800, 300, id(font_14), TextAlign::BASELINE_RIGHT, "ppm");
451 |
452 | /* wind */
453 | it.print(400, 280, id(icon_70), TextAlign::CENTER_LEFT, "");
454 | it.printf(525, 280, id(font_55), TextAlign::CENTER_RIGHT, "%s", id(globalDisplayVal).at(19).c_str());
455 | it.printf(525, 280, id(font_14), TextAlign::CENTER_LEFT, "km/h");
456 |
457 | /* Moon icon */
458 | //ESP_LOGI("Moon icon", "%s", id(moonPhase).state.c_str());
459 | it.printf(800, 360, id(icon_70), TextAlign::CENTER_RIGHT, "%s", id(globalDisplayVal).at(20).c_str());
460 |
461 | /* rain next 3 h */
462 | it.print(400, 190, id(icon_70), TextAlign::CENTER_LEFT, "");
463 | it.printf(550, 190, id(font_55), TextAlign::CENTER_RIGHT, "%s", id(globalDisplayVal).at(21).c_str());
464 | it.printf(555, 190, id(font_14), TextAlign::CENTER_LEFT, "mm");
465 | it.printf(444, 200, id(font_14), TextAlign::CENTER_LEFT, "3h");
466 |
467 |
468 | // it.line(0, 299, 400, 299);
469 |
470 |
471 |
472 | # Example configuration entry
473 | deep_sleep:
474 | run_duration: 15s
475 | sleep_duration: 33s
476 | id: deep_sleep_1
477 |
478 |
--------------------------------------------------------------------------------
/fonts/Google_Sans_Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krikk/esp32_E-Paper_Display/c7d93c4f57b3e6243ab41ffbe333eb6467874356/fonts/Google_Sans_Bold.ttf
--------------------------------------------------------------------------------
/fonts/Google_Sans_Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krikk/esp32_E-Paper_Display/c7d93c4f57b3e6243ab41ffbe333eb6467874356/fonts/Google_Sans_Medium.ttf
--------------------------------------------------------------------------------
/fonts/materialdesignicons-webfont_6.5.95.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krikk/esp32_E-Paper_Display/c7d93c4f57b3e6243ab41ffbe333eb6467874356/fonts/materialdesignicons-webfont_6.5.95.ttf
--------------------------------------------------------------------------------
/images/back.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krikk/esp32_E-Paper_Display/c7d93c4f57b3e6243ab41ffbe333eb6467874356/images/back.jpg
--------------------------------------------------------------------------------
/images/front.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krikk/esp32_E-Paper_Display/c7d93c4f57b3e6243ab41ffbe333eb6467874356/images/front.jpg
--------------------------------------------------------------------------------
/images/inside.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krikk/esp32_E-Paper_Display/c7d93c4f57b3e6243ab41ffbe333eb6467874356/images/inside.jpg
--------------------------------------------------------------------------------
/images/side.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krikk/esp32_E-Paper_Display/c7d93c4f57b3e6243ab41ffbe333eb6467874356/images/side.jpg
--------------------------------------------------------------------------------
/openhab/esp32epaper.rules:
--------------------------------------------------------------------------------
1 | configuration: {}
2 | triggers:
3 | - id: "1"
4 | configuration:
5 | cronExpression: 57 * * * * ? *
6 | type: timer.GenericCronTrigger
7 | conditions: []
8 | actions:
9 | - inputs: {}
10 | id: "2"
11 | configuration:
12 | type: application/javascript;version=ECMAScript-2021
13 | script: >-
14 | var ZonedDateTime = Java.type("java.time.ZonedDateTime");
15 |
16 |
17 | var out = "";
18 |
19 |
20 | var now = ZonedDateTime.now()
21 |
22 |
23 | //var date = now.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"));
24 |
25 | var date = now.format(java.time.format.DateTimeFormatter.ofPattern("E d.MMM yyyy"));
26 |
27 | // set time 1 minutes ahead, case rule runs on sec 57
28 |
29 | var time = (now.plusMinutes(1)).format(java.time.format.DateTimeFormatter.ofPattern("HH:mm"));
30 |
31 | //console.info(timestamp);
32 |
33 |
34 | out = date + "#" + time + "#";
35 |
36 |
37 | // PV Power
38 |
39 | // averageSince returns null when PVPower is 0 ..???!!
40 |
41 | var PersistenceExtensions = Java.type("org.openhab.core.persistence.extensions.PersistenceExtensions");
42 |
43 |
44 |
45 | var lastValue = parseInt(items.getItem("HikingDDS2382Power").state);
46 |
47 | //console.info("esp32rules", "PVPower lastValue value: " + lastValue);
48 |
49 |
50 | //var pvPower = items.getItem("HikingDDS2382Power").history.averageSince(now.minusHours(12));
51 |
52 | var pvPower = items.getItem("HikingDDS2382Power").history.averageSince(now.minusMinutes(1));
53 |
54 |
55 | //console.info("esp32rules", "PVPower average value: " + pvPower);
56 |
57 |
58 | if ((pvPower != null) && (lastValue != 0.0)) {
59 | //console.info("esp32rules if", parseInt(pvPower));
60 | out += parseInt(pvPower);
61 | }
62 |
63 | else {
64 | //console.info("esp32ruless else", parseInt(items.getItem("HikingDDS2382Power").state));
65 | out += parseInt(items.getItem("HikingDDS2382Power").state);
66 | }
67 |
68 |
69 |
70 | // total power
71 |
72 | //console.info("esp32rules total power: " , parseInt(items.getItem("emonAccumulatedWatts").history.averageSince(now.minusMinutes(1))));
73 |
74 | out += "#" + parseInt(items.getItem("emonAccumulatedWatts").history.averageSince(now.minusMinutes(1)));
75 |
76 |
77 | // Aussen Temperatur
78 |
79 | out += "#" + parseFloat(items.getItem("AussenTemperatur").state).toFixed(1);
80 |
81 |
82 | // Aussen Temperatur Min
83 |
84 | out += "#" + parseFloat(items.getItem("AussenTemperaturTodayMin").state).toFixed(1);
85 |
86 |
87 | // Aussen Temperatur Max
88 |
89 | out += "#" + parseFloat(items.getItem("AussenTemperaturTodayMax").state).toFixed(1);
90 |
91 |
92 | // Aussen Luftfeuchte
93 |
94 | out += "#" + parseInt(items.getItem("AussenLuftfeuchte").state).toFixed(0);
95 |
96 |
97 | // wohnzimmer temperatur
98 |
99 | out += "#" + parseFloat(items.getItem("Aqara1Temperatur").state).toFixed(1);
100 |
101 |
102 | // wohnzimmer Luftfeuchte
103 |
104 | out += "#" + parseInt(items.getItem("Aqara1Luftfeuchte").state);
105 |
106 |
107 | // bad temperatur
108 |
109 | out += "#" + parseFloat(items.getItem("Aqara2Temperatur").state).toFixed(1);
110 |
111 | // bad Luftfeuchte
112 |
113 | out += "#" + parseInt(items.getItem("Aqara2Luftfeuchte").state);
114 |
115 |
116 | // schlafzimmer temperatur
117 |
118 | out += "#" + parseFloat(items.getItem("SchlafzimmerTemperatur").state).toFixed(1);
119 |
120 | // schlafzimmer Luftfeuchte
121 |
122 | out += "#" + parseInt(items.getItem("SchlafzimmerLuftfeuchte").state);
123 |
124 |
125 | // S.Zimmer
126 |
127 | out += "#" + parseFloat(items.getItem("Aqara3Temperatur").state).toFixed(1);
128 |
129 | // S.Zimmer Luftfeuchte
130 |
131 | out += "#" + parseInt(items.getItem("Aqara3Luftfeuchte").state);
132 |
133 |
134 | // L.Zimmer
135 |
136 | out += "#" + parseFloat(items.getItem("ArbeitszimmerTemperatur").state).toFixed(1);
137 |
138 | // L.Zimmer Luftfeuchte
139 |
140 | out += "#" + parseInt(items.getItem("ArbeitszimmerLuftfeuchte").state);
141 |
142 |
143 | // CO2 Vorraum
144 |
145 | out += "#" + parseInt(items.getItem("Vorraum1StkCO2").state);
146 |
147 |
148 | // Wind
149 |
150 | out += "#" + parseInt(items.getItem("BalkonWindgeschwindigkeit").history.averageSince(now.minusMinutes(30)));
151 |
152 |
153 | // moon
154 |
155 | // "", # NEW NEW=🌑 Neumond
156 |
157 | // "", # WAXING_CRESCENT WAXING_CRESCENT=🌑→🌓 zunehmender Halbmond
158 |
159 | // "", # FIRST_QUARTER FIRST_QUARTER=🌓 erstes Viertel
160 |
161 | // "", # WAXING_GIBBOUS WAXING_GIBBOUS=🌓→🌕 zunehmender Mond
162 |
163 | // "", # FULL FULL=🌕 Vollmond
164 |
165 | // "", # WANING_GIBBOUS WANING_GIBBOUS=🌕→🌗 abnehmender Mond
166 |
167 | // "", # THIRD_QUARTER THIRD_QUARTER=🌗 letztes Viertel
168 |
169 | // "", # WANING_CRESCENT WANING_CRESCENT=🌗→🌑 abnehmender Halbmond
170 |
171 |
172 | //console.log("Moon phase: ", items.getItem("MoonPhase").state)
173 |
174 |
175 | switch (items.getItem("MoonPhase").state) {
176 | case "NEW":
177 | out += "#" + "";
178 | break;
179 | case "🌑 Neumond":
180 | out += "#" + "";
181 | break;
182 | case "WAXING_CRESCENT":
183 | out += "#" + "";
184 | break;
185 | case "🌑→🌓 zunehmender Halbmond":
186 | out += "#" + "";
187 | break;
188 | case "FIRST_QUARTER":
189 | out += "#" + "";
190 | break;
191 | case "🌓 erstes Viertel":
192 | out += "#" + "";
193 | break;
194 | case "WAXING_GIBBOUS":
195 | out += "#" + "";
196 | break;
197 | case "🌓→🌕 zunehmender Mond":
198 | out += "#" + "";
199 | break;
200 | case "FULL":
201 | out += "#" + "";
202 | break;
203 | case "🌕 Vollmond":
204 | out += "#" + "";
205 | break;
206 | case "WANING_GIBBOUS":
207 | out += "#" + "";
208 | break;
209 | case "🌕→🌗 abnehmender Mond":
210 | out += "#" + "";
211 | break;
212 | case "THIRD_QUARTER":
213 | out += "#" + "";
214 | break;
215 | case "🌗 letztes Viertel":
216 | out += "#" + "";
217 | break;
218 | case "WANING_CRESCENT":
219 | out += "#" + "";
220 | break;
221 | case "🌗→🌑 abnehmender Halbmond":
222 | out += "#" + "";
223 | break;
224 | default:
225 | out += "#" + " ";
226 | console.warn("esp32rules.rules", "Undefinierte MoonPhase!!")
227 | }
228 |
229 |
230 | // Regen nächste 3 h
231 |
232 | out += "#" + parseFloat(items.getItem("gOpenWeatherMap_ForecastHours03_Rain").state);
233 |
234 |
235 | // sleeptime in sec, longer sleep time in night
236 |
237 | var hours = parseInt(now.format(java.time.format.DateTimeFormatter.ofPattern("HH")));
238 |
239 | if (hours >=23 || hours < 7) {
240 | out += "#" + 5*60;
241 | }
242 |
243 | else {
244 | out += "#" + 60;
245 | }
246 |
247 |
248 | // esp32E-Paper display OTA mode
249 |
250 | out += "#" + ((items.getItem("esp32epaperOtaMode").state == "OFF") ? "0" : "1");
251 |
252 | //console.log("OTA: ", (items.getItem("esp32epaperOtaMode").state == "OFF") ? "0" : "1");
253 |
254 |
255 | // set to 1 to read battery voltage & wifi signal sensor
256 |
257 | // read battery sensor every full hour
258 |
259 | var minutes = now.format(java.time.format.DateTimeFormatter.ofPattern("mm"));
260 |
261 | //console.log("minutes", minutes);
262 |
263 | out += "#" + ( minutes == "00" ? "1" : "0");
264 |
265 | //out += "#" + "1";
266 |
267 |
268 |
269 | if (items.getItem("esp32epaperOtaMode").state == "ON") {
270 | // always update display in OTA mode
271 | out += "#" + "5100";
272 | }
273 |
274 | else {
275 | // esp32E-Paper update display: if Presence is OFF, do not update display
276 | // if Presence is ON, set wait time after Display refresh normally > 5000ms
277 | out += "#" + ((items.getItem("UnknownPresence").state == "OFF") ? "0" : "5100");
278 | //out += "#" + "0";
279 | }
280 |
281 |
282 | var mqttActions = actions.Things.getActions("mqtt","mqtt:broker:mosquitto_raspi");
283 |
284 | if (mqttActions === null) {
285 | console.error("Actions not found, check mqtt broker");
286 | }
287 |
288 | else {
289 |
290 | mqttActions.publishMQTT("esp32epaper/input/DisplayValues", out, true)
291 | }
292 |
293 |
294 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
295 |
296 | //console.info("out: Date Time PVWatt TotalWatt AussenT AusTMin AusTMax AussenH WohnzT WohnzH BadT BadH SchlT SchlH SZiT SZiH LZiT LZiH CO2 Wind Mond RainForc SleepT OTA ReadBatt UpdDisplay");
297 |
298 | // out: Mon 7.Mar 2022#13:00#53#185#1.5#-0.4#1.8#47#22.0#48#22.8#66#19.0#58#19.3#55#19.4#54#1179#12##0#60#0#0#1
299 |
300 |
301 | console.info("out: ", out);
302 | type: script.ScriptAction
303 |
--------------------------------------------------------------------------------
/secrets.yaml.example:
--------------------------------------------------------------------------------
1 | wifi_ssid: "yourssid"
2 | wifi_password: "yourpassword"
3 | api_password: "yourpassword"
4 | ota_password: "yourpassword"
5 | mqtt_host: 192.168.1.111
6 | mqtt_user: mqttuser
7 | mqtt_password: mqttpassword
--------------------------------------------------------------------------------
/wifi.yaml:
--------------------------------------------------------------------------------
1 | wifi:
2 | ssid: !secret wifi_ssid
3 | password: !secret wifi_password
4 | fast_connect: on
5 | power_save_mode: high
6 | # for changing ip
7 | # use_address: 192.168.11.210
8 |
9 | # Optional manual IP
10 | manual_ip:
11 | static_ip: 192.168.11.33
12 | gateway: 192.168.11.1
13 | subnet: 255.255.255.0
14 | dns1: 192.168.11.1
15 |
16 | # Enable fallback hotspot (captive portal) in case wifi connection fails
17 | # ap:
18 | # ssid: "$devicename Fallback Hotspot"
19 | # password: "xxxxxx"
20 |
21 | #captive_portal:
22 |
23 | # Enable Home Assistant API
24 | #api:
25 | # password: !secret api_password
26 |
27 | ota:
28 | password: !secret ota_password
--------------------------------------------------------------------------------