ESP32-C3 with integrated GC9A01 - cheap touch controller

Hello, thank you so much for putting this example and thread together. I picked up one of these modules with the intention of making a light switch out of it and was pleasantly surprised to find this post. It was a nice jump start on getting this working in ESPHome.

I worked on the touchscreen support today and have a first cut working. It was just the minimal functionality I could get up and running in an evening. It is missing:

  • The position of the touch event
  • Setting the rst and irq pins
  • Support for rotating
  • Many other things I’m sure

I don’t know when I will have time to work on it further so please feel free to fork the component repo and do a pull request with any changes/fixes.
GadgetFactory/CST816S_touchscreen: ESPHome Component for CST816S Touchscreen (github.com)

Here is the the esphome yaml file that includes basic touch support:

substitutions:
  devicename: round-touchscreen
  friendname: Wallround-touchscreenWatch01
  location: master
  board: esp32-c3-devkitm-1
  repin: GPIO1
  dcpin: GPIO2
  bkpin: GPIO3
  clpin: GPIO6
  mopin: GPIO7
  cspin: GPIO10

esphome:
  name: $devicename
  friendly_name: $friendname

esp32:
  board: $board
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: ""

ota:
  password: ""

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Round-Touchscreen"
    password: ""

captive_portal:

time:
  - platform: homeassistant
    timezone: "America/Denver"
    id: esptime

sensor:
  - platform: uptime
    name: "$devicename Uptime"
  - platform: wifi_signal
    name: "$devicename WiFi Signal"
    update_interval: 60s    

external_components:
  - source: github://mcmanigle/esphome-components
    components: ["gc9a01"]
  - source: github://GadgetFactory/[email protected]

text_sensor:
  - platform: CST816S_touchscreen
    id: my_touch_screen
    on_value:
      then:
        - display.page.show_next: watchface
        - component.update: watchface

i2c:
  scl: 5
  sda: 4

spi:
  mosi_pin: $mopin
  clk_pin: $clpin
#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.

# 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: ALWAYS_ON

font:
  - file: "gfonts://Roboto"
    id: font_16
    size: 16
  - file: "gfonts://Roboto"
    id: font_32
    size: 32

color:
  - id: my_red
    red: 100%
    green: 3%
    blue: 5%
  - id: my_green
    red: 3%
    green: 100%
    blue: 5%
  - id: my_blue
    red: 3%
    green: 5%
    blue: 100%

display:
  - platform: gc9a01
    id: watchface
    reset_pin: $repin
    cs_pin: $cspin
    dc_pin: $dcpin

# Rotate the screen so usb socket is pointing down
    rotation: 90

    pages:
      - id: page1
        lambda: |-
          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_green), TextAlign::CENTER, "Page 1");
          it.circle(120, 120, 115, id(my_red));
      - id: page2
        lambda: |-
          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_green), TextAlign::CENTER, "Page 2");
          it.circle(120, 120, 115, id(my_red));

8 Likes

Would this M5Stack Dial - ESP32-S3 Smart Rotary Knob w/ 1.28" Round Touch Screenbe a (better / easier) alternative?
I don’t have it, just received the newsletter.
I’m in no way affiliated.

Edit: actually pasted the link, copying is not enough🤣
Edit2: Thanks Daryl

would what?

alternative to what?

what newsletter?

Probably referring to the M5Dial which had it’s official release today.

Which no doubt will require a similar amount of effort to get working in ESPHome.

The touch part is FT3267 which isn’t mentioned in esp docs.

Can’t see it in openhasp either.

Hi all,

First of all thanks for spending time on this and sharing your progress - it’s awesome. I just received my display and has successfully flashed it with the example code above, posted by @jackgassett .

Now, I would like to show some metadata from a media_player, so I’ve created a template sensor in HA and added it to the ESPHome yaml. Also, I’ve added this line

it.printf(128, 58, id(font_16), id(my_blue), "%s", id(channel).state.c_str());

But no matter what I try it just shows… nothing. All the things in the example (it.strftime, it.circle etc) works. So: Am I doing anything wrong, or am I simply doing it too early :slight_smile:

Here’s the complete yaml:

