Kivy multi-threading and update screen

3,215 views
Skip to first unread message

Philippe Faure

unread,
Apr 17, 2017, 6:14:28 PM4/17/17
to Kivy users support
0 down vote favorite

I am wrote a piece of python code that handles multiple processes and it works great, but I am trying to add a Kivy based display to this code. I have updated the code to be able to use threads instead of processes, which works. The issue that I have is that I can't seem to get the threads to write back to the screen. I am using @mainthread. I found a sample code here, and was able to get the threads to update the screen, but for some reason my code doesn't seem to work.


Here is menu.kv

#:import functions functions
#:import labelB labelB

<UpdateButton@LabelB>:
    font_size: '36sp'


<ScreenManagement>:
    MenuScreen:
    RunScreen:
    ProgramScreen:


<MenuScreen>:
    name: 'menu'
    AnchorLayout:
        anchor_x: 'center'
        anchor_y: 'center'
        GridLayout:
            cols: 2
#            orientation: 'horizontal'
            padding: 20
            spacing: 10
            Button
                text: "Start Application"
                on_release: root.manager.current = 'run'

<RunScreen>:
    name: 'run'
    BoxLayout:
        orientation: 'vertical'
        GridLayout:
            cols: 2
            orientation: 'horizontal'
            padding: 20
            spacing: 10
            Label:
                text: "How many fillers are to be used [1-4]? "
            TextInput:
                text: '1'
                id: NumberofFillers
                input_type: 'number'
#                on_text_validate: functions.RepresentsFloat(
            Label:
                text: "What is the blanket weight (Lbs)? "
                halign: 'right'
            TextInput:
                text: '12'
                id: BlanketWeight
            Label:
                text: "What is the Material weight (grams) ? "
                halign: 'right'
            TextInput:
                text: '819'
                id: MaterialWeight
            Label:
                text: "How many Columns are there? "
                halign: 'right'
            TextInput:
                text: '4'
                id: NumberofCols
            Label:
                text: "How many rows are there? "
                halign: 'right'
            TextInput:
                text: '1'
                id: NumberofRows
        BoxLayout:
            size: 1, .1
            size_hint: 1, .15
            Button
                text: "Start"
                on_release: root.routine()
            Button
                text: "Return"
                on_release: root.manager.current = 'menu'


<ProgramScreen>:
    numberofcells: numberofcells
    cellweight: cellweight
    filler1: filler1
    filler2: filler2
    filler3: filler3
    filler4: filler4
    name: 'program'
    BoxLayout:
        orientation: 'vertical'
        GridLayout:
            size_hint: 1, .20
            cols: 2
            Label:
                text: "Total Number of Cells"
                halign: 'center'
            Label:
                text: "Target weight for each cup"
                halign: 'center'
            Label:
                id: numberofcells
                text: 'Start_C'
                halign: 'center'
            Label:
                id: cellweight
                text: 'Start_W'
                halign: 'center'

        GridLayout:
            cols: 4
            UpdateButton:
                id: filler1
                text: 'Filler 1'
                halign: 'center'
                padding_y: '300'
                bcolor: 1,0,1,1
            UpdateButton:
                id: filler2
                text: 'Filler 2'
                halign: 'center'
                padding_y: '300'
                bcolor: 1,0,0,1
            UpdateButton:
                id: filler3
                text: 'Filler 3'
                halign: 'center'
                padding_y: '300'
                bcolor: 1,0,1,0
            UpdateButton:
                id: filler4
                text: 'Filler 4'
                halign: 'center'
                padding_y: '40 '
                bcolor: 0,0,1,1

Here is my main.py


from kivy.app import App
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.lang import Builder
from kivy.properties import *
from kivy.core.window import Window
from kivy.clock import Clock, mainthread
from functools import partial

import time
import numpy  # sudo apt-get python-numpy
import threading
import labelB

import config


########################################################################
class ScreenManagement(ScreenManager):
    pass

class MenuScreen(Screen):
    pass    

