Kleines Display mit Temperatursensor und Bewegungsmelder

Leistungsmerkmale:
- Kommuniziert per WLAN über ESPHome mit Home Assistant
- Der eingebaute aqara Temperatur-/Feuchte-/(optional Luftdruck-) Sensor sendet per Zigbee
- Die Software ist ota (Over the Air) also per Wlan aktualisierbar
- Der Bewegungsmelder RCWL-0516 arbeitet mit 3,2GHz und kann mit 3,3 oder 5V betrieben werden
- Die RGB-LED auf der Rückseite kann bei durchscheinendem Gehäuse für Farbeffekte genutzt werden



Vorteile der Kombination:
Zuerst hatte ich einen Temperatursensor an der Wand und den Bewegungsmelder RCWL-0516 mit einem 3,3V Mininetzteil und cc2530 in einem Steckdosengehäuse.


Nun sind die beiden Geräte vereint und zusätzlich habe ich noch eine kleine Anzeige, die mir einige Wichtige Dinge anzeigt und mit der man auch noch ein paar Sachen steuern kann.
Ein weiterer Vorteil ist, daß die Batterie im aqara nicht mehr gewechselt werden muss.
Der Radarsensor ist sehr empfindlich. Wenn man jedoch absolut beweglos im Raum sitzt wird man nicht erkannt.
Ein Umblättern des Buches reicht aber schon.
Benötigte Hardware:
cyd touchscreen esp32, Aqara Smart Temperatur Luftfeuchtigkeit, RCWL-0516

Gehäuse:

Fangen wir mit dem Druck des Gehäuses an: In der ZIP-Datei befinden sich die .stl Dateien sowie die fusion360_f3d-Datei.
Der Druck kann schon mal gestartet werden.
Ich benutze zum Verschrauben messing Einsatzmuttern 5 Stück M3 und eine M4 für den Deckel.
Mit Lötstation etwa mit 230° reindrücken (wenn nicht regelbar: vorsichtig, sonst seid ihr komplett durch)

Programmierung des ESP-Displays:
Der ESP wird auf den ersten Start vorbereitet (es wird ein "OTA-Bootloader" installiert).
Im Home Assistant wird das Addon ESPHome Builder sowie die Installation ESPHome installiert

Im ESPHome Builder ein neues Device erstellen. Name vergeben. "ESP32" auswählen. Skip.
Auf EDIT klicken und den Teil des Codes der unterhalb von "captive_portal:" in meiner Datei steht einfügen.
SAVE und dann INSTALL drücken
Dann Manual download wählen. -> und die Datei im Factory format speichern.

