Search code examples
pythonsocketsloopspython-multithreadingtcpsocket

Python : Running loop while waiting for server socket connection


I have a LCD that I use to display timecode of the movie I am watching on my XBMC, and I would like to display current date and hour when nothing is played. The machine hosting the LCD is running a python TCPSocketServer server receiving text to be displayed from my XBMC installation. The problem is that I have to keep an active network connection so XBMC can send the hour and date through the socket and the LCD can display it. In my opinion, the best thing to do would be to display the date of the machine hosting the LCD and then switch to a "timecode view" when the socket is active.

Is there any "pythonic" way set up a TCPSocketServer that compute a loop for displaying the date while waiting for a connection, and then change its behaviour to compute data received through the socket ?

Many thanks in advance


Solution

  • Here's some code that does what you want. It starts up a TCP server listening to text sent to port 9876. When it gets some text it sends it to the Display object. The code also sets up a timer which runs once a second, sending "idle text" (ie: the current timestamp) to the display object.

    The display gets normal update text (from the server), and "idle text" from the 2nd thread. The object knows how long it's been since it got some real text, and displays one or the other messages.

    If the display object was attached to hardware, it could use a multiprocessing.RLock or other mechanism to protect itself.

    Have fun!

    lcdcontrol.py source

    import signal, SocketServer, threading, time
    
    class Display(object):
        IDLE_TIME = 5               # seconds
    
        def __init__(self):
            self.updated = None
            self.write('Hello')
    
        def _write(self, arg):
            print 'DISPLAY:', arg
    
        def write(self, arg):
            """
            update display and 'last updated' timestamp
            """
            self._write(arg)
            self.updated = time.time()
    
        def idle(self, arg):
            """
            update display only if it's been a few seconds
            """
            if time.time() - self.updated >= self.IDLE_TIME:
                self._write(arg)
    
    class DisplayHandler(SocketServer.BaseRequestHandler):
    
        DisplayObj = None          # class var
    
        def handle(self):
            text = self.request.recv(1024).strip()
            print "{} wrote: {}".format(
                self.client_address[0], text,
                )
            # send text to LCD immediately
            self.DisplayObj.write(text)
    
    
    def check_idle(display_obj):
        """
        update display with current time if it's idle
        """
        while True:
            display_obj.idle(
                time.strftime('time: %H:%M:%S'),
            )
            time.sleep(1)
    
    
    def start_server(host, port):
        """
        start (single threaded) server
        """
        SocketServer.TCPServer(
            (host, port), 
            DisplayHandler,
        ).serve_forever()
    
    
    def main(host, port):
        display = Display()
    
        # store global display obj so Handler can get to it
        DisplayHandler.DisplayObj = display
    
        print 'serving on {}:{}'.format(host, port)
        print 'Example: echo beer | nc localhost {}'.format(port)
        print
    
        server_t = threading.Thread(
            target=start_server, args=(host, port)
        )
        server_t.daemon = True
        server_t.start()
    
        idle_t = threading.Thread(
            target=check_idle, args=[display],
            )
        idle_t.daemon = True
        idle_t.start()
    
        # wait for control-C to interrupt
        try:
            signal.pause()
        except KeyboardInterrupt:
            pass
    
    if __name__ == "__main__":
        main("localhost", 9876)
    

    sample run

    Here I started the server, waited a few seconds, then typed fortune -s | nc localhost 9876 to send a short fortune cookie to my LCD server.

    Notice the "idle timer" stopped at :07, output the fortune cookie, waited five seconds, then continued with :13, :14. The message displayed for five seconds before switching back to the idle timestamp.

    python ./lcdcontrol.py
    DISPLAY: Hello
    serving on localhost:9876
    Example: echo beer | nc localhost 9876
    
    DISPLAY: time: 13:08:06
    DISPLAY: time: 13:08:07
    127.0.0.1 wrote: Some people need a good imaginary cure for their painful imaginary ailment.
    DISPLAY: Some people need a good imaginary cure for their painful imaginary ailment.
    DISPLAY: time: 13:08:13
    DISPLAY: time: 13:08:14
    DISPLAY: time: 13:08:15
    DISPLAY: time: 13:08:16