class RunScreen(Screen):

    def routine(self):
        NumberofFiller = int(self.ids.NumberofFillers.text)
        BlanketWeight = int(self.ids.BlanketWeight.text)
        MaterialWeight =int(self.ids.MaterialWeight.text)
        NumberofRows = int(self.ids.NumberofRows.text)
        NumberofCols = int(self.ids.NumberofCols.text)
        NumberofCells = NumberofRows * NumberofCols 
        config.CellWeight = round((( ( float(BlanketWeight)*453.592) - MaterialWeight) / NumberofCells),9) 
        print ("Each of the %d cells will get %.3fg of pellets" % (NumberofCells,config.CellWeight) )
        Number = self.manager.get_screen('program').numberofcells
        Number.text = str(NumberofCells)
        Weight = self.manager.get_screen('program').cellweight
        Weight.text = str(round(config.CellWeight,4))
        self.parent.current = 'program' 
#        time.sleep(1)
        threading.Thread(target=ProgramScreen().build, args=(NumberofFiller , NumberofCells,)).start()
#       ProgramScreen().build(NumberofFiller , NumberofCells)

class ProgramScreen(Screen):

    @mainthread
    def update_label(self, int_counter, new_text):
#    def update_label(self, int_counter, new_text, *largs):
        if int_counter == 0 :
            print "here ",  int_counter, new_text
#            fill = self.manager.get_screen('program').filler1
#            fill.text = new_text
            self.filler1.text = new_text
        elif int_counter == 1 :
            self.filler2.text = new_text
        elif int_counter == 2 :
            self.filler3.text = new_text
        elif int_counter == 3 :
            self.filler4.text = new_text
        else:
            self.filler1.text = "fault"

#dummy function to be replaced with a function that will call GPIO for input and feedback.
    def func (self,value):
        print 'func', value, 'starting'
        for i in xrange(10*(value+1)): 
            if ((i%3) == 0):
                Clock.schedule_once(lambda dt: self.update_label( int(value), str(i)), 0)
#               Clock.schedule_once(self.update_label:
#               self.update_label(int(value),str(i))
                print value, i 
                time.sleep(1)

        print 'func', value, 'finishing'

    def build(self, NumberofFiller, NumberofCells):
        CellCounter = 0
        while CellCounter < NumberofCells:
            try:
                threads = []
                print ('Counter:',CellCounter,'Fillers:',NumberofFiller,'Cells:',NumberofCells)     
                for i in range (NumberofFiller):
                    t = threading.Thread(target=self.func, args=((CellCounter%NumberofFiller),))
                    t.start()
                    threads.append(t)
                    CellCounter = CellCounter +1
                    if (CellCounter >= NumberofCells):
                        break
                for x in threads:
                    x.join()
#                print (threads)
            except (KeyboardInterrupt, SystemExit):
                functions.cleanAndExit() 
"""
        #to go back to main menu when program has finished running.
        self.parent.current = 'menu'
"""

########################################################################
#Builder.load_file("Menu_red.kv") #Not needed
class Menu_redApp(App):
    def build(self):
        return ScreenManagement()

#----------------------------------------------------------------------
if __name__ == "__main__":
    Menu_redApp().run()


The only way that I could find to have the screen update, after executing self.parent.current = 'program' was to run the rest of the code as a thread. But I now I can't seem to get the threads to write back to the main function to update the screen. In the end, once the text has been updated, I will need to change the color of the boxes, but that will come in due course.


Thought/comments/suggestions would be greatly appreciated. I noticed that there isn't a lot on this subject from a Python kivy point of view. I have attached the two files to this email.


thank you

main_red.py
menu_red.kv

Rufus Smith

unread,
Sep 2, 2017, 1:47:53 AM9/2/17
to Kivy users support
You're missing a few files here, mainly labelB and config modules.
I can't run your example.


Philippe Faure

unread,
Nov 26, 2017, 10:39:25 PM11/26/17
to Kivy users support
Sorry, it has taken me so long to get back to you.

I managed to get the thread issues working, but then I found that the threads (up to 4) were all running on 1 core (not 4). So I am believe that I need to change over to use processes instead of threading (around line 582). The issue now is that I need to find a way for each process to update the original screen.  I was using the @mainthread option for threads, but I don't believe that that is going to work.

the other option would be to use threads, but make them run on multiple cores, which I haven't been able to find.

any recommendations would be greatly appreciated.

Thank you
config.py
main_thread.py
fileprocessing.py
functions.py
hx711.py
labelB.py
logger.py
main_process.py
menu.kv
numeric_keys.json
Reply all
Reply to author
Forward
0 new messages