Google Analytics

Wednesday, May 7, 2014

Learning Kivy - Part 3

Prev

Table of Contents


  • Learning Kivy - Part 1
  • Learning Kivy - Part 2
  • Learning Kivy - Part 3
  • Learning Kivy - Part 4
  • Learning Kivy - Part 5
  • Learning Kivy - Part 6



  • This part is going to work through some of the issues of working with a Kivy GUI while communicating with networked resources.  Dusty's articles have a lot of insight on obtaining weather information from a URL, but this isn't what I'm doing.  I'm having to communicate with Python sockets.  So, this part is going to work through some of the issues I'm encountering, using just simple screens to work with so the 'Learning' part of this isn't overwhelming!

    For the Home Security/Automation project I'm working on, I have a distributed design: 
    • The Sensor portion is running on a Beaglebone Black (BBB), which is connected to all of the wired sensors, and to the local network.
    • The Controller portion, is running on one of the computers here, which is also connected to the local network.
    • The GUI portion, will be running on many of the computers here, which are also connected to the local network.
    • Future - GUI external to the local network.
    Here's a text diagram:


    Sensor     <========>Controller<=======>GUI
    On BBB                         computer                    computer

    • The Sensor sends/receives messages with the Controller.
    • The GUI sends/receives messages with the Controller.
    • The Sensor detects, and sends info to the Controller.  It also responds to the Controller on queries from the Controller.
    • The GUI displays as directed by the Controller.  It also responds to the Controller on queries from the Controller.
    • The Controller is directing everything; it has all of the program logic in it.
    Since these processes are running on different computers, they are talking via the local network, which means using Python Sockets.

    I've struggled with implementing the GUI and having it communicate with the Controller.

    Here's what I'm currently (subject to change!) understanding about communications between the Controller and the GUI:
    1. The GUI code needs to be in a thread.  Per some insight from web pages I've come across, this needs to be the main thread.  I'm taking this to mean not in a thread I'm creating via a Thread call.
    2. The Socket code needs to be in a separate thread.
    3. Once I make a call to the run method of the class that extends App (e.g., in the previous examples, the Simple(App) class), the GUI doesn't return until after the GUI window is closed.
    I'm going to write a small test, based on our Simple.py and simple.kv classes, that will test item 3.

    The code for simple.kv stays the same (see previous articles if you need the listing).

    The code for Simple.py follows:

    from kivy.app import App
    from kivy.uix.boxlayout import BoxLayout

    class Box_a(BoxLayout):    
        print("Box_a entry")
    class Simple(App):
        def build(self):
            print("inside build")        
            return  Box_a()            

    def main():    
        print("===========BEFORE CALL TO SIMPLE.RUN=======")    
        b = Simple()    
        b.run()

        print("===========AFTER CALL TO SIMPLE.RUN=======")        
        return 0

    if __name__ == '__main_':
        main()


    The output from running this shows a black GUI with the words "Hello World from inside simple.kv, via Box_a".  The terminal shows the text printed
    "Box_a entry", "===BEFORE CALL TO SIMPLE.RUN===", and "inside build".

    It does not have "===AFTER CALL TO SIMPLE.RUN===", which means the GUI is still inside the b.run() invocation of the Simple object.

    So any ability to communicate with a Python Socket has either got to come before the call to Simple.run(), or somehow within Simple.run().

    Since this is the GUI, it's going to mainly be concerned with receiving network messages from the Controller via Python Sockets.

    After a Socket is bound for receiving messages within the GUI, it blocks on a call to receive a message.  This means, if I were to put the part of the code that reads the Python socket prior to the Kivy App run method (Simple.run()), then the code will never get to the Simple.run() method, because it is blocking on the Socket receive method.  And, I can't put the Socket receive code after the Simple.run() because it never returns either.

    The typical approach to avoiding Socket blocking is to put this portion of the code into a thread.  This means the part of the code that reads the socket has to be put into a thread prior to the call to Simple.run().

    I want to see if a thread can be called before the Simple.run() is invoked.

    So, I'm going to create a class that has a method that will:

    • be invoked via a Thread call back.
    • this method will sleep for a time long enough for the GUI to come up
    • Do a print method to see if it shows up in the terminal before the GUI is exited.
    Note a couple of things about the new code:

    • It has a new class ThreadedClass, which has a callable method delayed_print, which has a sleep for 5 seconds call in it before printing out a statement.
    • It makes use of a Thread, which invokes the ThreadedClass callback delayed_print
    • It starts the Thread via the Thread.start() method, which causes the callback method to be executed within the thread.
    • It has two new import statements, in order to support Thread and sleep.
    • It makes print statements that show up, in time-sequence, to the terminal.  Because the thread callback method delayed_print has a sleep(5) seconds, the printout from this method will be behind everything else
    • Remember to give enough time for the sleep to finish before you shutdown the program!!!

    So here's the new code for Simple.py:


    import time
    from threading import Thread
    from kivy.app import App
    from kivy.uix.boxlayout import BoxLayout


    class Box_a(BoxLayout):
        print("Box_a entry")
        

    class Simple(App):
            
        def build(self):
            print("inside build")
            return  Box_a()

    class ThreadedClass():
        def delayed_print(self):
            print("===inside delayed_print=====")
            time.sleep(5)
            print("==========time.sleep has elapsed======")        
        
    def main():
        
        print("===========BEFORE CALL TO THREADEDCLASS=======")
        tc = ThreadedClass()
        threadRef = Thread(name="ThreadedClassName",target=tc.delayed_print)
        threadRef.start()
        print("===========AFTER CALL TO THREADEDCLASS=======")
        
        print("===========BEFORE CALL TO SIMPLE.RUN=======")
        b = Simple()
        b.run()

        print("===========AFTER CALL TO SIMPLE.RUN=======")
        
        return 0

    if __name__ == '__main__':

        main()


    Here's the output from my terminal from my run, after giving the sleep statement time to execute:

    [INFO   ] Kivy v1.7.2
    [INFO   ] [Logger      ] Record log in /home/superben/.kivy/logs/kivy_14-05-07_71.txt
    [INFO   ] [Factory     ] 144 symbols loaded
    [DEBUG  ] [Cache       ] register with limit=None, timeout=Nones
    [DEBUG  ] [Cache       ] register with limit=None, timeout=60s
    [DEBUG  ] [Cache       ] register with limit=None, timeout=Nones
    [INFO   ] [Image       ] Providers: img_tex, img_dds, img_pygame, img_pil, img_gif 
    [DEBUG  ] [Cache       ] register with limit=1000, timeout=60s
    [DEBUG  ] [Cache       ] register with limit=1000, timeout=3600s
    Box_a entry
    ===========BEFORE CALL TO THREADEDCLASS=======
    ===inside delayed_print=====
     ===========AFTER CALL TO THREADEDCLASS=======
    ===========BEFORE CALL TO SIMPLE.RUN=======
    [DEBUG  ] [App         ] Loading kv <./simple.kv>
    inside build
    [DEBUG  ] [Window      ] Ignored (import error)
    [INFO   ] [Window      ] Provider: pygame(['window_egl_rpi'] ignored)
    [DEBUG  ] [Window      ] Display driver x11
    [DEBUG  ] [Window      ] Actual window size: 800x600
    [DEBUG  ] [Window      ] Actual color bits r8 g8 b8 a0
    [DEBUG  ] [Window      ] Actual depth bits: 24
    [DEBUG  ] [Window      ] Actual stencil bits: 8
    [DEBUG  ] [Window      ] Actual multisampling samples: 2
    [INFO   ] [GL          ] OpenGL version <4 .4.0="" 331.20="" nvidia="">
    [INFO   ] [GL          ] OpenGL vendor
    [INFO   ] [GL          ] OpenGL renderer
    [INFO   ] [GL          ] OpenGL parsed version: 4, 4
    [INFO   ] [GL          ] Shading version <4 .40="" cg="" compiler="" nvidia="" via="">
    [INFO   ] [GL          ] Texture max size <16384>
    [INFO   ] [GL          ] Texture max units <32>
    [DEBUG  ] [Shader      ] Fragment compiled successfully
    [DEBUG  ] [Shader      ] Vertex compiled successfully
    [DEBUG  ] [ImagePygame ] Load
    [INFO   ] [Window      ] virtual keyboard not allowed, single mode, not docked
    [INFO   ] [Text        ] Provider: pygame
    [INFO   ] [OSC         ] using for socket
    [DEBUG  ] [Base        ] Create provider from mouse
    [DEBUG  ] [Base        ] Create provider from probesysfs
    [DEBUG  ] [ProbeSysfs  ] using probsysfs!
    [INFO   ] [Base        ] Start application main loop
    [INFO   ] [GL          ] NPOT texture support is available
    ==========time.sleep has elapsed======





    No comments:

    Post a Comment