Excellent! With that I now have a working touch screen. As you noted, it doesn’t support everything, but the basics are definitely working. The following code shows the ability to swipe between screens, as well as detect a single touch - so this could now be used as the basis for, for example, swiping between screens and toggling on/off different lights etc. It also detects swipe up/down (so that could be used to dim/brighten lights or change a thermostat) as well as a long touch - I haven’t added examples for those in the code, but it’s just a case of adding those terms in.
update actually, I will update the code below if there are any major changes I think might be of use. If you want access some sample image files, I have uploaded them here. Be aware that you are probably much better off searching one of the many image libraries out there for the images you actually want, and then use something like gimp to modify them so that they are the correct size for where you want to use them on the screen.
# V1.1 - 16/11/2024
# Compiled and tested on esphome 2024.10.3 and HA 2024.11.1
#
# There are a lot of comments in this code - some are to help guide when using
# Some code is commented out - those are works in progress and in general can be ignored
#
# PLEASE NOTE:
#
# Try this with any other versions of HA or Esphome than noted above and it might work, but otherwise "you are in a maze of twisty little passages, all alike" so
# good luck in your future endeavours. This is because an external component is still required to support the touch controller, and as that is not always updated
# when an update to esphome or home assistant is released, you may see errors when compiling. In short, if you see compilation errors, always
# check to see if you are running versions of esphome and home assistant that are the same as noted above. If you are running a newer
# version then check the community discussions for resolution
#
# To use, create a config in esphome with this code. Change or remove references to sensors as required to suit/match your environment.
# This code refers to external image files - feel free to remove and/or change. For legal reasons I cannot share these.
# Pay close attention to the "substitutions" section and update to suit you
# Then compile and save the file locally
# Then go to https://web.esphome.io/ and connect the device to the pc and prepare it for first use
# Then upload the saved binary to the device
# Once installed, you will be able to use OTA updates
# Notes - feel free to remove
# - it is a single core processor running at up to 160Mhz, 400KB SRAM, 348KB ROM, 4MB flash
# - display resolution is 240*240 IPS
# - battery connector is a JST 1.25 2P. Note that this is for a 3V lithium battery (3.3V would be fine. It does have
# over current protection but even so I would avoid 3.7V or greater just in case).
# - Can power direct through the jst with DC but you need to initially provide power via USB
# before it recognises that there is power coming in via the battery port
# - it has a tiny click button called "switch" - unclear how to use. Have seen some images that refer to it as on/off
# but it does not seem to do that. The circuit diags seem to imply it is IO8 which is used for the screen so maybe
# it could be used to toggle the screen on or off. It is deeply recessed and so tiny I doubt many people would use it anyhow.
# - it has two larger buttons for reset and boot
# - I/O interface connector is a SH 1.0 4P (GND, +3.3V, TX, RX) so UART - could for example use with
# DS18B20 temp sensor (to measure the room temp)
# URM07 distance sensor (to trigger the screen to wake up as someone approaches)
# other options may include the following, but be aware many require more than 3V to be reliable, so may require separate power
# SEN0395 or LD2410 mmWave for presense detection
# MH-Z19C to measure CO2
# SM300D2 to measure all sorts of things (CO2, formaldehyde, TVOC, laser PM2.5, PM10, temperature and humidity)
# - It has bluetooth, and can function as a ble tracker but memory and processor constraints means that it can
# have issues (eg random rebooting) doing that AND running the display at the same time
# - processor is weak and memory low, so do not expect it to be able to handle animation too well
# - specs say power consumption is 100mA - but varies depending on load and if display is active
# - screen is 240 pixels wide, so radius is 120 pixels, circumference 754 pixels
# a square would have sides 170 pixels to just fit - maybe 169 to be safe
# adjust images to suit. Keep in mind that images will eat away the limited memory on the device.
substitutions:
devicename: wallwatch07
friendname: WallWatch07
location: guest
# Change the timezone to suit your location, or even just remove it - it will likely be picked up automatically
timez: Australia/Melbourne
board: esp32-c3-devkitm-1
# Display state on initial start
# Note that the screensaver only starts after the first touch of the display
# Change to ALWAYS_OFF if want the screen to be off after booting, ALWAYS_ON if want it on at start eg for initial troubleshooting
screenstart: ALWAYS_OFF
# Timeout for the screen
screensaver: 10 min
#GPIO pins for the LCD screen
repin: GPIO1
dcpin: GPIO2
# Note - you may see an error on compilation "WARNING GPIO2 is a Strapping PIN and should be avoided" - ignore this as you have no choice
# This error message may be removed via the "ignore_strapping_warning" option for the screen driver
bkpin: GPIO3
clpin: GPIO6
mopin: GPIO7
cspin: GPIO10
# GPIO pins for the touch screen
sdapin: GPIO4
sclpin: GPIO5
intpin: GPIO8
esphome:
name: $devicename
friendly_name: $friendname
project:
name: "ninkasi.wallwatch"
version: "1.0.1"
comment: Wall Controller $location
esp32:
board: $board
framework:
type: arduino
# Enable logging
# Change to avoid "Components should block for at most 20-30ms" warning messages in the log - an issue since 2023.7.0
# Not really a breaking change - it's an issue I suspect due to the device being slow and this error previously
# simply not being reported
logger:
level: DEBUG #makes uart stream available in esphome logstream
logs:
component: ERROR
#Turn off UART logging over RX/TX
baud_rate: 0
#uart:
# rx_pin: GPIO20
# tx_pin: GPIO21
# baud_rate: 9600
# id: uart_bus
# Enable Home Assistant API
api:
encryption:
key: !secret esphome_encryption_key
ota:
password: !secret ota_password
platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "$devicename Fallback Hotspot"
password: !secret fallback_password
captive_portal:
time:
- platform: homeassistant
# Feel free to remove the timezone reference - it will probably work it out automatically
timezone: "$timez"
id: esptime
# Amazingly, ble_tracker now seems to work
# Things to note if using it:
# 1. Make sure you use https://web.esphome.io/ to wipe the device first and then upload the new image
# Do not use OTA after enabling esp32_ble_tracker: as it needs to change the partition to support the code
# 2. Comment out "#eight_bit_color: false" in the display section. Drawback is screen redraw is slower and colours less vibrant.
# 3. Don't be surprised if the thing locks ups or reboots randomly - this is bleeding edge
# 4. Refer to https://esphome.io/components/binary_sensor/ble_presence.html for more info
#esp32_ble_tracker:
#binary_sensor:
# # Presence based on MAC address
# - platform: ble_presence
# mac_address: AC:37:43:77:5F:4C
# name: "ESP32 BLE Tracker Google Home Mini"
# min_rssi: -80dB
# # Presence based on BLE Service UUID
# - platform: ble_presence
# service_uuid: '11aa'
# name: "ESP32 BLE Tracker Test Service 16 bit"
# # Presence based on iBeacon UUID
# - platform: ble_presence
# ibeacon_uuid: '68586f1e-89c2-11eb-8dcd-0242ac130003'
# name: "ESP32 BLE Tracker Test Service iBeacon"
select:
- platform: template
name: Thermostat Mode
id: mode
options:
- "off"
- "cool"
- "heat"
initial_option: "off"
optimistic: true
# - select.next: mode
# set_action:
# - logger.log:
# format: "Chosen option: %s"
# args: ["x.c_str()"]
sensor:
- platform: uptime
name: "$devicename Uptime"
- platform: wifi_signal
name: "$devicename WiFi Signal"
update_interval: 60s
- platform: homeassistant
id: outdoor_temperature
entity_id: sensor.gw1000_v1_7_6_outdoor_temperature
- platform: homeassistant
id: max_temperature
entity_id: sensor.brighton_east_temp_max_0
- platform: homeassistant
id: min_temperature
entity_id: sensor.brighton_east_temp_min_1
- platform: homeassistant
id: thermostat_temperature
entity_id: climate.thermostat_sitting_2
attribute: temperature
- platform: homeassistant
id: room_temperature
entity_id: sensor.lumi_lumi_sens_temperature_2
- platform: template
name: $devicename free memory
lambda: return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
icon: "mdi:memory"
entity_category: diagnostic
state_class: measurement
unit_of_measurement: "b"
update_interval: 60s
external_components:
- source: github://GadgetFactory/[email protected]
# Still need this external driver to support the touch screen
# Supported added via https://github.com/esphome/esphome/pull/5941
# Currently testing
spi:
mosi_pin: $mopin
clk_pin: $clpin
# Don't use software - makes it crawl
# force_sw: True
#mosi = Master Out Slave In
#miso = Master In Slave Out or fermented bean paste. In this case, most likely the former rather than the latter. Doesn't matter as not used.
i2c:
sda: $sdapin
scl: $sclpin
#Following helped someone get rid of the "Components should block" error messages but didn't for me
#frequency: 400kHz
number:
# This is a binary off or on to toggle the virtual thermostat states between off or heat or cool
# Where 0 is off and 1 is heat and 2 is cool
# There is now a 'climate.toggle' option but for me I don't want to cycle through all the options
- platform: template
optimistic: true
name: "$devicename therm state"
id: thermostat_state
min_value: 0
max_value: 2
# on boot, use the value it had if possible
restore_value: True
# if can't restore the value, then make it 0 ie off
initial_value: 0
update_interval: 1s
step: 1
#Create a script to turn the screen off after a set period
script:
- id: screentime
mode: restart
then:
- light.turn_on: back_light
- delay: $screensaver
- light.turn_off: back_light
# - platform: CST816S_touchscreen
text_sensor:
# - platform: ble_scanner
# name: "BLE Devices Scanner"
- platform: homeassistant
id: light_state
entity_id: light.guest_lamp
- platform: CST816S_touchscreen
id: my_touch_screen
on_value:
then:
- script.execute: screentime # See script to set timeout delay
- if:
condition:
text_sensor.state:
id: my_touch_screen
state: 'SWIPE RIGHT'
then:
- display.page.show_next: watchface
- component.update: watchface
- logger.log: "Swiped right, go to next page"
- if:
condition:
text_sensor.state:
id: my_touch_screen
state: 'SWIPE LEFT'
then:
- display.page.show_previous: watchface
- delay: 0.5s
- component.update: watchface
- logger.log: "Swiped left, go back a page"
- if:
condition:
and:
- text_sensor.state:
id: my_touch_screen
state: 'LONG PRESS'
- display.is_displaying_page: page1
then:
- logger.log: "Long press on page1"
# - select.next: mode
# - homeassistant.service:
# service: climate.set_hvac_mode
# data:
# hvac_mode: "off"
# target:
# entity_id:
# - climate.thermostat_sitting_2
# Note that this is changing a sensor value in HA, and and automation is then acting on that to then change the
# thermostat state eg from cooling to off
- number.decrement: thermostat_state
- delay: 0.5s
- component.update: watchface
- if:
condition:
and:
- text_sensor.state:
id: my_touch_screen
state: 'SWIPE UP'
- display.is_displaying_page: page1
then:
- logger.log: "Swiped up on page1"
#Note that this is the reverse of what you might expect
# This is directly reducing the thermostat set temperature in HA by one degree
- homeassistant.service:
service: climate.set_temperature
data_template:
entity_id: climate.thermostat_sitting_2
temperature: '{{(state_attr(''climate.thermostat_sitting_2'' , ''temperature'')|round(0)) - 1 }}'
- component.update: watchface
- if:
condition:
and:
- text_sensor.state:
id: my_touch_screen
state: 'SWIPE DOWN'
- display.is_displaying_page: page1
then:
- logger.log: "Swiped down on page1"
#Note that this is the reverse of what you might expect
# This is directly increasing the thermostat set temperature in HA by one degree
- homeassistant.service:
service: climate.set_temperature
data_template:
entity_id: climate.thermostat_sitting_2
temperature: '{{(state_attr(''climate.thermostat_sitting_2'' , ''temperature'')|round(0)) + 1 }}'
- component.update: watchface
- if:
condition:
and:
- text_sensor.state:
id: my_touch_screen
state: 'LONG PRESS'
- display.is_displaying_page: page2
then:
- logger.log: "Long press on page2"
- display.page.show: page1
- component.update: watchface
- if:
condition:
and:
- text_sensor.state:
id: my_touch_screen
state: 'LONG PRESS'
- display.is_displaying_page: page3
then:
- logger.log: "Long press on page3"
- display.page.show: page1
- component.update: watchface
- if:
condition:
and:
- text_sensor.state:
id: my_touch_screen
state: 'SINGLE CLICK'
then:
- logger.log: "Single click to toggle backlight"
- light.toggle: back_light
- if:
condition:
and:
- text_sensor.state:
id: my_touch_screen
state: 'LONG PRESS'
- display.is_displaying_page: page4
then:
- logger.log: "Long press on page4 - toggle light"
# Option to toggle a light - note need to go to settings/devices/esphome click on configure for the device and allow the device to make HA calls
- homeassistant.service:
service: light.toggle
data:
entity_id: light.guest_lamp
# refresh the screen
- component.update: watchface
- if:
condition:
and:
- text_sensor.state:
id: my_touch_screen
state: 'LONG PRESS'
- display.is_displaying_page: page5
then:
- logger.log: "Long press on page5"
- display.page.show: page1
- component.update: watchface
# States currently detected
# 'SWIPE UP' - note that this appears to be reversed
# 'SWIPE DOWN - note that this appears to be reversed
# 'SWIPE LEFT'
# 'SWIPE RIGHT'
# 'LONG PRESS'
# 'SINGLE CLICK' - note that this can be temperamental. It works most of the time, but as the device is capacitive
# it is looking for voltage changes and might miss a quick tap. The longer actions like swiping are more reliable.
# For that reason, I'm just using the single tap to toggle the screen on or off. I'd note that this is also device
# dependent ie the sensitivity seems to vary between devices so you may have better luck.
# Need to turn on backlight as by default is not on
output:
- platform: ledc
pin: $bkpin
id: gpio_3_backlight_pwm
light:
- platform: monochromatic
output: gpio_3_backlight_pwm
name: "Display Backlight"
id: back_light
restore_mode: $screenstart
font:
- file: "gfonts://Roboto"
id: font_12
size: 12
- file: "gfonts://Roboto"
id: font_16
size: 16
- file: "gfonts://Roboto"
id: font_24
size: 24
- file: "gfonts://Roboto"
id: font_32
size: 32
- file: "gfonts://Roboto"
id: font_64
size: 64
color:
- id: my_red
hex: FF0000
- id: my_green
hex: 008000
- id: my_blue
hex: 0000FF
- id: my_yellow
hex: FFFF00
# Some pretty images - not critical
# I have a collection of images I have either created or modified from various sources using gimp
# As I do not have the rights to all the images I can't provide them here
# Again assumes you have them in config\icons or config\images
# Do not use large or complex images as the poor processor will struggle
# Ideally use images that are the right size and do not need resizing - to save memory and improve screen refresh speed
animation:
# This is a gif file that has two 8*8 pixel, 72dpi images of the sun so it looks like it glows as it alternates between them
# I used the resize option to make it the size I wanted
# Long term I plan to make different icons and change them based on the actual weather
# I may or may not use animations - the effect is very subtle and likely not worth the effort
- file: "icons/2051v2.gif"
id: clear_day
resize: 40x40
type: RGB565 #default is binary ie greyscale
# This is an 80*116 pixel, 144 pixel/inch image of a light bulb in the off position
- file: "images/lightoff.png"
id: light_off
type: BINARY
# This is an 80*116 pixel, 144 pixel/inch image of a glowing light bulb
- file: "images/lighton.png"
id: light_on
type: RGB565
# This is an 28*40 pixel, 72 pixel/inch image of a green arrow pointing down
- file: "images/greenarrow.png"
id: greenarrow
type: RGB565
# This is an 28*40 pixel, 72 pixel/inch image of a red arrow pointing up
- file: "images/redarrow.png"
id: redarrow
type: RGB565
- file: "images/wifi-qr.png"
id: qrcode
type: BINARY
# This is a QR code with your guest or home wifi in it - can create using https://qifi.org/
# Ideally resize the resulting image to, say, 169 pixels by 169 pixels
# If you want to give someone access to your wifi, then if their device has a camera then they
# may be able to scan the qr code and automatically join
# As a side note, if you have a decent 3d printer you can also use https://printer.tools/qrcode2stl/
# to create a STL file that you can use to print a physical qr code - I reduce that by 40% then print it
# using white PLA and 0.1mm resolution, then colour the ridges in with a black permanent marker
display:
- platform: ili9xxx
model: GC9A01A
id: watchface
invert_colors: true
reset_pin: $repin
cs_pin: $cspin
dc_pin:
number: $dcpin
ignore_strapping_warning: true
# The above is to remove the strapping pin warning message
# The next are optional
# width: 240
# height: 240
# Disable this if using ble_tracker as the poor thing doesn't have enough memory
# Enable it if not using ble_tracker as you will find the screen looks and responds better
# Disable this if you are encountering random reboots or other issues
# eight_bit_color: false
# How often to refresh the display
update_interval: 1s
# Rotate the screen so usb socket is pointing down
# rotation: 90
# No longer needed as integrated ili9xxx driver rotates the screen by default
pages:
- id: page1
lambda: |-
it.image(100, 20, id(redarrow), COLOR_ON, COLOR_OFF);
it.printf(140, 30, id(font_12), id(my_red), "swipe");
it.image(100, 190, id(greenarrow), COLOR_ON, COLOR_OFF);
it.printf(140, 200, id(font_12), id(my_green), "swipe");
it.printf(120,120, id(font_64), TextAlign::CENTER, "%.1f°", id(thermostat_temperature).state);
it.printf(120, 170, id(font_24), id(my_blue), TextAlign::CENTER, "Now: %.1f°", id(room_temperature).state);
it.circle(120, 120, 119, id(my_red));
if (id(thermostat_state).state == 0)
it.printf(120,80, id(font_24), id(my_green), TextAlign::CENTER, "HVAC Off");
if (id(thermostat_state).state == 1)
it.printf(120,80, id(font_24), id(my_red), TextAlign::CENTER, "Heater On");
if (id(thermostat_state).state == 2)
it.printf(120,80, id(font_24), id(my_blue), TextAlign::CENTER, "AC On");
- id: page2
lambda: |-
id(clear_day).next_frame();
it.image(100, 30, id(clear_day), COLOR_ON, COLOR_OFF);
it.strftime(120,80, id(font_16), id(my_blue), TextAlign::CENTER, "%A %b %d", id(esptime).now());
it.strftime(120,120, id(font_32), TextAlign::CENTER, "%I:%M %p", id(esptime).now());
it.printf(120, 170, id(font_32), id(my_blue), TextAlign::CENTER, "Now: %.1f°", id(outdoor_temperature).state);
it.printf(120, 200, id(font_16), id(my_green), TextAlign::CENTER, "outside");
it.circle(120, 120, 119, id(my_yellow));
- id: page3
lambda: |-
id(clear_day).next_frame();
it.image(100, 30, id(clear_day), COLOR_ON, COLOR_OFF);
it.strftime(120,80, id(font_16), id(my_blue), TextAlign::CENTER, "%A %b %d", id(esptime).now());
it.printf(120,120, id(font_24), TextAlign::CENTER, "Today Min/Max:");
it.printf(120, 170, id(font_32), id(my_blue), TextAlign::CENTER, "%.1f/%.1f°", id(min_temperature).state,id(max_temperature).state);
it.printf(120, 200, id(font_16), id(my_green), TextAlign::CENTER, "weather");
it.circle(120, 120, 119, id(my_blue));
- id: page4
lambda: |-
if (id(light_state).state == "off")
it.image(85, 60, id(light_off), COLOR_ON, COLOR_OFF);
if (id(light_state).state == "on")
it.image(85, 60, id(light_on), COLOR_ON, COLOR_OFF);
it.printf(120, 200, id(font_16), id(my_blue), TextAlign::CENTER, "lamp");
it.circle(120, 120, 119, id(my_green));
- id: page5
lambda: |-
it.image(35, 35, id(qrcode));
it.printf(120, 220, id(font_16), id(my_green), TextAlign::CENTER, "guest wifi");
it.circle(120, 120, 119, id(my_blue));
- id: page6
lambda: |-
id(clear_day).next_frame();
it.printf(120,30, id(font_16), id(my_blue), TextAlign::CENTER, "help");
it.printf(120,60, id(font_16), TextAlign::CENTER, "Swipe up/down");
it.printf(120,80, id(font_16), TextAlign::CENTER, "to change temp");
it.printf(120,110, id(font_16), id(my_yellow), TextAlign::CENTER, "Long press on thermostat");
it.printf(120,130, id(font_16), id(my_yellow), TextAlign::CENTER, "to toggle off/cool/heat");
it.printf(120,160, id(font_16), TextAlign::CENTER, "Swipe L/R to change screens");
it.printf(120,190, id(font_16), id(my_yellow), TextAlign::CENTER, "Tap to turn screen");
it.printf(120,210, id(font_16), id(my_yellow), TextAlign::CENTER, "on/off");
it.circle(120, 120, 119, id(my_green));