Download this file
#!/usr/bin/python

# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
# 
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see .

# Matthieu Weber, 2011


import asyncore, socket, serial, time

MAX_MESSAGE_LENGTH = 1024


class Client(asyncore.dispatcher):

  def __init__(self, host, socket, address):
    asyncore.dispatcher.__init__(self, socket)
    self.host = host
    self.messages = []

  def handle_read(self):
    client_message = self.recv(MAX_MESSAGE_LENGTH)
    #print "C:read '%s'" % client_message
    self.host.push(client_message)

  def push(self, message):
    #print "C:push '%s'" % message
    self.messages.append(message)

  def writable(self):
    return bool(self.messages)

  def handle_write(self):
    msg = self.messages.pop(0)
    #print "C:write '%s'" % msg
    self.send(msg)

  def handle_close(self):
    #print "C:close"
    self.host.remove_client(self)

class SerialChannel(asyncore.file_dispatcher):
  def __init__(self, server, serial_port):
    self.serial_port = serial_port
    asyncore.file_dispatcher.__init__(self, self.serial_port.fileno())
    self.server = server
    self.outdata = ""
    self.indata = ""
 
  def push(self, data):
    #print "S:push '%s'" % data
    self.outdata += data
 
  def handle_write(self):
    #print "S:write '%s'" % self.outdata
    sent = self.send(self.outdata)
    self.outdata = self.outdata[sent:]
 
  def writable(self):
    return bool(self.outdata)
 
  def handle_read(self):
    self.indata += self.recv(128)
    while '\n' in self.indata:
      idx = self.indata.find('\n')
      frame = self.indata[:idx+1]
      self.indata = self.indata[idx + 1:]
      #print "S:read '%s' left '%s'" % (frame, self.indata)
      self.server.broadcast(frame)

  def handle_close(self):
    #print "S:close"
    self.server.serial_closed()
    self.close()

class Host(asyncore.dispatcher):
  def __init__(self, address=('localhost', 9876)):
    asyncore.dispatcher.__init__(self)
    self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
    self.set_reuse_addr()
    self.bind(address)
    self.listen(1)
    self.remote_clients = set()
    self.serial_channel = None
    self.serial_retry = 0.0
    self.connect_serial()

  def handle_accept(self):
    socket, addr = self.accept() # For the remote client.
    self.remote_clients.add(Client(self, socket, addr))
    #print "H:accept %s" % str(addr)

  def handle_read(self):
    pass

  def readable(self):
    if not self.serial_channel and time.time() > self.serial_retry:
      self.connect_serial()
    return True

  def push(self, message):
    if not self.serial_channel and time.time() > self.serial_retry:
      self.connect_serial()
    if self.serial_channel:
      self.serial_channel.push(message)

  def broadcast(self, message):
    for client in self.remote_clients:
      #print "H:broadcast '%s' to '%s'" % (message, str(client))
      client.push(message)

  def remove_client(self, client):
    client.close()
    self.remote_clients.remove(client)
    #print "H:remove client"

  def connect_serial(self):
    try:
      ser = serial.Serial('/dev/ttyUSB0', 57600, timeout=0.1)
      self.serial_channel = SerialChannel(self, ser)
      #print "H:connected"
    except serial.serialutil.SerialException:
      #print "H:retry later"
      self.serial_retry = time.time() + 1
   
  def serial_closed(self):
    self.serial_channel = None
    self.serial_retry = time.time() + 1
    #print "H:serial_closed()"


if __name__ == '__main__':
  host = Host()
  asyncore.loop(timeout=1.0)