kivymd MDProgressBar UI not updating even with threading - python

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)

Related

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()

python- Kivy Screen Manager within .py file

I am building a multiple screen App with Kivy and I would like to use the ScreenManager to navigate between the multiple screens. I have seen examples and documentation for how to create the screens within a .kv file, but I want to know how to create them within the .py file.
Problem: When I create the screen subclasses as shown below, my app
window returns a blank screen.
Question: What is the correct way to
create the Screen subclasses within a .py file?
Right now I have two Screen subclasses defined: 'welcomeScreen', and 'functionScreen'. Each consists of a layout with some widgets.
kivy.require('1.9.1')
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
import kivy.uix.boxlayout
import kivy.uix.button
from kivy.uix.screenmanager import ScreenManager, Screen
class PanelBuilderApp(App): # display the welcome screen
def build(self):
# Create the screen manager and add widgets to the base sm widget
sm = kivy.uix.screenmanager.ScreenManager()
sm.add_widget(Screen(name='welcomeScreen'))
sm.add_widget(Screen(name='functionScreen'))
# sm.current= 'welcomeScreen'
return sm
class welcomeScreen(Screen): #welcomeScreen subclass
def __init__(self, **kwargs): #constructor method
super(welcomeScreen, self).__init__(**kwargs) #init parent
welcomePage = FloatLayout()
box = kivy.uix.boxlayout.BoxLayout(orientation='vertical', size_hint=(0.4, 0.3),
padding=8, pos_hint={'top': 0.5, 'center_x': 0.5})
welcomeLabel = Label(text='Hello and welcome to the Panel Builder version 1.0.\nApp by John Vorsten\nClick below to continue',
halign= 'center', valign= 'center', size_hint= (0.4, 0.2), pos_hint= {'top': 1, 'center_x': 0.5})
welcomeBox = kivy.uix.button.Button(text= 'Click to continue')
welcomeBox.bind(on_press= self.callback)
welcomeBox2 = kivy.uix.button.Button(text='not used')
welcomePage.add_widget(welcomeLabel)
box.add_widget(welcomeBox)
box.add_widget(welcomeBox2)
welcomePage.add_widget(box)
self.add_widget(welcomePage)
def callback(instance):
print('The button has been pressed')
sm.switch_to(Screen(name= 'functionScreen'))
# sm.current = Screen(name= 'functionScreen')
class functionScreen(Screen): #For later function navigation
def __init__(self, **kwargs): #constructor method
super(functionScreen, self).__init__(**kwargs) #init parent
functionPage = kivy.uix.floatlayout.FloatLayout()
functionLabel = Label(text='Welcome to the function page. Here you will choose what functions to use',
halign='center', valign='center', size_hint=(0.4,0.2), pox_hint={'top': 1, 'center_x': 0.5})
functionPage.add_widget(functionLabel)
self.add_widget(functionPage)
# sm.add_widget('Name') #Add more names later when you create more screens
# OR#
# for i in ScreenDirectory:
# sm.add_widget(ScreenDirectory[i])
PanelBuilderApp().run()
if __name__ == '__main__':
pass
I understand I can add the definitions to a .kv file, and I will probably do this as the app grows. However, I like being explicit as I am learning how to use kivy.
I think you think using Screen(name='welcomeScreen') you are using welcomeScreen but that is not true, if you want to use welcomeScreen you should use it directly.
On the other hand you have typographical errors so I have corrected, I recommend you follow the kivy tutorials, obviously you must have a solid OOP base (and I think you do not have it so your task is to reinforce).
import kivy
kivy.require('1.9.1')
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
class PanelBuilderApp(App): # display the welcome screen
def build(self):
sm = ScreenManager()
sm.add_widget(WelcomeScreen(name='welcomeScreen'))
sm.add_widget(FunctionScreen(name='functionScreen'))
return sm
class WelcomeScreen(Screen): #welcomeScreen subclass
def __init__(self, **kwargs): #constructor method
super(WelcomeScreen, self).__init__(**kwargs) #init parent
welcomePage = FloatLayout()
box = BoxLayout(orientation='vertical', size_hint=(0.4, 0.3),
padding=8, pos_hint={'top': 0.5, 'center_x': 0.5})
welcomeLabel = Label(text='Hello and welcome to the Panel Builder version 1.0.\nApp by John Vorsten\nClick below to continue',
halign= 'center', valign= 'center', size_hint= (0.4, 0.2), pos_hint= {'top': 1, 'center_x': 0.5})
welcomeBox = Button(text= 'Click to continue', on_press=self.callback)
welcomeBox2 = Button(text='not used')
welcomePage.add_widget(welcomeLabel)
box.add_widget(welcomeBox)
box.add_widget(welcomeBox2)
welcomePage.add_widget(box)
self.add_widget(welcomePage)
def callback(self, instance):
print('The button has been pressed')
self.manager.current = 'functionScreen'
class FunctionScreen(Screen): #For later function navigation
def __init__(self, **kwargs): #constructor method
super(FunctionScreen, self).__init__(**kwargs) #init parent
functionPage = FloatLayout()
functionLabel = Label(text='Welcome to the function page. Here you will choose what functions to use',
halign='center', valign='center', size_hint=(0.4,0.2), pos_hint={'top': 1, 'center_x': 0.5})
functionPage.add_widget(functionLabel)
self.add_widget(functionPage)
if __name__ == '__main__':
PanelBuilderApp().run()

