Create menu with buttons to tap when motion detected

Hi there,

Trying to have my pyportal titano show up 6 buttons when it detects motion. I’m sure I can figure out the action assignment (MQTT to Home Assistant) however I’ve obtained the following code from ChatGPT but when installing it as a file (and the bitmap files) to the device with the respective libraries installed, it just comes up with a Ctrl-D option to restart and not working at all. Any assistance would be excellent!
Code I have as follows:

import time
import board
import digitalio
import adafruit_imageload
import displayio
from adafruit_pyportal import PyPortal

# Configuration for the PyPortal display
DISPLAY_WIDTH = 480
DISPLAY_HEIGHT = 320

# Path to image files for action buttons
BUTTON_IMAGE_PATHS = [
    "button1.bmp",
    "button2.bmp",
    "button3.bmp",
    "button4.bmp",
    "button5.bmp",
    "button6.bmp",
]

# Initialize the PyPortal
pyportal = PyPortal(default_bg=None)

# Set up PIR motion sensor
motion_sensor = digitalio.DigitalInOut(board.D13)
motion_sensor.direction = digitalio.Direction.INPUT

# Load the button images
button_images = [None] * len(BUTTON_IMAGE_PATHS)
for idx, button_path in enumerate(BUTTON_IMAGE_PATHS):
    button_images[idx] = adafruit_imageload.load(button_path)

