blob: 87867331d4c2f007410d9c8ce29e6e11989c1ddb [file] [log] [blame]
cliechtif1c882c2009-07-23 02:30:06 +00001#! /usr/bin/env python
2"""\
3Multi-port serial<->TCP/IP forwarder.
cliechti32c10332009-08-05 13:23:43 +00004- RFC 2217
cliechtif1c882c2009-07-23 02:30:06 +00005- check existence of serial port periodically
6- start/stop forwarders
7- each forwarder creates a server socket and opens the serial port
8- serial ports are opened only once. network connect/disconnect
9 does not influence serial port
10- only one client per connection
11"""
Chris Liechti4caf6a52015-08-04 01:07:45 +020012import os
cliechtif1c882c2009-07-23 02:30:06 +000013import select
Chris Liechti4caf6a52015-08-04 01:07:45 +020014import socket
15import sys
16import time
17import traceback
cliechti32c10332009-08-05 13:23:43 +000018
cliechtif1c882c2009-07-23 02:30:06 +000019import serial
cliechti32c10332009-08-05 13:23:43 +000020import serial.rfc2217
Chris Liechti6e683ed2015-08-04 16:59:04 +020021import serial.tools.list_ports
cliechti32c10332009-08-05 13:23:43 +000022
cliechtif1c882c2009-07-23 02:30:06 +000023import dbus
24
Chris Liechti6e683ed2015-08-04 16:59:04 +020025# Try to import the avahi service definitions properly. If the avahi module is
26# not available, fall back to a hard-coded solution that hopefully still works.
27try:
28 import avahi
29except ImportError:
30 class avahi:
31 DBUS_NAME = "org.freedesktop.Avahi"
32 DBUS_PATH_SERVER = "/"
33 DBUS_INTERFACE_SERVER = "org.freedesktop.Avahi.Server"
34 DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
35 IF_UNSPEC = -1
36 PROTO_UNSPEC, PROTO_INET, PROTO_INET6 = -1, 0, 1
37
38
39
cliechtif1c882c2009-07-23 02:30:06 +000040class ZeroconfService:
41 """\
42 A simple class to publish a network service with zeroconf using avahi.
43 """
44
45 def __init__(self, name, port, stype="_http._tcp",
46 domain="", host="", text=""):
47 self.name = name
48 self.stype = stype
49 self.domain = domain
50 self.host = host
51 self.port = port
52 self.text = text
53 self.group = None
54
55 def publish(self):
56 bus = dbus.SystemBus()
57 server = dbus.Interface(
58 bus.get_object(
59 avahi.DBUS_NAME,
60 avahi.DBUS_PATH_SERVER
61 ),
62 avahi.DBUS_INTERFACE_SERVER
63 )
64
65 g = dbus.Interface(
66 bus.get_object(
67 avahi.DBUS_NAME,
68 server.EntryGroupNew()
69 ),
70 avahi.DBUS_INTERFACE_ENTRY_GROUP
71 )
72
73 g.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0),
74 self.name, self.stype, self.domain, self.host,
75 dbus.UInt16(self.port), self.text)
76
77 g.Commit()
78 self.group = g
79
80 def unpublish(self):
81 if self.group is not None:
82 self.group.Reset()
83 self.group = None
84
85 def __str__(self):
86 return "%r @ %s:%s (%s)" % (self.name, self.host, self.port, self.stype)
87
88
89
90class Forwarder(ZeroconfService):
91 """\
92 Single port serial<->TCP/IP forarder that depends on an external select
cliechti32c10332009-08-05 13:23:43 +000093 loop.
94 - Buffers for serial -> network and network -> serial
95 - RFC 2217 state
96 - Zeroconf publish/unpublish on open/close.
cliechtif1c882c2009-07-23 02:30:06 +000097 """
98
Chris Liechti825637e2015-08-05 14:14:00 +020099 def __init__(self, device, name, network_port, on_close=None, log=None):
cliechtif1c882c2009-07-23 02:30:06 +0000100 ZeroconfService.__init__(self, name, network_port, stype='_serial_port._tcp')
101 self.alive = False
102 self.network_port = network_port
103 self.on_close = on_close
Chris Liechti825637e2015-08-05 14:14:00 +0200104 self.log = log
cliechtif1c882c2009-07-23 02:30:06 +0000105 self.device = device
106 self.serial = serial.Serial()
107 self.serial.port = device
108 self.serial.baudrate = 115200
109 self.serial.timeout = 0
110 self.socket = None
111 self.server_socket = None
cliechti32c10332009-08-05 13:23:43 +0000112 self.rfc2217 = None # instantiate later, when connecting
cliechtif1c882c2009-07-23 02:30:06 +0000113
114 def __del__(self):
115 try:
116 if self.alive: self.close()
117 except:
118 pass # XXX errors on shutdown
119
120 def open(self):
121 """open serial port, start network server and publish service"""
Chris Liechti01587b12015-08-05 02:39:32 +0200122 self.buffer_net2ser = bytearray()
123 self.buffer_ser2net = bytearray()
cliechtif1c882c2009-07-23 02:30:06 +0000124
125 # open serial port
126 try:
127 self.serial.open()
128 self.serial.setRTS(False)
Chris Liechti4caf6a52015-08-04 01:07:45 +0200129 except Exception as msg:
cliechtif1c882c2009-07-23 02:30:06 +0000130 self.handle_serial_error(msg)
131
cliechtid9a06ce2009-08-10 01:30:53 +0000132 self.serial_settings_backup = self.serial.getSettingsDict()
133
cliechtif1c882c2009-07-23 02:30:06 +0000134 # start the socket server
Chris Liechti977916f2015-08-08 17:12:53 +0200135 # XXX add IPv6 support: use getaddrinfo for socket options, bind to multiple sockets?
136 # info_list = socket.getaddrinfo(None, port, 0, socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
cliechtif1c882c2009-07-23 02:30:06 +0000137 self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
138 self.server_socket.setsockopt(
139 socket.SOL_SOCKET,
140 socket.SO_REUSEADDR,
141 self.server_socket.getsockopt(
142 socket.SOL_SOCKET,
143 socket.SO_REUSEADDR
144 ) | 1
145 )
146 self.server_socket.setblocking(0)
147 try:
148 self.server_socket.bind( ('', self.network_port) )
149 self.server_socket.listen(1)
Chris Liechti4caf6a52015-08-04 01:07:45 +0200150 except socket.error as msg:
cliechtif1c882c2009-07-23 02:30:06 +0000151 self.handle_server_error()
152 #~ raise
Chris Liechti825637e2015-08-05 14:14:00 +0200153 if self.log is not None:
154 self.log.info("%s: Waiting for connection on %s..." % (self.device, self.network_port))
cliechtif1c882c2009-07-23 02:30:06 +0000155
156 # zeroconfig
157 self.publish()
158
159 # now we are ready
160 self.alive = True
161
162 def close(self):
163 """Close all resources and unpublish service"""
Chris Liechti825637e2015-08-05 14:14:00 +0200164 if self.log is not None:
165 self.log.info("%s: closing..." % (self.device, ))
cliechtif1c882c2009-07-23 02:30:06 +0000166 self.alive = False
167 self.unpublish()
168 if self.server_socket: self.server_socket.close()
169 if self.socket:
170 self.handle_disconnect()
171 self.serial.close()
172 if self.on_close is not None:
173 # ensure it is only called once
174 callback = self.on_close
175 self.on_close = None
176 callback(self)
177
cliechti32c10332009-08-05 13:23:43 +0000178 def write(self, data):
179 """the write method is used by serial.rfc2217.PortManager. it has to
180 write to the network."""
181 self.buffer_ser2net += data
182
cliechtif1c882c2009-07-23 02:30:06 +0000183 def update_select_maps(self, read_map, write_map, error_map):
184 """Update dictionaries for select call. insert fd->callback mapping"""
185 if self.alive:
186 # always handle serial port reads
187 read_map[self.serial] = self.handle_serial_read
188 error_map[self.serial] = self.handle_serial_error
189 # handle serial port writes if buffer is not empty
190 if self.buffer_net2ser:
191 write_map[self.serial] = self.handle_serial_write
192 # handle network
193 if self.socket is not None:
194 # handle socket if connected
195 # only read from network if the internal buffer is not
196 # already filled. the TCP flow control will hold back data
197 if len(self.buffer_net2ser) < 2048:
198 read_map[self.socket] = self.handle_socket_read
199 # only check for write readiness when there is data
200 if self.buffer_ser2net:
201 write_map[self.socket] = self.handle_socket_write
202 error_map[self.socket] = self.handle_socket_error
203 else:
204 # no connection, ensure clear buffer
Chris Liechti01587b12015-08-05 02:39:32 +0200205 self.buffer_ser2net = bytearray()
cliechtif1c882c2009-07-23 02:30:06 +0000206 # check the server socket
207 read_map[self.server_socket] = self.handle_connect
208 error_map[self.server_socket] = self.handle_server_error
209
210
211 def handle_serial_read(self):
212 """Reading from serial port"""
213 try:
214 data = os.read(self.serial.fileno(), 1024)
215 if data:
216 # store data in buffer if there is a client connected
217 if self.socket is not None:
cliechti32c10332009-08-05 13:23:43 +0000218 # escape outgoing data when needed (Telnet IAC (0xff) character)
219 if self.rfc2217:
220 data = serial.to_bytes(self.rfc2217.escape(data))
cliechtif1c882c2009-07-23 02:30:06 +0000221 self.buffer_ser2net += data
222 else:
223 self.handle_serial_error()
Chris Liechti4caf6a52015-08-04 01:07:45 +0200224 except Exception as msg:
cliechtif1c882c2009-07-23 02:30:06 +0000225 self.handle_serial_error(msg)
226
227 def handle_serial_write(self):
228 """Writing to serial port"""
229 try:
230 # write a chunk
Chris Liechti01587b12015-08-05 02:39:32 +0200231 n = os.write(self.serial.fileno(), bytes(self.buffer_net2ser))
cliechtif1c882c2009-07-23 02:30:06 +0000232 # and see how large that chunk was, remove that from buffer
233 self.buffer_net2ser = self.buffer_net2ser[n:]
Chris Liechti4caf6a52015-08-04 01:07:45 +0200234 except Exception as msg:
cliechtif1c882c2009-07-23 02:30:06 +0000235 self.handle_serial_error(msg)
236
237 def handle_serial_error(self, error=None):
238 """Serial port error"""
239 # terminate connection
240 self.close()
241
242 def handle_socket_read(self):
243 """Read from socket"""
244 try:
245 # read a chunk from the serial port
246 data = self.socket.recv(1024)
247 if data:
cliechti32c10332009-08-05 13:23:43 +0000248 # Process RFC 2217 stuff when enabled
249 if self.rfc2217:
250 data = serial.to_bytes(self.rfc2217.filter(data))
cliechtif1c882c2009-07-23 02:30:06 +0000251 # add data to buffer
252 self.buffer_net2ser += data
253 else:
254 # empty read indicates disconnection
255 self.handle_disconnect()
256 except socket.error:
257 self.handle_socket_error()
258
259 def handle_socket_write(self):
260 """Write to socket"""
261 try:
262 # write a chunk
Chris Liechti01587b12015-08-05 02:39:32 +0200263 count = self.socket.send(bytes(self.buffer_ser2net))
cliechtif1c882c2009-07-23 02:30:06 +0000264 # and remove the sent data from the buffer
265 self.buffer_ser2net = self.buffer_ser2net[count:]
266 except socket.error:
267 self.handle_socket_error()
268
269 def handle_socket_error(self):
270 """Socket connection fails"""
271 self.handle_disconnect()
272
273 def handle_connect(self):
274 """Server socket gets a connection"""
275 # accept a connection in any case, close connection
276 # below if already busy
277 connection, addr = self.server_socket.accept()
278 if self.socket is None:
279 self.socket = connection
Chris Liechti6e683ed2015-08-04 16:59:04 +0200280 # More quickly detect bad clients who quit without closing the
281 # connection: After 1 second of idle, start sending TCP keep-alive
282 # packets every 1 second. If 3 consecutive keep-alive packets
283 # fail, assume the client is gone and close the connection.
284 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
285 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1)
286 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 1)
287 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)
cliechtif1c882c2009-07-23 02:30:06 +0000288 self.socket.setblocking(0)
cliechtia35cad42009-08-10 20:57:48 +0000289 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
Chris Liechti825637e2015-08-05 14:14:00 +0200290 if self.log is not None:
Chris Liechti977916f2015-08-08 17:12:53 +0200291 self.log.warning('%s: Connected by %s:%s' % (self.device, addr[0], addr[1]))
cliechtid9a06ce2009-08-10 01:30:53 +0000292 self.serial.setRTS(True)
293 self.serial.setDTR(True)
Chris Liechti825637e2015-08-05 14:14:00 +0200294 if self.log is not None:
295 self.rfc2217 = serial.rfc2217.PortManager(self.serial, self, logger=log.getChild(self.device))
296 else:
297 self.rfc2217 = serial.rfc2217.PortManager(self.serial, self)
cliechtif1c882c2009-07-23 02:30:06 +0000298 else:
299 # reject connection if there is already one
300 connection.close()
Chris Liechti825637e2015-08-05 14:14:00 +0200301 if self.log is not None:
Chris Liechti977916f2015-08-08 17:12:53 +0200302 self.log.warning('%s: Rejecting connect from %s:%s' % (self.device, addr[0], addr[1]))
cliechtif1c882c2009-07-23 02:30:06 +0000303
304 def handle_server_error(self):
cliechti7aed8332009-08-05 14:19:31 +0000305 """Socket server fails"""
cliechtif1c882c2009-07-23 02:30:06 +0000306 self.close()
307
308 def handle_disconnect(self):
309 """Socket gets disconnected"""
cliechtid9a06ce2009-08-10 01:30:53 +0000310 # signal disconnected terminal with control lines
cliechtie67c1f42009-09-10 15:07:44 +0000311 try:
312 self.serial.setRTS(False)
313 self.serial.setDTR(False)
314 finally:
315 # restore original port configuration in case it was changed
316 self.serial.applySettingsDict(self.serial_settings_backup)
317 # stop RFC 2217 state machine
318 self.rfc2217 = None
319 # clear send buffer
Chris Liechti01587b12015-08-05 02:39:32 +0200320 self.buffer_ser2net = bytearray()
cliechtie67c1f42009-09-10 15:07:44 +0000321 # close network connection
322 if self.socket is not None:
323 self.socket.close()
324 self.socket = None
Chris Liechti825637e2015-08-05 14:14:00 +0200325 if self.log is not None:
Chris Liechti977916f2015-08-08 17:12:53 +0200326 self.log.warning('%s: Disconnected' % self.device)
cliechtif1c882c2009-07-23 02:30:06 +0000327
328
329def test():
330 service = ZeroconfService(name="TestService", port=3000)
331 service.publish()
332 raw_input("Press any key to unpublish the service ")
333 service.unpublish()
334
335
336if __name__ == '__main__':
Chris Liechti01587b12015-08-05 02:39:32 +0200337 import logging
cliechtif1c882c2009-07-23 02:30:06 +0000338 import optparse
339
Chris Liechti825637e2015-08-05 14:14:00 +0200340 VERBOSTIY = [
341 logging.ERROR, # 0
342 logging.WARNING, # 1 (default)
343 logging.INFO, # 2
344 logging.DEBUG, # 3
345 ]
346
cliechtif1c882c2009-07-23 02:30:06 +0000347 parser = optparse.OptionParser(usage="""\
348%prog [options]
349
350Announce the existence of devices using zeroconf and provide
cliechti32c10332009-08-05 13:23:43 +0000351a TCP/IP <-> serial port gateway (implements RFC 2217).
cliechtif1c882c2009-07-23 02:30:06 +0000352
353Note that the TCP/IP server is not protected. Everyone can connect
354to it!
355
356If running as daemon, write to syslog. Otherwise write to stdout.
357""")
358
Chris Liechti9d61c302015-08-05 14:35:16 +0200359 group = optparse.OptionGroup(parser, "Serial Port Settings")
cliechtif1c882c2009-07-23 02:30:06 +0000360
Chris Liechti9d61c302015-08-05 14:35:16 +0200361 group.add_option("", "--ports-regex",
362 dest="ports_regex",
363 help="specify a regex to search against the serial devices and their descriptions (default: %default)",
364 default='/dev/ttyUSB[0-9]+',
365 metavar="REGEX")
Chris Liechti825637e2015-08-05 14:14:00 +0200366
Chris Liechti9d61c302015-08-05 14:35:16 +0200367 parser.add_option_group(group)
cliechtif1c882c2009-07-23 02:30:06 +0000368
Chris Liechti9d61c302015-08-05 14:35:16 +0200369 group = optparse.OptionGroup(parser, "Network Settings")
cliechtif1c882c2009-07-23 02:30:06 +0000370
Chris Liechti9d61c302015-08-05 14:35:16 +0200371 group.add_option("", "--tcp-port",
Chris Liechti7225dd72015-08-04 17:08:05 +0200372 dest="base_port",
373 help="specify lowest TCP port number (default: %default)",
374 default=7000,
375 type='int',
376 metavar="PORT")
377
Chris Liechti9d61c302015-08-05 14:35:16 +0200378 parser.add_option_group(group)
379
380 group = optparse.OptionGroup(parser, "Daemon")
381
382 group.add_option("-d", "--daemon",
383 dest="daemonize",
384 action="store_true",
385 help="start as daemon",
386 default=False)
387
388 group.add_option("", "--pidfile",
389 dest="pid_file",
390 help="specify a name for the PID file",
391 default=None,
392 metavar="FILE")
393
394 parser.add_option_group(group)
395
396 group = optparse.OptionGroup(parser, "Diagnostics")
397
398 group.add_option("-o", "--logfile",
399 dest="log_file",
400 help="write messages file instead of stdout",
401 default=None,
402 metavar="FILE")
403
404 group.add_option("-q", "--quiet",
405 dest="verbosity",
406 action="store_const",
407 const=0,
408 help="suppress most diagnostic messages",
409 default=False)
410
411 group.add_option("-v", "--verbose",
412 dest="verbosity",
413 action="count",
414 help="increase diagnostic messages",
415 default=1)
416
417 parser.add_option_group(group)
Chris Liechti6e683ed2015-08-04 16:59:04 +0200418
cliechtif1c882c2009-07-23 02:30:06 +0000419 (options, args) = parser.parse_args()
420
Chris Liechti825637e2015-08-05 14:14:00 +0200421 # set up logging
422 logging.basicConfig(level=VERBOSTIY[min(options.verbosity, len(VERBOSTIY) - 1)])
423 log = logging.getLogger('port_publisher')
Chris Liechti01587b12015-08-05 02:39:32 +0200424
cliechtif1c882c2009-07-23 02:30:06 +0000425 # redirect output if specified
426 if options.log_file is not None:
427 class WriteFlushed:
428 def __init__(self, fileobj):
429 self.fileobj = fileobj
430 def write(self, s):
431 self.fileobj.write(s)
432 self.fileobj.flush()
433 def close(self):
434 self.fileobj.close()
Chris Liechti825637e2015-08-05 14:14:00 +0200435 sys.stdout = sys.stderr = WriteFlushed(open(options.log_file, 'a'))
cliechtif1c882c2009-07-23 02:30:06 +0000436 # atexit.register(lambda: sys.stdout.close())
437
438 if options.daemonize:
439 # if running as daemon is requested, do the fork magic
440 # options.quiet = True
441 import pwd
442 # do the UNIX double-fork magic, see Stevens' "Advanced
443 # Programming in the UNIX Environment" for details (ISBN 0201563177)
444 try:
445 pid = os.fork()
446 if pid > 0:
447 # exit first parent
448 sys.exit(0)
Chris Liechti4caf6a52015-08-04 01:07:45 +0200449 except OSError as e:
Chris Liechti825637e2015-08-05 14:14:00 +0200450 log.critical("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
cliechtif1c882c2009-07-23 02:30:06 +0000451 sys.exit(1)
452
453 # decouple from parent environment
454 os.chdir("/") # don't prevent unmounting....
455 os.setsid()
456 os.umask(0)
457
458 # do second fork
459 try:
460 pid = os.fork()
461 if pid > 0:
Chris Liechti9d61c302015-08-05 14:35:16 +0200462 # exit from second parent, save eventual PID before
cliechtif1c882c2009-07-23 02:30:06 +0000463 if options.pid_file is not None:
Chris Liechti9d61c302015-08-05 14:35:16 +0200464 open(options.pid_file, 'w').write("%d" % pid)
cliechtif1c882c2009-07-23 02:30:06 +0000465 sys.exit(0)
Chris Liechti4caf6a52015-08-04 01:07:45 +0200466 except OSError as e:
Chris Liechti825637e2015-08-05 14:14:00 +0200467 log.critical("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
cliechtif1c882c2009-07-23 02:30:06 +0000468 sys.exit(1)
469
470 if options.log_file is None:
471 import syslog
472 syslog.openlog("serial port publisher")
473 # redirect output to syslog
474 class WriteToSysLog:
475 def __init__(self):
476 self.buffer = ''
477 def write(self, s):
478 self.buffer += s
479 if '\n' in self.buffer:
480 output, self.buffer = self.buffer.split('\n', 1)
481 syslog.syslog(output)
482 def flush(self):
483 syslog.syslog(self.buffer)
484 self.buffer = ''
485 def close(self):
486 self.flush()
487 sys.stdout = sys.stderr = WriteToSysLog()
488
489 # ensure the that the daemon runs a normal user, if run as root
490 #if os.getuid() == 0:
491 # name, passwd, uid, gid, desc, home, shell = pwd.getpwnam('someuser')
492 # os.setgid(gid) # set group first
493 # os.setuid(uid) # set user
494
495 # keep the published stuff in a dictionary
496 published = {}
cliechtif1c882c2009-07-23 02:30:06 +0000497 # get a nice hostname
498 hostname = socket.gethostname()
499
500 def unpublish(forwarder):
501 """when forwarders die, we need to unregister them"""
502 try:
503 del published[forwarder.device]
504 except KeyError:
505 pass
506 else:
Chris Liechti825637e2015-08-05 14:14:00 +0200507 log.info("unpublish: %s" % (forwarder))
cliechtif1c882c2009-07-23 02:30:06 +0000508
509 alive = True
510 next_check = 0
511 # main loop
512 while alive:
513 try:
514 # if it is time, check for serial port devices
515 now = time.time()
516 if now > next_check:
517 next_check = now + 5
Chris Liechti6e683ed2015-08-04 16:59:04 +0200518 connected = [d for d, p, i in serial.tools.list_ports.grep(options.ports_regex)]
519 # Handle devices that are published, but no longer connected
520 for device in set(published).difference(connected):
Chris Liechti825637e2015-08-05 14:14:00 +0200521 log.info("unpublish: %s" % (published[device]))
Chris Liechti6e683ed2015-08-04 16:59:04 +0200522 unpublish(published[device])
523 # Handle devices that are connected but not yet published
524 for device in set(connected).difference(published):
525 # Find the first available port, starting from 7000
Chris Liechti7225dd72015-08-04 17:08:05 +0200526 port = options.base_port
Chris Liechti6e683ed2015-08-04 16:59:04 +0200527 ports_in_use = [f.network_port for f in published.values()]
528 while port in ports_in_use:
529 port += 1
530 published[device] = Forwarder(
Chris Liechti9d61c302015-08-05 14:35:16 +0200531 device,
532 "%s on %s" % (device, hostname),
533 port,
534 on_close=unpublish,
535 log=log
Chris Liechti6e683ed2015-08-04 16:59:04 +0200536 )
Chris Liechti825637e2015-08-05 14:14:00 +0200537 log.warning("publish: %s" % (published[device]))
Chris Liechti6e683ed2015-08-04 16:59:04 +0200538 published[device].open()
cliechtif1c882c2009-07-23 02:30:06 +0000539
540 # select_start = time.time()
541 read_map = {}
542 write_map = {}
543 error_map = {}
544 for publisher in published.values():
545 publisher.update_select_maps(read_map, write_map, error_map)
Chris Liechti8f9b4442015-08-05 14:57:30 +0200546 readers, writers, errors = select.select(
547 read_map.keys(),
548 write_map.keys(),
549 error_map.keys(),
550 5
551 )
cliechtif1c882c2009-07-23 02:30:06 +0000552 # select_end = time.time()
553 # print "select used %.3f s" % (select_end - select_start)
554 for reader in readers:
555 read_map[reader]()
556 for writer in writers:
557 write_map[writer]()
558 for error in errors:
559 error_map[error]()
560 # print "operation used %.3f s" % (time.time() - select_end)
561 except KeyboardInterrupt:
562 alive = False
Chris Liechti7225dd72015-08-04 17:08:05 +0200563 sys.stdout.write('\n')
cliechtif1c882c2009-07-23 02:30:06 +0000564 except SystemExit:
565 raise
566 except:
cliechtie67c1f42009-09-10 15:07:44 +0000567 #~ raise
cliechtif1c882c2009-07-23 02:30:06 +0000568 traceback.print_exc()