substitutions:
  devicename: round-touchscreen
  friendname: Wallround-touchscreenWatch01
  location: master
  board: esp32-c3-devkitm-1
  repin: GPIO1
  dcpin: GPIO2
  bkpin: GPIO3
  clpin: GPIO6
  mopin: GPIO7
  cspin: GPIO10

esphome:
  name: $devicename
  friendly_name: $friendname

esp32:
  board: $board
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

time:
  - platform: homeassistant
    timezone: "America/Denver"
    id: esptime

sensor:
  - platform: uptime
    name: "$devicename Uptime"
  - platform: wifi_signal
    name: "$devicename WiFi Signal"
    update_interval: 60s    

external_components:
  - source: github://mcmanigle/esphome-components
    components: ["gc9a01"]
  - source: github://GadgetFactory/[email protected]

text_sensor:
  - platform: CST816S_touchscreen
    id: my_touch_screen
    on_value:
      then:
        - display.page.show_next: watchface
        - component.update: watchface
  - platform: homeassistant
    name: "emerge metadata channel"
    id: channel
    entity_id: sensor.emerge_metadata_channel


i2c:
  scl: 5
  sda: 4

spi:
  mosi_pin: $mopin
  clk_pin: $clpin
#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.

# 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: ALWAYS_ON

font:
  - file: "gfonts://Roboto"
    id: font_16
    size: 16
  - file: "gfonts://Roboto"
    id: font_32
    size: 32
    glyphs: "!%()'&/+,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅÄÖÉ abcdefghijklmnopqrstuvwxyzæøåäöéá"
color:
  - id: my_red
    red: 100%
    green: 3%
    blue: 5%
  - id: my_green
    red: 3%
    green: 100%
    blue: 5%
  - id: my_blue
    red: 3%
    green: 5%
    blue: 100%

display:
  - platform: gc9a01
    id: watchface
    reset_pin: $repin
    cs_pin: $cspin
    dc_pin: $dcpin

# Rotate the screen so usb socket is pointing down
    rotation: 90

    pages:
      - id: page1
        lambda: |-
          // Print metadata from HA text sensor, 
          it.printf(128, 58, id(font_16), id(my_blue), TextAlign::BASELINE_CENTER, "%s", id(channel).state.c_str());



Nevermind :grimacing: Guess who didn’t add the ESPHome device to Home Assistant, so that HA could send the sensor data to it :roll_eyes: Sorry.

2 Likes

Sorry noob question, how would I modify the code if I wanted for example climate screen, basic lights control and a swipe that switches between those screen?
Is there a guide for that because I’m not sure where to start

Sorry - still a work in progress. Currently can only display one screen of info (or setup a timer to toggle through multiple screens). Touch screen we know is possible, and jackgassett has put together some initial code above, so this is hopefully coming soon.

1 Like

It looks interesting, and I’m sure will have a purpose (the RFID feature is very interesting), but for my use cases not really. It is roughly double the price, is over 3cm thick vs 1cm - so may require cutting a hole into the wall otherwise it will stick out quite a bit, plus the orange colour would not be looked at kindly by my wife! :slight_smile:

1 Like

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));     
8 Likes

…and finally for those still playing at home, here’s a working example on how to use the controller to swipe between screens showing different information, show a thermostat and adjust the temps using swipe up/down, and toggle a light on/off. Note that the controller can show sensor states from home assistant, but to get home assistant to act on changes to the controller you need to create an automation that performs an action based on changes to the sensors that the controller creates in HA. Can take a moment to get your head around it, but it makes sense once you’ve done it a few times. For example, in the code for the controller there is:

  - platform: template
    optimistic: true
    name: "$devicename light state"
    id: light_state
    min_value: 0
    max_value: 1
    initial_value: 0
    update_interval: 1s
    step: 1

This creates a sensor in HA that is either “0” or “1”. Then you also have:

        - if:
            condition:
              and:
                - text_sensor.state:
                    id: my_touch_screen
                    state: 'SINGLE CLICK'
                - display.is_displaying_page: page4     
            then:
              - logger.log: "Single click on page4"           
              - number.increment: light_state

This toggles the sensor value between 1 or 0 whenever the screen is on page 4 and is tapped.

Then in HA you need to create an automation along the lines of:

alias: Wallwatch - light toggle
description: ""
trigger:
  - platform: state
    entity_id:
      - number.wallwatch01_wallwatch01_light_state