# Function to display the action buttons
def display_buttons():
    group = displayio.Group()
    for idx, image in enumerate(button_images):
        sprite = displayio.Sprite(image, x=80 + (idx % 2) * 200, y=60 + (idx // 2) * 100)
        group.append(sprite)
    pyportal.splash.append(group)

# Function to remove the action buttons from the display
def remove_buttons():
    pyportal.splash.pop()

while True:
    if motion_sensor.value:
        # Motion detected, display the action buttons
        display_buttons()
        # Wait for a short time to debounce the motion sensor
        time.sleep(0.5)
        while motion_sensor.value:
            # Wait until motion is no longer detected
            time.sleep(0.1)
        # Motion stopped, remove the action buttons
        remove_buttons()
    
    # Small delay to avoid busy-waiting
    time.sleep(0.1)

Hi Tom,

AI is great, but in this case you don’t even need to reach for it! Adafruit have written a full guide on displayio, the library used to assemble graphics for your screen:

At a glance, it seems like the chatgpt code isn’t actually writing to the pyportal, just assembling things in a displayio buffer.

image
Above is an excerpt from the relevant guide:

2 Likes

Thanks James! I’ll take a proper look at this over the weekend and see how I go :slight_smile:

Hi @James thanks again for this - I managed to give it a go and got the sprite on the main screen and cycling through the images
However I did find this tutorial which is almost exactly what I need to do (based on some small modifications which I’m sure I can manage)…I’ve managed to comment out what I don’t need (temp sensor as that doesn’t come in the titano obviously), however I can’t get the touch screen to work once it loads up my buttons on screen? Here is my code below… I suspect its got something to do wiht the calibration section? I’ve been googling and looking around and cant seem to find anything that would suggest I need to change it. Also ran a touch screen test code which registers the touches so I know it works. But suspect its because this was code for a pyportal and I’m running on a Titano (?)

# SPDX-FileCopyrightText: 2020 Anne Barela for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import board
import displayio
import busio
from digitalio import DigitalInOut
from analogio import AnalogIn
import neopixel
import adafruit_adt7410
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi_wifimanager
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text.label import Label
from adafruit_button import Button
import adafruit_touchscreen
import adafruit_minimqtt.adafruit_minimqtt as MQTT

# ------------- WiFi ------------- #

# Get wifi details and more from a secrets.py file
try:
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in secrets.py, please add them there!")
    raise

# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)

# ------- Sensor Setup ------- #
# init. the temperature sensor
# i2c_bus = busio.I2C(board.SCL, board.SDA)
#adt = adafruit_adt7410.ADT7410(i2c_bus, address=0x48)
#adt.high_resolution = True
# temperature = "blaa"
# init. the light sensor
light_sensor = AnalogIn(board.LIGHT)

# init. the motion sensor
movement_sensor = DigitalInOut(board.D3)

button1_state = 0
button2_state = 0

# ------------- Screen eliments ------------- #

display = board.DISPLAY

# Backlight function
def set_backlight(val):
    """Adjust the TFT backlight.
    :param val: The backlight brightness. Use a value between ``0`` and ``1``, where ``0`` is
                off, and ``1`` is 100% brightness.
    """
    val = max(0, min(1.0, val))
    try:
        board.DISPLAY.auto_brightness = False
    except AttributeError:
        pass
    board.DISPLAY.brightness = val


# Touchscreen setup
ts = adafruit_touchscreen.Touchscreen(
    board.TOUCH_XL,
    board.TOUCH_XR,
    board.TOUCH_YD,
    board.TOUCH_YU,
    calibration=((5200, 59000), (5800, 57000)),
    size=(480, 320),
)

# ---------- Set the font and preload letters ----------
# Be sure to put your font into a folder named "fonts".
font = bitmap_font.load_font("/fonts/Helvetica-Bold-16.bdf")
# This will preload the text images.
font.load_glyphs(b"abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890- ()")

# ------------- User Inretface Eliments ------------- #

# Make the display context
splash = displayio.Group()
board.DISPLAY.root_group = splash

# Make a background color fill
color_bitmap = displayio.Bitmap(480, 320, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0x3D0068
bg_sprite = displayio.TileGrid(color_bitmap, x=0, y=0, pixel_shader=color_palette)
splash.append(bg_sprite)

buttons = []
# Default button styling:
BUTTON_WIDTH = 200
BUTTON_HEIGHT = 140
BUTTON_MARGIN = 10

# Button Objects
button_1 = Button(
    x=BUTTON_MARGIN,
    y=BUTTON_MARGIN,
    width=BUTTON_WIDTH,
    height=BUTTON_HEIGHT,
    label="Button 1",
    label_font=font,
    style=Button.SHADOWROUNDRECT,
    label_color=0x505050,
    fill_color=0x9E9E9E,
    outline_color=0x464646,
)
buttons.append(button_1)

button_2 = Button(
    x=BUTTON_MARGIN,
    y=BUTTON_MARGIN * 2 + BUTTON_HEIGHT,
    width=BUTTON_WIDTH,
    height=BUTTON_HEIGHT,
    label="Button 2",
    label_font=font,
    style=Button.SHADOWROUNDRECT,
    label_color=0x505050,
    fill_color=0x9E9E9E,
    outline_color=0x464646,
)
buttons.append(button_2)

for b in buttons:
    splash.append(b.group)

# Text Label Objects
# temperature_label = Label(font, text="temperature", color=0xE300D2)
# temperature_label.x = 130
# temperature_label.y = 20
# splash.append(temperature_label)

# light_label = Label(font, text="lux", color=0xE300D2)
# light_label.x = 130
# light_label.y = 40
# splash.append(light_label)

# motion_label = Label(font, text="motion", color=0xE300D2)
# motion_label.x = 130
# motion_label.y = 60
# splash.append(motion_label)

feed1_label = Label(font, text="MQTT Action1", color=0xE39300)
feed1_label.x = 240
feed1_label.y = 70
splash.append(feed1_label)

feed2_label = Label(font, text="MQTT Action2", color=0x00DCE3)
feed2_label.x = 240
feed2_label.y = 230
splash.append(feed2_label)


# ------------- MQTT Topic Setup ------------- #

mqtt_topic = "test/topic"
# mqtt_temperature = "pyportal/temperature"
mqtt_lux = "pyportal/lux"
mqtt_PIR = "pyportal/pir"
mqtt_button1 = "pyportal/button1"
mqtt_button2 = "pyportal/button2"
mqtt_feed1 = "pyportal/feed1"
mqtt_feed2 = "pyportal/feed2"

# ------------- MQTT Functions ------------- #

# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connect(client, userdata, flags, rc):
    # This function will be called when the client is connected
    # successfully to the broker.
    print("Connected to MQTT Broker!")
    print("Flags: {0}\n RC: {1}".format(flags, rc))


def disconnected(client, userdata, rc):
    # This method is called when the client is disconnected
    print("Disconnected from MQTT Broker!")


def subscribe(client, userdata, topic, granted_qos):
    # This method is called when the client subscribes to a new feed.
    print("Subscribed to {0} with QOS level {1}".format(topic, granted_qos))


def publish(client, userdata, topic, pid):
    # This method is called when the client publishes data to a feed.
    print("Published to {0} with PID {1}".format(topic, pid))


def message(client, topic, message):
    """Method callled when a client's subscribed feed has a new
    value.
    :param str topic: The topic of the feed with a new value.
    :param str message: The new value
    """
    print("New message on topic {0}: {1}".format(topic, message))
    if topic == "pyportal/feed1":
        feed1_label.text = "Next Bus: {}".format(message)
    if topic == "pyportal/feed2":
        feed2_label.text = "Weather: \n    {}".format(message)
    if topic == "pyportal/button1":
        if message == "1":
            buttons[0].label = "ON"
            buttons[0].selected = False
            print("Button 1 ON")
        else:
            buttons[0].label = "OFF"
            buttons[0].selected = True
            print("Button 1 OFF")


# ------------- Network Connection ------------- #

# Connect to WiFi
print("Connecting to WiFi...")
wifi.connect()
print("Connected to WiFi!")

# Initialize MQTT interface with the esp interface
MQTT.set_socket(socket, esp)

# Set up a MiniMQTT Client
client = MQTT.MQTT(
    broker=secrets["broker"],
    port=1883,
    username=secrets["user"],
    password=secrets["pass"],
)

# Connect callback handlers to client
client.on_connect = connect
client.on_disconnect = disconnected
client.on_subscribe = subscribe
client.on_publish = publish
client.on_message = message

print("Attempting to connect to %s" % client.broker)
client.connect()

print(
    "Subscribing to %s, %s, %s, and %s"
    % (mqtt_feed1, mqtt_feed2, mqtt_button1, mqtt_button2)
)
client.subscribe(mqtt_feed1)
client.subscribe(mqtt_feed2)
client.subscribe(mqtt_button1)
client.subscribe(mqtt_button2)

# ------------- Code Loop ------------- #
while True:
    # Poll the message queue
    client.loop()

    # Read sensor data and format
    light_value = lux = light_sensor.value
    light_label.text = "Light Sensor: {}".format(light_value)
    #temperature = round(adt.temperature)
    #temperature_label.text = "Temp Sensor: {}".format(temperature)
    movement_value = movement_sensor.value
    motion_label.text = "PIR Sensor: {}".format(movement_value)

    # Read display button press
    touch = ts.touch_point
    if touch:
        for i, b in enumerate(buttons):
            if b.contains(touch):
                print("Sending button%d pressed" % i)
                if i == 0:
                    # Toggle switch button type
                    if button1_state == 0:
                        button1_state = 1
                        b.label = "ON"
                        b.selected = False
                        print("Button 1 ON")
                    else:
                        button1_state = 0
                        b.label = "OFF"
                        b.selected = True
                        print("Button 1 OFF")
                    print("Sending button 1 state: ")
                    client.publish(mqtt_button1, button1_state)
                    # for debounce
                    while ts.touch_point:
                        print("Button 1 Pressed")
                if i == 1:
                    # Momentary button type
                    b.selected = True
                    print("Sending button 2 state: ")
                    client.publish(mqtt_button2, 1)
                    # for debounce
                    while ts.touch_point:
                        print("Button 2 Pressed")
                    print("Button 2 reliced")
                    print("Sending button 2 state: ")
                    client.publish(mqtt_button2, 0)
                    b.selected = False

    # Publish sensor data to MQTT
    print("Sending light sensor value: %d" % light_value)
    client.publish(mqtt_lux, light_value)

    print("Sending temperature value: %d" % temperature)
    client.publish(mqtt_temperature, temperature)

    print("Sending motion sensor value: %d" % movement_value)
    client.publish(mqtt_PIR, "{}".format(movement_value))
1 Like

Hi Tom,

The next thing I’d try is to isolate out the touch code and libraries into a new sketch, verify that works, and add stuff in until it breaks.

Bear in mind you tend to get what you code for, so GPT and copying other things will usually give you a few errors to solve like this.

Keen to keep helping on this one!

1 Like

Hi James, thanks for responding and your help to date!

I’ve actually used the code from the tutorial online in previous reply - no GPT code in here. From here I’ve managed to add 4 buttons at the right size on the screen, and removed all redundant items (I think) so now I’m left with just have the buttons, connecting to wifi, and MQTT actions. However I can’t get the buttons to actually ‘click in’ when touched, like it does in the video. I remove other code now and it comes up with errors, so I can’t see where I’m going wrong.

Any thoughts based on this refined code?

import board
import displayio
import busio
from digitalio import DigitalInOut
from analogio import AnalogIn
import neopixel
import adafruit_adt7410
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi_wifimanager
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text.label import Label
from adafruit_button import Button
import adafruit_touchscreen
import adafruit_minimqtt.adafruit_minimqtt as MQTT


try:
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in secrets.py, please add them there!")
    raise
    
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)

light_sensor = AnalogIn(board.LIGHT)

movement_sensor = DigitalInOut(board.D3)

button1_state = 0
button2_state = 0
button3_state = 0

display = board.DISPLAY

def set_backlight(val):
    """Adjust the TFT backlight.
    :param val: The backlight brightness. Use a value between ``0`` and ``1``, where ``0`` is
                off, and ``1`` is 100% brightness.
    """
    val = max(0, min(1.0, val))
    try:
        board.DISPLAY.auto_brightness = False
    except AttributeError:
        pass
    board.DISPLAY.brightness = val

ts = adafruit_touchscreen.Touchscreen(
    board.TOUCH_XL,
    board.TOUCH_XR,
    board.TOUCH_YD,
    board.TOUCH_YU,
    calibration=((5200, 59000), (5800, 57000)),
    size=(480, 320),
)

font = bitmap_font.load_font("/fonts/Helvetica-Bold-16.bdf")

font.load_glyphs(b"abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890- ()")

splash = displayio.Group()
board.DISPLAY.root_group = splash

color_bitmap = displayio.Bitmap(480, 320, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0x000000
bg_sprite = displayio.TileGrid(color_bitmap, x=0, y=0, pixel_shader=color_palette)
splash.append(bg_sprite)

buttons = []
BUTTON_WIDTH = 225
BUTTON_HEIGHT = 140
BUTTON_MARGIN = 10

button_1 = Button(
    x=BUTTON_MARGIN,
    y=BUTTON_MARGIN,
    width=BUTTON_WIDTH,
    height=BUTTON_HEIGHT,
    label="Button 1",
    label_font=font,
    style=Button.SHADOWROUNDRECT,
    label_color=0x505050,
    fill_color=0xe08b2f,
    outline_color=0x464646,
)
buttons.append(button_1)

button_2 = Button(
    x=BUTTON_MARGIN,
    y=BUTTON_MARGIN * 2 + BUTTON_HEIGHT,
    width=BUTTON_WIDTH,
    height=BUTTON_HEIGHT,
    label="Button 2",
    label_font=font,
    style=Button.SHADOWROUNDRECT,
    label_color=0x505050,
    fill_color=0x40e34a,
    outline_color=0x464646,
)
buttons.append(button_2)

button_3 = Button(
    x=BUTTON_MARGIN * 2 + BUTTON_WIDTH,
    y=BUTTON_MARGIN,
    width=BUTTON_WIDTH,
    height=BUTTON_HEIGHT,
    label="Button 3",
    label_font=font,
    style=Button.SHADOWROUNDRECT,
    label_color=0x505050,
    fill_color=0x9E9E9E,
    outline_color=0x464646,
)
buttons.append(button_3)

button_4 = Button(
    x=BUTTON_MARGIN * 2 + BUTTON_WIDTH,
    y=BUTTON_MARGIN * 2 + BUTTON_HEIGHT,
    width=BUTTON_WIDTH,
    height=BUTTON_HEIGHT,
    label="Button 4",
    label_font=font,
    style=Button.SHADOWROUNDRECT,
    label_color=0x505050,
    fill_color=0x9E9E9E,
    outline_color=0x464646,
)
buttons.append(button_4)

for b in buttons:
    splash.append(b.group)



#feed1_label = Label(font, text="MQTT1", color=0xE39300)
#feed1_label.x = 240
#feed1_label.y = 70
#splash.append(feed1_label)

#feed2_label = Label(font, text="MQTT2", color=0x00DCE3)
#feed2_label.x = 240
#feed2_label.y = 230
#splash.append(feed2_label)



mqtt_topic = "test/topic"
mqtt_lux = "pyportal/lux"
mqtt_PIR = "pyportal/pir"
mqtt_button1 = "pyportal/button1"
mqtt_button2 = "pyportal/button2"
mqtt_feed1 = "pyportal/feed1"
mqtt_feed2 = "pyportal/feed2"


def connect(client, userdata, flags, rc):
    print("Connected to MQTT Broker!")
    print("Flags: {0}\n RC: {1}".format(flags, rc))


def disconnected(client, userdata, rc):
    print("Disconnected from MQTT Broker!")


def subscribe(client, userdata, topic, granted_qos):
    print("Subscribed to {0} with QOS level {1}".format(topic, granted_qos))


def publish(client, userdata, topic, pid):
    print("Published to {0} with PID {1}".format(topic, pid))


def message(client, topic, message):
    """Method callled when a client's subscribed feed has a new
    value.
    :param str topic: The topic of the feed with a new value.
    :param str message: The new value
    """
    print("New message on topic {0}: {1}".format(topic, message))
    if topic == "pyportal/feed1":
        feed1_label.text = "Next Bus: {}".format(message)
    if topic == "pyportal/feed2":
        feed2_label.text = "Weather: \n    {}".format(message)
    if topic == "pyportal/button1":
        if message == "1":
            buttons[0].label = "ON"
            buttons[0].selected = False
            print("Button 1 ON")
        else:
            buttons[0].label = "OFF"
            buttons[0].selected = True
            print("Button 1 OFF")



print("Connecting to WiFi...")
wifi.connect()
print("Connected to WiFi!")

MQTT.set_socket(socket, esp)


client = MQTT.MQTT(
    broker=secrets["broker"],
    port=1883,
    username=secrets["user"],
    password=secrets["pass"],
)


client.on_connect = connect
client.on_disconnect = disconnected
client.on_subscribe = subscribe
client.on_publish = publish
client.on_message = message

print("Attempting to connect to %s" % client.broker)
client.connect()

print(
    "Subscribing to %s, %s, %s, and %s"
    % (mqtt_feed1, mqtt_feed2, mqtt_button1, mqtt_button2)
)
client.subscribe(mqtt_feed1)
client.subscribe(mqtt_feed2)
client.subscribe(mqtt_button1)
client.subscribe(mqtt_button2)


while True:
    client.loop()

    touch = ts.touch_point
    if touch:
        for i, b in enumerate(buttons):
            if b.contains(touch):
                print("Sending button%d pressed" % i)
                if i == 0:
                    if button1_state == 0:
                        button1_state = 1
                        b.label = "ON"
                        b.selected = False
                        print("Button 1 ON")
                    else:
                        button1_state = 0
                        b.label = "OFF"
                        b.selected = True
                        print("Button 1 OFF")
                    print("Sending button 1 state: ")
                    client.publish(mqtt_button1, button1_state)
                    while ts.touch_point:
                        print("Button 1 Pressed")
                if i == 1:
                    b.selected = True
                    print("Sending button 2 state: ")
                    client.publish(mqtt_button2, 1)
                    while ts.touch_point:
                        print("Button 2 Pressed")
                    print("Button 2 reliced")
                    print("Sending button 2 state: ")
                    client.publish(mqtt_button2, 0)
                    b.selected = False

    print("Sending light sensor value: %d" % light_value)
    client.publish(mqtt_lux, light_value)


    print("Sending motion sensor value: %d" % movement_value)
    client.publish(mqtt_PIR, "{}".format(movement_value))
3 Likes

Do you get these messages in your console (I actually don’t know where this is going)

Have you run a simpler test? Like blinking the display when the screen is touched?

3 Likes

Hey Tom.

Can you please let us know any error messages that you’re getting, and what the STDOUT and behaviour currently is so that we can diagnose what is going wrong?

I’ve refactored and reformatted your code to make it a bit more readable/memory-efficient and added some # TODO items to double check in your implementation, a number of the changes were made with automated formatting extensions and tools I use in my personal workflows. It may have broken if the patterns you follow in your implementation don’t line up with it although at a glance this looks alright:

import board
import displayio
import busio
from digitalio import DigitalInOut
from analogio import AnalogIn
import neopixel
import adafruit_adt7410  # TODO: unused, remove this line
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi_wifimanager
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text.label import Label  # TODO: unused, remove this line
from adafruit_button import Button
import adafruit_touchscreen
import adafruit_minimqtt.adafruit_minimqtt as MQTT

try:
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in secrets.py, please add them there!")
    raise

BUTTON_WIDTH = 225
BUTTON_HEIGHT = 140
BUTTON_MARGIN = 10

MQTT_TOPIC = "test/topic"
MQTT_LUX = "pyportal/lux"
MQTT_PIR = "pyportal/pir"
MQTT_BUTTON_ONE = "pyportal/button1"
MQTT_BUTTON_TWO = "pyportal/button2"
MQTT_FEED_ONE = "pyportal/feed1"
MQTT_FEED_TWO = "pyportal/feed2"

ESP = adafruit_esp32spi.ESP_SPIcontrol(
    busio.SPI(board.SCK, board.MOSI, board.MISO),
    DigitalInOut(board.ESP_CS),
    DigitalInOut(board.ESP_BUSY),
    DigitalInOut(board.ESP_RESET),
)


LIGHT_SENSOR = AnalogIn(board.LIGHT)
MOVEMENT_SENSOR = DigitalInOut(board.D3)

button_states = [True, True, True]

display = board.DISPLAY  # TODO: unused

TOUCHSCREEN = adafruit_touchscreen.Touchscreen(
    board.TOUCH_XL,
    board.TOUCH_XR,
    board.TOUCH_YD,
    board.TOUCH_YU,
    calibration=((5200, 59000), (5800, 57000)),
    size=(480, 320),
)
FONT = bitmap_font.load_font("/fonts/Helvetica-Bold-16.bdf")
FONT.load_glyphs(b"abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890- ()")
SPLASH = displayio.Group()
board.DISPLAY.root_group = SPLASH

color_palette = displayio.Palette(1)
color_palette[0] = 0x000000
bg_sprite = displayio.TileGrid(
    displayio.Bitmap(480, 320, 1), x=0, y=0, pixel_shader=color_palette
)
SPLASH.append(bg_sprite)

buttons = [
    Button(
        x=BUTTON_MARGIN,
        y=BUTTON_MARGIN,
        width=BUTTON_WIDTH,
        height=BUTTON_HEIGHT,
        label="Button One",
        label_font=FONT,
        style=Button.SHADOWROUNDRECT,
        label_color=0x505050,
        fill_color=0xE08B2F,
        outline_color=0x464646,
    ),
    Button(
        x=BUTTON_MARGIN,
        y=BUTTON_MARGIN * 2 + BUTTON_HEIGHT,
        width=BUTTON_WIDTH,
        height=BUTTON_HEIGHT,
        label="Button Two",
        label_font=FONT,
        style=Button.SHADOWROUNDRECT,
        label_color=0x505050,
        fill_color=0x40E34A,
        outline_color=0x464646,
    ),
    Button(
        x=BUTTON_MARGIN * 2 + BUTTON_WIDTH,
        y=BUTTON_MARGIN,
        width=BUTTON_WIDTH,
        height=BUTTON_HEIGHT,
        label="Button Three",
        label_font=FONT,
        style=Button.SHADOWROUNDRECT,
        label_color=0x505050,
        fill_color=0x9E9E9E,
        outline_color=0x464646,
    ),
    Button(
        x=BUTTON_MARGIN * 2 + BUTTON_WIDTH,
        y=BUTTON_MARGIN * 2 + BUTTON_HEIGHT,
        width=BUTTON_WIDTH,
        height=BUTTON_HEIGHT,
        label="Button Four",
        label_font=FONT,
        style=Button.SHADOWROUNDRECT,
        label_color=0x505050,
        fill_color=0x9E9E9E,
        outline_color=0x464646,
    ),
]


for button in buttons:
    SPLASH.append(button.group)

# TODO: remove unused code
# feed_one_label = Label(FONT, text="MQTT1", color=0xE39300)
# feed_one_label.x = 240
# feed_one_label.y = 70
# SPLASH.append(feed_one_label)
# feed_two_label = Label(FONT, text="MQTT2", color=0x00DCE3)
# feed_two_label.x = 240
# feed_two_label.y = 230
# SPLASH.append(feed_two_label)


def set_backlight(val):
    """Adjust the TFT backlight.
    :param val: The backlight brightness. Use a value between ``0`` and ``1``, where ``0`` is
                off, and ``1`` is 100% brightness.
    """
    try:
        board.DISPLAY.auto_brightness = False
    except AttributeError:
        pass
    board.DISPLAY.brightness = max(0, min(1.0, val))


def connect(client, userdata, flags, rc):
    print("Connected to MQTT Broker!")
    print("Flags: {0}\n RC: {1}".format(flags, rc))


def disconnected(client, userdata, rc):
    print("Disconnected from MQTT Broker!")


def subscribe(client, userdata, topic, granted_qos):
    print("Subscribed to {0} with QOS level {1}".format(topic, granted_qos))


def publish(client, userdata, topic, pid):
    print("Published to {0} with PID {1}".format(topic, pid))


def message(client, topic, message):
    """Method callled when a client's subscribed feed has a new
    value.
    :param str topic: The topic of the feed with a new value.
    :param str message: The new value
    """
    print("New message on topic {0}: {1}".format(topic, message))
    try:  # TODO: can remove this try-except if feed_one_label and feed_two_label get defined
        if topic == "pyportal/feed1":
            feed_one_label.text = "Next Bus: {}".format(
                message
            )  # TODO: ensure this gets defined
        if topic == "pyportal/feed2":
            feed_two_label.text = "Weather: \n    {}".format(
                message
            )  # TODO: ensure this gets defined
        if topic == "pyportal/button1":
            if message == "1":
                buttons[0].label = "ON"
                buttons[0].selected = False
                print("Button 1 ON")
            else:
                buttons[0].label = "OFF"
                buttons[0].selected = True
                print("Button 1 OFF")
    except Exception as exception:  # TODO: see try
        print(f"Exception on message: {exception}")


def wifi_connect():
    print("Connecting to WiFi...")
    adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(
        ESP, secrets, neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
    ).connect()
    print("Connected to WiFi!")


wifi_connect()
MQTT.set_socket(socket, ESP)
client = MQTT.MQTT(
    broker=secrets["broker"],
    port=1883,
    username=secrets["user"],
    password=secrets["pass"],
)
client.on_connect = connect
client.on_disconnect = disconnected
client.on_subscribe = subscribe
client.on_publish = publish
client.on_message = message

print(f"Attempting to connect to {client.broker}")
client.connect()

print(
    f"Subscribing to {MQTT_FEED_ONE}, {MQTT_FEED_TWO}, {MQTT_BUTTON_ONE}, and {MQTT_BUTTON_TWO}"
)
client.subscribe(MQTT_FEED_ONE)
client.subscribe(MQTT_FEED_TWO)
client.subscribe(MQTT_BUTTON_ONE)
client.subscribe(MQTT_BUTTON_TWO)


while True:
    client.loop()

    # TODO: remove unused code
    # light_label.text = "Light Sensor: {}".format(light_value)
    # temperature = round(adt.temperature)
    # temperature_label.text = "Temp Sensor: {}".format(temperature)
    # motion_label.text = "PIR Sensor: {}".format(movement_value)

    touch = TOUCHSCREEN.touch_point
    if touch:
        for i, b in enumerate(buttons):
            if b.contains(touch):
                print(f"Sending button {i + 1} pressed")
                if button_states[i]:
                    button_states[i] = False
                    b.label = "OFF"
                    b.selected = True
                    print(f"Button {i + 1} OFF")
                    print(f"Sending button {i + 1} state")
                else:
                    button_states[i] = True
                    b.label = "ON"
                    b.selected = False
                    print(f"Button {i + 1} ON")
                if i == 0:
                    client.publish(MQTT_BUTTON_ONE, button_states[0])
                elif i == 1:
                    client.publish(MQTT_BUTTON_TWO, button_states[1])
                while TOUCHSCREEN.touch_point:
                    print(f"Button {i} Pressed")

    print(f"Sending light sensor value: {LIGHT_SENSOR.value}")
    client.publish(MQTT_LUX, LIGHT_SENSOR.value)
    print(f"Sending motion sensor value: {MOVEMENT_SENSOR.value}")
    client.publish(MQTT_PIR, "{}".format(MOVEMENT_SENSOR.value))

If possible, I also almost always recommend formatting your modules to be called by:

if __name__ == "__main__":
    main()

and define your implementation in classes/functions accordingly based on your personal style, although it isn’t that important in this case.

Are any unhandled exceptions coming up when you try running this on your Titano?

By the way, just going to throw a couple book recommendations out there in case anyone is interested, figured I may as well:

Robert (Uncle Bob) Martin - Clean Code: A Handbook of Agile Software Craftsmanship

Andy Hunt and Dave Thomas - The Pragmatic Programmer

1 Like

Hey James, these are the messages I’m seeing in Mu Serial feed:

Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.
code.py output:
Warning: The group property is being deprecated. User code should be updated to add the Button directly to the Display or other Groups.
Warning: The group property is being deprecated. User code should be updated to add the Button directly to the Display or other Groups.
Warning: The group property is being deprecated. User code should be updated to add the Button directly to the Display or other Groups.
Warning: The group property is being deprecated. User code should be updated to add the Button directly to the Display or other Groups.
Connecting to WiFi...
Connected to WiFi!
Attempting to connect to 192.168.0.87
Traceback (most recent call last):
  File "code.py", line 217, in <module>
  File "adafruit_minimqtt/adafruit_minimqtt.py", line 560, in connect
MMQTTException: Repeated connect failures

Code done running.

Press any key to enter the REPL. Use CTRL-D to reload.

Adafruit CircuitPython 8.2.9 on 2023-12-06; Adafruit PyPortal Titano with samd51j20
>>> [D

Line 217 relates to client.connect() (And I’m now using @Bryce 's cleaned up code below) - my firewall I think is blocking the mqtt commands to Home Assistant (which I’m having a friend fix for me on Friday) but you’d think it would still at least click in or something like that? I’ve tried a simple code to have button on screen flash when button is touched but just getting even more errors (Python not my strong point, but amending code I think I’m ok at!):

import board
from adafruit_pyportal import PyPortal
import displayio
from adafruit_button import Button

# Create PyPortal object
pyportal = PyPortal()

# Create a button
button = Button(x=50, y=50, width=220, height=120, label="Flash", style=Button.SHADOWROUNDRECT, label_font="fonts/Arial-Bold-24.bdf")

# Set up the display group
splash = displayio.Group()
splash.append(button.group)

# Set up the PyPortal display to show the splash group
pyportal.splash.append(splash)

while True:
    touch_point = pyportal.touchscreen.touch_point
    if touch_point:
        if button.contains(touch_point):
            # Flash the screen
            pyportal.set_background_color(0xFFFFFF)
            pyportal.display.refresh()
            pyportal.set_background_color(0x000000)
            pyportal.display.refresh()

I’m using this code to test it, which also doesnt work, however the code further below registers when i touch the screen!

import board
import displayio
import terminalio
from adafruit_display_shapes.rect import Rect
from adafruit_display_text import label
from adafruit_touchscreen import Touchscreen

# Set up the display
display = board.DISPLAY

# Set up the touchscreen
touch = Touchscreen(board.TOUCH_XL, board.TOUCH_XR, board.TOUCH_YU, board.TOUCH_YD, calibration=((5200, 59000), (5800, 57000)))

# Create the display group
group = displayio.Group()

# Create a button
button_width, button_height = 100, 50
button = Rect(50, 100, button_width, button_height, fill=0x00FF00)
group.append(button)

# Create a label for the button
button_label = label.Label(terminalio.FONT, text="Press me!", color=0x000000)
button_label.x = button.x + (button_width - button_label.bounding_box[2]) // 2
button_label.y = button.y + (button_height - button_label.bounding_box[3]) // 2
group.append(button_label)

# Add the group to the display
display.show(group)

while True:
    point = touch.touch_point

    if point is not None:
        # Check if the touch point is within the button
        if button.x < point[0] < button.x + button_width and button.y < point[1] < button.y + button_height:
            # Change the button color to simulate a press effect
            button.fill = 0xFF0000
        else:
            button.fill = 0x00FF00

    # Wait for a short time to avoid rapid toggling

This is the code that registers with the touchscreen:

import time
import board
import adafruit_touchscreen

# Touchscreen pins
touch_xl = board.TOUCH_XL
touch_xr = board.TOUCH_XR
touch_yd = board.TOUCH_YD
touch_yu = board.TOUCH_YU

# Set your screen dimensions
screen_width = 480
screen_height = 320

# Number of calibration points
num_calibration_points = 3

# Initialize the touchscreen
ts = adafruit_touchscreen.Touchscreen(touch_xl, touch_xr, touch_yd, touch_yu, size=(screen_width, screen_height))

# Calibration points on the screen
calibration_points = [(50, 50), (screen_width // 2, screen_height // 2), (screen_width - 50, screen_height - 50)]

# Collect touchscreen data for calibration
calibration_data = []

print("Touchscreen Calibration:")
for point in range(num_calibration_points):
    print(f"Press the touchscreen at point {point + 1}: ({calibration_points[point][0]}, {calibration_points[point][1]})")
    while True:
        touch = ts.touch_point
        if touch:
            print("Raw Touch:", touch)
            calibration_data.append((calibration_points[point], touch))
            time.sleep(0.5)  # Add a delay to avoid registering multiple touches
            break

# Calculate calibration parameters
x_factors = []
y_factors = []
for (calibrated, raw) in calibration_data:
    x_factors.append(calibrated[0] / raw[0])
    y_factors.append(calibrated[1] / raw[1])

x_factor = sum(x_factors) / len(x_factors)
y_factor = sum(y_factors) / len(y_factors)

print(f"Calibration Factors: x_factor = {x_factor}, y_factor = {y_factor}")

# Use the calculated factors for touchscreen input
ts.calibration = ((int(x_factor * 65536), 0), (0, int(y_factor * 65536)))

# Test the calibrated touchscreen
while True:
    touch = ts.touch_point
    if touch:
        print("Calibrated Touch:", touch)

I’ve tried bringing over bits of code from this bottom file (sensor and calibration info) given this code recognises touch screen but my goal code doesn’t, but can’t seem to bring it over without it causing all sorts of errors. It seems a hybrid solution of the 4 buttons code with some of this text at bottom here should bring this all together?

2 Likes

Thanks Bryce! I’m going to get those books but no errors apart from what I’ve just put below - MQTT connection issue but only the screen calibration code works with touch screen, your code and the other one I put above does not. Weird!

2 Likes

Hi all,

I’ve made substantial progress on this and I’m almost there. I’ve got the 4 buttons on screen and registering in my Mu serial log when I touch each one.

So now I’ve got code with successfully connects to MQTT broker (not touch screen not registering), and this code below (touchscreen working with buttons, but can’t get MQTT/wifi to work!)

So now I’m just trying to carry over the MQTT action/login code to this code below. I’ve got the file below but not seeing it ‘linking’ or referring to the MQTT sections I’ve added in from other code. Would anyone know why this code isn’t working? I’ve carried over and commented out so many things but can’t get it running after many variations, its either an error or opens buttons but MQTT doesnt register:

import time
import board
import displayio
from digitalio import DigitalInOut, Direction
from adafruit_display_shapes.rect import Rect
import adafruit_touchscreen
import adafruit_minimqtt.adafruit_minimqtt as MQTT

# Touchscreen pins
touch_xl = board.TOUCH_XL
touch_xr = board.TOUCH_XR
touch_yd = board.TOUCH_YD
touch_yu = board.TOUCH_YU

# Set your screen dimensions
screen_width = 480
screen_height = 320

# Initialize the touchscreen
ts = adafruit_touchscreen.Touchscreen(touch_xl, touch_xr, touch_yd, touch_yu, size=(screen_width, screen_height))

# Create a display
display = board.DISPLAY
group = displayio.Group()
display.show(group)

# Create three buttons
button1 = Rect(5, 5, 220, 150, fill=0x00FF00)
button2 = Rect(240, 5, 220, 150, fill=0xFF0000)
button3 = Rect(5, 160, 220, 150, fill=0x0000FF)
button4 = Rect(240, 160, 220, 150, fill=0xF2D00D)

group.append(button1)
group.append(button2)
group.append(button3)
group.append(button4)

buttons = [button1, button2, button3, button4]

# MQTT Broker settings from secrets.py
try:
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in secrets.py, please add them there!")
    raise

# MQTT topic to publish commands
mqtt_topic = b"button_press"

# Create an MQTT client
client = MQTT.MQTT(
    broker=secrets["broker"],
    port=1883,
    username=secrets["user"],
    password=secrets["pass"],
)
def connect(client, userdata, flags, rc):
    print("Connected to MQTT Broker!")
    print("Flags: {0}\n RC: {1}".format(flags, rc))


def disconnected(client, userdata, rc):
    print("Disconnected from MQTT Broker!")


def subscribe(client, userdata, topic, granted_qos):
    print("Subscribed to {0} with QOS level {1}".format(topic, granted_qos))


def publish(client, userdata, topic, pid):
    print("Published to {0} with PID {1}".format(topic, pid))

def message(client, topic, message):
    """Method callled when a client's subscribed feed has a new
    value.
    :param str topic: The topic of the feed with a new value.
    :param str message: The new value
    """
    print("New message on topic {0}: {1}".format(topic, message))
    if topic == "pyportal/feed1":
        feed1_label.text = "Next Bus: {}".format(message)
    if topic == "pyportal/feed2":
        feed2_label.text = "Weather: \n    {}".format(message)
    if topic == "pyportal/button1":
        if message == "1":
            buttons[0].label = "ON"
            buttons[0].selected = False
            print("Button 1 ON")
            client.publish(mqtt_button1, button1_state)
        else:
            buttons[0].label = "OFF"
            buttons[0].selected = True
            print("Button 1 OFF")


print("Attempting to connect to %s" % client.broker)
client.connect()

while True:
    touch = ts.touch_point
    if touch:
        for button in buttons:
            if button.x < touch[0] < button.x + button.width and button.y < touch[1] < button.y + button.height:
                button_index = buttons.index(button) + 1
                print(f"Button {button_index} Pressed")

# Send MQTT command when a button is pressed
                mqtt_message = f"Button {button_index} pressed"
                client.on_connect = connect
                client.on_disconnect = disconnected
                client.on_subscribe = subscribe
                client.on_publish = publish
                client.on_message = message

                time.sleep(0.5)  # Add a delay to avoid registering multiple touches

print(
    "Subscribing to %s, %s, %s, and %s"
    % (mqtt_feed1, mqtt_feed2, mqtt_button1, mqtt_button2)
)
client.subscribe(mqtt_feed1)
client.subscribe(mqtt_feed2)
client.subscribe(mqtt_button1)
client.subscribe(mqtt_button2)


time.sleep(0.01)  # Add a small delay to reduce CPU usage
1111

hi @Tom252062 - it doesn’t look like you’re actually sending a message.

I think these lines should be outside while True since they are defining the callback functions. They do not need to be executed every time a button is pressed

client.on_connect = connect
client.on_disconnect = disconnected
client.on_subscribe = subscribe
client.on_publish = publish
client.on_message = message

perhaps I misunderstand the code, but it doesn’t look like a publish event is ever created ie. where does mqtt_message get published? it gets defined as a string but is not used elsewhere.

I can see in your previous posts that you have used client.publish() to publish data, but it is missing from your latest iteration

2 Likes

Thanks Michael! I’ll have a play with this. The MQTT commands was something I was going to address once I had the wifi/MQTT code actually connecting. I’ve got something giving me a hand with this, but I’ll be sure to publish the final code on here once I’ve got it working

I’ve had an update on this for people who are looking to do the same project. I sought the help of an expert (thanks Christopher) to compile this code for me, and from his excellent work now have working code for the titano which turns on when you tap the screen (and off after 15s of activity) and also sends MQTT commands to my 4 devices based on tapping a button:

# Copyright 2023 Christopher Martin. This program may be freely copied as long as this attribution is included 

import time
import board
import displayio
import busio
from digitalio import DigitalInOut
from analogio import AnalogIn
import neopixel
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi.adafruit_esp32spi_wifimanager import ESPSPI_WiFiManager
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from adafruit_display_shapes.rect import Rect
from adafruit_bitmap_font import bitmap_font
from adafruit_button import Button
import adafruit_touchscreen
import adafruit_minimqtt.adafruit_minimqtt as MQTT

OUTPUT_DEBUG_TEXT = True

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Define and setup all the sensors and computer-to-computer I/O hardware

ESP = adafruit_esp32spi.ESP_SPIcontrol(
    busio.SPI(board.SCK, board.MOSI, board.MISO),
    DigitalInOut(board.ESP_CS),
    DigitalInOut(board.ESP_BUSY),
    DigitalInOut(board.ESP_RESET),
    debug = OUTPUT_DEBUG_TEXT
)

TOUCHSCREEN = adafruit_touchscreen.Touchscreen(
    board.TOUCH_XL,
    board.TOUCH_XR,
    board.TOUCH_YD,
    board.TOUCH_YU,
    size=(480, 320)
)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Prepare the ESP chip to connect to WiFi

try:
    from secrets import secrets
except ImportError:
    if OUTPUT_DEBUG_TEXT:
        print("WiFi secrets are kept in secrets.py, please add them there!")
    raise

wifiNet = ESPSPI_WiFiManager(
    ESP,
    secrets,
    neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2),
    5,
    ESPSPI_WiFiManager.NORMAL,
    OUTPUT_DEBUG_TEXT
)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Prepare the display

# Create a display and group
display = board.DISPLAY
group = displayio.Group()

# Having the button parameters defined as constants makes it easy to adjust their size and spacing in just one place.
# These numbers will allow for a lot of "meaningless" space around the buttons which is useful when the screen must
#  be touched to turn on but you don't want to accidentally press a button while you can't see the screen.
BUTTON_WIDTH = 150
BUTTON_HEIGHT = 70
BUTTON_MARGIN = 60

# Create four fancy buttons in an array using the parameters defined above...
FONT = bitmap_font.load_font("/fonts/Helvetica-Bold-16.bdf")
FONT.load_glyphs(b"abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890- ()")
buttons = [
    Button(
        x=BUTTON_MARGIN,
        y=BUTTON_MARGIN,
        width=BUTTON_WIDTH,
        height=BUTTON_HEIGHT,
        label="Fan",
        label_font=FONT,
        style=Button.SHADOWROUNDRECT,
        label_color=0x505050,
        fill_color=0x00FF00,
        outline_color=0x464646,
    ),
    Button(
        x=BUTTON_MARGIN * 2 + BUTTON_WIDTH,
        y=BUTTON_MARGIN,
        width=BUTTON_WIDTH,
        height=BUTTON_HEIGHT,
        label="Blinds",
        label_font=FONT,
        style=Button.SHADOWROUNDRECT,
        label_color=0x505050,
        fill_color=0xFF0000,
        outline_color=0x464646,
    ),
    Button(
        x=BUTTON_MARGIN,
        y=BUTTON_MARGIN * 2 + BUTTON_HEIGHT,
        width=BUTTON_WIDTH,
        height=BUTTON_HEIGHT,
        label="Aircon",
        label_font=FONT,
        style=Button.SHADOWROUNDRECT,
        label_color=0x505050,
        fill_color=0x0000FF,
        outline_color=0x464646,
    ),
    Button(
        x=BUTTON_MARGIN * 2 + BUTTON_WIDTH,
        y=BUTTON_MARGIN * 2 + BUTTON_HEIGHT,
        width=BUTTON_WIDTH,
        height=BUTTON_HEIGHT,
        label="Lamp",
        label_font=FONT,
        style=Button.SHADOWROUNDRECT,
        label_color=0x505050,
        fill_color=0xF2D00D,
        outline_color=0x464646,
    )
]

# Put all buttons into the display group
for button in buttons:
    group.append(button)

# Show the group, which now contains the buttons, on the display
display.show(group)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Define the MQTT topics, tell MQTT to use the ESP chip, and prepare to connect to the broker

MQTT_BUTTON_ONE = "home/bedroom/fan"
MQTT_BUTTON_TWO = "home/bedroom/blinds"
MQTT_BUTTON_THREE = "home/bedroom/aircon"
MQTT_BUTTON_FOUR = "home/bedroom/lamp"

MQTT.set_socket(socket, ESP)

client = MQTT.MQTT(
    broker=secrets["broker"],
    port=1883,
    username=secrets["user"],
    password=secrets["pass"],
)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Prepare some stuff to be used during the user-interface loop

# Map each button to the message it must publish when touched
button_message = [MQTT_BUTTON_ONE, MQTT_BUTTON_TWO, MQTT_BUTTON_THREE, MQTT_BUTTON_FOUR]

# At the start of each UI cycle, these values have the described meanings:
#  >=0 means the index of a button which was being touched during the last cycle
#  -1 means a non-button part of the screen was being touched during the last cycle
#  -2 means the screen was not being touched during the last cycle
# Note: this concept assumes a single touch point; multi-touch gestures are not supported
button_was_touched = -2  # Initialise assuming the screen is not being touched on power-up

# When given the result of TOUCHSCREEN.touch_point, this function returns the index of a button,
#  or -1 if no button is touched, or -2 if the screen isn't touched at all.
# Note: this concept assumes a single touch point; multi-touch gestures are not supported
def indexOfTouchedButton(touch=None):
    if touch:
        for butIdx, button in enumerate(buttons):
            if button.contains(touch):
                return butIdx
        return -1
    return -2

# Define how many seconds to wait, while the screen is not being touched, before turning off the display to save power
POWERSAVE_DELAY = 15.0

# Define how many seconds to sleep, reducing CPU usage, before starting the next UI loop checking for touches
DEBOUNCE_DELAY = 0.15

powerSaveCountdown = POWERSAVE_DELAY

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Finally, here is the user-interface event-handling loop which never ends

while True:
    time.sleep(DEBOUNCE_DELAY)  # Sleep briefly to debounce buttons and reduce CPU usage
    touched = indexOfTouchedButton(TOUCHSCREEN.touch_point)
    if touched <= -2:
        # The screen is not being touched...
        if powerSaveCountdown > 0:
            powerSaveCountdown -= DEBOUNCE_DELAY
        elif display.brightness > 0.5:
            display.brightness = 0
    else:
        # The screen is being touched...
        if display.brightness < 0.5:
            display.brightness = 1
            powerSaveCountdown = POWERSAVE_DELAY
            # In this version, the user has asked to sleep for 0.5 seconds
            time.sleep(0.5)
            touched = indexOfTouchedButton(TOUCHSCREEN.touch_point)
            button_was_touched = -2  # Pretend the user was not touching the screen a moment ago
        if (touched >= 0) and (touched != button_was_touched):
            # A button that wasn't being touched during the last cycle is being touched now...
            data = "OFF" if buttons[touched].selected else "ON"
            if OUTPUT_DEBUG_TEXT:
                print(f"Publishing data {data} to topic {button_message[touched]} associated with button {touched + 1}")
            for attempt in range(5):
                try:
                    try:
                        client.publish(button_message[touched], data)
                    except:
                        try:
                            client.connect()
                        except:
                            try:
                                wifiNet.connect()
                            except:
                                wifiNet.reset()
                                time.sleep(1)  # Sleep for a second to ensure the reset is complete before continuing
                                raise
                            raise
                        raise
                except Exception as err:
                    if OUTPUT_DEBUG_TEXT:
                        print(repr(err))
                else:
                    buttons[touched].selected = not buttons[touched].selected
                    break
            else:
                if OUTPUT_DEBUG_TEXT:
                    print("Publish attempt failed after multiple attempts")
    button_was_touched = touched

and the code for the secrets.py file:

# SPDX-FileCopyrightText: 2020 Anne Barela for Adafruit Industries
#
# SPDX-License-Identifier: MIT

secrets = {
    'ssid': 'YOUR WIFI',
    'password' : 'PASSWORD',
    'broker' : 'HOME ASSISTANT IP ADDRESS',
    'user' : 'MOSQUITTOUSERNAME',
    'pass' : 'MOSQUITTOPASSWORD'
    }

(and obviously mosquitto add-on and integration installed through Home Assstant)

4 Likes

Glad you got there in the end @Tom252062 :partying_face:

I’m sure other users will be interested to see the finished project if you’re willing to do a writeup :smiley: It sounds like a pretty cool user-experience to have a project that looks and feels like an off-the-shelf product, where the touch display experience is well considered :kissing: :ok_hand:

1 Like

Thanks @Michael, I’ll probably do that some time soon! Just waiting on a 3D printed housing for it to assist with writeup photos.

My only issue is that sometimes the time in actioning the MQTT commands are very slow (3-8 mins). It’s only about 5m away from my access point (and signal between -50 and -55dBm) so my understanding is the titano wifi library/software isn’t great?. Tried updating to CP 9.0.0 alpha 6 but just get an error when the titano loads up my code.
I’m also looking to replace the button text with images, but will consider posting it in projects if I can resolve these things!

1 Like

It might be time to look at asyncio which is for writing concurrent code.
things get a little tricky though!

Hey @Michael, just did some reading on asyncio, forgive my ignorance but how would this solve the problem I have here? asyncio looks to help with running commands concurrently among many other things, but seems my error is wifi module/software related?

Ah forgive me if I misinterpreted - my intention was that perhaps you could run the MQTT transactions without holding up the rest of your system, like making the interface non-responsive

no worries @Michael, I’ve started writing the tutorial also as my 3D printed case came today :smiley:

Presuming its not a wifi issue, would you suggest I consider modifying the QoS from 0 to 1 or even 2? It seems the adafruit_minimqtt library can allow for this

To your final point though, while you’re much more of an expert that me on this stuff, given its such simple commands being sent from the Titano at the moment, there wouldnt be much system strain on the unit anyway would there?