Kivy + Python + Raspberry - How to implement countdown - python

I'm quite new to python programming and I'm currently building a photobooth using Kivy and Python.
In general it is working (I can press the button and it starts the function to take 3 pictures and updates the tumbnail on the screen) , but I'm not able to change the label text (actionLabel) to show a countdown before the takePhotos function starts.
import os, time, Image, sys, datetime, subprocess,glob
import kivy
kivy.require('1.10.0') # replace with your current kivy version !
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.image import Image as kivyImage
from kivy.clock import Clock
from kivy.graphics import Color, Rectangle
import RPi.GPIO as GPIO
#GPIO varialbes
#buttonPin = 23
GPIO.setmode(GPIO.BCM)
GPIO.setup(23,GPIO.IN)
GPIO_status = False
# Some variables
photoTitle = "My first Photobox!"
total_photos = 3 #Number of photos to be takes
#Function for photo taking
def takePhotos():
#Take first picture - Folder for inbound pictures /home/pi/PB_Inbox/photobooth%H%M%S.jpg
time.sleep(3)
subprocess.call("gphoto2 --capture-image-and-download --filename /home/pi/PB_Inbox/photobooth%H%M%S.jpg", shell=True)
#Take all other picture
for x in range (0,total_photos-1):
subprocess.call("gphoto2 --capture-image-and-download --filename /home/pi/PB_Inbox/photobooth%H%M%S.jpg", shell=True)
#Process pictures
subprocess.call("sudo sh /home/pi/Documents/Photo_Booth/Image_prep_3", shell=True)
print('done')
class MyApp(App):
# Display the latest thumbnail
photo = kivyImage(source="/home/pi/PB_Thumb/Thumb.png")
actionLabel = Label(text="Push the button", size_hint=(1, 0.2),color=[1,0,0,1],font_size='40sp')
def build(self):
# Set up the layout
photobox = GridLayout(rows=3, spacing=10, padding=10)
# Create the UI objects (and bind them to callbacks, if necessary)
headerLabel = Label(text="The Greatest Photobox", size_hint=(1, 0.1),font_size='40sp') # Button: 20% width, 100% height
# Add the UI elements to the layout
photobox.add_widget(headerLabel)
photobox.add_widget(self.photo)
photobox.add_widget(self.actionLabel)
# Periodically refresh the displayed photo using the callback function
Clock.schedule_interval(self.callback, 0.3)
return photobox
# Callback for thumbnail refresh and listening to GPIO for input
def callback(self, instance):
self.photo.reload()
if self.readSensor() == False:
pass
#print('waiting')
else:
#provided as an argument
takePhotos()
#Read status of the sensor and return True if Buzzer has been pushed
def readSensor(self):
sensor = GPIO.input(23)
if sensor == 1:
return True
else:
return False
if __name__ == '__main__':
MyApp().run()
Can someone show me how to do this?
Thx

