Boaztheostrich
Posts: 5
Joined: Sun Dec 10, 2023 6:45 am

Debounce needed on one install but not another, identical hardware

Sun Dec 10, 2023 6:50 am

Hi all, I've got a problem I've been banging my head against for the past several hours if anyone on here is willing to help I would really appreciate it.

The situation is as follows: I have one raspberry pi 4 8gb attached to a 4 inch touch screen with a keyboard switch wired to the gpio pins as a push button.

On one raspberry Pi OS install, running the below code it only takes one photo per button press.

Code: Select all

from picamera2 import Picamera2, Preview
import time
from datetime import datetime
from gpiozero import Button
from signal import pause
picam2 = Picamera2()
button = Button(16)
camera_config = picam2.create_preview_configuration(main={"size": (4056, 3040)}, lores={"size": (480, 360)}, display="lores", buffer_count=5)
picam2.configure(camera_config)

# test edit

def capture():
    with open("picture_count.txt", "r") as file:
        number = int(file.read())

    number += 1

    metadata = picam2.capture_file("/home/boaz/Desktop/images/" + "pibz_" + str(number) +".jpg")

    with open("picture_count.txt", "w") as file:
        file.write(str(number))

    print(metadata)

picam2.start_preview(Preview.QTGL, width=549, height=412)
picam2.start()

while True:
    button.when_pressed = capture
However on the other install, using the same hardware and everything else, only changing the SD card pushing the "shutter button" takes somewhere between 5 and 8 photos. I can't for the life of me figure out what might be going on. I have used the first install for multiple projects so maybe there is something there that could contribute but I am really not sure.

dbrion1
Posts: 445
Joined: Tue May 30, 2023 4:42 pm
Location: North of France

Re: Debounce needed on one install but not another, identical hardware

Sun Dec 10, 2023 4:13 pm

have you thought of ... adding a line to make the pi sleep, say, 0.1 second?
Something like

Code: Select all

import time        #  at the beginnig of your script
#                               and
  time.sleep(0.1) # in the loop (be ware of different indents)
edited import time is redundant, sorry. Remains sleeping for a small time (very primitive way of debouncig)
Last edited by dbrion1 on Sun Dec 10, 2023 4:41 pm, edited 1 time in total.

Boaztheostrich
Posts: 5
Joined: Sun Dec 10, 2023 6:45 am

Re: Debounce needed on one install but not another, identical hardware

Sun Dec 10, 2023 4:32 pm

I did try that but it’s possible I could have placed it somewhere more optimal in the code. I believe I had it at the bottom of the capture function. Which didn’t help at all, where would you suggest I place the sleep function?

I basically want the button input specifically to “sleep” after receiving the first input, is that the best way to approach this?

dbrion1
Posts: 445
Joined: Tue May 30, 2023 4:42 pm
Location: North of France

Re: Debounce needed on one install but not another, identical hardware

Sun Dec 10, 2023 4:43 pm

Code: Select all

while True:
    button.when_pressed = capture
    time.sleep(0.3)
(unchecked)

ghp
Posts: 3670
Joined: Wed Jun 12, 2013 12:41 pm
Location: Stuttgart Germany

Re: Debounce needed on one install but not another, identical hardware

Sun Dec 10, 2023 10:46 pm

When creating the Button, set a bounce_time parameter. https://gpiozero.readthedocs.io/en/stab ... tml#button

IMHO the loop around "button.when_pressed = capture" is useless. This line sets the callback method for the button which is good enough when running once.

A usual 'let the program run forever' snippet is

Code: Select all

wait = threading.Event()
try:
     wait.wait()
except KeyboardInterrupt:
    pass
print("terminating")

ame
Posts: 8736
Joined: Sat Aug 18, 2012 1:21 am
Location: New Zealand

Re: Debounce needed on one install but not another, identical hardware

Mon Dec 11, 2023 1:14 am

Does nobody read documentation these days?
https://gpiozero.readthedocs.io/en/stab ... tml#button

This doesn't do what you think it does:

Code: Select all

while True:
    button.when_pressed = capture
Should be:

Code: Select all

button.when_pressed = capture

while True:
    pass
    
Because you don't want your code to exit.

Or better:

Code: Select all

from signal import pause
.
.
.
button.when_pressed = capture

pause()
Oh no, not again.

Boaztheostrich
Posts: 5
Joined: Sun Dec 10, 2023 6:45 am

Re: Debounce needed on one install but not another, identical hardware

Mon Dec 11, 2023 4:04 am

Hi all,

Thanks for your feedback but I actually was able to resolve this on my own.

Code: Select all

from picamera2 import Picamera2, Preview
import time
from datetime import datetime
from gpiozero import Button
import threading
import os

picam2 = Picamera2()
button = Button(16)
camera_config = picam2.create_preview_configuration(main={"size": (4056, 3040)}, lores={"size": (480, 360)}, display="lores", buffer_count=5)
picam2.configure(camera_config)

# Global flag to indicate whether the capture function is in progress
capture_in_progress = False