condition: []
action:
  - service: light.toggle
    data: {}
    target:
      entity_id: light.hue_white_lamp_1_4
mode: single

So now whenever the controller screen is on page 4, if you tap the screen the entity sensor will change state and HA will detect that and the automation will toggle the light on or off. You could of course make this so it specifically turns on when the entity is “1” or off when the entity is “0” - I will probably do that in the future so I can have the light icon state on the controller match the physical light state. (On a side note, I am currently finding that the “SINGLE CLICK” is not always detected and/or it thinks it is a swipe so I’m using both single click as well as long press in my code to toggle the light.)

To do other things, such as control a thermostat, you basically need to follow the same basic steps - create a sensor that changes state depending on what you do on the controller, and create automations in HA that perform actions depending on what the sensor states are. There are I expect better ways of doing this, but it works for me. :wink:

Here is the example code in full:

# V0.3 - 10/10/2023
# Compiled and tested on esphome 2023.9.3
substitutions:
  devicename: wallwatch01
  friendname: WallWatch01
  location: master
  board: esp32-c3-devkitm-1
#GPIO pins for the LCD screen
  repin: GPIO1
  dcpin: GPIO2
  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

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
  logs:
    component: ERROR

# Enable Home Assistant API
api:
  encryption:
    key: !secret esphome_encryption_key

ota:
  password: !secret ota_password

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Wallwatch01 Fallback Hotspot"
    password: !secret fallback_password

captive_portal:

time:
  - platform: homeassistant
    timezone: "Australia/Melbourne"
    id: esptime

# Test - see if the thing picks up BLE info
#esp32_ble_tracker:
#nope - locks up
#SpokeTooSoon - actually does work, kinda. Slows down the startup significantly then claims to scan but nothing is found
#Either is not supported or antenna is really weak. Suspect both.

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
# Add in the thermostat and temp sensor we want to see
  - platform: homeassistant
    id: thermostat_temperature
    entity_id: climate.thermostat_master
    attribute: temperature
  - platform: homeassistant
    id: room_temperature
    entity_id: sensor.wall_control_06_wall06_temp

external_components:
  - source: github://zagnuts/esphome-components
    components: [ gc9a01 ]
    refresh: 30min
  - source: github://GadgetFactory/[email protected] 

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 error messages but didn't for me
  #frequency: 400kHz

number:
  # Create some virtual sensors - use automations to perform actions based on the state of these
  # This is to adjust the virtual thermostat
  - platform: template
    optimistic: true
    name: "$devicename thermostat"
    id: thermostat_adjust
    min_value: 18
    max_value: 35
    initial_value: 20
    update_interval: 1s
    step: 1
  # 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
  - platform: template
    optimistic: true
    name: "$devicename therm state"
    id: thermostat_state
    min_value: 0
    max_value: 2
    initial_value: 0
    update_interval: 1s
    step: 1
  # This is a binary off or on to toggle the light off or on
  - platform: template
    optimistic: true
    name: "$devicename light state"
    id: light_state
    min_value: 0
    max_value: 1
    initial_value: 0
    update_interval: 1s
    step: 1

text_sensor:
  - platform: CST816S_touchscreen
    id: my_touch_screen
    on_value:
      then:
        - 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   
              - component.update: watchface
              - logger.log: "Swiped left, go back a page"
        - if:
            condition:
              and:
                - text_sensor.state:
                    id: my_touch_screen
                    state: 'SINGLE CLICK'
                - display.is_displaying_page: page1     
            then:
              - logger.log: "Clicked on page1"
        - if:
            condition:
              and:
                - text_sensor.state:
                    id: my_touch_screen
                    state: 'SINGLE CLICK'
                - display.is_displaying_page: page2     
            then:
              - logger.log: "Clicked on page2"
        - 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
              - number.decrement: thermostat_adjust
        - 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              
              - number.increment: thermostat_adjust
        - 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"           
              - number.increment: light_state
 # Have found that the 'single click' does not always register hence also using 'long press' to toggle light 
        - if:
            condition:
              and:
                - text_sensor.state:
                    id: my_touch_screen
                    state: 'SINGLE CLICK'
                - display.is_displaying_page: page4     
            then:
              - logger.log: "Single click on page4"           
              - number.increment: light_state

# 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'

