├── 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 --------------------------------------------------------------------------------