Use Clock.create_trigger to trigger the count down.
Remove the time.sleep(3) in function, takePhotos().
Use Clock.schedule_once to invoke function takePhotos() i.e. Clock.schedule_once(takePhotos, 3)
Separate the Kivy App into Python Script and kv file.
Programming Guide ยป Events and Properties
In Kivy applications, you have to avoid long/infinite loops or sleeping.
Example
main.py
import os, time, sys, datetime, subprocess, glob
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.properties import ObjectProperty, NumericProperty
import RPi.GPIO as GPIO
# GPIO variables
# buttonPin = 23
GPIO.setmode(GPIO.BCM)
GPIO.setup(23, GPIO.IN)
GPIO_status = False
# Some variables
total_photos = 3 # Number of photos to be takes
# Function for photo taking
def takePhotos(dt):
# Take 3 picture
for x in range(total_photos):
subprocess.call("gphoto2 --capture-image-and-download --filename /home/pi/PB_Inbox/photobooth%H%M%S.jpg", shell=True)
# Process pictures
subprocess.call("sudo sh /home/pi/Documents/Photo_Booth/Image_prep_3", shell=True)
print('done')
class PhotoBox(BoxLayout):
photo = ObjectProperty(None)
action_button = ObjectProperty(None)
count_down_trigger = ObjectProperty(None)
count = NumericProperty(3)
def __init__(self, **kwargs):
super(PhotoBox, self).__init__(**kwargs)
# Display the latest thumbnail
self.photo.source = "/home/pi/PB_Thumb/Thumb.png"
self.count_down_trigger = Clock.create_trigger(self.count_down, 1)
def on_press_camera_button(self):
self.count = 3
self.count_down_trigger()
def count_down(self, dt):
self.action_button.text = str(self.count)
self.count -= 1
if self.count >= 0:
self.count_down_trigger()
else:
self.action_button.text = "Say Cheese!"
# Periodically refresh the displayed photo using the callback function
# Clock.schedule_interval(self.callback, 0.3) # infinite loop
Clock.schedule_once(self.callback, 0.3)
self.action_button.text = "Push the button" # reset text
# Callback for thumbnail refresh and listening to GPIO for input
def callback(self, dt):
self.photo.reload()
if self.readSensor():
# provided as an argument
Clock.schedule_once(takePhotos, 3) # call takePhotos method once after 3 seconds
else:
pass
#print('waiting')
# Read status of the sensor and return True if Buzzer has been pushed
def readSensor(self):
sensor = True # GPIO.input(23)
if sensor == 1:
return True
else:
return False
class MyApp(App):
title = "My first Photobox!"
def build(self):
return PhotoBox()
if __name__ == '__main__':
MyApp().run()
my.kv
#:kivy 1.10.0
<PhotoBox>:
photo: photo
action_button: action_button
orientation:'vertical'
spacing: 10
padding: 10
Label:
text: "The Greatest Photobox"
size_hint: (1, 0.1)
font_size: '40sp'
AsyncImage:
id: photo
Button:
id: action_button
text: "Push the button"
size_hint: (1, 0.2)
color: [1,0,0,1]
font_size: '40sp'
on_press: root.on_press_camera_button()
Output

Related

transitions for widget attributes in kivy

I just wanted to ask, is it possible to set transitions for attributes changes in kivy? Let's say my widget with id toolbar has opacity 0:
self.root.ids.toolbar.opacity = 0
Is it possible to set a transition if I plan to change it into opacity 1?
self.root.ids.toolbar.opacity = 1
Answers, suggestions, and corrections are appreciated. Thanks!
I have used kivy for some time and ran into similar problem, but I'm not an expert! So there might be better ways to do this.
That being said, here's a small example introducing fading effect. I believe, you should be able to implement a version for your own problem based on this.
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_string("""
<Screen1>:
test_widget : ID1
BoxLayout:
padding : (50, 50)
Button:
text: 'Trigger'
on_release:
root.trigger()
Label:
id : ID1
text: "Your Fading Text"
font_size : 30
bold : True
""")
class Screen1(Screen):
# Loading object properties based on the id
test_widget = ObjectProperty()
# Variables for the fading effect
event = None
trans_type = "fade_out"
# The trigger function schedules a task using Clock
def trigger(self):
if self.event != None:
# See below
self.event.cancel()
# The clock calls the given function every X seconds
self.event = Clock.schedule_interval(lambda _:self.transition(self.test_widget), 0.03)
def transition(self, widget):
# Changing the opacity of the widget, pretty self explanatory I think
if self.trans_type == "fade_out":
if (widget.opacity > 0.5):
widget.opacity -= 0.05
else:
self.trans_type = "fade_in"
# We need to make sure to cancel events, otherwise they would not stop
self.event.cancel()
elif self.trans_type == "fade_in":
if (widget.opacity < 1):
widget.opacity += 0.05
else:
self.trans_type = "fade_out"
self.event.cancel()
class TestApp(App):
def build(self):
# Creating a simple screen using ScreenManager and Screen
sm = ScreenManager()
sm.add_widget(Screen1(name='screen1'))
return sm
if __name__ == '__main__':
# Starting the app
TestApp().run()
Also, you might be interested in:
https://kivy.org/doc/stable/api-kivy.animation.html
This might simplify your solution, however I'm not familiar with it, so you have to figure that out for yourself :)

Accessing data from outside a running Kivy application