# 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: ALWAYS_ON

# The following assumes you have the named fonts in config\fonts
# If not, you can always download on the fly eg
#  - file: "gfonts://Roboto"
#    id: font_16
#    size: 16
font:
  - file: 'fonts/GoogleSans-Medium.ttf'
    id: font_16
    size: 16
  - file: 'fonts/GoogleSans-Medium.ttf'
    id: font_24
    size: 24
  - file: 'fonts/GoogleSans-Medium.ttf'
    id: font_32
    size: 32

color:
  - id: my_red
    red: 100%
    green: 3%
    blue: 5%
  - id: my_green
    red: 3%
    green: 100%
    blue: 5%
  - id: my_blue
    red: 3%
    green: 5%
    blue: 100%

# Some pretty images - not critical
# Again assumes you have them in config\icons
# Do not use large or complex images as the poor processor will struggle
animation:
  - file: "icons/2051v2.gif"
    id: clear_day
    resize: 40x40
    type: RGB565 #default is binary ie greyscale
  - file: "images/light2.png"
    id: light_bulb
#    resize: 80x80
#    type: RGB565
# Need the following image section currently with esphome since 2023.7.0
# Otherwise you will probably get 'id' is a required option for [0] errors when trying to compile
# I will note here a pet hate with breaking changes - I know that this is community developed code
# but still, it's a total pain when updating brings in weird issues like this and I continue to be in 
# awe when the community discover work arounds that are so left field
image:
  - file: "icons/2051v2.gif"
    id: gifArt

display:
#  - platform: ili9xxx
#    model: gc9a01
# Above is for when or if this is merged into the ili9xxx platform
# Until then though, need to use an external component
  - platform: gc9a01
    id: watchface
    reset_pin: $repin
    cs_pin: $cspin
    dc_pin: $dcpin

# Rotate the screen so usb socket is pointing down
    rotation: 90

# Print the date on one line in blue
# Print the current time on the next line, but in a bigger font and in default white
# Print the outside temp on the next line but in green
# Surround the lot by a red circle with centre at 120, 120 and a radius of 115 because why not
# Procrastinating now on the touchscreen, so instead added a second page and cycling between two screens because again why not
    pages:
      - id: page1
        lambda: |-
          id(clear_day).next_frame();
          it.image(100, 30, id(clear_day), COLOR_ON, COLOR_OFF);
          it.printf(120,80, id(font_16), id(my_blue), TextAlign::CENTER, "Thermostat");
          it.printf(120,120, id(font_24), TextAlign::CENTER, "Set Temp: %.1f°", id(thermostat_temperature).state);
          it.printf(120, 170, id(font_32), id(my_green), TextAlign::CENTER, "Now: %.1f°", id(room_temperature).state);
          it.circle(120, 120, 115, id(my_red));
      - 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_green), TextAlign::CENTER, "Now: %.1f°", id(outdoor_temperature).state);
          it.circle(120, 120, 115, id(my_red));          
      - 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_green), TextAlign::CENTER, "%.1f/%.1f°", id(min_temperature).state,id(max_temperature).state);
          it.circle(120, 120, 115, id(my_blue));
      - id: page4
        lambda: |-
          id(clear_day).next_frame();
          it.image(80, 80, id(light_bulb), COLOR_ON, COLOR_OFF);
          it.circle(120, 120, 115, id(my_blue));          

4 Likes

Well done! Very cool.

I suggest posting this in the Cookbook section of the ESPhome website.

1 Like

Hahaha, I just realized, I probably should have written in my post what the minimal touchscreen support could do instead of what was missing. :slight_smile: I was a little tired and just trying to push the code out before calling it a day.

It should be fully working for all of the gestures which is probably the bulk of what most use cases will be.

Next up is working on the x,y coordinates and the ability to convert to polar coordinates.

My plan is to be able to put a color wheel on the screen so you can easily and intuitively select the color of your lights with it. I’ve designed a 3d printed enclosure that lets you put this over an existing light switch so you can prevent the power from being cut off from the smart bulbs while still allowing convenient control in the same location. It will be a smarter light switch.

I started out with a touchless light switch that uses a gesture sensor to turn the lights on and off or dim them with just the swipe of a hand. That is working pretty well and is pretty neat but I think this round display might be a nice upgrade. Here are some pictures of the progress so far:

