I do have an interface to store settings and to start a process. However, I cannot close everything once the process starts because kivy gets stuck after calling process.run. Here is a minimal example:
#! /usr/bin/env python
"""
Activate the touch keyboard. It is important that this part is on top
because the global config should be initiated first.
"""
from kivy.config import Config
Config.set('kivy', 'keyboard_mode', 'multi')
# the main app
from kivy.app import App
# The Builder is used to define the main interface.
from kivy.lang import Builder
# The start screen is defined as BoxLayout
from kivy.uix.boxlayout import BoxLayout
# The pHBot class
from phbot import pHBot
# The pHBot is defined as process. Otherwise it would not be possible to use the close button.
from multiprocessing import Process, Queue
# Definition of the Main interface
Builder.load_string('''
<Interface>:
orientation: 'vertical'
Button:
text: 'Run pH-Bot'
font_size: 40
on_release: app.run_worker()
Button:
text: 'Close pH-Bot'
font_size: 40
on_release: app.stop_phbot()
''')
# Interface as a subclass of BoxLayout without any further changes. This part is used by kivy.
class Interface(BoxLayout):
pass
class SettingsApp(App):
"""
The settings App is the main app of the pHBot application. It is initiated by kivy and contains the functions
defining the main interface.
"""
def build(self):
"""
This function initializes the app interface and has to be called "build(self)". It returns the user interface
defined by the Builder.
"""
# A queque for the control all processes.
self.qu_stop = Queue()
# returns the user interface defined by the Builder
return Interface()
def run_worker(self):
"""
The pHBot application is started as a second process.
"""
bot = pHBot(self.qu_stop)
phbot = Process(target=bot.ph_control())
# start the process
phbot.run()
def stop_phbot(self):
self.qu_stop.put('STOP')
if __name__ == '__main__':
SettingsApp().run()
The second class is within a file called phbot.py:
import time
class pHBot:
def __init__(self, qu_stop_in):
self.qu_stop_in = qu_stop_in
def ph_control(self):
while True:
if self.qu_stop_in.full():
if self.qu_stop_in.get() == 'STOP':
break
print('Back and forth forever ...')
time.sleep(2)
What am I missing here ?
Note that a Process is started with start(). Calling run() really immediately launches the worker from the same process, and thus it is blocking. The relevant lines in run_worker should therefore be:
bot = pHBot(self.qu_stop)
phbot = Process(target=bot.ph_control)
# start the process
phbot.start()
In addition, in your worker, don't check whether the Queue is full. Rather, do a non-blocking get and handle the Queue.Empty exception:
import Queue
...
def ph_control(self):
while True:
try:
item = self.qu_stop_in.get(False)
if item == 'STOP':
break
except Queue.Empty:
print "Nothing to see"
print('Back and forth forever ...')
time.sleep(2)
phbot = Process(target=bot.ph_control())
You are calling bot.ph_control - that's what the () syntax does. I'd guess you need to pass the function itself (Process(target=bot.ph_control))), but I'm not familiar with multiprocessing.
Related
I'm trying to change the screen after running this function which is a thread. but it gives me the error
TypeError: Cannot create graphics instruction outside the main Kivy thread
I researched online regarding this and they told me to use clock property but I don't know how I can use it here. I just want to be able to change graphic instructions from this command. but if I don't thread it the app goes into not responding.
This is my code:
class Notice(Screen):
def thread_notice(self):
t1 = Thread(target= Notice.sendNotice, args =(self,))
t1.daemon = True
t1.start()
def sendNotice(self):
my_big_chunk_of_code()
kv.current = 'menu'
You cannot do any kivy instructions outside of the main thread, which means you cant run sendNotice on a diffrent thread becuase it has kivy code in it... if my_big_chunk_of_code() takes time then remove the kv.current = 'menu' line and move it to, for example the thread_notice function.
By the way in the thread I suggest making the target: self.sendNotice instead of Notice.sendNotice which i think creates another instance of that class (not sure tho), it should work the same
I am late but well I am posting this answer so that It will help other in future , So Because you can not create graphics instruction outside the main Kivy thread, so you have to be bit tricky about it, I was facing the same problem while ago, So here is how I solve it, So you should first create a popup before starting a thread and than start your thread and if you want to pass argument in your function inside thread, you should use a lambda function like this.
self.showLoadingPopUp() # showing pop up before starting thread
try:
# Using lambda function for passing arguments in function inside thread
threading.Thread(target=lambda : self.yourFunction(argument)).start()
except:
print("Error: unable to start thread")
This will not block your UI and when you create popup function make sure to use a class variable for that, So you can able to close that popup from your thread function, here is the exaple -
class MyApp(App):
def __init__(self):
super().__init__()
self.window = None
self.loading = None
# Main Build function
def build(self):
self.window = GridLayout()
self.window.cols = 2
return self.window
def showLoadingPopUp(self):
content = BoxLayout(orientation='vertical')
self.loading = Popup(title="Loading", title_color='#FFFFFF', content=content, size=('700dp', '200dp'),
size_hint=(None, None),
auto_dismiss=False,
background='#FFFFFF')
content.add_widget(Label(text="Loading...", color="#FFFFFF"))
def _on_d(*args):
self.loading.is_visable = False
self.loading.bind(on_dismiss=_on_d)
self.loading.is_visable = True
self.loading.open()
while not self.loading.is_visable:
EventLoop.idle()
def yourFunction(self, argument):
for i in list:
# code
self.loading.dismiss() # dismissing old popup
So In short this function will first create a popup and than it will start a thread and when our work is done the thread will just close the popup with class variable in the end. Hope it's help.
This simple program doesn't execute correctly, locking in the message "Read message" after press the button. I think I'm not wrong in multiprocessing code because I can run this code without kivy with no problems, but if I simply add same import modules from kivy, the process stops returning any value. In fact my tests show the process even start to run.
I did a very simple code to show the problem. You can simply copy and run. When I press the button test the program locks immediately and the main thread does't receive the message from process.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from multiprocessing import Process, Queue, Event
kv = '''
<InterfaceView>:
Button:
text: 'teste'
on_press: root.do_process()
'''
def pprosess(message, q, stop):
q.put(message)
stop.wait()
Builder.load_string(kv)
class InterfaceView(BoxLayout):
def do_process(self):
q = Queue()
stop = Event()
p = Process(target=pprosess, args=('any message', q, stop))
p.daemon = True
p.start()
print('Read message')
print('message: ', q.get())
stop.set()
p.join()
print('Process closed')
class SimpleApp(App):
def build(self):
return InterfaceView()
if __name__ == '__main__':
SimpleApp().run()
The code below executes very well the same thing without kivy, believe me, or not. Here you can observe the correct terminal output.
from multiprocessing import Process, Queue, Event
def pprosess(message, q, stop):
q.put(message)
stop.wait()
def main():
q = Queue()
stop = Event()
p = Process(target=pprosess, args=('any message', q, stop))
p.daemon = True
p.start()
print('Read message')
print('message: ', q.get())
stop.set()
p.join()
print('Process closed')
if __name__ == '__main__':
main()
I'm very frustrated struggling with this for days long. Please, somebody help me.
The stop.wait() event is causing the problem. It is equivalent to time.sleep(). Remove stop should fix the problem.
Programming Guide » Events and Properties » Main loop
In Kivy applications, you have to avoid long/infinite loops or sleeping.
Example
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from multiprocessing import Process, Queue, Event
kv = '''
<InterfaceView>:
Button:
text: 'teste'
on_press: root.do_process()
'''
def pprosess(message, q):
q.put(message)
Builder.load_string(kv)
class InterfaceView(BoxLayout):
def do_process(self):
q = Queue()
p = Process(target=pprosess, args=('any message', q))
p.daemon = True
p.start()
print('Read message')
print('message: ', q.get())
p.join()
print('Process closed')
class SimpleApp(App):
def build(self):
return InterfaceView()
if __name__ == '__main__':
SimpleApp().run()
Output
My problem, in fact, was with Spyder Development Environment and/or some of its components.
I was using the current IPython console to run my code, when I change to execute in an external terminal the problem was solved.
Thanks #embryo and specially #ikolim for helping. Their tests and suggestion lead me to the problem.
So, if you use Spyder Development Environment, attention to bad interaction effects between IPython console, python multiprocessing module and kivy.
All codes I posted are correct with no problems.
Following Matt's answer from this Question I am trying to use
clock.schedule in another process in order to alter my ListProperty called "data_items" but still I get the error "global name 'set_property' is not defined".
beedo.py:
class MyW(Widget):
data_items = ListProperty([])
def set_property(camera, *largs):
self.data_items.append(camera)
print(data_items)
return
....
class beedoApp(App):
def build(self):
return MyW()
....
def do_list(camera):
Clock.schedule_once(partial(set_property, camera))
print "do list startet.."
def socket_server()
....
def event_handler
....
list1 = Process (target=do_list, args=(camera))
list1.start()
if __name__ == "__main__":
server1 = Process (target=socket_server, args=(....)
server2 = Process (target=socket_server, args=(....)
handler = Process (target=event_handler, args=(....)
server1.start()
server2.start()
handler.start()
beedoApp().run()
server1.join()
server2.join()
handler.join()
In the file beedo.kv I defined a SimpleListAdapter:
ListView:
adapter:
sla.SimpleListAdapter(data=root.data_items, cls=ListItemButton)
The screen of the alarm device looks like this:
Screen
I have uploaded the complete code here:
beedo.py
https://www.dropbox.com/s/pyqpfefkia6lj0r/beedo.py?dl=0
Please, if you can help I would be really thankful !! regards, Pipe007
I have a script which has a record and stop button, the record button does an infinite loop, but it also blocks the other button (stop button). All I wanted to build is a process which starts at click of record button and stops are click of stop button. Here is the script:
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# This file is in the public domain
### END LICENSE
from locale import gettext as _
from gi.repository import Gtk # pylint: disable=E0611
import logging
logger = logging.getLogger('recordme')
from recordme_lib import Window
from recordme.AboutRecordmeDialog import AboutRecordmeDialog
from recordme.PreferencesRecordmeDialog import PreferencesRecordmeDialog
class RecordmeWindow(Window):
__gtype_name__ = "RecordmeWindow"
record = False
def finish_initializing(self, builder): # pylint: disable=E1002
"""Set up the main window"""
super(RecordmeWindow, self).finish_initializing(builder)
self.AboutDialog = AboutRecordmeDialog
self.PreferencesDialog = PreferencesRecordmeDialog
# Code for other initialization actions should be added here.
self.button1 = self.builder.get_object('button1')
self.button2 = self.builder.get_object('button2')
def on_button1_clicked(self, widget):
while(not self.record):
print 'button1 clicked'
while gtk.events_pending():
gtk.main_iteration(False)
Any ideas about this problem ?
I encountered similar programs in WX, which is also event based. The best (and possibly only) way I found to solve the problem is to create a function that runs on a timer during the main loop. Mine ran periodically, but you could also just set it to wait and close the loop when you run your function. In GTK, you have to do this with another module, "gobject". Here is an example of a method that runs periodically in GTK.
import gobject
class gtk_object(object):
def __init__(self):
gobject.timeout_add(100, self.my_function)
def my_function(self):
#do something here, like stopping the loop or having a timer to stop the loop
return True
Assuming your record functionality is cpu-intensive and/or may block and/or needs soft realtime assurance, I would recommend moving it off to a separate "worker" thread.
Then, create a window and your buttons.
Here, when "record" is clicked, I signal the worker to start recording; when "stop" is clicked, signal worker to stop; Optionally, when stop is clicked, terminate the main loop if you want your app to exit.
Additional control logic to terminate the app when window is closed and terminate the worker thread correctly is at the very bottom.
#!/usr/bin/env python
import time
import logging
import threading
from gi.repository import Gtk
class Worker(threading.Thread):
should_record = False
quit = False
def run(self):
while not self.quit:
if self.should_record:
logging.warn("recording...")
# cpu-intensive code here
else:
time.sleep(0.1)
class MainWindow(Gtk.Window):
def __init__(self):
super(MainWindow, self).__init__()
self.worker = Worker()
self.worker.start()
hb = Gtk.Box()
self.add(hb)
record = Gtk.Button("Record")
stop = Gtk.Button("Stop")
hb.add(record)
hb.add(stop)
def command(arg):
self.worker.should_record = arg
record.connect("clicked", lambda _b: command(True))
stop.connect("clicked", lambda _b: command(False))
# optional, if you want to quit the app on stop as well
stop.connect("clicked", lambda _b: Gtk.main_quit())
if __name__ == "__main__":
main = MainWindow()
try:
# optional, if you want to support close window to quit app
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
finally:
main.worker.quit = True
main.worker.join()
old stuff
Ideally you wan to use Gtk.main() instead of Gtk.main_iteration() in Gtk+ 3.
In Gtk+ 2, module name was gtk rather than gi.repository.Gtk.
Then you can quit wit with:
Gtk.main_quit
def main_quit()
The Gtk.main_quit() function terminates the current main loop level
started by the most recent call to the Gtk.main() function. The
nesting level of the main loop is reduced by calling this function.
You can have several nested main loops, in which case, you'd have to quit each of those.
Alternatively you can also use gtk_dialog.run() then default action for a button is to exit the loop.
GTK+ (as most UI toolkits) is event-based. That means it runs internal "event loop" - a loop that collects and processes events, such as handling user input and redrawing windows. All event handlers are dispatched from main loop. In order to process events, loop must be "spinning".
In your example, you are blocking main loop:
def on_button1_clicked(self, widget):
while(not self.record):
print 'button1 clicked'
as long as this function does not finish, control does not return to main loop so it cannot process other events, or redraw windows.
You can add this snippet form PyGTK FAQ in order to allow main loop to process event in the meantime:
while gtk.events_pending():
gtk.main_iteration(False)
I've been trying to perform communication between a kivy GUI module (my server) and another python module (my client). But so far I have problems running the xml rpc server along with the GUI run() function. I still have that problem even after running my server in a thread.
I hope someone has suggestions on how to fix my code, or just how to xml-rpc along with kivy.
Here is my code:
import kivy
kivy.require('1.7.1')
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.app import App
from threading import Thread
from kivy.clock import Clock
Builder.load_file('kivy_gui.kv')
class RoamClientInterface(GridLayout):
"""
Sets up connection with XMLRPC server
"""
move = False
"""
driveForward() -> Moves robot forward
"""
def driveForward(self):
self.move = True
"""
stop() -> stops robot from moving
"""
def stop(self):
self.move = False
def returnBool(self):
return self.move
class ClientInterface(App):
def build(self):
return RoamClientInterface()
def sendCommands(dt):
print "start"
print ""
from SimpleXMLRPCServer import SimpleXMLRPCServer
server = SimpleXMLRPCServer(("localhost", 5000))
print "initialize server"
print ""
server.register_instance(RoamClientInterface())
print "register instance"
print ""
# while True:
try:
print "try handle request"
print ""
server.handle_request()
print "print handle request"
print ""
except KeyboardInterrupt:
import sys
sys.exit()
if __name__ == '__main__':
serverThread = Thread(target=sendCommands(4))
serverThread.start()
# Clock.schedule_once(sendCommands)
ClientInterface().run()
I got to solve the problem.
It is actually necessary to put it into a inside the RoamClientInterface to make it work, instead of putting it into my main function like I have it above.
I can give more detail info (show code) if anybody needs help