I'm working on a program with a GUI interface, which I've implemented using a kivy app. My end-goal is to enter a desired voltage in the kivy textbox and for it to be set using an Arduino + a DAC converter. For now I'm just trying to access the voltage from the text input from outside the kivy app in my main, so that I can separate the GUI cleanly from communication with the arduino, and other necessary calculations.
The problem is that the program doesn't continue after App.run() until the kivy app has been closed and the entered voltage has been lost. I've tried using the multiprocessing library, but the start() function also runs as long as the app is running. I'm also not sure what the best way is to access the voltage, I tried having it as a class member of the GUI, but maybe that's not a good idea.
This is my GUI.py code:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.properties import StringProperty, NumericProperty
from kivy.uix.button import Button
class GUI_GridLayout(GridLayout):
voltage_label_1 = StringProperty("Voltage 1?")
voltage_label_2 = StringProperty("Voltage 2?")
voltage_input_1 = TextInput()
voltage_input_2 = TextInput()
submit_button_label_1 = StringProperty("Submit 1")
submit_button_label_2 = StringProperty("Submit 2")
voltage_output_1 = NumericProperty(0)
voltage_output_2 = NumericProperty(0)
def press_1(self):
voltage_1 = self.voltage_input_1.text
self.voltage_output_1 = float(voltage_1)
def press_2(self):
voltage_2 = self.voltage_input_2.text
self.voltage_output_2 = float(voltage_2)
def build(self):
self.add_widget(Label(text=self.voltage_label_1))
self.add_widget(self.voltage_input_1)
self.add_widget(Label(text=self.voltage_label_2))
self.add_widget(self.voltage_input_2)
self.submit_button_1 = Button(text=self.submit_button_label_1)
self.submit_button_1.bind(on_press=self.press_1)
self.add_widget(self.submit_button_1)
self.submit_button_2 = Button(text=self.submit_button_label_2)
self.submit_button_2.bind(on_press=self.press_2)
self.add_widget(self.submit_button_2)
class apason_GUIApp(App):
def build(self):
return GUI_GridLayout()
The corresponding kv file:
#:kivy 1.0.9
<GUI_GridLayout>:
cols: 3
voltage_input_1: input_1
voltage_input_2: input_2
Label:
font_size: 50
text: str(root.voltage_label_1)
TextInput:
id: input_1
font_size: 50
multiline: False
text: root.voltage_input_1.text
Button:
font_size: 50
text: str(root.submit_button_label_1)
on_press: root.press_1()
Label:
font_size: 50
center_x: root.width / 4
text: str(root.voltage_label_2)
TextInput:
id: input_2
font_size: 50
multiline: False
text: root.voltage_input_2.text
Button:
font_size: 50
text: str(root.submit_button_label_2)
on_press: root.press_2()
And here's my main:
import GUI.GUI as gui
import multiprocessing as multiproc
import time
class GetVoltage:
def __init__(self):
self.voltage_1 = 0
self.voltage_2 = 0
def fetchVoltage(self, interface):
self.voltage_1 = interface.voltage_output_1
self.voltage_2 = interface.voltage_output_2
def run(self, interface):
while (True):
self.fetchVoltage(interface)
print(self.voltage_1)
print(self.voltage_2)
time.sleep(1)
if __name__ == '__main__':
interface = gui.apason_GUIApp()
interface_process = multiproc.Process(target=interface.run())
checker = GetVoltage()
checker_process = multiproc.Process(target=checker.run(interface))
interface_process.start()
checker_process.start()
Since you already have one process when you run main.py, you really only need to start the second process. And you can communicate between the processes using a Queue. Here is a modified version of your main.py that uses this approach:
import GUI as gui
import multiprocessing as multiproc
class GetVoltage:
def run(self, queue):
while True:
voltage = queue.get() # get the data from the other process by using the Queue
print(voltage)
if __name__ == '__main__':
q = multiproc.Queue()
# start the GetVoltage process
checker = GetVoltage()
checker_process = multiproc.Process(target=checker.run, args=(q,), daemon=True)
checker_process.start()
# start the App
interface = gui.apason_GUIApp()
interface.q = q
interface.run() # this does not return until the App closes
# kill the GetVoltage process after the App exits
checker_process.kill()
And a couple minor changes in the App code:
def press_1(self):
voltage_1 = self.voltage_input_1.text
self.voltage_output_1 = float(voltage_1)
App.get_running_app().q.put('voltage_1 = ' + voltage_1) # put the data in the Queue
def press_2(self):
voltage_2 = self.voltage_input_2.text
self.voltage_output_2 = float(voltage_2)
App.get_running_app().q.put('voltage_2 = ' + voltage_2) # put the data in the Queue

