def post(self, connection): # Add to queue (POST). if 'file' not in connection.post[1] or 'port' not in connection.post[0] or 'action' not in connection.post[0]: self.reply(connection, 400) return False port = connection.post[0]['port'][0] action = connection.post[0]['action'][0] if port not in ports or not ports[port]: log('port not found: %s' % port) self.reply(connection, 404) return False post = connection.post[1].pop('file') def cb(success, ret): self.reply(connection, 200 if success else 400, '' if ret is None else ret.encode('utf8'), 'text/plain;charset=utf8') os.unlink(post[0]); connection.socket.close() if action == 'queue_add': ports[port].call('queue_add_file', [connection.data['role'], post[0], post[1]], {}, cb) elif action == 'audio_add': ports[port].call('audio_add_file', [connection.data['role'], post[0], post[1]], {}, cb) elif action == 'import': ports[port].call('import_file', [connection.data['role'], post[0], post[1]], {}, cb) else: os.unlink(post[0]); self.reply(connection, 400) return False return True
def remove_port(cls, port): # {{{ log('removing port %s' % port) if port not in ports: return if ports[port]: # Close serial port, in case it still exists. cls._disable('admin', port) del ports[port] cls._broadcast(None, 'del_port', port)
def detect(cls, port): # {{{ resumeinfo = [(yield), None] log('detecting printer on %s' % port) if port not in ports or ports[port] != None: log('port is not in detectable state') return ports[port] = False c = websockets.call(resumeinfo, detect, port) while c(): c.args = (yield websockets.WAIT)
def get_vars(success, vars): if not success: log('failed to get vars') return # The child has opened the port now; close our handle. if detectport is not None: log('Driver started; closing server port') detectport.close() self.uuid = vars['uuid'] # Copy settings from orphan with the same run_id, then kill the orphan. if self.run_id in orphans and orphans[self.run_id].uuid == self.uuid: orphans[self.run_id].call('admin', 'export_settings', (), {}, get_settings)
def call(self, name, args, kargs, cb): # {{{ data = json.dumps([self.next_mid, name, args, kargs]) + '\n' #log('calling %s on %d' % (repr(data), self.process.stdin.fileno())) try: self.process.stdin.write(data) except: log('killing printer handle because of IOError') #traceback.print_exc() cb(False, None) Connection._disable('admin', self.port) return self.waiters[0][self.next_mid] = cb self.next_mid += 1
def add_port(cls, port): # {{{ resumeinfo = [(yield), None] if port in ports: log('already existing port %s cannot be added' % port) return if re.match(config['blacklist'], port) or re.match(config['add-blacklist'], port): #log('skipping blacklisted port %s' % port) return ports[port] = None cls._broadcast(None, 'new_port', port); if autodetect: c = websockets.call(resumeinfo, cls.detect, port) while c(): c.args = (yield websockets.WAIT)
def get_settings(success, settings): if not success: log('failed to get settings') return self.call('import_settings', ['admin', settings], {}, lambda success, ret: None) GLib.source_remove(orphans[self.run_id].input_handle) orphans[self.run_id].call('die', ('admin', 'replaced by new connection',), {}, lambda success, ret: None) try: orphans[self.run_id].process.kill() orphans[self.run_id].process.communicate() except OSError: pass del orphans[self.run_id]
def print_done(port, completed, reason): # {{{ Connection._broadcast(None, 'printing', port.port, False) if config['done']: cmd = config['done'] cmd = cmd.replace('[[STATE]]', 'completed' if completed else 'aborted').replace('[[REASON]]', reason) log('running %s' % cmd) p = subprocess.Popen(cmd, stdout = subprocess.PIPE, shell = True, close_fds = True) def process_done(fd, cond): data = p.stdout.read() if data: return True log('Flashing done; return: %s' % repr(p.wait())) return False GLib.io_add_watch(p.stdout.fileno(), GLib.IO_IN, process_done)
def timeout(): id[1] += 1 if id[1] >= 30: # Timeout. Give up. GLib.source_remove(watcher) printer.close() log('Timeout waiting for printer on port %s; giving up.' % port) ports[port] = None return False if not id[2]: printer.write(protocol.single['ID']) else: id[2] = False return True
def output(fd, cond): d = '' try: d = process.stdout.read() except: data[0] += '\nError writing %s firmware: ' % board + traceback.format_exc() log(repr(data[0])) resumeinfo[0](data[0]) return False if d != '': self._broadcast(None, 'message', port, '\n'.join(data[0].split('\n')[-4:])) data[0] += d return True resumeinfo[0](data[0]) return False
def _call (self, name, a, ka): # {{{ resumeinfo = [(yield), None] #log('other: %s %s %s' % (name, repr(a), repr(ka))) if not self.printer or self.printer not in ports or not ports[self.printer]: self.printer = self.find_printer() if not self.printer: log('No printer found') yield ('error', 'No printer found') def reply(success, ret): if success: resumeinfo[0](ret) else: log('printer errors') resumeinfo[0](None) #Connection._disable('admin', self.printer) ports[self.printer].call(name, (self.socket.data['role'],) + tuple(a), ka, reply) yield (yield websockets.WAIT)
def upload(self, port, board): # {{{ assert self.socket.data['role'] in ('benjamin', 'admin') assert ports[port] is None resumeinfo = [(yield), None] sudo, brd, protocol, baudrate, mcu = self._get_info(board) self.disable(port) data = [''] filename = fhs.read_data(os.path.join('firmware', brd + '.hex'), opened = False) command = sudo + (config['avrdude'], '-q', '-q', '-c', protocol) + baudrate + ('-p', mcu, '-P', port, '-U', 'flash:w:' + filename + ':i') log('Flashing firmware: ' + ' '.join(command)) process = subprocess.Popen(command, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, close_fds = True) def output(fd, cond): d = '' try: d = process.stdout.read() except: data[0] += '\nError writing %s firmware: ' % board + traceback.format_exc() log(repr(data[0])) resumeinfo[0](data[0]) return False if d != '': self._broadcast(None, 'message', port, '\n'.join(data[0].split('\n')[-4:])) data[0] += d return True resumeinfo[0](data[0]) return False fl = fcntl.fcntl(process.stdout.fileno(), fcntl.F_GETFL) fcntl.fcntl(process.stdout.fileno(), fcntl.F_SETFL, fl | os.O_NONBLOCK) GLib.io_add_watch(process.stdout, GLib.IO_IN | GLib.IO_PRI | GLib.IO_HUP, output) self._broadcast(None, 'blocked', port, 'uploading firmware for %s' % board) self._broadcast(None, 'message', port, '') d = (yield websockets.WAIT) try: process.kill() # In case it wasn't dead yet. except OSError: pass process.communicate() # Clean up. self._broadcast(None, 'blocked', port, None) self._broadcast(None, 'message', port, '') if autodetect: websockets.call(None, self.detect, port)() if d: yield ('firmware upload for %s: ' % board + d) else: yield ('firmware for %s successfully uploaded' % board)
def _broadcast(cls, target, name, *args): # {{{ if target is not None: #log('broadcasting to target %d' % target) if target not in Connection.connections: log('ignoring targeted broadcast of %s to missing connection %d' % (repr((name, args)), target)) return target = Connection.connections[target].socket if target.monitor: #log('%s %s' % (name, repr(args))) getattr(target, name).event(*args) else: log("not broadcasting to target, because it isn't set to monitor") elif httpd: #log('broadcasting to all: %s' % repr((name, args))) for c in httpd.websockets: if c.monitor and c.initialized: #log('broadcasting to one') getattr(c, name).event(*args)
def printer_input(self, fd, cond): # {{{ line = self.process.stdout.readline() if line == '': log('%s died.' % self.name) self.process.communicate() # Clean up the zombie. for t in range(3): for w in self.waiters[t]: self.waiters[t][w](False, 'Printer died') Connection._disable('admin', self.port) return False data = json.loads(line) #log('printer input:' + repr(data)) if data[1] == 'broadcast': Connection._broadcast(data[2], data[3], self.port, *(data[4:])) elif data[1] == 'disconnect': # Don't remember a printer that hasn't sent its name yet. port = self.port ports[self.port] = None # If there already is an orphan with the same uuid, kill the old orphan. for o in [x for x in orphans if x[0] == self.uuid]: # This for loop always runs 0 or 1 times, never more. log('killing duplicate orphan') del orphans[x] orphans[self.run_id] = self Connection._broadcast(None, 'del_printer', port) if autodetect: websockets.call(None, Connection.detect, self.port)() elif data[1] == 'error': if data[0] is None: # Error on command without id. log('error on command without id: %s' % repr(data)) else: self.waiters[0].pop(data[0])(False, data[2]) elif data[1] == 'return': self.waiters[0].pop(data[0])(True, data[2]) elif data[1] == 'movecb': self.waiters[1].pop(data[0])(True, data[2]) elif data[1] == 'tempcb': self.waiters[2].pop(data[0])(True, data[2]) else: raise AssertionError('invalid reply from printer process: %s' % repr(data)) return True
def check(packet): num = (len(packet) + 3) // 4 l = len(packet) - num for t in range(num): s = packet[l + t] if s & 7 != t: log('bad index %x %x %x' % (s, l, t)) return False for bit in range(5): check = 0 for p in range(3): check ^= (packet[3 * t + p] if 3 * t + p < l + t else 0) & mask[bit][p] check ^= s & mask[bit][3] check ^= check >> 4 check ^= check >> 2 check ^= check >> 1 if check & 1 != 0: log('bad checksum') return False log('good') return True
if config['local'] != '': websockets.call(None, Connection.add_port, '-')() # Assume a GNU/Linux system; if you have something else, you need to come up with a way to iterate over all your serial ports and implement it here. Patches welcome, especially if they are platform-independent. try: # Try Linux sysfs. for tty in os.listdir('/sys/class/tty'): websockets.call(None, Connection.add_port, '/dev/' + tty)() except: # Try more generic approach. Don't use this by default, because it doesn't detect all ports on GNU/Linux. try: import serial.tools.list_ports for tty in serial.tools.list_ports.comports(): websockets.call(None, Connection.add_port, tty[0])() except: traceback.print_exc() log('Not probing serial ports, because an error occurred: %s' % sys.exc_info()[1]) # Set default printer. {{{ if ' ' in config['printer']: default_printer = config['printer'].rsplit(' ', 1) else: default_printer = (config['printer'], None) # }}} httpd = Server(config['port'], Connection, disconnect_cb = Connection.disconnect, httpdirs = fhs.read_data('html', dir = True, multiple = True), address = config['address'], log = config['log'], tls = tls) log('running') websockets.fgloop()
def process_done(fd, cond): data = p.stdout.read() if data: return True log('Flashing done; return: %s' % repr(p.wait())) return False
def reply(success, ret): if success: resumeinfo[0](ret) else: log('printer errors') resumeinfo[0](None)
def close_port(success, data): log('reconnect complete; closing server port') printer.close()
def boot_printer_input(fd, cond): id[2] = True ids = [protocol.single[code] for code in ('ID', 'STARTUP')] # CMD:1 ID:8 Checksum:3 while len(id[0]) < 12: data = printer.read(12 - len(id[0])) id[0] += data #log('incomplete id: ' + id[0]) if len(id[0]) < 12: return True if id[0][0] not in ids or not protocol.check(map(ord, id[0])): log('skip non-id: %s (%s)' % (''.join('%02x' % ord(x) for x in id[0]), repr(id[0]))) f = len(id[0]) for start in ids: if start in id[0][1:]: p = id[0].index(start) if p < f: f = p log('Keeping some') break else: id[0] = '' id[0] = id[0][f:] return True # We have something to handle; cancel the timeout, but keep the serial port open to avoid a reset. (I don't think this even works, but it doesn't hurt.) GLib.source_remove(timeout_handle) # This printer was running and tried to send an id. Check the id. id[0] = id[0][1:9] if id[0] in orphans: log('accepting orphan %s on %s' % (id[0], port)) ports[port] = orphans.pop(id[0]) ports[port].port = port def close_port(success, data): log('reconnect complete; closing server port') printer.close() log('reconnecting %s' % port) ports[port].call('reconnect', ['admin', port], {}, lambda success, ret: ports[port].call('send_printer', ['admin', None], {}, close_port) if success else close_port) return False run_id = nextid() log('accepting unknown printer on port %s (id %s)' % (port, ''.join('%02x' % ord(x) for x in run_id))) log('orphans: %s' % repr(orphans.keys())) process = subprocess.Popen((config['driver'], '--cdriver', config['cdriver'], '--port', port, '--run-id', run_id, '--allow-system', config['allow-system']) + (('--system',) if fhs.is_system else ()), stdin = subprocess.PIPE, stdout = subprocess.PIPE, close_fds = True) ports[port] = Port(port, process, printer, run_id) return False
def detect(port): # {{{ if port == '-' or port.startswith('!'): run_id = nextid() process = subprocess.Popen((config['driver'], '--cdriver', config['local'] or config['cdriver'], '--port', port, '--run-id', run_id, '--allow-system', config['allow-system']) + (('--system',) if fhs.is_system else ()), stdin = subprocess.PIPE, stdout = subprocess.PIPE, close_fds = True) ports[port] = Port(port, process, None, run_id) return False if not os.path.exists(port): log("not detecting on %s, because file doesn't exist." % port) return False log('detecting on %s' % port) if config['predetect']: subprocess.call(config['predetect'].replace('#PORT#', port), shell = True) try: printer = serial.Serial(port, baudrate = 115200, timeout = 0) except serial.SerialException: log('failed to open serial port.') traceback.print_exc(); return False # We need to get the printer id first. If the printer is booting, this can take a while. id = [None, None, None, None] # data, timeouts, had data # Wait to make sure the command is interpreted as a new packet. def part2(): id[0] = '' id[1] = 0 id[2] = False def timeout(): id[1] += 1 if id[1] >= 30: # Timeout. Give up. GLib.source_remove(watcher) printer.close() log('Timeout waiting for printer on port %s; giving up.' % port) ports[port] = None return False if not id[2]: printer.write(protocol.single['ID']) else: id[2] = False return True def boot_printer_input(fd, cond): id[2] = True ids = [protocol.single[code] for code in ('ID', 'STARTUP')] # CMD:1 ID:8 Checksum:3 while len(id[0]) < 12: data = printer.read(12 - len(id[0])) id[0] += data #log('incomplete id: ' + id[0]) if len(id[0]) < 12: return True if id[0][0] not in ids or not protocol.check(map(ord, id[0])): log('skip non-id: %s (%s)' % (''.join('%02x' % ord(x) for x in id[0]), repr(id[0]))) f = len(id[0]) for start in ids: if start in id[0][1:]: p = id[0].index(start) if p < f: f = p log('Keeping some') break else: id[0] = '' id[0] = id[0][f:] return True # We have something to handle; cancel the timeout, but keep the serial port open to avoid a reset. (I don't think this even works, but it doesn't hurt.) GLib.source_remove(timeout_handle) # This printer was running and tried to send an id. Check the id. id[0] = id[0][1:9] if id[0] in orphans: log('accepting orphan %s on %s' % (id[0], port)) ports[port] = orphans.pop(id[0]) ports[port].port = port def close_port(success, data): log('reconnect complete; closing server port') printer.close() log('reconnecting %s' % port) ports[port].call('reconnect', ['admin', port], {}, lambda success, ret: ports[port].call('send_printer', ['admin', None], {}, close_port) if success else close_port) return False run_id = nextid() log('accepting unknown printer on port %s (id %s)' % (port, ''.join('%02x' % ord(x) for x in run_id))) log('orphans: %s' % repr(orphans.keys())) process = subprocess.Popen((config['driver'], '--cdriver', config['cdriver'], '--port', port, '--run-id', run_id, '--allow-system', config['allow-system']) + (('--system',) if fhs.is_system else ()), stdin = subprocess.PIPE, stdout = subprocess.PIPE, close_fds = True) ports[port] = Port(port, process, printer, run_id) return False printer.write(protocol.single['ID']) timeout_handle = GLib.timeout_add(500, timeout) watcher = GLib.io_add_watch(printer.fileno(), GLib.IO_IN, boot_printer_input) # Wait at least a second before sending anything, otherwise the bootloader thinks we might be trying to reprogram it. # This is only a problem for RAMPS; don't wait for ports that cannot be RAMPS. if 'ACM' in port: GLib.timeout_add(1500, part2) else: part2()