Mit einem Chrome oder Edge Browser die Seite: https://web.esphome.io/ öffnen.
Den ESP per USB verbinden und die gespeicherte Datei auswählen und auf den ESP laden.
Das Gerät sollte sich nun mit WLan verbinden und In ESPHome online sein.
Zukünftige Änderungen an der YAML Datei können nach SAVE und dann INSTALL online übertragen werden.
Änderungen der YAML:
hier können Schriften geänder werden die dann ganz unten z.B. hier it.printf(120, 85, id(fetcha) genutzt werden.
font:
- file: "gfonts://Roboto"
id: fetcha
size: 25
Hier sind die ICONS definiert:
image:
- file: mdi:home-thermometer
id: hometemperature
resize: 40x40
Weitere bilder findet man auf der Seite: https://pictogrammers.com/library/mdi/
Hier müsst ihr die Entität eurer Sensoren aus Home Assistant eintragen und ihnen eine id zuweisen, die dann unten bei der Textausgabe genutzt wird.
sensor:
- platform: homeassistant
id: innentemperatur
entity_id: sensor.temp_kuche_temperature
internal: true
Zum "binary_sensor:"
Jeder touch-screen-button hat eine on_press Methode.
Interessant ist nur der Else-Zweig weil dieser Teil für die zweite Seite des Display mit den Buttons zuständig ist:
else:
- homeassistant.service:
service: cover.open_cover // Rolladen öffnen Service in HA aufrufen
data:
entity_id: cover.rolladen_1 // mit der Entität cover.rolladen_1
Alles andere einfach mal ausprobieren
Zusammenlöten:


Im Moment habe ich ein abgeschnittenes USB-Kabel durch einen Lüftungsschlitz geführt.
später verschwindet die Zuleitung in der Wand.
Schlitzen und tapezieren des Zimmers:
...
Hier nochmals der komplette Code:
esphome: # Stand 07.01.2025
name: display
friendly_name: display
esp32:
board: esp32dev
framework:
type: arduino
logger:
api:
encryption:
key: "/BSXATeNoYAg7HGn7zWfu01TsHhtQ2L4vZpUIyTtxgw="
ota:
platform: esphome
password: "1b805235a7ac84fb9711a70584c2977e"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Display Fallback Hotspot"
password: "OJeDHki1Wid9"
captive_portal:
globals:
- id: show_return_page
type: bool
restore_value: yes
initial_value: "false"
# Setup a pin to control the backlight and the LED
output:
- platform: ledc
pin: GPIO21
id: backlight_pwm
- platform: ledc
id: output_red
pin: GPIO4
inverted: true
- platform: ledc
id: output_green
pin: GPIO16
inverted: true
- platform: ledc
id: output_blue
pin: GPIO17
inverted: true
light:
- platform: monochromatic
output: backlight_pwm
name: Display Backlight
id: backlight
restore_mode: ALWAYS_ON
- platform: rgb
name: LED
red: output_red
id: led
green: output_green
blue: output_blue
restore_mode: ALWAYS_OFF
# Setup SPI for the display. The ESP32-2432S028R uses separate SPI buses for display and touch
spi:
- id: tft
clk_pin: GPIO14
mosi_pin: GPIO13
miso_pin: GPIO12
- id: touch
clk_pin: GPIO25
mosi_pin: GPIO32
miso_pin: GPIO39
touchscreen:
platform: xpt2046
spi_id: touch
cs_pin: GPIO33
interrupt_pin: GPIO36
update_interval: 50ms
threshold: 400
calibration:
x_min: 280
x_max: 3860
y_min: 340
y_max: 3860
transform:
swap_xy: false
# verwendete Schriften
font:
- file: "gfonts://Roboto"
id: fetcha
size: 25
- file: "gfonts://Kanit"
id: hora
size: 60
- file: "gfonts://Roboto"
id: info
size: 15
- file: "gfonts://Roboto"
id: info2
size: 20
- file: "gfonts://Roboto"
id: botones
size: 11
# Verwendtet Farben
color:
- id: black
hex: '000000'
- id: orange
hex: 'eb9c17'
- id: red
hex: 'b20b23'
- id: green
hex: '148e23'
- id: grey
hex: '464646'
# verwendete icons
image:
- file: mdi:home-thermometer
id: hometemperature
resize: 40x40
- file: mdi:weather-partly-cloudy
id: weather
resize: 40x40
- file: mdi:window-shutter-open
id: offen
resize: 40x40
- file: mdi:window-shutter
id: zu
resize: 40x40
- file: mdi:lightbulb
id: licht
resize: 40x40
- file: mdi:page-previous
id: back
resize: 40x40
# Replace the home gif as you want.
#animation:
# - file: "habbit.gif"
# id: ha
# resize: 70x70
# type: TRANSPARENT_BINARY
# This will fetch time from Home Assistant
time:
- platform: homeassistant
id: esptime
# Create sensors from HA you want to use and show.
sensor:
- platform: homeassistant
id: innentemperatur
entity_id: sensor.temp_kuche_temperature
internal: true
- platform: homeassistant
id: innenluftfeuchte
entity_id: sensor.temp_kuche_humidity
internal: true
- platform: homeassistant
id: absolutinnen
entity_id: sensor.absolute_feuchte_innen
internal: true
- platform: homeassistant
id: aussentemperatur
entity_id: sensor.aussen_temperatur
internal: true
- platform: homeassistant
id: aussenfeuchte
entity_id: sensor.aussen_luftfeuchtigkeit
internal: true
- platform: homeassistant
id: absolutaussen
entity_id: sensor.absolute_feuchte_aussen
internal: true
- platform: homeassistant
id: totalstrom
entity_id: sensor.strom_verbrauch
internal: true
text_sensor:
- platform: homeassistant
id: Licht
entity_id: switch.licht
internal: true
# Hier wird geschaltet!
binary_sensor:
- platform: touchscreen
name: Button 1
x_min: 140
x_max: 280
y_min: 0
y_max: 65
on_press:
then:
- if:
condition:
lambda: 'return !id(show_return_page);'
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
else:
- homeassistant.service:
service: cover.open_cover
data:
entity_id: cover.rolladen_1
- homeassistant.service:
service: cover.open_cover
data:
entity_id: cover.rolladen_2
- platform: touchscreen
name: Button 2
x_min: 0
x_max: 140
y_min: 0
y_max: 65
on_press:
then:
- if:
condition:
lambda: 'return !id(show_return_page);'
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
else:
- homeassistant.service:
service: cover.close_cover
data:
entity_id: cover.rolladen_1
- homeassistant.service:
service: cover.close_cover
data:
entity_id: cover.rolladen_2
- platform: touchscreen
name: Button 3
x_min: 140
x_max: 280
y_min: 65
y_max: 130
on_press:
then:
- if:
condition:
lambda: 'return !id(show_return_page);'
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
else:
- homeassistant.service:
service: cover.open_cover
data:
entity_id: cover.rolladen3
- platform: touchscreen
name: Button 4
x_min: 0
x_max: 140
y_min: 65
y_max: 130
on_press:
then:
- if:
condition:
lambda: 'return !id(show_return_page);'
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
else:
- homeassistant.service:
service: cover.close_cover
data:
entity_id: cover.rolladen3
- platform: touchscreen
name: Button 5
x_min: 140
x_max: 280
y_min: 130
y_max: 195
on_press:
then:
- if:
condition:
lambda: 'return !id(show_return_page);'
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
else:
- homeassistant.service:
service: cover.open_cover
data:
entity_id: cover.rolladen6
- platform: touchscreen
name: Button 6
x_min: 0
x_max: 140
y_min: 130
y_max: 195
on_press:
then:
- if:
condition:
lambda: 'return !id(show_return_page);'
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
else:
- homeassistant.service:
service: cover.close_cover
data:
entity_id: cover.rolladen6
- platform: touchscreen
name: Button 7
x_min: 140
x_max: 280
y_min: 195
y_max: 260
on_press:
then:
- if:
condition:
lambda: 'return !id(show_return_page);'
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
else:
- homeassistant.service:
service: switch.toggle
data:
entity_id: switch.licht
- platform: touchscreen
name: Button 8
x_min: 0
x_max: 140
y_min: 195
y_max: 260
on_press:
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
# Setup the ili9xxx platform
# Display is used as 240x320 by default so we rotate it to 90° model: ili9342 changed to model: ili9341
display:
- platform: ili9xxx
id: esp_display
model: ili9341
spi_id: tft
cs_pin: GPIO15
dc_pin: GPIO2
rotation: 0
invert_colors: false
lambda: |-
if (id(show_return_page)) {
int button_width = 100;
int button_height = 65;
int x_start = 15;
int y_start = 15;
int x_padding = 10;
int y_padding = 10;
// Define los textos para los botones
const char* button_texts[] = {
"Wohnzimmer",
"Wohnzimmer",
"Esszimmer",
"Esszimmer",
"Kueche",
"Kueche",
"Licht WZ",
"Start"
};
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 2; col++) {
int button_index = row * 2 + col;
int x = x_start + col * (button_width + x_padding);
int y = y_start + row * (button_height + y_padding);
it.rectangle(x, y, button_width, button_height, id(grey));
int text_width = strlen(button_texts[button_index]) * 5.5;
int text_height = 16;
it.print(x + (button_width - text_width) / 2, y + (button_height - text_height) / 2 + 20, id(botones), button_texts[button_index]);
}
}
it.image(45, 20, id(offen), id(orange));
it.image(155, 20, id(zu), id(grey));
it.image(45, 95, id(offen), id(orange));
it.image(155, 95, id(zu), id(grey));
it.image(45, 170, id(offen), id(orange));
it.image(155, 170, id(zu), id(grey));
if (id(Licht).state == "on") {
it.image(45, 245, id(licht), id(orange));
} else {
it.image(45, 245, id(licht), id(grey));
}
it.image(155, 245, id(back), id(green));
} else {
static int y = 182;
static int y_direction = 4; // Velocidad del movimiento
const int y_min = 180;
const int y_max = 187;
it.fill(id(black));
it.strftime(120, 12, id(info), TextAlign::CENTER, "%d/%m/%Y", id(esptime).now());
it.strftime(120, 43, id(hora), TextAlign::CENTER, "%H:%M", id(esptime).now());
it.printf(120, 85, id(fetcha), TextAlign::CENTER, "Strom %.0f", id(totalstrom).state);
it.line(50, 110, 190, 110);
static int current_text_index = 0;
static float text_timer = 0;
const float text_interval = 4.0; // Intervalo para cambiar el texto en segundos
if (current_text_index == 0) {
it.image(15, 130, id(hometemperature), id(green));
it.print(70, 130, id(info), "Innen");
it.printf(175, 130, id(info), "%.1f C", id(innentemperatur).state);
it.print(70, 160, id(info), "Luftfeuchte");
it.printf(175, 160, id(info), "%.1f %%", id(innenluftfeuchte).state);
it.print(70, 190, id(info), "Absolute");
it.printf(175, 190, id(info), "%.1f %%", id(absolutinnen).state);
it.image(15, 230, id(weather), id(orange));
it.print(70, 230, id(info), "Aussen");
it.printf(175, 230, id(info2), "%.1f C", id(aussentemperatur).state);
it.print(70, 260, id(info), "Luftfeuchte");
it.printf(175, 260, id(info), "%.1f %%", id(aussenfeuchte).state);
it.print(70, 290, id(info), "Absolute");
it.printf(175, 290, id(info), "%.1f %%", id(absolutaussen).state);
}
}