In a kivy application I am using a button to change variables, how do I pass this to a function that will be ran as a thread?

I have created a simple program that demonstrates my current dilemma. Whenever I press button 2 on the kivy GUI I would like to increment a variable. After this variable is incremented I would like to create a thread that runs separately to do something to the value of the variable. For this instance just adding a sleep of 5 seconds before printing it back will be fine. I have to use threading because any use of sleep or interrupts inside gui, stops it from working during that sleep or kills it completely.
You might need to install the kivy lib for the kivy application to work on windows
import threading
from kivy.config import Config
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import StringProperty
from datetime import datetime
#import adafruit_rfm9x
#from gpiozero import MotionSensor
#import sys
import time
#from gpiozero import MotionSensor
import subprocess
global buttonVal
# Setting up size of window for kivy app
Config.set('graphics', 'resizable', '0')
# fix the width of the window
Config.set('graphics', 'width', '800')
# fix the height of the window
Config.set('graphics', 'height', '480')
# Kivy app dependencies, buttons, labels, etc
Builder.load_string("""
<MySec>:
orientation: 'vertical'
size: root.width, root.height
rotation: 90
Label:
text: 'Master Wall Mount'
font_size: 45
Button:
id: kv_sec
text: root.seconds_string
font_size: 80
Button:
id: button2
text: 'Button 2'
on_press: root.button_toggle()
Button:
id: button3
text: 'Button 3'
""")
# function to turn on screen
def turn_on():
CONTROL = "vcgencmd"
CONTROL_UNBLANK = [CONTROL, "display_power", "1"]
subprocess.call(CONTROL_UNBLANK)
# function to turn off screen
def turn_off():
CONTROL = "vcgencmd"
CONTROL_BLANK = [CONTROL, "display_power", "0"]
subprocess.call(CONTROL_BLANK)
# function that will run as a thread to run the pir sensor infinitely to determine if motion is present and proceed
# to adjust screen brightness.
def pirsensor():
# Setting GPIO pin 4 as pir sensor
pir = MotionSensor(4)
# dhtDevice = adafruit_dht.DHT22(board.D23)
while True:
if pir.motion_detected:
turn_on()
print("Motion Detected!")
time.sleep(120.0)
print("Sleeping for 2 minutes")
else:
turn_off()
print("No Motion Detected!")
def send_button_data():
print(buttonVal)
# pirsenor thread using pirsensor function
t1 = threading.Thread(target=pirsensor)
t2 = threading.Thread(target=send_button_data())
class MySec(BoxLayout):
seconds_string = StringProperty('')
count = 0
def button_toggle(self):
self.count += 1
print(self.count)
buttonVal = self.count
class TestThreadApp(App):
print("Running Thread for PIR Sensor")
t1.start()
print("Running Thread to display current button value" )
t2.start()
def build(self):
Clock.schedule_interval(lambda dt: self.update_time(), 1)
return MySec()
def update_time(self):
self.root.seconds_string = datetime.now().strftime("%H:%M:%S")
TestThreadApp().run()

kivymd MDProgressBar UI not updating even with threading

