blob: fd49943cc8b26e293c9a894296811083594cf446 [file] [log] [blame]
cliechtif1c882c2009-07-23 02:30:06 +00001#! /usr/bin/env python
Chris Liechtifbdd8a02015-08-09 02:37:45 +02002#
3# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
4#
5# SPDX-License-Identifier: BSD-3-Clause
cliechtif1c882c2009-07-23 02:30:06 +00006"""\
7Multi-port serial<->TCP/IP forwarder.
cliechti32c10332009-08-05 13:23:43 +00008- RFC 2217
cliechtif1c882c2009-07-23 02:30:06 +00009- check existence of serial port periodically
10- start/stop forwarders
11- each forwarder creates a server socket and opens the serial port
12- serial ports are opened only once. network connect/disconnect
13 does not influence serial port
14- only one client per connection
15"""
Chris Liechti4caf6a52015-08-04 01:07:45 +020016import os
cliechtif1c882c2009-07-23 02:30:06 +000017import select
Chris Liechti4caf6a52015-08-04 01:07:45 +020018import socket
19import sys
20import time
21import traceback
cliechti32c10332009-08-05 13:23:43 +000022
cliechtif1c882c2009-07-23 02:30:06 +000023import serial
cliechti32c10332009-08-05 13:23:43 +000024import serial.rfc2217
Chris Liechti6e683ed2015-08-04 16:59:04 +020025import serial.tools.list_ports
cliechti32c10332009-08-05 13:23:43 +000026
cliechtif1c882c2009-07-23 02:30:06 +000027import dbus
28
Chris Liechti6e683ed2015-08-04 16:59:04 +020029# Try to import the avahi service definitions properly. If the avahi module is
30# not available, fall back to a hard-coded solution that hopefully still works.
31try:
32 import avahi
33except ImportError:
34 class avahi:
35 DBUS_NAME = "org.freedesktop.Avahi"
36 DBUS_PATH_SERVER = "/"
37 DBUS_INTERFACE_SERVER = "org.freedesktop.Avahi.Server"
38 DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
39 IF_UNSPEC = -1
40 PROTO_UNSPEC, PROTO_INET, PROTO_INET6 = -1, 0, 1
41
42
43
cliechtif1c882c2009-07-23 02:30:06 +000044class ZeroconfService:
45 """\
46 A simple class to publish a network service with zeroconf using avahi.
47 """
48
49 def __init__(self, name, port, stype="_http._tcp",
50 domain="", host="", text=""):
51 self.name = name
52 self.stype = stype
53 self.domain = domain
54 self.host = host
55 self.port = port
56 self.text = text
57 self.group = None
58
59 def publish(self):
60 bus = dbus.SystemBus()
61 server = dbus.Interface(
62 bus.get_object(
63 avahi.DBUS_NAME,
64 avahi.DBUS_PATH_SERVER
65 ),
66 avahi.DBUS_INTERFACE_SERVER
67 )
68
69 g = dbus.Interface(
70 bus.get_object(
71 avahi.DBUS_NAME,
72 server.EntryGroupNew()
73 ),
74 avahi.DBUS_INTERFACE_ENTRY_GROUP
75 )
76
77 g.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0),
78 self.name, self.stype, self.domain, self.host,
79 dbus.UInt16(self.port), self.text)
80
81 g.Commit()
82 self.group = g
83
84 def unpublish(self):
85 if self.group is not None:
86 self.group.Reset()
87 self.group = None
88
89 def __str__(self):
90 return "%r @ %s:%s (%s)" % (self.name, self.host, self.port, self.stype)
91
92
93
94class Forwarder(ZeroconfService):
95 """\
96 Single port serial<->TCP/IP forarder that depends on an external select
cliechti32c10332009-08-05 13:23:43 +000097 loop.
98 - Buffers for serial -> network and network -> serial
99 - RFC 2217 state
100 - Zeroconf publish/unpublish on open/close.
cliechtif1c882c2009-07-23 02:30:06 +0000101 """
102
Chris Liechti825637e2015-08-05 14:14:00 +0200103 def __init__(self, device, name, network_port, on_close=None, log=None):
cliechtif1c882c2009-07-23 02:30:06 +0000104 ZeroconfService.__init__(self, name, network_port, stype='_serial_port._tcp')
105 self.alive = False
106 self.network_port = network_port
107 self.on_close = on_close
Chris Liechti825637e2015-08-05 14:14:00 +0200108 self.log = log
cliechtif1c882c2009-07-23 02:30:06 +0000109 self.device = device
110 self.serial = serial.Serial()
111 self.serial.port = device
112 self.serial.baudrate = 115200
113 self.serial.timeout = 0
114 self.socket = None
115 self.server_socket = None
cliechti32c10332009-08-05 13:23:43 +0000116 self.rfc2217 = None # instantiate later, when connecting
cliechtif1c882c2009-07-23 02:30:06 +0000117
118 def __del__(self):
119 try:
120 if self.alive: self.close()
121 except:
122 pass # XXX errors on shutdown
123
124 def open(self):
125 """open serial port, start network server and publish service"""
Chris Liechti01587b12015-08-05 02:39:32 +0200126 self.buffer_net2ser = bytearray()
127 self.buffer_ser2net = bytearray()
cliechtif1c882c2009-07-23 02:30:06 +0000128
129 # open serial port
130 try:
131 self.serial.open()
132 self.serial.setRTS(False)
Chris Liechti4caf6a52015-08-04 01:07:45 +0200133 except Exception as msg:
cliechtif1c882c2009-07-23 02:30:06 +0000134 self.handle_serial_error(msg)
135
cliechtid9a06ce2009-08-10 01:30:53 +0000136 self.serial_settings_backup = self.serial.getSettingsDict()
137
cliechtif1c882c2009-07-23 02:30:06 +0000138 # start the socket server
Chris Liechti977916f2015-08-08 17:12:53 +0200139 # XXX add IPv6 support: use getaddrinfo for socket options, bind to multiple sockets?
140 # info_list = socket.getaddrinfo(None, port, 0, socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
cliechtif1c882c2009-07-23 02:30:06 +0000141 self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
142 self.server_socket.setsockopt(
143 socket.SOL_SOCKET,
144 socket.SO_REUSEADDR,
145 self.server_socket.getsockopt(
146 socket.SOL_SOCKET,
147 socket.SO_REUSEADDR
148 ) | 1
149 )
150 self.server_socket.setblocking(0)
151 try:
152 self.server_socket.bind( ('', self.network_port) )
153 self.server_socket.listen(1)
Chris Liechti4caf6a52015-08-04 01:07:45 +0200154 except socket.error as msg:
cliechtif1c882c2009-07-23 02:30:06 +0000155 self.handle_server_error()
156 #~ raise
Chris Liechti825637e2015-08-05 14:14:00 +0200157 if self.log is not None:
158 self.log.info("%s: Waiting for connection on %s..." % (self.device, self.network_port))
cliechtif1c882c2009-07-23 02:30:06 +0000159
160 # zeroconfig
161 self.publish()
162
163 # now we are ready
164 self.alive = True
165
166 def close(self):
167 """Close all resources and unpublish service"""
Chris Liechti825637e2015-08-05 14:14:00 +0200168 if self.log is not None:
169 self.log.info("%s: closing..." % (self.device, ))
cliechtif1c882c2009-07-23 02:30:06 +0000170 self.alive = False
171 self.unpublish()
172 if self.server_socket: self.server_socket.close()
173 if self.socket:
174 self.handle_disconnect()
175 self.serial.close()
176 if self.on_close is not None:
177 # ensure it is only called once
178 callback = self.on_close
179 self.on_close = None
180 callback(self)
181
cliechti32c10332009-08-05 13:23:43 +0000182 def write(self, data):
183 """the write method is used by serial.rfc2217.PortManager. it has to
184 write to the network."""
185 self.buffer_ser2net += data
186
cliechtif1c882c2009-07-23 02:30:06 +0000187 def update_select_maps(self, read_map, write_map, error_map):
188 """Update dictionaries for select call. insert fd->callback mapping"""
189 if self.alive:
190 # always handle serial port reads
191 read_map[self.serial] = self.handle_serial_read
192 error_map[self.serial] = self.handle_serial_error
193 # handle serial port writes if buffer is not empty
194 if self.buffer_net2ser:
195 write_map[self.serial] = self.handle_serial_write
196 # handle network
197 if self.socket is not None:
198 # handle socket if connected
199 # only read from network if the internal buffer is not
200 # already filled. the TCP flow control will hold back data
201 if len(self.buffer_net2ser) < 2048:
202 read_map[self.socket] = self.handle_socket_read
203 # only check for write readiness when there is data
204 if self.buffer_ser2net:
205 write_map[self.socket] = self.handle_socket_write
206 error_map[self.socket] = self.handle_socket_error
207 else:
208 # no connection, ensure clear buffer
Chris Liechti01587b12015-08-05 02:39:32 +0200209 self.buffer_ser2net = bytearray()
cliechtif1c882c2009-07-23 02:30:06 +0000210 # check the server socket
211 read_map[self.server_socket] = self.handle_connect
212 error_map[self.server_socket] = self.handle_server_error
213
214
215 def handle_serial_read(self):
216 """Reading from serial port"""
217 try:
218 data = os.read(self.serial.fileno(), 1024)
219 if data:
220 # store data in buffer if there is a client connected
221 if self.socket is not None:
cliechti32c10332009-08-05 13:23:43 +0000222 # escape outgoing data when needed (Telnet IAC (0xff) character)
223 if self.rfc2217:
224 data = serial.to_bytes(self.rfc2217.escape(data))
cliechtif1c882c2009-07-23 02:30:06 +0000225 self.buffer_ser2net += data
226 else:
227 self.handle_serial_error()
Chris Liechti4caf6a52015-08-04 01:07:45 +0200228 except Exception as msg:
cliechtif1c882c2009-07-23 02:30:06 +0000229 self.handle_serial_error(msg)
230
231 def handle_serial_write(self):
232 """Writing to serial port"""
233 try:
234 # write a chunk
Chris Liechti01587b12015-08-05 02:39:32 +0200235 n = os.write(self.serial.fileno(), bytes(self.buffer_net2ser))
cliechtif1c882c2009-07-23 02:30:06 +0000236 # and see how large that chunk was, remove that from buffer
237 self.buffer_net2ser = self.buffer_net2ser[n:]
Chris Liechti4caf6a52015-08-04 01:07:45 +0200238 except Exception as msg:
cliechtif1c882c2009-07-23 02:30:06 +0000239 self.handle_serial_error(msg)
240
241 def handle_serial_error(self, error=None):
242 """Serial port error"""
243 # terminate connection
244 self.close()
245
246 def handle_socket_read(self):
247 """Read from socket"""
248 try:
249 # read a chunk from the serial port
250 data = self.socket.recv(1024)
251 if data:
cliechti32c10332009-08-05 13:23:43 +0000252 # Process RFC 2217 stuff when enabled
253 if self.rfc2217:
254 data = serial.to_bytes(self.rfc2217.filter(data))
cliechtif1c882c2009-07-23 02:30:06 +0000255 # add data to buffer
256 self.buffer_net2ser += data
257 else:
258 # empty read indicates disconnection
259 self.handle_disconnect()
260 except socket.error:
261 self.handle_socket_error()
262
263 def handle_socket_write(self):
264 """Write to socket"""
265 try:
266 # write a chunk
Chris Liechti01587b12015-08-05 02:39:32 +0200267 count = self.socket.send(bytes(self.buffer_ser2net))
cliechtif1c882c2009-07-23 02:30:06 +0000268 # and remove the sent data from the buffer
269 self.buffer_ser2net = self.buffer_ser2net[count:]
270 except socket.error:
271 self.handle_socket_error()
272
273 def handle_socket_error(self):
274 """Socket connection fails"""
275 self.handle_disconnect()
276
277 def handle_connect(self):
278 """Server socket gets a connection"""
279 # accept a connection in any case, close connection
280 # below if already busy
281 connection, addr = self.server_socket.accept()
282 if self.socket is None:
283 self.socket = connection
Chris Liechti6e683ed2015-08-04 16:59:04 +0200284 # More quickly detect bad clients who quit without closing the
285 # connection: After 1 second of idle, start sending TCP keep-alive
286 # packets every 1 second. If 3 consecutive keep-alive packets
287 # fail, assume the client is gone and close the connection.
288 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
289 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1)
290 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 1)
291 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)
cliechtif1c882c2009-07-23 02:30:06 +0000292 self.socket.setblocking(0)
cliechtia35cad42009-08-10 20:57:48 +0000293 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
Chris Liechti825637e2015-08-05 14:14:00 +0200294 if self.log is not None:
Chris Liechti977916f2015-08-08 17:12:53 +0200295 self.log.warning('%s: Connected by %s:%s' % (self.device, addr[0], addr[1]))
cliechtid9a06ce2009-08-10 01:30:53 +0000296 self.serial.setRTS(True)
297 self.serial.setDTR(True)
Chris Liechti825637e2015-08-05 14:14:00 +0200298 if self.log is not None:
299 self.rfc2217 = serial.rfc2217.PortManager(self.serial, self, logger=log.getChild(self.device))
300 else:
301 self.rfc2217 = serial.rfc2217.PortManager(self.serial, self)
cliechtif1c882c2009-07-23 02:30:06 +0000302 else:
303 # reject connection if there is already one
304 connection.close()
Chris Liechti825637e2015-08-05 14:14:00 +0200305 if self.log is not None:
Chris Liechti977916f2015-08-08 17:12:53 +0200306 self.log.warning('%s: Rejecting connect from %s:%s' % (self.device, addr[0], addr[1]))
cliechtif1c882c2009-07-23 02:30:06 +0000307
308 def handle_server_error(self):
cliechti7aed8332009-08-05 14:19:31 +0000309 """Socket server fails"""
cliechtif1c882c2009-07-23 02:30:06 +0000310 self.close()
311
312 def handle_disconnect(self):
313 """Socket gets disconnected"""
cliechtid9a06ce2009-08-10 01:30:53 +0000314 # signal disconnected terminal with control lines
cliechtie67c1f42009-09-10 15:07:44 +0000315 try:
316 self.serial.setRTS(False)
317 self.serial.setDTR(False)
318 finally:
319 # restore original port configuration in case it was changed
320 self.serial.applySettingsDict(self.serial_settings_backup)
321 # stop RFC 2217 state machine
322 self.rfc2217 = None
323 # clear send buffer
Chris Liechti01587b12015-08-05 02:39:32 +0200324 self.buffer_ser2net = bytearray()
cliechtie67c1f42009-09-10 15:07:44 +0000325 # close network connection
326 if self.socket is not None:
327 self.socket.close()
328 self.socket = None
Chris Liechti825637e2015-08-05 14:14:00 +0200329 if self.log is not None:
Chris Liechti977916f2015-08-08 17:12:53 +0200330 self.log.warning('%s: Disconnected' % self.device)
cliechtif1c882c2009-07-23 02:30:06 +0000331
332
333def test():
334 service = ZeroconfService(name="TestService", port=3000)
335 service.publish()
336 raw_input("Press any key to unpublish the service ")
337 service.unpublish()
338
339
340if __name__ == '__main__':
Chris Liechti01587b12015-08-05 02:39:32 +0200341 import logging
cliechtif1c882c2009-07-23 02:30:06 +0000342 import optparse
343
Chris Liechti825637e2015-08-05 14:14:00 +0200344 VERBOSTIY = [
345 logging.ERROR, # 0
346 logging.WARNING, # 1 (default)
347 logging.INFO, # 2
348 logging.DEBUG, # 3
349 ]
350
cliechtif1c882c2009-07-23 02:30:06 +0000351 parser = optparse.OptionParser(usage="""\
352%prog [options]
353
354Announce the existence of devices using zeroconf and provide
cliechti32c10332009-08-05 13:23:43 +0000355a TCP/IP <-> serial port gateway (implements RFC 2217).
cliechtif1c882c2009-07-23 02:30:06 +0000356
357Note that the TCP/IP server is not protected. Everyone can connect
358to it!
359
360If running as daemon, write to syslog. Otherwise write to stdout.
361""")
362
Chris Liechti9d61c302015-08-05 14:35:16 +0200363 group = optparse.OptionGroup(parser, "Serial Port Settings")
cliechtif1c882c2009-07-23 02:30:06 +0000364
Chris Liechtiab2ffc42015-08-14 19:36:38 +0200365 group.add_option("--ports-regex",
Chris Liechti9d61c302015-08-05 14:35:16 +0200366 dest="ports_regex",
367 help="specify a regex to search against the serial devices and their descriptions (default: %default)",
368 default='/dev/ttyUSB[0-9]+',
369 metavar="REGEX")
Chris Liechti825637e2015-08-05 14:14:00 +0200370
Chris Liechti9d61c302015-08-05 14:35:16 +0200371 parser.add_option_group(group)
cliechtif1c882c2009-07-23 02:30:06 +0000372
Chris Liechti9d61c302015-08-05 14:35:16 +0200373 group = optparse.OptionGroup(parser, "Network Settings")
cliechtif1c882c2009-07-23 02:30:06 +0000374
Chris Liechtiab2ffc42015-08-14 19:36:38 +0200375 group.add_option("--tcp-port",
Chris Liechti7225dd72015-08-04 17:08:05 +0200376 dest="base_port",
377 help="specify lowest TCP port number (default: %default)",
378 default=7000,
379 type='int',
380 metavar="PORT")
381
Chris Liechti9d61c302015-08-05 14:35:16 +0200382 parser.add_option_group(group)
383
384 group = optparse.OptionGroup(parser, "Daemon")
385
386 group.add_option("-d", "--daemon",
387 dest="daemonize",
388 action="store_true",
389 help="start as daemon",
390 default=False)
391
Chris Liechtiab2ffc42015-08-14 19:36:38 +0200392 group.add_option("--pidfile",
Chris Liechti9d61c302015-08-05 14:35:16 +0200393 dest="pid_file",
394 help="specify a name for the PID file",
395 default=None,
396 metavar="FILE")
397
398 parser.add_option_group(group)
399
400 group = optparse.OptionGroup(parser, "Diagnostics")
401
402 group.add_option("-o", "--logfile",
403 dest="log_file",
404 help="write messages file instead of stdout",
405 default=None,
406 metavar="FILE")
407
408 group.add_option("-q", "--quiet",
409 dest="verbosity",
410 action="store_const",
411 const=0,
412 help="suppress most diagnostic messages",
413 default=False)
414
415 group.add_option("-v", "--verbose",
416 dest="verbosity",
417 action="count",
418 help="increase diagnostic messages",
419 default=1)
420
421 parser.add_option_group(group)
Chris Liechti6e683ed2015-08-04 16:59:04 +0200422
cliechtif1c882c2009-07-23 02:30:06 +0000423 (options, args) = parser.parse_args()
424
Chris Liechti825637e2015-08-05 14:14:00 +0200425 # set up logging
426 logging.basicConfig(level=VERBOSTIY[min(options.verbosity, len(VERBOSTIY) - 1)])
427 log = logging.getLogger('port_publisher')
Chris Liechti01587b12015-08-05 02:39:32 +0200428
cliechtif1c882c2009-07-23 02:30:06 +0000429 # redirect output if specified
430 if options.log_file is not None:
431 class WriteFlushed:
432 def __init__(self, fileobj):
433 self.fileobj = fileobj
434 def write(self, s):
435 self.fileobj.write(s)
436 self.fileobj.flush()
437 def close(self):
438 self.fileobj.close()
Chris Liechti825637e2015-08-05 14:14:00 +0200439 sys.stdout = sys.stderr = WriteFlushed(open(options.log_file, 'a'))
cliechtif1c882c2009-07-23 02:30:06 +0000440 # atexit.register(lambda: sys.stdout.close())
441
442 if options.daemonize:
443 # if running as daemon is requested, do the fork magic
444 # options.quiet = True
445 import pwd
446 # do the UNIX double-fork magic, see Stevens' "Advanced
447 # Programming in the UNIX Environment" for details (ISBN 0201563177)
448 try:
449 pid = os.fork()
450 if pid > 0:
451 # exit first parent
452 sys.exit(0)
Chris Liechti4caf6a52015-08-04 01:07:45 +0200453 except OSError as e:
Chris Liechti825637e2015-08-05 14:14:00 +0200454 log.critical("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
cliechtif1c882c2009-07-23 02:30:06 +0000455 sys.exit(1)
456
457 # decouple from parent environment
458 os.chdir("/") # don't prevent unmounting....
459 os.setsid()
460 os.umask(0)
461
462 # do second fork
463 try:
464 pid = os.fork()
465 if pid > 0:
Chris Liechti9d61c302015-08-05 14:35:16 +0200466 # exit from second parent, save eventual PID before
cliechtif1c882c2009-07-23 02:30:06 +0000467 if options.pid_file is not None:
Chris Liechti9d61c302015-08-05 14:35:16 +0200468 open(options.pid_file, 'w').write("%d" % pid)
cliechtif1c882c2009-07-23 02:30:06 +0000469 sys.exit(0)
Chris Liechti4caf6a52015-08-04 01:07:45 +0200470 except OSError as e:
Chris Liechti825637e2015-08-05 14:14:00 +0200471 log.critical("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
cliechtif1c882c2009-07-23 02:30:06 +0000472 sys.exit(1)
473
474 if options.log_file is None:
475 import syslog
476 syslog.openlog("serial port publisher")
477 # redirect output to syslog
478 class WriteToSysLog:
479 def __init__(self):
480 self.buffer = ''
481 def write(self, s):
482 self.buffer += s
483 if '\n' in self.buffer:
484 output, self.buffer = self.buffer.split('\n', 1)
485 syslog.syslog(output)
486 def flush(self):
487 syslog.syslog(self.buffer)
488 self.buffer = ''
489 def close(self):
490 self.flush()
491 sys.stdout = sys.stderr = WriteToSysLog()
492
493 # ensure the that the daemon runs a normal user, if run as root
494 #if os.getuid() == 0:
495 # name, passwd, uid, gid, desc, home, shell = pwd.getpwnam('someuser')
496 # os.setgid(gid) # set group first
497 # os.setuid(uid) # set user
498
499 # keep the published stuff in a dictionary
500 published = {}
cliechtif1c882c2009-07-23 02:30:06 +0000501 # get a nice hostname
502 hostname = socket.gethostname()
503
504 def unpublish(forwarder):
505 """when forwarders die, we need to unregister them"""
506 try:
507 del published[forwarder.device]
508 except KeyError:
509 pass
510 else:
Chris Liechti825637e2015-08-05 14:14:00 +0200511 log.info("unpublish: %s" % (forwarder))
cliechtif1c882c2009-07-23 02:30:06 +0000512
513 alive = True
514 next_check = 0
515 # main loop
516 while alive:
517 try:
518 # if it is time, check for serial port devices
519 now = time.time()
520 if now > next_check:
521 next_check = now + 5
Chris Liechti6e683ed2015-08-04 16:59:04 +0200522 connected = [d for d, p, i in serial.tools.list_ports.grep(options.ports_regex)]
523 # Handle devices that are published, but no longer connected
524 for device in set(published).difference(connected):
Chris Liechti825637e2015-08-05 14:14:00 +0200525 log.info("unpublish: %s" % (published[device]))
Chris Liechti6e683ed2015-08-04 16:59:04 +0200526 unpublish(published[device])
527 # Handle devices that are connected but not yet published
528 for device in set(connected).difference(published):
529 # Find the first available port, starting from 7000
Chris Liechti7225dd72015-08-04 17:08:05 +0200530 port = options.base_port
Chris Liechti6e683ed2015-08-04 16:59:04 +0200531 ports_in_use = [f.network_port for f in published.values()]
532 while port in ports_in_use:
533 port += 1
534 published[device] = Forwarder(
Chris Liechti9d61c302015-08-05 14:35:16 +0200535 device,
536 "%s on %s" % (device, hostname),
537 port,
538 on_close=unpublish,
539 log=log
Chris Liechti6e683ed2015-08-04 16:59:04 +0200540 )
Chris Liechti825637e2015-08-05 14:14:00 +0200541 log.warning("publish: %s" % (published[device]))
Chris Liechti6e683ed2015-08-04 16:59:04 +0200542 published[device].open()
cliechtif1c882c2009-07-23 02:30:06 +0000543
544 # select_start = time.time()
545 read_map = {}
546 write_map = {}
547 error_map = {}
548 for publisher in published.values():
549 publisher.update_select_maps(read_map, write_map, error_map)
Chris Liechti8f9b4442015-08-05 14:57:30 +0200550 readers, writers, errors = select.select(
551 read_map.keys(),
552 write_map.keys(),
553 error_map.keys(),
554 5
555 )
cliechtif1c882c2009-07-23 02:30:06 +0000556 # select_end = time.time()
557 # print "select used %.3f s" % (select_end - select_start)
558 for reader in readers:
559 read_map[reader]()
560 for writer in writers:
561 write_map[writer]()
562 for error in errors:
563 error_map[error]()
564 # print "operation used %.3f s" % (time.time() - select_end)
565 except KeyboardInterrupt:
566 alive = False
Chris Liechti7225dd72015-08-04 17:08:05 +0200567 sys.stdout.write('\n')
cliechtif1c882c2009-07-23 02:30:06 +0000568 except SystemExit:
569 raise
570 except:
cliechtie67c1f42009-09-10 15:07:44 +0000571 #~ raise
cliechtif1c882c2009-07-23 02:30:06 +0000572 traceback.print_exc()