def capture():
    global capture_in_progress
    with open("picture_count.txt", "r") as file:
        number = int(file.read())

    number += 1

    # Get the home directory of the current user
    home_directory = os.path.expanduser("~")

    # Construct the path to the images directory
    images_directory = os.path.join(home_directory, "Desktop", "images")

    # Ensure the images directory exists, create it if necessary
    os.makedirs(images_directory, exist_ok=True)

    # Capture the image with the updated path
    metadata = picam2.capture_file(os.path.join(images_directory, "pibz_" + str(number) + ".jpg"))

    with open("picture_count.txt", "w") as file:
        file.write(str(number))

    print(metadata)

    # Set the flag to indicate that the capture is complete
    capture_in_progress = False

def button_monitor():
    global capture_in_progress
    while True:
        if not capture_in_progress and button.is_pressed:
            print("Button pressed!")

            # Print the username of the current user
            username = os.path.basename(os.path.expanduser("~"))
            print(f"Current user: {username}")

            # Set the flag to indicate that the capture is in progress
            capture_in_progress = True
            
            # Start the capture function in a new thread
            capture_thread = threading.Thread(target=capture)
            capture_thread.start()

def display_preview():
    picam2.start_preview(Preview.QTGL, width=549, height=412)
    picam2.start()

# Create and start the threads
button_thread = threading.Thread(target=button_monitor)
preview_thread = threading.Thread(target=display_preview)

button_thread.start()
preview_thread.start()

# Wait for the threads to finish (this won't happen since they run indefinitely)
button_thread.join()
preview_thread.join()
Here is my final code if you're interested in taking a look through it. It works great for what I am doing and has some big improvements from the previous code.

Thanks,
Boaz

ame
Posts: 8736
Joined: Sat Aug 18, 2012 1:21 am
Location: New Zealand

Re: Debounce needed on one install but not another, identical hardware

Mon Dec 11, 2023 4:23 am

Boaztheostrich wrote:
Mon Dec 11, 2023 4:04 am
Hi all,

Thanks for your feedback but I actually was able to resolve this on my own.

Code: Select all

from picamera2 import Picamera2, Preview
import time
from datetime import datetime
from gpiozero import Button
import threading
import os

picam2 = Picamera2()
button = Button(16)
camera_config = picam2.create_preview_configuration(main={"size": (4056, 3040)}, lores={"size": (480, 360)}, display="lores", buffer_count=5)
picam2.configure(camera_config)

# Global flag to indicate whether the capture function is in progress
capture_in_progress = False

def capture():
    global capture_in_progress
    with open("picture_count.txt", "r") as file:
        number = int(file.read())

    number += 1

    # Get the home directory of the current user
    home_directory = os.path.expanduser("~")

    # Construct the path to the images directory
    images_directory = os.path.join(home_directory, "Desktop", "images")

    # Ensure the images directory exists, create it if necessary
    os.makedirs(images_directory, exist_ok=True)

    # Capture the image with the updated path
    metadata = picam2.capture_file(os.path.join(images_directory, "pibz_" + str(number) + ".jpg"))

    with open("picture_count.txt", "w") as file:
        file.write(str(number))

    print(metadata)

    # Set the flag to indicate that the capture is complete
    capture_in_progress = False

def button_monitor():
    global capture_in_progress
    while True:
        if not capture_in_progress and button.is_pressed:
            print("Button pressed!")

            # Print the username of the current user
            username = os.path.basename(os.path.expanduser("~"))
            print(f"Current user: {username}")

            # Set the flag to indicate that the capture is in progress
            capture_in_progress = True
            
            # Start the capture function in a new thread
            capture_thread = threading.Thread(target=capture)
            capture_thread.start()

def display_preview():
    picam2.start_preview(Preview.QTGL, width=549, height=412)
    picam2.start()

# Create and start the threads
button_thread = threading.Thread(target=button_monitor)
preview_thread = threading.Thread(target=display_preview)

button_thread.start()
preview_thread.start()

# Wait for the threads to finish (this won't happen since they run indefinitely)
button_thread.join()
preview_thread.join()
Here is my final code if you're interested in taking a look through it. It works great for what I am doing and has some big improvements from the previous code.

Thanks,
Boaz
Threads?! :o
Oh no, not again.

ghp
Posts: 3670
Joined: Wed Jun 12, 2013 12:41 pm
Location: Stuttgart Germany

Re: Debounce needed on one install but not another, identical hardware

Mon Dec 11, 2023 7:32 am

Replacing a callback approach by polling is a valid approach.
My proposal would be to add a slight delay in the button poll loop:

Code: Select all

def button_monitor():
    global capture_in_progress
    while True:
        time.sleep(0.05)  # reduce CPU usage
        if not capture_in_progress and button.is_pressed:
            print("Button pressed!")
        ... other code skipped ...
Check cpu usage with 'top' in a console window before and after adding this delay. Should - hopefully - show a drastic reduction. On my system the process reduces from 100% to less than 1%. Also reduces current consumption of your Pi.

Return to “Python”