1

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.
->Edit, I have removed the extra code that I don't believe is pertinent to my question.
->Edit #2:, I have still simplified the code to what I believe is bare. To still show the issue that I have. From what I have read, it may be the x.join code that is causing me the issue, and locking up the main loop. I need to run the while loop 5 times, but can only run 2 threads at a time. Need to hold the while loop in a non-blocking state while threads finish before proceeding.

Here is menu.kv

<ScreenManagement>:
    MenuScreen:
    ProgramScreen:


<MenuScreen>:
    name: 'menu'
    AnchorLayout:
        GridLayout:
            cols: 2
            Button
                text: "Start Application"
                on_release: root.routine()

<ProgramScreen>:
    filler1: filler1
    filler2: filler2
    filler3: filler3
    filler4: filler4
    name: 'program'
    GridLayout:
        cols: 4
        Button:
            id: filler1
            text: 'Filler 1'
            halign: 'center'
            padding_y: '300'
            bcolor: 1,0,1,1
        Button:
            id: filler2
            text: 'Filler 2'
            halign: 'center'
            padding_y: '300'
            bcolor: 1,0,0,1
        Button:
            id: filler3
            text: 'Filler 3'
            halign: 'center'
            padding_y: '300'
            bcolor: 1,0,1,0
        Button:
            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
import threading
import time


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

class MenuScreen(Screen):
    def routine(self):
        self.parent.current = 'program' 
        threading.Thread(target=ProgramScreen().build).start()

class ProgramScreen(Screen):

    @mainthread
    def update_label(self, int_counter, new_text):
        if int_counter == 0 :
            print "here ",  int_counter, 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):
                self.update_label(int(value),str(i))
                print value, i 
                time.sleep(1)
        print 'func', value, 'finishing'

    def build(self):
        NumberofFiller = 2
        NumberofCells = 5
        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

                for x in threads:
                    #Problem I believe is here.
                    x.join()
                    #Need a way to pause the While loop for the first set of threads to finish before starting the next threads.
#                print (threads)
            except (KeyboardInterrupt, SystemExit):
                functions.cleanAndExit() 


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

#----------------------------------------------------------------------
if __name__ == "__main__":
    Menu_red2App().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.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
user1086924
  • 33
  • 2
  • 7

1 Answers1

1

The problem is that when you start the outer thread in your routine method, you set your target to ProgramScreen().build, which will in fact create a new screen. So it will not affect the screen you have in your screenmanager.
To set the target to the build method of the ProgramScreen you got in the screenmanager, you can get that screen by its name.
You allready gave it the name program.
So your MenuScreen class should look like this:

class MenuScreen(Screen):
    def routine(self):
        self.parent.current = 'program' 
        threading.Thread(target=self.manager.get_screen('program').build).start()
el3ien
  • 5,362
  • 1
  • 17
  • 33
  • That is what I thought I did. Main program creates a thread `threading.Thread(target=ProgramScreen().build, args=(NumberofFiller , NumberofCells,)).start()`, Which should be independant of the main program. Then this calls separate threads. I believe that the issue is that I "join" the threads (I don't want to proceed till all sub-threads have completed). But, I can't see how this would block the main code. Nothing is connected to Raspberry Pi at the moment. – user1086924 Apr 17 '17 at 23:40
  • @user1086924 if you could cut your code down to a minimal example that is "copy paste / runnable" like mine, and still reproduce the problem, it would be easy to help. And maybe you will even see the problem yourself at that point. Just keep removing stuff untill you only have the threading issue left. – el3ien Apr 17 '17 at 23:47
  • I have reduced the code down some more. I have a feeling that it may be the `x.join` that is blocking. I need to find a way to count to 5 but only start 2 threads at a time. What ever loop is used to count to 5 can not be blocking the main code.. – user1086924 Apr 18 '17 at 11:55
  • @user1086924 have you tried what I suggested in my answer? – el3ien Apr 18 '17 at 15:47
  • I ran home at lunch to give it a try. Your suggestion worked. I applied it to the reduced code (posted above), and my full code and both work now as expected. This answer is closed. – user1086924 Apr 18 '17 at 16:52
  • @user1086924 nice – el3ien Apr 18 '17 at 16:58