Gesture sensor light switch


Round Touchscreen light switch (still lots to do still)


Still need to put in a battery holder and get the right connector to connect a battery to the round touchscreen.

The gesture sensor uses esp-now to enable very fast wakeup from deep sleep and turning the light on or off within 500mS. I’ll work on making that work with this round gesture sensor as time allows. So far the battery has lasted several months with no problem and I think I calculated it should last over a year.

I haven’t measured the current draw of this round touchscreen yet, but it should be able to work off battery for an acceptable amount of time. We can always put larger batteries in too.

1 Like

I love it - my method is to put a yellow sticky note on the switches with “magic enabled” written on it. Works, but your solution is far more elegant. :slight_smile:

Fully understand the whole pushing the thing out before calling it a day - I did the same thing when initially putting the original post together ie did a big burst of trying to nut out the screen and then when I finally got it to work I had to put it aside for a while. But huge thank you for getting the touch code working - I agree it does pretty much everything we need. It would be nice to have the ability to identify where on the screen it’s being touched, but it’s so damn small the only use case might be for dimming the lights , changing music volume, or temperature settings - but all of those can be done with swipes as well. Anyhoo, found a few more spare minutes and have added light on/off rather than toggle.

# V0.3 - 10/10/2023
# Compiled and tested on esphome 2023.9.3
substitutions:
  devicename: wallwatch01
  friendname: WallWatch01
  location: master
  board: esp32-c3-devkitm-1
#GPIO pins for the LCD screen
  repin: GPIO1
  dcpin: GPIO2
  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

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
  logs:
    component: ERROR

# Enable Home Assistant API
api:
  encryption:
    key: !secret esphome_encryption_key

ota:
  password: !secret ota_password

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Wallwatch01 Fallback Hotspot"
    password: !secret fallback_password

captive_portal:

time:
  - platform: homeassistant
    timezone: "Australia/Melbourne"
    id: esptime

# Test - see if the thing picks up BLE info
#esp32_ble_tracker:
#nope - locks up
#SpokeTooSoon - actually does work, kinda. Slows down the startup significantly then claims to scan but nothing is found
#Either is not supported or antenna is really weak. Suspect both.

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
# Add in the thermostat and temp sensor we want to see
  - platform: homeassistant
    id: thermostat_temperature
    entity_id: climate.thermostat_master
    attribute: temperature
  - platform: homeassistant
    id: room_temperature
    entity_id: sensor.wall_control_06_wall06_temp

external_components:
  - source: github://zagnuts/esphome-components
    components: [ gc9a01 ]
    refresh: 30min
  - source: github://GadgetFactory/[email protected] 

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 error messages but didn't for me
  #frequency: 400kHz

number:
  # Create some virtual sensors - use automations to perform actions based on the state of these
  # This is to adjust the virtual thermostat
  - platform: template
    optimistic: true
    name: "$devicename thermostat"
    id: thermostat_adjust
    min_value: 18
    max_value: 35
    initial_value: 20
    update_interval: 1s
    step: 1
  # 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
  - platform: template
    optimistic: true
    name: "$devicename therm state"
    id: thermostat_state
    min_value: 0
    max_value: 2
    initial_value: 0
    update_interval: 1s
    step: 1
  # This is a binary off or on to toggle the light off or on
  - platform: template
    optimistic: true
    name: "$devicename light state"
    id: light_state
    min_value: 0
    max_value: 1
    initial_value: 0
    update_interval: 1s
    step: 1

text_sensor:
  - platform: CST816S_touchscreen
    id: my_touch_screen
    on_value:
      then:
        - 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   
              - component.update: watchface
              - logger.log: "Swiped left, go back a page"
        - if:
            condition:
              and:
                - text_sensor.state:
                    id: my_touch_screen
                    state: 'SINGLE CLICK'
                - display.is_displaying_page: page1     
            then:
              - logger.log: "Clicked on page1"
        - if:
            condition:
              and:
                - text_sensor.state:
                    id: my_touch_screen
                    state: 'SINGLE CLICK'
                - display.is_displaying_page: page2     
            then:
              - logger.log: "Clicked on page2"
        - 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
              - number.decrement: thermostat_adjust
        - 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              
              - number.increment: thermostat_adjust
        - if:
            condition:
              and:
                - text_sensor.state:
                    id: my_touch_screen
                    state: 'SINGLE CLICK'
                - display.is_displaying_page: page4     
            then:
              - logger.log: "Single click on page4"           
              - number.increment: light_state
              # refresh the screen
              - component.update: watchface
 # Have found that the 'single click' does not always register hence also using 'long press' to toggle 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"           
              - number.increment: light_state
              # refresh the screen
              - 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'