I have the below code in test.py and test.kv.
The MDProgressBar UI does not move until end of the program. I've read similar questions in stackoverflow but the solutions posted were only snippets.
Assuming self.process_some_data() is a function that takes a long while to complete, the MDProgressBar UI instance self.progress_bar is supposed to reflect current progress. Based on solutions from other forum posts, I've
created a thread for self.process_some_data() function.
used Clock.schedule_once() for self.update_progress_bar() function which updates self.progress_bar.value in main thread at different stages of completion.
included decorator #mainthread for self.update_progress_bar() function which does not seem to make any difference.
Feel free to edit the code and repost the full solution. Thanks.
main.py
from kivymd.app import MDApp
from kivymd.uix.progressbar import MDProgressBar
from kivy.clock import Clock
from kivy.uix.popup import Popup
from kivymd.uix.dialog import MDDialog
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
import time
import threading
from kivy.clock import mainthread
from functools import partial
class Screen1(Screen):
def show_popup(self):
self.progress_bar = MDProgressBar()
self.popup = Popup(
title ='Progress',
content = self.progress_bar,
auto_dismiss = False, # dialog does NOT close if click outside it
size_hint = (None, None),
size = (400, 400)
)
self.popup.bind( on_open = lambda x: self.run_thread() )
# self.progress_bar.max = 100
self.progress_bar.value = 10
self.popup.open()
print(self.progress_bar.value)
#mainthread
def update_progress_bar(self, val, _):
self.progress_bar.value = val
print(self.progress_bar.value)
if val >= 100:
# self.popup.dismiss()
dialog = MDDialog(title="Status", text="Completed")
dialog.open()
def run_thread(self):
t1 = threading.Thread(target=self.process_some_data())
t1.start()
# t1.join()
def process_some_data(self):
time.sleep(1) # simulate program is running something
Clock.schedule_once(partial(self.update_progress_bar, 25), 0)
time.sleep(1) # simulate program is running something
Clock.schedule_once(partial(self.update_progress_bar, 50), 0)
time.sleep(1) # simulate program is running something
Clock.schedule_once(partial(self.update_progress_bar, 75), 0)
time.sleep(1) # simulate program is running something
Clock.schedule_once(partial(self.update_progress_bar, 100), 0)
# Create the App class
class MyApp(MDApp):
def build(self):
return Builder.load_file("test.kv")
# run the App
if __name__ in ("__main__"):
MyApp().run()
test.kv
Screen1:
Button:
text: "Run program"
on_release: root.show_popup()
size_hint: 0.4, 0.1
pos_hint: {"center_x": 0.5, "center_y": 0.5}
In your run_thread() method, the line:
t1 = threading.Thread(target=self.process_some_data())
runs the process_some_data() method, then sets the target of the created thread to the return of that method. I think you just need to remove the () from that method, so that the target is set to the process_some_data method and not its return:
t1 = threading.Thread(target=self.process_some_data)

Kivy accordion interaction

I have images loading in the kivy accordion and I want to print out the x and y coordinates below the images when I press the mouse button. I can't for the life of my figure this out. Every time I add anything it seems like it shuts the whole program down. I know that the widget I have will print to the console but I want to print it to the screen.
Here is the code I am using:
from kivy.uix.accordion import Accordion, AccordionItem
from kivy.uix.image import Image
from kivy.app import App
from kivy.uix.widget import Widget
class MouseWidget(Widget):
def on_touch_down(self, touch):
print(touch)
class MyApp(App):
def build(self):
root = Accordion(orientation='horizontal')
item= AccordionItem(title='Picture1')
src = "picture1.png"
image = Image(source=src,pos=(200, 100))
# add image to AccordionItem
item.add_widget(image)
root.add_widget(item)
item= AccordionItem(title='Picture2')
src = "picture2.png"
image = Image(source=src,pos=(200, 100))
# add image to AccordionItem
item.add_widget(image)
root.add_widget(item)
return root
if __name__ == '__main__':
MyApp().run()
Here's a simple modification to your program that adds the touch position to a label below the image, using kivy language to automatically bind to the necessary properties so that the display is updated when they change.
I'm not sure what problems you had in particular, so let me know if the way it works is not clear!
from kivy.uix.accordion import Accordion, AccordionItem
from kivy.uix.image import Image
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.properties import ObjectProperty, StringProperty
from kivy.uix.boxlayout import BoxLayout
Builder.load_string('''
<MouseWidget>:
image: image
label: label
orientation: 'vertical'
Image:
id: image
source: root.source
Label:
id: label
size_hint_y: None
height: 50
text: 'no touch detected'
''')
class MouseWidget(BoxLayout):
image = ObjectProperty()
label = ObjectProperty()
source = StringProperty()
def on_touch_down(self, touch):
if self.image.collide_point(*touch.pos):
self.label.text = str(touch.pos)
def on_touch_up(self, touch):
self.label.text = 'no touch detected'
class MyApp(App):
def build(self):
root = Accordion(orientation='horizontal')
item= AccordionItem(title='Picture1')
src = "picture1.png"
image = MouseWidget(source=src)
# add image to AccordionItem
item.add_widget(image)
root.add_widget(item)
item= AccordionItem(title='Picture2')
src = "picture2.png"
image = MouseWidget(source=src)
# add image to AccordionItem
item.add_widget(image)
root.add_widget(item)
return root
if __name__ == '__main__':
MyApp().run()
Every time I add anything it seems like it shuts the whole program down.
This sounds like your changes crashed the program. You should check the output of your program by running it in a terminal. It will print information about the error that can help you track down your mistakes.

Categories

Resources