├── images ├── side.jpeg ├── tilt.jpeg ├── buttons.jpeg ├── display.jpeg ├── fusion1.jpg ├── fusion2.jpg ├── standalone-display.jpeg └── integrated-display-back.jpeg ├── 3d_print ├── ESP32-2432S028 │ ├── mount.3mf │ ├── inner-body.stl │ ├── outer-body.stl │ ├── table-mount.stl │ ├── locking-body.stl │ └── touch-display.f3d └── ILI9341-only-display │ ├── mount.3mf │ ├── inner-body.stl │ ├── outer-body.stl │ ├── locking-body.stl │ ├── table-mount.stl │ └── touch-display.f3d ├── LICENSE ├── README.md └── esphome ├── ili9341-with-external-esp.yml └── esp32-2432s028.yml /images/side.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/images/side.jpeg -------------------------------------------------------------------------------- /images/tilt.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/images/tilt.jpeg -------------------------------------------------------------------------------- /images/buttons.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/images/buttons.jpeg -------------------------------------------------------------------------------- /images/display.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/images/display.jpeg -------------------------------------------------------------------------------- /images/fusion1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/images/fusion1.jpg -------------------------------------------------------------------------------- /images/fusion2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/images/fusion2.jpg -------------------------------------------------------------------------------- /images/standalone-display.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/images/standalone-display.jpeg -------------------------------------------------------------------------------- /3d_print/ESP32-2432S028/mount.3mf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/3d_print/ESP32-2432S028/mount.3mf -------------------------------------------------------------------------------- /images/integrated-display-back.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/images/integrated-display-back.jpeg -------------------------------------------------------------------------------- /3d_print/ESP32-2432S028/inner-body.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/3d_print/ESP32-2432S028/inner-body.stl -------------------------------------------------------------------------------- /3d_print/ESP32-2432S028/outer-body.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/3d_print/ESP32-2432S028/outer-body.stl -------------------------------------------------------------------------------- /3d_print/ESP32-2432S028/table-mount.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/3d_print/ESP32-2432S028/table-mount.stl -------------------------------------------------------------------------------- /3d_print/ILI9341-only-display/mount.3mf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/3d_print/ILI9341-only-display/mount.3mf -------------------------------------------------------------------------------- /3d_print/ESP32-2432S028/locking-body.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/3d_print/ESP32-2432S028/locking-body.stl -------------------------------------------------------------------------------- /3d_print/ESP32-2432S028/touch-display.f3d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/3d_print/ESP32-2432S028/touch-display.f3d -------------------------------------------------------------------------------- /3d_print/ILI9341-only-display/inner-body.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/3d_print/ILI9341-only-display/inner-body.stl -------------------------------------------------------------------------------- /3d_print/ILI9341-only-display/outer-body.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/3d_print/ILI9341-only-display/outer-body.stl -------------------------------------------------------------------------------- /3d_print/ILI9341-only-display/locking-body.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/3d_print/ILI9341-only-display/locking-body.stl -------------------------------------------------------------------------------- /3d_print/ILI9341-only-display/table-mount.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/3d_print/ILI9341-only-display/table-mount.stl -------------------------------------------------------------------------------- /3d_print/ILI9341-only-display/touch-display.f3d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akuehlewind/ESPHome-touch-display-mount/HEAD/3d_print/ILI9341-only-display/touch-display.f3d -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Adrian Kuehlewind 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2.8" ILI9341 Touch Display Enclosure for ESP32 with Home Assistant Integration 2 | 3 | This project provides ESPHome config files and 3D-printable files for an enclosure designed to house a 2.8" ILI9341 touch display. Compatible with standalone 2.8" ILI9341 touch display connected to an ESP32 board or using the ESP32-2432S028 (integrating an ESP32 Wroom module and ILI9341 display on one board), this setup serves as an external, touchscreen-enabled display for your Home Assistant environment. 4 | 5 | The enclosure is designed for mounting beneath a desk and includes a swivel joint, allowing for easy adjustments to the viewing angle while keeping all cables neatly concealed for a clean, streamlined look. The touchscreen offers multiple customizable buttons linked to Home Assistant automations, making it easy to control functions such as lighting—and you’re free to create additional buttons as desired. 6 | 7 | The joint supports an adjustable tilt of +/- 35 degrees up and down. The display’s positioning tension can be fine-tuned by adjusting the screw tightness. The screw hole is deliberately sized slightly smaller than the M4 screw, requiring a bit of force during insertion to ensure a secure hold that prevents any looseness. 8 | 9 | Simply print the files and flash the firmware to the ESP using ESPHome, connect it to Home Assistant. 10 | 11 | The images show the version with an ESP32-2432S028. I recommend using the ESP32-2432S028 as its just the power cable you need to pass-thru into the case and everything else is hidden. Down below you'll find some images of the ILI9341 display with an external ESP32. 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ### Features: 21 | 22 | - Customizable 3D-printed enclosure with hidden cable design for a clean setup 23 | - Swivel joint for adjustable viewing angles 24 | - Supports 2.8" ILI9341 touch display, compatible with ESP32 or ESP32-2432S028 (includes ESP32 Wroom and display on one board) 25 | - Connect to Home Assistant compatibility to control automations such as lighting 26 | - Using [ESPHome](https://www.esphome.io) which makes it easy to code and modify for your needs 27 | 28 | 29 | ## Parts needed 30 | 31 | | Part | Price | Comment | 32 | |------------------------------|---------|-------------------------------------------------| 33 | | ESP32-2432S028 | 15$ | 2.8" ILI9341 with integrated ESP | 34 | | 2x M4 3.5x16 screw (flathead)| 0.10$ | Screws to connect the mount case with the base | 35 | 36 | or 37 | 38 | | Part | Price | Comment | 39 | |------------------------------|---------|-------------------------------------------------| 40 | | 2.8" ILI9341 | 8$ | 2.8" ILI9341 | 41 | | ESP32 Wroom 32D | 8$ | or some ESP32 | 42 | | 2x M4 3.5x16 screw (flathead)| 0.10$ | Screws to connect the mount case with the base | 43 | 44 | 45 | 46 | ## How 47 | - Flash your ESP32 via ESPHome and use the yaml from the esphome folder. Adjust to your needs 48 | - Print the parts. 49 | - Use two M4 screws to attach the display case and the base 50 | - *Connect all pins (when using a solo display with an ESP32) 51 | 52 | ## HomeAssistant 53 | - Create some automations in home assistant using the exposed buttons as trigger. 54 |
55 |
56 |
57 | # Options 58 | 59 | ## a) Using an ESP32-2432S028 60 | Simply connect to USB and flash the ESP using the yml file in the esphome directory. If your board is designed different check the pin layout 61 | 62 | | ESP32-2432S028 | PIN | Comment | 63 | |------------------------------|--------------|------------------------------------------------------| 64 | | LCD | | | 65 | | clk_pin | GPIO14 | SPI LCD Clock | 66 | | mosi_pin | GPIO13 | SPI LCD MOSI (sometimes also labeled as SDI) | 67 | | miso_pin | GPIO12 | SPI LCD MISO (sometimes also labeled as SDO | 68 | | cs_pin | GPIO15 | Display CS | 69 | | dc_pin | GPIO2 | Display DC | 70 | | Touchscreen | | | 71 | | clk_pin | GPIO25 | SPI Touchscreen Clock | 72 | | mosi_pin | GPIO32 | SPI Touchscreen MOSI (sometimes also labeled as DIN) | 73 | | miso_pin | GPIO39 | SPI Touchscreen MISO (sometimes also labeled as DO) | 74 | | cs_pin | GPIO33 | Touchscreen CS | 75 | | interrupt_pin | GPIO36 | Touchscreen Interrupt | 76 | | LED | | | 77 | | ledc | GPIO21 | Backlight LED display | 78 | | ledc | GPIO4 | Onboard LED (not used for this project) | 79 | 80 | To power the device, use the VIN and GND pins from the JST port (typically provided with a JST-to-pin cable). Pass the cable through the case using the hole in the center which runs through the swivel joint and connect these ends to the 5V and GND wires of an old USB cable 81 | 82 | 83 | ## b) Using an 2.8" ILI9141 standalone connected to an ESP32 Wroom 32D 84 | Follow the connection diagram, which shows how to put everything together. 85 | 86 | | ILI9341 | PIN ESP32 | Comment | 87 | |------------------------------|--------------|------------------------------------------------------| 88 | | GND | GND | Ground | 89 | | VCC | 3.3V | Power | 90 | | LCD | | | 91 | | SCK | GPIO18 | SPI LCD Clock | 92 | | SDI (MOSI) | GPIO23 | SPI LCD MOSI (sometimes also labeled as SDI) | 93 | | SDOK (MISO) | GPIO19 | SPI LCD MISO (sometimes also labeled as SDO | 94 | | CS | GPIO27 | Display CS | 95 | | D/C | GPIO26 | Display DC | 96 | | RESET | GPI16 | Display Reset | 97 | | Touchscreen | | | 98 | | T_CLK | GPIO25 | SPI Touchscreen Clock | 99 | | T_DO | GPIO35 | SPI Touchscreen MOSI (sometimes also labeled as DIN) | 100 | | T_DIN | GPIO32 | SPI Touchscreen MISO (sometimes also labeled as DO) | 101 | | T_CS | GPIO33 | Touchscreen CS | 102 | | T_IRQ | GPIO34 | Touchscreen Interrupt | 103 | | LED | | | 104 | | ledc | GPIO4 | Backlight LED display | 105 | 106 | 107 | ## 3D Print 108 | See folder print to get all SLT files and the fusion360 file. Feel free to adjust and remix 109 | 110 | # DISCLAIMER 111 | This project is to be considered as a work in progress. 112 | Feel free to adjust and remix the 3D cover to your needs. Its designed in Fusion 360, see fusion 360 file in the 3d_print folder. 113 | 114 | # Other 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | # More projects 123 | Looking for more cool projects using this display? Check out the [LoctekMotion Touch Display GitHub repository](https://github.com/3DJupp/LoctekMotion-TouchDisplay)! This awesome project takes the same 2.8" ILI9341 touchscreen setup and repurposes it—not for lights, but for controlling a height-adjustable Flexispot desk with ease. If you're into smart home automation and custom builds, it's definitely worth a look! 124 | 125 | 126 | -------------------------------------------------------------------------------- /esphome/ili9341-with-external-esp.yml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: smartdisplay 3 | friendly_name: SmartDisplay 4 | 5 | esp32: 6 | board: esp32dev 7 | framework: 8 | type: arduino 9 | 10 | logger: 11 | 12 | api: 13 | encryption: 14 | key: "XXXXXXXXXXXXXXXXXXXXX/LI=" 15 | 16 | ota: 17 | - platform: esphome 18 | password: "XXXXXXXXXXXXXXXXXXXXX" 19 | 20 | wifi: 21 | ssid: !secret wifi_ssid 22 | password: !secret wifi_password 23 | ap: 24 | ssid: "Smartdisplay Fallback Hotspot" 25 | password: "XXXXXXXXXXXXXXXXXXXXX" 26 | 27 | captive_portal: 28 | 29 | time: 30 | - platform: homeassistant 31 | id: homeassistant_time 32 | on_time: 33 | - seconds: 0 34 | minutes: /1 # Update every minute 35 | then: 36 | - component.update: my_display 37 | 38 | font: 39 | 40 | # gfonts://family[@weight] 41 | - file: "gfonts://Roboto@500" 42 | id: big_text 43 | size: 60 44 | bpp: 4 45 | glyphs: 46 | ['&', '@', '!', ',', '.', '?', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0', 47 | '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 48 | 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 49 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f', 50 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 51 | 'u', 'v', 'w', 'x', 'y', 'z','å', 'Ä', 'ä', 'Ö', 'ö', 'Ü', 'ü', '/'] 52 | 53 | # gfonts://family[@weight] 54 | - file: "gfonts://Roboto@300" 55 | id: notice_text 56 | size: 16 57 | bpp: 4 58 | glyphs: 59 | ['&', '@', '!', ',', '.', '?', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0', 60 | '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 61 | 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 62 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f', 63 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 64 | 'u', 'v', 'w', 'x', 'y', 'z','å', 'Ä', 'ä', 'Ö', 'ö', 'Ü', 'ü', '/'] 65 | 66 | # gfonts://family[@weight] 67 | - file: "gfonts://Roboto@400" 68 | id: label 69 | size: 13 70 | bpp: 4 71 | glyphs: 72 | ['&', '@', '!', ',', '.', '?', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0', 73 | '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 74 | 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 75 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f', 76 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 77 | 'u', 'v', 'w', 'x', 'y', 'z','å', 'Ä', 'ä', 'Ö', 'ö', 'Ü', 'ü', '/'] 78 | 79 | - file: 'fonts/materialdesignicons-webfont.ttf' 80 | id: materialdesign_icons 81 | size: 35 82 | glyphs: ["\U000F095F", "\U000F0769", "\U000F08DD","\U000F1051"] # Get them from https://github.com/Templarian/MaterialDesign-Webfont/blob/master/fonts/materialdesignicons-webfont.ttf and copy to a folder /fonts 83 | 84 | 85 | color: 86 | - id: ACTIVE 87 | hex: "FEC600" 88 | - id: INACTIVE 89 | hex: "808080" 90 | 91 | # If you want a background image for your screen you can uncomment the next lines. Keep in mind rendering images slows down refresh of the display 92 | #image: 93 | # - file: "images/my_background_image.jpg" 94 | # id: background 95 | # type: RGB565 96 | 97 | sensor: 98 | - platform: wifi_signal 99 | name: "Wifi Signal" 100 | update_interval: 600s 101 | - platform: uptime 102 | name: "Uptime" 103 | id: uptime_s 104 | update_interval: 15s 105 | 106 | binary_sensor: 107 | # Adjust touchscreen touch areas if needed 108 | # To trigger button actions use HA and just create an automation that uses the exposed buttons as triggers on change 109 | - platform: status 110 | name: "Node Status" 111 | id: system_status 112 | - platform: touchscreen 113 | name: Button 1 # exposes a button to HA 114 | x_min: 190 115 | x_max: 268 116 | y_min: 18 117 | y_max: 63 118 | - platform: touchscreen 119 | name: Button 2 # exposes a button to HA 120 | x_min: 190 121 | x_max: 268 122 | y_min: 72 123 | y_max: 115 124 | - platform: touchscreen 125 | name: Button 3 # exposes a button to HA 126 | x_min: 190 127 | x_max: 268 128 | y_min: 126 129 | y_max: 169 130 | - platform: touchscreen 131 | name: Button 4 # exposes a button to HA 132 | x_min: 190 133 | x_max: 268 134 | y_min: 178 135 | y_max: 224 136 | 137 | text_sensor: 138 | - platform: template 139 | name: "Uptime (formatted)" 140 | lambda: |- 141 | uint32_t dur = id(uptime_s).state; 142 | int dys = 0; 143 | int hrs = 0; 144 | int mnts = 0; 145 | if (dur > 86399) { 146 | dys = trunc(dur / 86400); 147 | dur = dur - (dys * 86400); 148 | } 149 | if (dur > 3599) { 150 | hrs = trunc(dur / 3600); 151 | dur = dur - (hrs * 3600); 152 | } 153 | if (dur > 59) { 154 | mnts = trunc(dur / 60); 155 | dur = dur - (mnts * 60); 156 | } 157 | char buffer[17]; 158 | sprintf(buffer, "%ud %02uh %02um %02us", dys, hrs, mnts, dur); 159 | return {buffer}; 160 | icon: mdi:clock-start 161 | update_interval: 60s 162 | 163 | # Import HA entities 164 | # Adjust to your needs. I used the entities to change the background color of the button depending on the state 165 | # There is for sure a better solution but feel free to adjust. 166 | - platform: homeassistant 167 | id: button1 168 | entity_id: light.buero_schreibtisch 169 | on_value: 170 | then: 171 | - component.update: my_display 172 | - platform: homeassistant 173 | id: button2 174 | entity_id: light.buro_decke 175 | on_value: 176 | then: 177 | - component.update: my_display 178 | - platform: homeassistant 179 | id: button3 180 | entity_id: light.buero_kugel 181 | on_value: 182 | then: 183 | - component.update: my_display 184 | - platform: homeassistant 185 | id: button4 186 | entity_id: light.buro_schreibtisch_ambient_2 187 | on_value: 188 | then: 189 | - component.update: my_display 190 | 191 | switch: 192 | - platform: restart 193 | name: "Restart" 194 | 195 | spi: 196 | - id: lcd 197 | clk_pin: 18 198 | mosi_pin: 23 199 | miso_pin: 19 200 | - id: my_touchscreen 201 | clk_pin: 25 202 | mosi_pin: 32 203 | miso_pin: 35 204 | 205 | output: 206 | - platform: ledc 207 | pin: 4 208 | id: gpio_backlight_pwm 209 | 210 | light: 211 | - platform: monochromatic 212 | output: gpio_backlight_pwm 213 | name: "Power Display Backlight" 214 | id: back_light 215 | restore_mode: ALWAYS_ON 216 | 217 | touchscreen: 218 | platform: xpt2046 219 | spi_id: my_touchscreen 220 | cs_pin: 33 221 | interrupt_pin: 34 222 | update_interval: 50ms 223 | threshold: 400 224 | on_touch: 225 | - lambda: |- 226 | ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d", 227 | touch.x, 228 | touch.y, 229 | touch.x_raw, 230 | touch.y_raw 231 | ); 232 | calibration: 233 | x_min: 280 234 | x_max: 3860 235 | y_min: 340 236 | y_max: 3860 237 | transform: 238 | mirror_x: true 239 | mirror_y: false 240 | 241 | display: 242 | - platform: ili9xxx 243 | id: my_display 244 | spi_id: lcd 245 | model: ILI9341 246 | color_palette: 8BIT 247 | cs_pin: 27 248 | dc_pin: 26 249 | reset_pin: 16 250 | rotation: 90 251 | invert_colors: false 252 | update_interval: never 253 | 254 | # # If you want to use a background image uncomment these lines. 255 | #color_palette: IMAGE_ADAPTIVE 256 | #color_palette_images: 257 | # - "images/smartpanel/smartpanel_bg.jpg" 258 | 259 | pages: 260 | - id: lockscreen_page 261 | lambda: |- 262 | std::map day_map 263 | { 264 | {2, "Montag"}, 265 | {3, "Dienstag"}, 266 | {4, "Mittwoch"}, 267 | {5, "Donnerstag"}, 268 | {6, "Freitag"}, 269 | {7, "Samstag"}, 270 | {1, "Sonntag"} 271 | }; 272 | 273 | std::map month_map 274 | { 275 | {1, "Januar"}, 276 | {2, "Februar"}, 277 | {3, "März"}, 278 | {4, "April"}, 279 | {5, "Mai"}, 280 | {6, "Juni"}, 281 | {7, "Juli"}, 282 | {8, "August"}, 283 | {9, "September"}, 284 | {10, "Oktober"}, 285 | {11, "November"}, 286 | {12, "Dezember"} 287 | }; 288 | 289 | //it.image(0, 0, id(background)); 290 | 291 | 292 | auto white = Color(255, 255, 255); 293 | 294 | int b_pos_y = 170; 295 | 296 | // Button 1 297 | int b1_pos_x = (it.get_width()/2) - 105; 298 | if (id(button1).state == "off") { 299 | it.filled_circle(b1_pos_x, b_pos_y, 30, id(INACTIVE)); 300 | } else { 301 | it.filled_circle(b1_pos_x, b_pos_y, 30, white); 302 | } 303 | it.printf(b1_pos_x, b_pos_y, id(materialdesign_icons),id(ACTIVE), TextAlign::CENTER, "\U000F095F"); 304 | it.printf(b1_pos_x, b_pos_y + 45, id(label),id(Color::WHITE), TextAlign::CENTER, "Tisch"); 305 | 306 | // Button 2 307 | int b2_pos_x = (it.get_width()/2) - 35; 308 | if (id(button2).state == "off") { 309 | it.filled_circle(b2_pos_x, b_pos_y, 30, id(INACTIVE)); 310 | } else { 311 | it.filled_circle(b2_pos_x, b_pos_y, 30, white); 312 | } 313 | it.printf(b2_pos_x, b_pos_y, id(materialdesign_icons),id(ACTIVE), TextAlign::CENTER, "\U000F0769"); 314 | it.printf(b2_pos_x, b_pos_y + 45, id(label),id(Color::WHITE), TextAlign::CENTER, "Decke"); 315 | 316 | // Button 3 317 | int b3_pos_x = (it.get_width()/2) + 35; 318 | if (id(button3).state == "off") { 319 | it.filled_circle(b3_pos_x, b_pos_y, 30, id(INACTIVE)); 320 | } else { 321 | it.filled_circle(b3_pos_x, b_pos_y, 30, white); 322 | } 323 | it.printf(b3_pos_x, b_pos_y, id(materialdesign_icons),id(ACTIVE), TextAlign::CENTER, "\U000F08DD"); 324 | it.printf(b3_pos_x, b_pos_y + 45, id(label),id(Color::WHITE), TextAlign::CENTER, "Kugel"); 325 | 326 | // Button 4 327 | int b4_pos_x = (it.get_width()/2) + 105; 328 | if (id(button4).state == "off") { 329 | it.filled_circle(b4_pos_x, b_pos_y, 30, id(INACTIVE)); 330 | } else { 331 | it.filled_circle(b4_pos_x, b_pos_y, 30, white); 332 | } 333 | it.printf(b4_pos_x, b_pos_y, id(materialdesign_icons),id(ACTIVE), TextAlign::CENTER, "\U000F1051"); 334 | it.printf(b4_pos_x, b_pos_y + 45, id(label),id(Color::WHITE), TextAlign::CENTER, "Ambient"); 335 | 336 | 337 | // Date Time 338 | std::string day_of_week = day_map[id(homeassistant_time).now().day_of_week]; 339 | int day_of_month = id(homeassistant_time).now().day_of_month; 340 | std::string month = month_map[id(homeassistant_time).now().month]; 341 | it.printf(it.get_width()/2, it.get_height()/2 - 90, id(notice_text),id(Color::WHITE), TextAlign::CENTER, "%s, %d. %s", day_of_week.c_str(),day_of_month,month.c_str()); 342 | it.strftime(it.get_width()/2, it.get_height()/2 - 45, id(big_text),id(Color::WHITE), TextAlign::CENTER, "%H:%M", id(homeassistant_time).now()); 343 | 344 | -------------------------------------------------------------------------------- /esphome/esp32-2432s028.yml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: smartdisplay 3 | friendly_name: SmartDisplay 4 | 5 | esp32: 6 | board: esp32dev 7 | framework: 8 | type: arduino 9 | 10 | logger: 11 | 12 | api: 13 | encryption: 14 | key: "XXXXXXXXXXXXXXXXXXXXX" 15 | 16 | ota: 17 | - platform: esphome 18 | password: "XXXXXXXXXXXXXXXXXXXXX" 19 | 20 | wifi: 21 | ssid: !secret wifi_ssid 22 | password: !secret wifi_password 23 | ap: 24 | ssid: "Smartdisplay Fallback Hotspot" 25 | password: "XXXXXXXXXXXXXXXXXXXXX" 26 | 27 | captive_portal: 28 | 29 | time: 30 | - platform: homeassistant 31 | id: homeassistant_time 32 | on_time: 33 | - seconds: 0 34 | minutes: /1 # Update every minute 35 | then: 36 | - component.update: my_display 37 | 38 | font: 39 | 40 | # gfonts://family[@weight] 41 | - file: "gfonts://Roboto@500" 42 | id: big_text 43 | size: 60 44 | bpp: 4 45 | glyphs: 46 | ['&', '@', '!', ',', '.', '?', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0', 47 | '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 48 | 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 49 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f', 50 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 51 | 'u', 'v', 'w', 'x', 'y', 'z','å', 'Ä', 'ä', 'Ö', 'ö', 'Ü', 'ü', '/'] 52 | 53 | # gfonts://family[@weight] 54 | - file: "gfonts://Roboto@300" 55 | id: notice_text 56 | size: 16 57 | bpp: 4 58 | glyphs: 59 | ['&', '@', '!', ',', '.', '?', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0', 60 | '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 61 | 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 62 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f', 63 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 64 | 'u', 'v', 'w', 'x', 'y', 'z','å', 'Ä', 'ä', 'Ö', 'ö', 'Ü', 'ü', '/'] 65 | 66 | # gfonts://family[@weight] 67 | - file: "gfonts://Roboto@400" 68 | id: label 69 | size: 13 70 | bpp: 4 71 | glyphs: 72 | ['&', '@', '!', ',', '.', '?', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0', 73 | '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 74 | 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 75 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f', 76 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 77 | 'u', 'v', 'w', 'x', 'y', 'z','å', 'Ä', 'ä', 'Ö', 'ö', 'Ü', 'ü', '/'] 78 | 79 | - file: 'fonts/materialdesignicons-webfont.ttf' # Get them from https://github.com/Templarian/MaterialDesign-Webfont/blob/master/fonts/materialdesignicons-webfont.ttf and copy to a folder /fonts 80 | id: materialdesign_icons 81 | size: 35 82 | glyphs: ["\U000F095F", "\U000F0769", "\U000F08DD","\U000F1051"] 83 | 84 | # If you want a background image for your screen you can uncomment the next lines. Keep in mind rendering images slows down refresh of the display 85 | #image: 86 | # - file: "images/my_background_image.jpg" 87 | # id: background 88 | # type: RGB565 89 | 90 | color: 91 | - id: ACTIVE 92 | hex: "FEC600" 93 | - id: INACTIVE 94 | hex: "808080" 95 | 96 | sensor: 97 | - platform: wifi_signal 98 | name: "Wifi Signal" 99 | update_interval: 600s 100 | - platform: uptime 101 | name: "Uptime" 102 | id: uptime_s 103 | update_interval: 15s 104 | 105 | binary_sensor: 106 | # Adjust touchscreen touch areas if needed 107 | # To trigger button actions use HA and just create an automation that uses the exposed buttons as triggers on change 108 | - platform: status 109 | name: "Node Status" 110 | id: system_status 111 | - platform: touchscreen 112 | name: Button 1 # exposes a button to HA 113 | x_min: 53 114 | x_max: 131 115 | y_min: 18 116 | y_max: 63 117 | - platform: touchscreen 118 | name: Button 2 # exposes a button to HA 119 | x_min: 53 120 | x_max: 131 121 | y_min: 72 122 | y_max: 115 123 | - platform: touchscreen 124 | name: Button 3 # exposes a button to HA 125 | x_min: 53 126 | x_max: 131 127 | y_min: 126 128 | y_max: 169 129 | - platform: touchscreen 130 | name: Button 4 # exposes a button to HA 131 | x_min: 53 132 | x_max: 131 133 | y_min: 178 134 | y_max: 224 135 | 136 | text_sensor: 137 | - platform: template 138 | name: "Uptime (formatted)" 139 | lambda: |- 140 | uint32_t dur = id(uptime_s).state; 141 | int dys = 0; 142 | int hrs = 0; 143 | int mnts = 0; 144 | if (dur > 86399) { 145 | dys = trunc(dur / 86400); 146 | dur = dur - (dys * 86400); 147 | } 148 | if (dur > 3599) { 149 | hrs = trunc(dur / 3600); 150 | dur = dur - (hrs * 3600); 151 | } 152 | if (dur > 59) { 153 | mnts = trunc(dur / 60); 154 | dur = dur - (mnts * 60); 155 | } 156 | char buffer[17]; 157 | sprintf(buffer, "%ud %02uh %02um %02us", dys, hrs, mnts, dur); 158 | return {buffer}; 159 | icon: mdi:clock-start 160 | update_interval: 60s 161 | 162 | # Import HA entities 163 | # Adjust to your needs. I used the entities to change the background color of the button depending on the state 164 | # There is for sure a better solution but feel free to adjust. 165 | - platform: homeassistant 166 | id: button1 167 | entity_id: light.buero_schreibtisch 168 | on_value: 169 | then: 170 | - component.update: my_display 171 | - platform: homeassistant 172 | id: button2 173 | entity_id: light.buro_decke 174 | on_value: 175 | then: 176 | - component.update: my_display 177 | - platform: homeassistant 178 | id: button3 179 | entity_id: light.buero_kugel 180 | on_value: 181 | then: 182 | - component.update: my_display 183 | - platform: homeassistant 184 | id: button4 185 | entity_id: light.buro_schreibtisch_ambient_2 186 | on_value: 187 | then: 188 | - component.update: my_display 189 | 190 | switch: 191 | - platform: restart 192 | name: "Restart" 193 | 194 | spi: 195 | - id: lcd 196 | clk_pin: GPIO14 197 | mosi_pin: GPIO13 198 | miso_pin: GPIO12 199 | - id: my_touchscreen 200 | clk_pin: GPIO25 201 | mosi_pin: GPIO32 202 | miso_pin: GPIO39 203 | 204 | output: 205 | - platform: ledc 206 | pin: GPIO21 207 | id: gpio_backlight_pwm 208 | - platform: ledc 209 | id: output_red 210 | pin: GPIO4 211 | inverted: true 212 | - platform: ledc 213 | id: output_green 214 | pin: GPIO16 215 | inverted: true 216 | - platform: ledc 217 | id: output_blue 218 | pin: GPIO17 219 | inverted: true 220 | 221 | light: 222 | - platform: monochromatic 223 | output: gpio_backlight_pwm 224 | name: "Power Display Backlight" 225 | id: back_light 226 | restore_mode: ALWAYS_ON 227 | - platform: rgb 228 | name: LED 229 | red: output_red 230 | id: led 231 | green: output_green 232 | blue: output_blue 233 | restore_mode: ALWAYS_OFF 234 | 235 | touchscreen: 236 | platform: xpt2046 237 | spi_id: my_touchscreen 238 | cs_pin: 33 239 | interrupt_pin: 36 240 | update_interval: 50ms 241 | threshold: 400 242 | on_touch: 243 | - lambda: |- 244 | ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d", 245 | touch.x, 246 | touch.y, 247 | touch.x_raw, 248 | touch.y_raw 249 | ); 250 | calibration: 251 | x_min: 280 252 | x_max: 3860 253 | y_min: 340 254 | y_max: 3860 255 | transform: 256 | mirror_x: true 257 | mirror_y: false 258 | 259 | display: 260 | - platform: ili9xxx 261 | id: my_display 262 | spi_id: lcd 263 | model: ILI9341 264 | color_palette: 8BIT 265 | cs_pin: 15 266 | dc_pin: 2 267 | #reset_pin: 16 268 | rotation: 90 269 | invert_colors: false 270 | update_interval: never 271 | 272 | # # If you want to use a background image uncomment these lines. 273 | #color_palette: IMAGE_ADAPTIVE 274 | #color_palette_images: 275 | # - "images/smartpanel/smartpanel_bg.jpg" 276 | 277 | pages: 278 | - id: lockscreen_page 279 | lambda: |- 280 | std::map day_map 281 | { 282 | {2, "Montag"}, 283 | {3, "Dienstag"}, 284 | {4, "Mittwoch"}, 285 | {5, "Donnerstag"}, 286 | {6, "Freitag"}, 287 | {7, "Samstag"}, 288 | {1, "Sonntag"} 289 | }; 290 | 291 | std::map month_map 292 | { 293 | {1, "Januar"}, 294 | {2, "Februar"}, 295 | {3, "März"}, 296 | {4, "April"}, 297 | {5, "Mai"}, 298 | {6, "Juni"}, 299 | {7, "Juli"}, 300 | {8, "August"}, 301 | {9, "September"}, 302 | {10, "Oktober"}, 303 | {11, "November"}, 304 | {12, "Dezember"} 305 | }; 306 | 307 | //it.image(0, 0, id(background)); 308 | 309 | 310 | auto white = Color(255, 255, 255); 311 | 312 | int b_pos_y = 170; 313 | 314 | // Button 1 315 | int b1_pos_x = (it.get_width()/2) - 105; 316 | if (id(button1).state == "off") { 317 | it.filled_circle(b1_pos_x, b_pos_y, 30, id(INACTIVE)); 318 | } else { 319 | it.filled_circle(b1_pos_x, b_pos_y, 30, white); 320 | } 321 | it.printf(b1_pos_x, b_pos_y, id(materialdesign_icons),id(ACTIVE), TextAlign::CENTER, "\U000F095F"); 322 | it.printf(b1_pos_x, b_pos_y + 45, id(label),id(Color::WHITE), TextAlign::CENTER, "Tisch"); 323 | 324 | // Button 2 325 | int b2_pos_x = (it.get_width()/2) - 35; 326 | if (id(button2).state == "off") { 327 | it.filled_circle(b2_pos_x, b_pos_y, 30, id(INACTIVE)); 328 | } else { 329 | it.filled_circle(b2_pos_x, b_pos_y, 30, white); 330 | } 331 | it.printf(b2_pos_x, b_pos_y, id(materialdesign_icons),id(ACTIVE), TextAlign::CENTER, "\U000F0769"); 332 | it.printf(b2_pos_x, b_pos_y + 45, id(label),id(Color::WHITE), TextAlign::CENTER, "Decke"); 333 | 334 | // Button 3 335 | int b3_pos_x = (it.get_width()/2) + 35; 336 | if (id(button3).state == "off") { 337 | it.filled_circle(b3_pos_x, b_pos_y, 30, id(INACTIVE)); 338 | } else { 339 | it.filled_circle(b3_pos_x, b_pos_y, 30, white); 340 | } 341 | it.printf(b3_pos_x, b_pos_y, id(materialdesign_icons),id(ACTIVE), TextAlign::CENTER, "\U000F08DD"); 342 | it.printf(b3_pos_x, b_pos_y + 45, id(label),id(Color::WHITE), TextAlign::CENTER, "Kugel"); 343 | 344 | // Button 4 345 | int b4_pos_x = (it.get_width()/2) + 105; 346 | if (id(button4).state == "off") { 347 | it.filled_circle(b4_pos_x, b_pos_y, 30, id(INACTIVE)); 348 | } else { 349 | it.filled_circle(b4_pos_x, b_pos_y, 30, white); 350 | } 351 | it.printf(b4_pos_x, b_pos_y, id(materialdesign_icons),id(ACTIVE), TextAlign::CENTER, "\U000F1051"); 352 | it.printf(b4_pos_x, b_pos_y + 45, id(label),id(Color::WHITE), TextAlign::CENTER, "Ambient"); 353 | 354 | 355 | // Date Time 356 | std::string day_of_week = day_map[id(homeassistant_time).now().day_of_week]; 357 | int day_of_month = id(homeassistant_time).now().day_of_month; 358 | std::string month = month_map[id(homeassistant_time).now().month]; 359 | it.printf(it.get_width()/2, it.get_height()/2 - 90, id(notice_text),id(Color::WHITE), TextAlign::CENTER, "%s, %d. %s", day_of_week.c_str(),day_of_month,month.c_str()); 360 | it.strftime(it.get_width()/2, it.get_height()/2 - 45, id(big_text),id(Color::WHITE), TextAlign::CENTER, "%H:%M", id(homeassistant_time).now()); 361 | 362 | --------------------------------------------------------------------------------