# 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: ALWAYS_ON

# The following assumes you have the named fonts in config\fonts
# If not, you can always download on the fly eg
#  - file: "gfonts://Roboto"
#    id: font_16
#    size: 16
font:
  - file: 'fonts/GoogleSans-Medium.ttf'
    id: font_16
    size: 16
  - file: 'fonts/GoogleSans-Medium.ttf'
    id: font_24
    size: 24
  - file: 'fonts/GoogleSans-Medium.ttf'
    id: font_32
    size: 32

color:
  - id: my_red
    red: 100%
    green: 3%
    blue: 5%
  - id: my_green
    red: 3%
    green: 100%
    blue: 5%
  - id: my_blue
    red: 3%
    green: 5%
    blue: 100%

# Some pretty images - not critical
# Again assumes you have them in config\icons
# Do not use large or complex images as the poor processor will struggle
animation:
  - file: "icons/2051v2.gif"
    id: clear_day
    resize: 40x40
    type: RGB565 #default is binary ie greyscale
  - file: "images/lightoff.png"
    id: light_off
  - file: "images/lighton.png"
    id: light_on
    type: RGB565
#    resize: 80x80
#    type: RGB565
# Need the following image section currently with esphome since 2023.7.0
# Otherwise you will probably get 'id' is a required option for [0] errors when trying to compile
# I will note here a pet hate with breaking changes - I know that this is community developed code
# but still, it's a total pain when updating brings in weird issues like this and I continue to be in 
# awe when the community discover work arounds that are so left field
image:
  - file: "icons/2051v2.gif"
    id: gifArt

display:
#  - platform: ili9xxx
#    model: gc9a01
# Above is for when or if this is merged into the ili9xxx platform
# Until then though, need to use an external component
  - platform: gc9a01
    id: watchface
    reset_pin: $repin
    cs_pin: $cspin
    dc_pin: $dcpin

# Rotate the screen so usb socket is pointing down
    rotation: 90

# Print the date on one line in blue
# Print the current time on the next line, but in a bigger font and in default white
# Print the outside temp on the next line but in green
# Surround the lot by a red circle with centre at 120, 120 and a radius of 115 because why not
# Procrastinating now on the touchscreen, so instead added a second page and cycling between two screens because again why not
    pages:
      - id: page1
        lambda: |-
          id(clear_day).next_frame();
          it.image(100, 30, id(clear_day), COLOR_ON, COLOR_OFF);
          it.printf(120,80, id(font_16), id(my_blue), TextAlign::CENTER, "Thermostat");
          it.printf(120,120, id(font_24), TextAlign::CENTER, "Set Temp: %.1f°", id(thermostat_temperature).state);
          it.printf(120, 170, id(font_32), id(my_green), TextAlign::CENTER, "Now: %.1f°", id(room_temperature).state);
          it.circle(120, 120, 115, id(my_red));
      - 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_green), TextAlign::CENTER, "Now: %.1f°", id(outdoor_temperature).state);
          it.circle(120, 120, 115, id(my_red));          
      - 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_green), TextAlign::CENTER, "%.1f/%.1f°", id(min_temperature).state,id(max_temperature).state);
          it.circle(120, 120, 115, id(my_blue));
      - id: page4
        lambda: |-
          it.image(80, 80, id(light_off), COLOR_ON, COLOR_OFF);
          if (id(light_state).state == 0)
            it.image(80, 80, id(light_off), COLOR_ON, COLOR_OFF);
          if (id(light_state).state == 1)
            it.image(80, 80, id(light_on), COLOR_ON, COLOR_OFF);
          it.circle(120, 120, 115, id(my_blue));          

and two automations

alias: Wallwatch - light turn on
description: ""
trigger:
  - platform: numeric_state
    entity_id: number.wallwatch01_wallwatch01_light_state
    above: 0