Kivy + Python + Raspberry - How to implement countdown

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

Issue with kivy Clock updating label

I'm making a label that should update every second or so. I tried making it with the clock schedule but it doesn't seem to work. The weird is if I use a button to call the same function it works fine.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from kivy.clock import Clock
class FirstLayout(BoxLayout):
r = 0
def __init__(self, **kwargs):
super(FirstLayout, self).__init__(**kwargs)
self.change = self.ids.temp_label
def my_callback(self, *args):
self.r += 1
print self.r
t = str(self.r)
self.change.text = t
class TryApp(App):
def build(self):
Clock.schedule_interval(FirstLayout().my_callback, 1)
return FirstLayout()
app = TryApp()
app.run()
the .kv file:
<FirstLayout>:
orientation: 'vertical'
Label:
id: temp_label
text: 'something'
Button:
on_press: root.my_callback()
When I run the code I get the prints showing that the function is running but the label doesn't update. Anything wrong with my logic there?
Thank you in advance.
PS: I know there are several questions about this here, sadly those I found were replied with answers about using the Clock which I already do
The problem is that the callback is for an instance that you don't use:
def build(self):
Clock.schedule_interval(FirstLayout().my_callback, 1) #<--- FirstLayout created and never used
return FirstLayout() #this one will be used :(
Instead, You need to call the method of the FirstLayout that you are using
def build(self):
first_layout = FirstLayout() # "There should be one ..." :)
Clock.schedule_interval(first_layout.my_callback, 1)
return first_layout

Kivy Python very basic Binding of Label Text

I know this might be a very basic question, but after spending hours wrapping my head around it I still can't figure it out.
I basically just want to bind the text of a label to a variable in the python code. lets call it value. however it should get updated everytime I run a loop Clock.schedule_interval(RootWidget.update, 1.0/1.0)
here is the python, simplified so its basically just the time, which is also printed just to see if it is actually working.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.core.window import Window
import time
class RootWidget(FloatLayout):
def update(self, *args):
value = time.time()
print value
self.ids.value_label.text = str(value)
class MainApp(App):
def build(self):
Window.size = (800, 480)
r = RootWidget()
Clock.schedule_interval(r.update, 1)
print 'build running'
return r
def on_pause(self):
return True
if __name__ == '__main__':
MainApp().run()
the kv file looks as such:
<RootWidget>:
Label:
id: value_label
text:
y: 20.0
x: 0.0
width: 100.0
italic: False
height: 50.0
Clock.schedule_interval(RootWidget.update, 1.0/1.0)
You need to schedule the update method of an instance, not the class itself.
For instance:
r = RootWidget()
Clock.schedule_interval(r.update, 1)
return r
The clock will pass some arguments by default, so you should also declare the update method to accept these. If you don't want to use them then you can just do:
def update(self, *args):
...

Categories

Resources