condition: []
action:
  - service: light.turn_on
    data: {}
    target:
      entity_id: light.hue_white_lamp_1_4
mode: single

and

alias: Wallwatch - light turn off
description: ""
trigger:
  - platform: numeric_state
    entity_id: number.wallwatch01_wallwatch01_light_state
    below: 1
condition: []
action:
  - service: light.turn_off
    data: {}
    target:
      entity_id: light.hue_white_lamp_1_4
mode: single

…or could combine into one automation eg:

alias: Wallwatch - turn light on or off
description: ""
trigger:
  - platform: state
    entity_id:
      - number.wallwatch01_wallwatch01_light_state
condition: []
action:
  - if:
      - condition: numeric_state
        entity_id: number.wallwatch01_wallwatch01_light_state
        above: 0
    then:
      - service: light.turn_on
        data: {}
        target:
          entity_id: light.hue_white_lamp_1_4
    else:
      - service: light.turn_off
        data: {}
        target:
          entity_id: light.hue_white_lamp_1_4
mode: single


2 Likes

The main use case I was thinking was to be able to quickly select a color from a color wheel. Oh, and I also want to drag up and down on the screen to dim or brighten lights.

That is what drew me to this round display in the first place, I thought it might look cool to put a color wheel like this on it:

1 Like

Oooh… yes, true. I tend to have set modes for parties etc as it can be a bit problematic hunting around trying to get that shade of pink that’s just right, but that would definitely be nice to have.

@zagnuts Can you please share the following files:
2051v2.gif
light2.png

Sorry, but I don’t have the rights to share those. I just found some images online and used gimp to shrink them to a size more suited to the screen. Frankly they are quick hacks done as a proof of concept. Now that the thing works, will probably look for some better images to use.

Tried installing the yaml file to device, says Device Disconnected. Even did a reset to device using Esptool. Also when I try to download the installation file from esphome get following error:

INFO ESPHome 2023.8.2
INFO Reading configuration /config/round-display.yaml...
WARNING GPIO2 is a Strapping PIN and should be avoided.
Attaching external pullup/down resistors to strapping pins can cause unexpected failures.
See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins
INFO Generating C++ source...
WARNING The image "/config/images/light2.png" you requested is very big. Please consider using the resize parameter.
INFO Compiling app...
Processing round-display (board: esp32-c3-devkitm-1; framework: arduino; platform: platformio/[email protected])
--------------------------------------------------------------------------------
HARDWARE: ESP32C3 160MHz, 320KB RAM, 4MB Flash
 - toolchain-riscv32-esp @ 8.4.0+2021r2-patch5
Dependency Graph
|-- AsyncTCP-esphome @ 1.2.2
|-- WiFi @ 2.0.0
|-- FS @ 2.0.0
|-- Update @ 2.0.0
|-- ESPAsyncWebServer-esphome @ 2.1.0
|-- DNSServer @ 2.0.0
|-- ESPmDNS @ 2.0.0
|-- noise-c @ 0.1.4
|-- SPI @ 2.0.0
|-- Wire @ 2.0.0
|-- CST816S @ 1.1.1+sha.dd4f520
Compiling .pioenvs/round-display/src/esphome/components/CST816S_touchscreen/CST816S_touchscreen.cpp.o
sh: 1: riscv32-esp-elf-g++: not found
Compiling .pioenvs/round-display/src/esphome/components/animation/animation.cpp.o
sh: 1: riscv32-esp-elf-g++: not found
Compiling .pioenvs/round-display/src/esphome/components/api/api_connection.cpp.o
sh: 1: riscv32-esp-elf-g++: not found
*** [.pioenvs/round-display/src/esphome/components/CST816S_touchscreen/CST816S_touchscreen.cpp.o] Error 127
Compiling .pioenvs/round-display/src/esphome/components/api/api_frame_helper.cpp.o
*** [.pioenvs/round-display/src/esphome/components/animation/animation.cpp.o] Error 127
*** [.pioenvs/round-display/src/esphome/components/api/api_connection.cpp.o] Error 127
sh: 1: riscv32-esp-elf-g++: not found
*** [.pioenvs/round-display/src/esphome/components/api/api_frame_helper.cpp.o] Error 127
========================== [FAILED] Took 4.61 seconds ==========================