def _tls_init(self): # Set up members for using tls, if requested. if self.tls in (False, '-'): self.tls = False return if self.tls in (None, True, ''): self.tls = fhs.module_get_config('network')['tls'] if self.tls == '': self.tls = socket.getfqdn() elif self.tls == '-': self.tls = False return # Use tls. fc = fhs.read_data(os.path.join('certs', self.tls + os.extsep + 'pem'), opened = False, packagename = 'network') fk = fhs.read_data(os.path.join('private', self.tls + os.extsep + 'key'), opened = False, packagename = 'network') if fc is None or fk is None: # Create new self-signed certificate. certfile = fhs.write_data(os.path.join('certs', self.tls + os.extsep + 'pem'), opened = False, packagename = 'network') csrfile = fhs.write_data(os.path.join('csr', self.tls + os.extsep + 'csr'), opened = False, packagename = 'network') for p in (certfile, csrfile): path = os.path.dirname(p) if not os.path.exists(path): os.makedirs(path) keyfile = fhs.write_data(os.path.join('private', self.tls + os.extsep + 'key'), opened = False, packagename = 'network') path = os.path.dirname(keyfile) if not os.path.exists(path): os.makedirs(path, 0o700) os.system('openssl req -x509 -nodes -days 3650 -newkey rsa:4096 -subj "/CN=%s" -keyout "%s" -out "%s"' % (self.tls, keyfile, certfile)) os.system('openssl req -subj "/CN=%s" -new -key "%s" -out "%s"' % (self.tls, keyfile, csrfile)) fc = fhs.read_data(os.path.join('certs', self.tls + os.extsep + 'pem'), opened = False, packagename = 'network') fk = fhs.read_data(os.path.join('private', self.tls + os.extsep + 'key'), opened = False, packagename = 'network') self._tls_cert = fc self._tls_key = fk
def _read_boards(self): boards = {} for d in fhs.read_data('hardware', packagename='arduino', dir=True, multiple=True): for board in os.listdir(d): boards_txt = os.path.join(d, board, 'boards' + os.extsep + 'txt') if not os.path.exists(boards_txt): continue with open(boards_txt) as b: for line in b: if line.startswith('#') or line.strip() == '': continue parse = re.match('([^.=]+)\.([^=]+)=(.*)$', line.strip()) if parse is None: log('Warning: invalid line in %s: %s' % (boards_txt, line.strip())) continue tag, option, value = parse.groups() if tag not in boards: boards[tag] = {} if option in boards[tag]: if boards[tag][option] != value: log('%s: duplicate tag %s.%s with different value (%s != %s); using %s' % (boards_txt, tag, option, value, boards[tag][option], boards[tag][option])) continue boards[tag][option] = value for tag in tuple(boards.keys()): if 'name' not in boards[tag]: boards[tag]['name'] = tag if any(x not in boards[tag] for x in ('upload.protocol', 'upload.speed', 'build.mcu', 'upload.maximum_size')): log('skipping %s because hardware information is incomplete (%s)' % (boards[tag]['name'], repr(boards[tag]))) del boards[tag] continue if int(boards[tag]['upload.maximum_size']) < 32000: # Not enough memory; don't complain about skipping this board. del boards[tag] continue if fhs.read_data(os.path.join( 'firmware', boards[tag]['build.mcu'] + os.extsep + 'hex'), opened=False) is None: log('skipping %s because firmware for %s is not installed' % (boards[tag]['name'], boards[tag]['build.mcu'])) del boards[tag] continue return boards
def _get_command(self, board, port): # {{{ if board == 'bbbmelzi ': return ('sudo', fhs.read_data(os.path.join('bb', 'flash-bb-0'), opened = False), fhs.read_data(os.path.join('bb', 'avrdude.conf'), opened = False), fhs.read_data(os.path.join('firmware', 'atmega1284p' + os.extsep + 'hex'), opened = False)) if board == 'bb4melzi ': return ('sudo', fhs.read_data(os.path.join('bb', 'flash-bb-4'), opened = False), fhs.read_data(os.path.join('bb', 'avrdude.conf'), opened = False), fhs.read_data(os.path.join('firmware', 'atmega1284p' + os.extsep + 'hex'), opened = False)) boards = read_boards() if board not in boards: raise ValueError('board type not supported') filename = fhs.read_data(os.path.join('firmware', boards[board]['build.mcu'] + os.extsep + 'hex'), opened = False) if filename is None: raise NotImplementedError('Firmware is not available') return ('avrdude', '-D', '-q', '-q', '-p', boards[board]['build.mcu'], '-C', '/etc/avrdude.conf', '-b', boards[board]['upload.speed'], '-c', boards[board]['upload.protocol'], '-P', port, '-U', 'flash:w:' + filename + ':i')
def create_printer(uuid=None): # {{{ if uuid is None: uuid = protocol.new_uuid() process = subprocess.Popen( (fhs.read_data('driver.py', opened=False), '--uuid', uuid, '--cdriver', fhs.read_data('franklin-cdriver', opened=False), '--allow-system', config['allow-system']) + (('--system', ) if fhs.is_system else ()) + (('--arc', 'False') if not config['arc'] else ()), stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True) printers[uuid] = Printer(None, process, None, None) return uuid
def _get_command(self, board, port): # {{{ if board == 'bbbmelzi ': return ('sudo', fhs.read_data('flash-bbb', opened=False), fhs.read_data('avrdude.conf', opened=False), fhs.read_data(os.path.join( 'firmware', 'atmega1284p' + os.extsep + 'hex'), opened=False)) if board == 'bb4melzi ': return ('sudo', fhs.read_data('flash-bb-O4', opened=False), fhs.read_data('avrdude.conf', opened=False), fhs.read_data(os.path.join( 'firmware', 'atmega1284p' + os.extsep + 'hex'), opened=False)) boards = self._read_boards() if board not in boards: raise ValueError('board type not supported') filename = fhs.read_data(os.path.join( 'firmware', boards[board]['build.mcu'] + os.extsep + 'hex'), opened=False) if filename is None: raise NotImplementedError('Firmware is not available') return ('avrdude', '-q', '-q', '-p', boards[board]['build.mcu'], '-b', boards[board]['upload.speed'], '-c', boards[board]['upload.protocol'], '-P', port, '-U', 'flash:w:' + filename + ':i')
def clear_cache(): basedirs = fhs.read_data('db', dir = True, opened = False, multiple = True) cachefile = os.path.join(basedirs[0],"cache.pickle"); if os.path.exists(cachefile): logging.info ("Clearing cache file {}".format(os.path.join(basedirs[0],"cache.pickle"))) try: os.remove(cachefile) except Exception as e: logging.exception("Clearing cache failed",e)
def clear_cache(): basedirs = fhs.read_data('db', dir=True, opened=False, multiple=True) cachefile = os.path.join(basedirs[0], "cache.pickle") if os.path.exists(cachefile): logging.info("Clearing cache file {}".format( os.path.join(basedirs[0], "cache.pickle"))) try: os.remove(cachefile) except Exception as e: logging.exception("Clearing cache failed", e)
def create_machine(uuid=None): # {{{ if uuid is None: uuid = protocol.new_uuid() process = subprocess.Popen( (fhs.read_data('driver.py', opened=False), '--uuid', uuid, '--allow-system', config['allow-system']) + (('--system', ) if fhs.is_system else ()), stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True) machines[uuid] = Machine(None, process, None) return uuid
def read_boards(): # {{{ boards = {} for d in fhs.read_data('hardware', packagename = 'arduino', dir = True, multiple = True): for board in os.listdir(d): boards_txt = os.path.join(d, board, 'boards' + os.extsep + 'txt') if not os.path.exists(boards_txt): continue with open(boards_txt) as b: for line in b: if line.startswith('#') or line.strip() == '': continue parse = re.match('([^.=]+)\.([^=]+)=(.*)$', line.strip()) if parse is None: log('Warning: invalid line in %s: %s' % (boards_txt, line.strip())) continue tag, option, value = parse.groups() if tag not in boards: boards[tag] = {} if option in boards[tag]: if boards[tag][option] != value: log('%s: duplicate tag %s.%s with different value (%s != %s); using %s' % (boards_txt, tag, option, value, boards[tag][option], boards[tag][option])) continue boards[tag][option] = value for tag in tuple(boards.keys()): if 'name' not in boards[tag]: boards[tag]['name'] = tag if any(x not in boards[tag] for x in ('upload.protocol', 'upload.speed', 'build.mcu', 'upload.maximum_size')): #log('skipping %s because hardware information is incomplete (%s)' % (boards[tag]['name'], repr(boards[tag]))) del boards[tag] continue if int(boards[tag]['upload.maximum_size']) < 30000: # Not enough memory; don't complain about skipping this board. del boards[tag] continue if fhs.read_data(os.path.join('firmware', boards[tag]['build.mcu'] + os.extsep + 'hex'), opened = False) is None: #log('skipping %s because firmware for %s is not installed' % (boards[tag]['name'], boards[tag]['build.mcu'])) del boards[tag] continue return boards
def read(use_cache=True, unique_names=False): '''Read all db files from all fhs data directories (and pwd) Return list of tracks.''' # db = list of tracks. # track = { 'name': name, 'files': list of (filename, offset), 'announcefiles' : list of (filename, offset),'fragments': list of fragments and groups, 'end': time } # fragment = ( 'fragment', name, start_time ) # group = ( 'group', name, list of fragments and groups ) tracks = [] basedirs = fhs.read_data('db', dir=True, opened=False, multiple=True) if use_cache: try: import pickle logging.info("Trying to use cache from {}".format( os.path.join(basedirs[0], "cache.pickle"))) tracks = pickle.load( open(os.path.join(basedirs[0], "cache.pickle"), 'rb')) except Exception as e: #TODO: Narrow exception class. logging.info("Cache not found") if len(tracks) > 1: return tracks # Parse all fragments files. used = set() usednames = set() for dirname in basedirs: for root, dirs, files in os.walk(dirname, followlinks=True): if FRAGMENTS in files: if makepath(root, FRAGMENTS) in used: continue used.add(makepath(root, FRAGMENTS)) parse_fragments(root, tracks, used, usednames) # Add all other files. for dirname in basedirs: for root, dirs, files in os.walk(dirname, followlinks=True): root = os.path.abspath(root) for filename in files: if makepath(root, filename) in used: continue if os.path.splitext(filename)[1] not in exts: continue tracks.append(add_unfragmented_file(filename, root, usednames)) used.add(makepath(filename, root)) tracks = load_test_tracks(tracks) try: import pickle logging.info("Writing cache") pickle.dump(tracks, open(os.path.join(basedirs[0], "cache.pickle"), 'wb')) except Exception as e: logging.warning("Writing cache failed") logging.exception(e) return tracks
def boot_printer_input(): id[2] = True ids = [protocol.single[code][0] for code in ('ID', 'STARTUP')] # CMD:1 ID:8 Checksum:3 while len(id[0]) < 12: try: data = printer.read(12 - len(id[0])) except OSError: continue except IOError: continue 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(id[0]): log('skip non-id: %s (%s)' % (''.join('%02x' % 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(bytes((start,))) if p < f: f = p log('Keeping some') if f == 0: f = 1 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.) websocketd.remove_timeout(timeout_handle[0]) # 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' % (''.join('%02x' % x for x in 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) broadcast(None, 'port_state', port, 2) return False run_id = nextid() log('accepting unknown printer on port %s (id %s)' % (port, ''.join('%02x' % x for x in run_id))) #log('orphans: %s' % repr(tuple(orphans.keys()))) process = subprocess.Popen((fhs.read_data('driver.py', opened = False), '--cdriver', fhs.read_data('franklin-cdriver', opened = False), '--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 find_path(name, packagename): # {{{ '''Search name from environment, in current directory, and in user configuration.''' # Allow overriding with environment keys. d = os.getenv('GUI_PATH_' + packagename.upper()) if d is not None and os.path.exists(os.path.join(d, name)): return d d = os.getenv('GUI_PATH') if d is not None and os.path.exists(os.path.join(d, name)): return d ret = fhs.read_data(name, opened = False, packagename = packagename) if ret is not None: return ret # Give up. sys.stderr.write('Warning: gui definition for %s not found\n' % name) return None
def read(use_cache = True, unique_names = False): '''Read all db files from all fhs data directories (and pwd) Return list of tracks.''' # db = list of tracks. # track = { 'name': name, 'files': list of (filename, offset), 'announcefiles' : list of (filename, offset),'fragments': list of fragments and groups, 'end': time } # fragment = ( 'fragment', name, start_time ) # group = ( 'group', name, list of fragments and groups ) tracks = [] basedirs = fhs.read_data('db', dir = True, opened = False, multiple = True) if use_cache: try: import pickle logging.info ("Trying to use cache from {}".format(os.path.join(basedirs[0],"cache.pickle"))) tracks = pickle.load(open(os.path.join(basedirs[0],"cache.pickle"),'rb')) except Exception as e: #TODO: Narrow exception class. logging.info("Cache not found") if len(tracks) > 1: return tracks # Parse all fragments files. used = set() usednames = set() for dirname in basedirs: for root, dirs, files in os.walk(dirname, followlinks=True): if FRAGMENTS in files: if makepath(root, FRAGMENTS) in used: continue used.add(makepath(root, FRAGMENTS)) parse_fragments(root, tracks, used, usednames) # Add all other files. for dirname in basedirs: for root, dirs, files in os.walk(dirname, followlinks=True): root = os.path.abspath(root) for filename in files: if makepath(root, filename) in used: continue if os.path.splitext(filename)[1] not in exts: continue tracks.append(add_unfragmented_file(filename, root, usednames)) used.add(makepath(filename,root)) tracks = load_test_tracks(tracks) try: import pickle logging.info ("Writing cache") pickle.dump(tracks,open(os.path.join(basedirs[0],"cache.pickle"),'wb')) except Exception as e: logging.warning ("Writing cache failed") logging.exception (e) return tracks
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)
'tls': 'True', }) # }}} # Global variables. {{{ httpd = None default_printer = (None, None) ports = {} autodetect = config['autodetect'].lower() == 'true' tls = config['tls'].lower() == 'true' orphans = {} scripts = {} # }}} # Load scripts. {{{ for d in fhs.read_data('scripts', dir = True, multiple = True): for s in os.listdir(d): name, ext = os.path.splitext(s) if ext != os.extsep + 'js' or name in scripts: continue dataname = os.path.join(d, name + os.extsep + 'dat') scripts[name] = [open(os.path.join(d, s)).read(), open(dataname).read() if os.path.exists(dataname) else None] nextscriptname = 0 while '%04d' % nextscriptname in scripts: nextscriptname += 1 # }}} class Server(websocketd.RPChttpd): # {{{ def auth_message(self, connection, is_websocket): path = connection.address.path for extra in ('/', '/websocket'):
'tls': 'True', }) # }}} # Global variables. {{{ httpd = None default_printer = (None, None) ports = {} autodetect = config['autodetect'].lower() == 'true' tls = config['tls'].lower() == 'true' orphans = {} scripts = {} # }}} # Load scripts. {{{ for d in fhs.read_data('scripts', dir=True, multiple=True): for s in os.listdir(d): name, ext = os.path.splitext(s) if ext != os.extsep + 'js' or name in scripts: continue dataname = os.path.join(d, name + os.extsep + 'dat') scripts[name] = [ open(os.path.join(d, s)).read(), open(dataname).read() if os.path.exists(dataname) else None ] nextscriptname = 0 while '%04d' % nextscriptname in scripts: nextscriptname += 1 # }}}
def boot_machine_input(): id[2] = True ids = [protocol.single[code][0] for code in ('ID', 'STARTUP')] # CMD:1 ID:8 + 16 Checksum:9 Total: 34 while len(id[0]) < 34: try: data = machine.read(34 - len(id[0])) except OSError: continue except IOError: continue id[0] += data #log('incomplete id: ' + id[0]) if len(id[0]) < 34: if len(id[0]) > 0 and id[0][0] == protocol.single[ 'CONTROLLER'][0]: # This is a controller. Spawn the process, then cancel this detection. websocketd.remove_timeout(timeout_handle[0]) machine.close() ports[port] = None broadcast(None, 'port_state', port, 0) log('Starting controller driver on ' + port) env = os.environ.copy() env['PORT'] = port subprocess.Popen(config['controller'], env=env, shell=True) return False return True if id[0][0] not in ids or not protocol.check(id[0]): log('skip non-id: %s (%s)' % (''.join('%02x' % 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(bytes((start, )), 1) if p < f: f = p log('Keeping some') if f == 0: f = 1 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.) websocketd.remove_timeout(timeout_handle[0]) # This machine was running and tried to send an id. Check the id. uuid = id[0][9:9 + 16] if (uuid[7] & 0xf0) != 0x40 or (uuid[9] & 0xc0) != 0x80: # Broken uuid; create a new one and set it. log('broken uuid: ' + repr(uuid)) uuid = None else: uuid = ''.join('%02x' % x for x in uuid[:16]) uuid = uuid[:8] + '-' + uuid[8:12] + '-' + uuid[ 12:16] + '-' + uuid[16:20] + '-' + uuid[20:32] id[0] = id[0][1:9] running_machine = [ p for p in machines if machines[p].run_id == id[0] ] assert len(running_machine) < 2 if len(running_machine) > 0: p = running_machine[0] assert p.uuid == uuid if p.port is not None: disable( p.uuid, 'disabled machine which was detected on different port' ) log('rediscovered machine %s on %s' % (''.join('%02x' % x for x in id[0]), port)) ports[port] = p.uuid p.port = port def close_port(success, data): log('reconnect complete; closing server port') machine.close() p.call( 'reconnect', ['admin', port], {}, lambda success, ret: (ports[port].call( 'send_machine', ['admin', None], {}, close_port) if success else close_port())) broadcast(None, 'port_state', port, 2) return False run_id = nextid() # Find uuid or create new Machine object. if uuid in machines: log('accepting known machine on port %s (uuid %s)' % (port, uuid)) machines[uuid].port = port ports[port] = uuid log('connecting %s to port %s' % (uuid, port)) machines[uuid].call('connect', ['admin', port, [chr(x) for x in run_id]], {}, lambda success, ret: None) else: log('accepting unknown machine on port %s' % port) # Close detect port so it doesn't interfere. machine.close() #log('machines: %s' % repr(tuple(machines.keys()))) process = subprocess.Popen( (fhs.read_data('driver.py', opened=False), '--uuid', uuid if uuid is not None else '', '--allow-system', config['allow-system']) + (('--system', ) if fhs.is_system else ()) + (('--arc', 'False') if not config['arc'] else ()), stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True) new_machine = Machine(port, process, run_id, send=uuid is not None) def finish(): log('finish detect %s' % repr(uuid)) ports[port] = uuid machines[uuid] = new_machine log('connecting new machine %s to port %s' % (uuid, port)) new_machine.call('connect', ['admin', port, [chr(x) for x in run_id]], {}, lambda success, ret: None) if uuid is None: def prefinish(success, uuid): assert success new_machine.uuid = uuid new_machine.finish(finish) new_machine.call('reset_uuid', ['admin'], {}, prefinish) else: finish() return False
def detect(port): # {{{ log('detecting machine on %s' % port) if port not in ports: log('port does not exist') return if ports[port] != None: # Abort detection in progress. if ports[port]: disable(ports[port], 'disabled to prepare for detection') if ports[port] != None: # This should never happen. log('BUG: port is not in detectable state. Please report this.') return broadcast(None, 'port_state', port, 1) if port == '-' or port.startswith('!'): run_id = nextid() process = subprocess.Popen( (fhs.read_data('driver.py', opened=False), '--uuid', '-', '--allow-system', config['allow-system']) + (('--system', ) if fhs.is_system else ()) + (('--arc', 'False') if not config['arc'] else ()), stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True) machines[port] = Machine(port, process, run_id) ports[port] = port return False if not os.path.exists(port): log("not detecting on %s, because file doesn't exist." % port) return False if config['predetect']: env = os.environ.copy() env['PORT'] = port subprocess.call(config['predetect'], env=env, shell=True) try: machine = serial.Serial(port, baudrate=115200, timeout=0) except serial.SerialException as e: log('failed to open serial port %s (%s).' % (port, str(e))) del ports[port] #traceback.print_exc() return False # We need to get the machine id first. If the machine 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] = b'' id[1] = 0 id[2] = False def timeout(): id[1] += 1 if id[1] >= 30: # Timeout. Give up. websocketd.remove_read(watcher) machine.close() log('Timeout waiting for machine on port %s; giving up.' % port) ports[port] = None broadcast(None, 'port_state', port, 0) return if not id[2]: machine.write(protocol.single['ID']) else: id[2] = False timeout_handle[0] = websocketd.add_timeout(time.time() + .5, timeout) def boot_machine_input(): id[2] = True ids = [protocol.single[code][0] for code in ('ID', 'STARTUP')] # CMD:1 ID:8 + 16 Checksum:9 Total: 34 while len(id[0]) < 34: try: data = machine.read(34 - len(id[0])) except OSError: continue except IOError: continue id[0] += data #log('incomplete id: ' + id[0]) if len(id[0]) < 34: if len(id[0]) > 0 and id[0][0] == protocol.single[ 'CONTROLLER'][0]: # This is a controller. Spawn the process, then cancel this detection. websocketd.remove_timeout(timeout_handle[0]) machine.close() ports[port] = None broadcast(None, 'port_state', port, 0) log('Starting controller driver on ' + port) env = os.environ.copy() env['PORT'] = port subprocess.Popen(config['controller'], env=env, shell=True) return False return True if id[0][0] not in ids or not protocol.check(id[0]): log('skip non-id: %s (%s)' % (''.join('%02x' % 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(bytes((start, )), 1) if p < f: f = p log('Keeping some') if f == 0: f = 1 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.) websocketd.remove_timeout(timeout_handle[0]) # This machine was running and tried to send an id. Check the id. uuid = id[0][9:9 + 16] if (uuid[7] & 0xf0) != 0x40 or (uuid[9] & 0xc0) != 0x80: # Broken uuid; create a new one and set it. log('broken uuid: ' + repr(uuid)) uuid = None else: uuid = ''.join('%02x' % x for x in uuid[:16]) uuid = uuid[:8] + '-' + uuid[8:12] + '-' + uuid[ 12:16] + '-' + uuid[16:20] + '-' + uuid[20:32] id[0] = id[0][1:9] running_machine = [ p for p in machines if machines[p].run_id == id[0] ] assert len(running_machine) < 2 if len(running_machine) > 0: p = running_machine[0] assert p.uuid == uuid if p.port is not None: disable( p.uuid, 'disabled machine which was detected on different port' ) log('rediscovered machine %s on %s' % (''.join('%02x' % x for x in id[0]), port)) ports[port] = p.uuid p.port = port def close_port(success, data): log('reconnect complete; closing server port') machine.close() p.call( 'reconnect', ['admin', port], {}, lambda success, ret: (ports[port].call( 'send_machine', ['admin', None], {}, close_port) if success else close_port())) broadcast(None, 'port_state', port, 2) return False run_id = nextid() # Find uuid or create new Machine object. if uuid in machines: log('accepting known machine on port %s (uuid %s)' % (port, uuid)) machines[uuid].port = port ports[port] = uuid log('connecting %s to port %s' % (uuid, port)) machines[uuid].call('connect', ['admin', port, [chr(x) for x in run_id]], {}, lambda success, ret: None) else: log('accepting unknown machine on port %s' % port) # Close detect port so it doesn't interfere. machine.close() #log('machines: %s' % repr(tuple(machines.keys()))) process = subprocess.Popen( (fhs.read_data('driver.py', opened=False), '--uuid', uuid if uuid is not None else '', '--allow-system', config['allow-system']) + (('--system', ) if fhs.is_system else ()) + (('--arc', 'False') if not config['arc'] else ()), stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True) new_machine = Machine(port, process, run_id, send=uuid is not None) def finish(): log('finish detect %s' % repr(uuid)) ports[port] = uuid machines[uuid] = new_machine log('connecting new machine %s to port %s' % (uuid, port)) new_machine.call('connect', ['admin', port, [chr(x) for x in run_id]], {}, lambda success, ret: None) if uuid is None: def prefinish(success, uuid): assert success new_machine.uuid = uuid new_machine.finish(finish) new_machine.call('reset_uuid', ['admin'], {}, prefinish) else: finish() return False def boot_machine_error(): log('error during machine detection on port %s.' % port) websocketd.remove_timeout(timeout_handle[0]) machine.close() ports[port] = None broadcast(None, 'port_state', port, 0) return False machine.write(protocol.single['ID']) timeout_handle = [websocketd.add_timeout(time.time() + .5, timeout)] watcher = websocketd.add_read(machine, boot_machine_input, boot_machine_error) def cancel(): websocketd.remove_timeout(timeout_handle[0]) websocketd.remove_read(watcher) machine.close() ports[port] = None ports[port] = cancel # Wait at least a second before sending anything, otherwise the bootloader thinks we might be trying to reprogram it. handle = websocketd.add_timeout(time.time() + 1.5, part2) def cancel(): websocketd.remove_timeout(handle) ports[port] = None ports[port] = cancel
def boot_machine_input(): id[2] = True ids = [protocol.single[code][0] for code in ('ID', 'STARTUP')] # CMD:1 ID:8 + 16 Checksum:9 Total: 34 while len(id[0]) < 34: try: data = machine.read(34 - len(id[0])) except OSError: continue except IOError: continue id[0] += data #log('incomplete id: ' + id[0]) if len(id[0]) < 34: if len(id[0]) > 0 and id[0][0] == protocol.single['CONTROLLER'][0]: # This is a controller. Spawn the process, then cancel this detection. websocketd.remove_timeout(timeout_handle[0]) machine.close() ports[port] = None broadcast(None, 'port_state', port, 0) log('Starting controller driver on ' + port) env = os.environ.copy() env['PORT'] = port subprocess.Popen(config['controller'], env = env, shell = True) return False return True if id[0][0] not in ids or not protocol.check(id[0]): log('skip non-id: %s (%s)' % (''.join('%02x' % 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(bytes((start,)), 1) if p < f: f = p log('Keeping some') if f == 0: f = 1 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.) websocketd.remove_timeout(timeout_handle[0]) # This machine was running and tried to send an id. Check the id. uuid = id[0][9:9 + 16] if (uuid[7] & 0xf0) != 0x40 or (uuid[9] & 0xc0) != 0x80: # Broken uuid; create a new one and set it. log('broken uuid: ' + repr(uuid)) uuid = None else: uuid = ''.join('%02x' % x for x in uuid[:16]) uuid = uuid[:8] + '-' + uuid[8:12] + '-' + uuid[12:16] + '-' + uuid[16:20] + '-' + uuid[20:32] id[0] = id[0][1:9] running_machine = [p for p in machines if machines[p].run_id == id[0]] assert len(running_machine) < 2 if len(running_machine) > 0: p = running_machine[0] assert p.uuid == uuid if p.port is not None: disable(p.uuid, 'disabled machine which was detected on different port') log('rediscovered machine %s on %s' % (''.join('%02x' % x for x in id[0]), port)) ports[port] = p.uuid p.port = port def close_port(success, data): log('reconnect complete; closing server port') machine.close() p.call('reconnect', ['admin', port], {}, lambda success, ret: (ports[port].call('send_machine', ['admin', None], {}, close_port) if success else close_port())) broadcast(None, 'port_state', port, 2) return False run_id = nextid() # Find uuid or create new Machine object. if uuid in machines: log('accepting known machine on port %s (uuid %s)' % (port, uuid)) machines[uuid].port = port ports[port] = uuid log('connecting %s to port %s' % (uuid, port)) machines[uuid].call('connect', ['admin', port, [chr(x) for x in run_id]], {}, lambda success, ret: None) else: log('accepting unknown machine on port %s' % port) # Close detect port so it doesn't interfere. machine.close() #log('machines: %s' % repr(tuple(machines.keys()))) process = subprocess.Popen((fhs.read_data('driver.py', opened = False), '--uuid', uuid if uuid is not None else '', '--allow-system', config['allow-system']) + (('--system',) if fhs.is_system else ()) + (('--arc', 'False') if not config['arc'] else ()), stdin = subprocess.PIPE, stdout = subprocess.PIPE, close_fds = True) new_machine = Machine(port, process, run_id, send = False) def finish(): log('finish detect %s' % repr(uuid)) ports[port] = uuid machines[uuid] = new_machine log('connecting new machine %s to port %s' % (uuid, port)) new_machine.call('connect', ['admin', port, [chr(x) for x in run_id]], {}, lambda success, ret: None) if uuid is None: def prefinish(success, uuid): assert success new_machine.uuid = uuid new_machine.finish(finish) new_machine.call('reset_uuid', ['admin'], {}, prefinish) else: new_machine.finish(finish) return False
def boot_printer_input(fd, cond): id[2] = True ids = [protocol.single[code][0] for code in ('ID', 'STARTUP')] # CMD:1 ID:8 Checksum:3 while len(id[0]) < 12: try: data = printer.read(12 - len(id[0])) except OSError: continue except IOError: continue 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(id[0]): log('skip non-id: %s (%s)' % (''.join('%02x' % 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(bytes((start, ))) if p < f: f = p log('Keeping some') if f == 0: f = 1 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' % (''.join('%02x' % x for x in 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' % x for x in run_id))) #log('orphans: %s' % repr(tuple(orphans.keys()))) process = subprocess.Popen( (fhs.read_data('driver.py', opened=False), '--cdriver', fhs.read_data('cdriver', opened=False), '--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((fhs.read_data('driver.py', opened = False), '--cdriver', config['local'] or fhs.read_data('cdriver', opened = False), '--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 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] = b'' 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][0] for code in ('ID', 'STARTUP')] # CMD:1 ID:8 Checksum:3 while len(id[0]) < 12: try: data = printer.read(12 - len(id[0])) except OSError: continue except IOError: continue 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(id[0]): log('skip non-id: %s (%s)' % (''.join('%02x' % 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(bytes((start,))) if p < f: f = p log('Keeping some') if f == 0: f = 1 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' % (''.join('%02x' % x for x in 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' % x for x in run_id))) #log('orphans: %s' % repr(tuple(orphans.keys()))) process = subprocess.Popen((fhs.read_data('driver.py', opened = False), '--cdriver', fhs.read_data('cdriver', opened = False), '--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()
machine.close() ports[port] = None ports[port] = cancel # Wait at least a second before sending anything, otherwise the bootloader thinks we might be trying to reprogram it. handle = websocketd.add_timeout(time.time() + 1.5, part2) def cancel(): websocketd.remove_timeout(handle) ports[port] = None ports[port] = cancel # }}} # Main loop. {{{ def _disconnect(socket, data): del Connection.connections[socket.connection.id] try: httpd = Server(config['port'], Connection, disconnect_cb = _disconnect, httpdirs = fhs.read_data('html', dir = True, multiple = True), address = config['address'], log = config['log'], tls = tls) udevsocket = fhs.write_runtime('udev.socket', packagename = 'franklin', opened = False) os.makedirs(os.path.dirname(udevsocket), exist_ok = True) if os.path.exists(udevsocket): os.unlink(udevsocket) udevserver = network.Server(udevsocket, Admin_Connection) except OSError: log('failed to start server: %s' % sys.exc_info()[1]) sys.exit(1) # }}} # Initialization. {{{ def create_machine(uuid = None): # {{{ if uuid is None: uuid = protocol.new_uuid() process = subprocess.Popen((fhs.read_data('driver.py', opened = False), '--uuid', uuid, '--allow-system', config['allow-system']) + (('--system',) if fhs.is_system else ()), stdin = subprocess.PIPE, stdout = subprocess.PIPE, close_fds = True)
machine.close() ports[port] = None ports[port] = cancel # Wait at least a second before sending anything, otherwise the bootloader thinks we might be trying to reprogram it. handle = websocketd.add_timeout(time.time() + 1.5, part2) def cancel(): websocketd.remove_timeout(handle) ports[port] = None ports[port] = cancel # }}} # Main loop. {{{ def _disconnect(socket, data): del Connection.connections[socket.connection.id] try: httpd = Server(config['port'], Connection, disconnect_cb = _disconnect, httpdirs = fhs.read_data('html', dir = True, multiple = True), address = config['address'], log = config['log'], tls = tls) udevsocket = fhs.write_runtime('udev.socket', packagename = 'franklin', opened = False) os.makedirs(os.path.dirname(udevsocket), exist_ok = True) if os.path.exists(udevsocket): os.unlink(udevsocket) udevserver = network.Server(udevsocket, Admin_Connection) except OSError: log('failed to start server: %s' % sys.exc_info()[1]) sys.exit(1) # }}} # Initialization. {{{ def create_machine(uuid = None): # {{{ if uuid is None: uuid = protocol.new_uuid() process = subprocess.Popen((fhs.read_data('driver.py', opened = False), '--uuid', uuid, '--allow-system', config['allow-system']) + (('--system',) if fhs.is_system else ()) + (('--arc', 'False') if not config['arc'] else ()), stdin = subprocess.PIPE, stdout = subprocess.PIPE, close_fds = True)
def create_machine(uuid = None): # {{{ if uuid is None: uuid = protocol.new_uuid() process = subprocess.Popen((fhs.read_data('driver.py', opened = False), '--uuid', uuid, '--allow-system', config['allow-system']) + (('--system',) if fhs.is_system else ()) + (('--arc', 'False') if not config['arc'] else ()), stdin = subprocess.PIPE, stdout = subprocess.PIPE, close_fds = True) machines[uuid] = Machine(None, process, None) return uuid
def detect(port): # {{{ if port == '-' or port.startswith('!'): run_id = nextid() process = subprocess.Popen( (fhs.read_data('driver.py', opened=False), '--cdriver', config['local'] or fhs.read_data('cdriver', opened=False), '--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 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] = b'' 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][0] for code in ('ID', 'STARTUP')] # CMD:1 ID:8 Checksum:3 while len(id[0]) < 12: try: data = printer.read(12 - len(id[0])) except OSError: continue except IOError: continue 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(id[0]): log('skip non-id: %s (%s)' % (''.join('%02x' % 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(bytes((start, ))) if p < f: f = p log('Keeping some') if f == 0: f = 1 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' % (''.join('%02x' % x for x in 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' % x for x in run_id))) #log('orphans: %s' % repr(tuple(orphans.keys()))) process = subprocess.Popen( (fhs.read_data('driver.py', opened=False), '--cdriver', fhs.read_data('cdriver', opened=False), '--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()
def boot_printer_input(): id[2] = True ids = [protocol.single[code][0] for code in ('ID', 'STARTUP')] # CMD:1 ID:8 + 16 Checksum:9 Total: 34 while len(id[0]) < 34: try: data = printer.read(34 - len(id[0])) except OSError: continue except IOError: continue id[0] += data #log('incomplete id: ' + id[0]) if len(id[0]) < 34: return True if id[0][0] not in ids or not protocol.check(id[0]): log('skip non-id: %s (%s)' % (''.join('%02x' % 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(bytes((start, )), 1) if p < f: f = p log('Keeping some') if f == 0: f = 1 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.) websocketd.remove_timeout(timeout_handle[0]) # This printer was running and tried to send an id. Check the id. uuid = id[0][9:9 + 16] if (uuid[7] & 0xf0) != 0x40 or (uuid[9] & 0xc0) != 0x80: # Broken uuid; create a new one and set it. log('broken uuid: ' + repr(uuid)) uuid = None else: uuid = ''.join('%02x' % x for x in uuid[:16]) uuid = uuid[:8] + '-' + uuid[8:12] + '-' + uuid[ 12:16] + '-' + uuid[16:20] + '-' + uuid[20:32] id[0] = id[0][1:9] running_printer = [ p for p in printers if printers[p].run_id == id[0] ] assert len(running_printer) < 2 if len(running_printer) > 0: p = running_printer[0] assert p.uuid == uuid if p.port is not None: disable( p.uuid, 'disabled printer which was detected on different port' ) log('rediscovered printer %s on %s' % (''.join('%02x' % x for x in id[0]), port)) ports[port] = p.uuid p.port = port def close_port(success, data): log('reconnect complete; closing server port') printer.close() p.call( 'reconnect', ['admin', port], {}, lambda success, ret: (ports[port].call( 'send_printer', ['admin', None], {}, close_port) if success else close_port())) broadcast(None, 'port_state', port, 2) return False run_id = nextid() # Find uuid or create new Printer object. if uuid in printers: log('accepting known printer on port %s (uuid %s)' % (port, uuid)) printers[uuid].port = port ports[port] = uuid log('connecting %s to port %s' % (uuid, port)) printers[uuid].call('connect', ['admin', port, [chr(x) for x in run_id]], {}, lambda success, ret: None) else: log('accepting unknown printer on port %s' % port) #log('printers: %s' % repr(tuple(printers.keys()))) process = subprocess.Popen( (fhs.read_data('driver.py', opened=False), '--cdriver', fhs.read_data('franklin-cdriver', opened=False), '--uuid', uuid if uuid is not None else '', '--allow-system', config['allow-system']) + (('--system', ) if fhs.is_system else ()) + (('--arc', 'False') if not config['arc'] else ()), stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True) new_printer = Printer(port, process, printer, run_id) def finish(success, uuid): assert success ports[port] = uuid printers[uuid] = new_printer log('connecting new printer %s to port %s' % (uuid, port)) new_printer.call('connect', ['admin', port, [chr(x) for x in run_id]], {}, lambda success, ret: None) if uuid is None: new_printer.call('reset_uuid', ['admin'], {}, finish) else: finish(True, uuid) return False
# }}} # }}} # Main loop. {{{ def _disconnect(socket, data): del Connection.connections[socket.connection.id] try: httpd = Server(config['port'], Connection, disconnect_cb=_disconnect, httpdirs=fhs.read_data('html', dir=True, multiple=True), address=config['address'], log=config['log'], tls=tls) except OSError: log('failed to start server: %s' % sys.exc_info()[1]) sys.exit(1) # }}} # Initialization. {{{ def create_machine(uuid=None): # {{{ if uuid is None: uuid = protocol.new_uuid() process = subprocess.Popen( (fhs.read_data('driver.py', opened=False), '--uuid', uuid,
def detect(port): # {{{ log('detecting machine on %s' % port) if port not in ports: log('port does not exist') return if ports[port] != None: # Abort detection in progress. if ports[port]: disable(ports[port], 'disabled to prepare for detection') if ports[port] != None: # This should never happen. log('BUG: port is not in detectable state. Please report this.') return broadcast(None, 'port_state', port, 1) if port == '-' or port.startswith('!'): run_id = nextid() process = subprocess.Popen((fhs.read_data('driver.py', opened = False), '--uuid', '-', '--allow-system', config['allow-system']) + (('--system',) if fhs.is_system else ()) + (('--arc', 'False') if not config['arc'] else ()), stdin = subprocess.PIPE, stdout = subprocess.PIPE, close_fds = True) machines[port] = Machine(port, process, run_id) ports[port] = port return False if not os.path.exists(port): log("not detecting on %s, because file doesn't exist." % port) return False if config['predetect']: env = os.environ.copy() env['PORT'] = port subprocess.call(config['predetect'], env = env, shell = True) try: machine = serial.Serial(port, baudrate = 115200, timeout = 0) except serial.SerialException as e: log('failed to open serial port %s (%s).' % (port, str(e))) del ports[port] #traceback.print_exc() return False # We need to get the machine id first. If the machine 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] = b'' id[1] = 0 id[2] = False def timeout(): id[1] += 1 if id[1] >= 30: # Timeout. Give up. websocketd.remove_read(watcher) machine.close() log('Timeout waiting for machine on port %s; giving up.' % port) ports[port] = None broadcast(None, 'port_state', port, 0) return if not id[2]: machine.write(protocol.single['ID']) else: id[2] = False timeout_handle[0] = websocketd.add_timeout(time.time() + .5, timeout) def boot_machine_input(): id[2] = True ids = [protocol.single[code][0] for code in ('ID', 'STARTUP')] # CMD:1 ID:8 + 16 Checksum:9 Total: 34 while len(id[0]) < 34: try: data = machine.read(34 - len(id[0])) except OSError: continue except IOError: continue id[0] += data #log('incomplete id: ' + id[0]) if len(id[0]) < 34: if len(id[0]) > 0 and id[0][0] == protocol.single['CONTROLLER'][0]: # This is a controller. Spawn the process, then cancel this detection. websocketd.remove_timeout(timeout_handle[0]) machine.close() ports[port] = None broadcast(None, 'port_state', port, 0) log('Starting controller driver on ' + port) env = os.environ.copy() env['PORT'] = port subprocess.Popen(config['controller'], env = env, shell = True) return False return True if id[0][0] not in ids or not protocol.check(id[0]): log('skip non-id: %s (%s)' % (''.join('%02x' % 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(bytes((start,)), 1) if p < f: f = p log('Keeping some') if f == 0: f = 1 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.) websocketd.remove_timeout(timeout_handle[0]) # This machine was running and tried to send an id. Check the id. uuid = id[0][9:9 + 16] if (uuid[7] & 0xf0) != 0x40 or (uuid[9] & 0xc0) != 0x80: # Broken uuid; create a new one and set it. log('broken uuid: ' + repr(uuid)) uuid = None else: uuid = ''.join('%02x' % x for x in uuid[:16]) uuid = uuid[:8] + '-' + uuid[8:12] + '-' + uuid[12:16] + '-' + uuid[16:20] + '-' + uuid[20:32] id[0] = id[0][1:9] running_machine = [p for p in machines if machines[p].run_id == id[0]] assert len(running_machine) < 2 if len(running_machine) > 0: p = running_machine[0] assert p.uuid == uuid if p.port is not None: disable(p.uuid, 'disabled machine which was detected on different port') log('rediscovered machine %s on %s' % (''.join('%02x' % x for x in id[0]), port)) ports[port] = p.uuid p.port = port def close_port(success, data): log('reconnect complete; closing server port') machine.close() p.call('reconnect', ['admin', port], {}, lambda success, ret: (ports[port].call('send_machine', ['admin', None], {}, close_port) if success else close_port())) broadcast(None, 'port_state', port, 2) return False run_id = nextid() # Find uuid or create new Machine object. if uuid in machines: log('accepting known machine on port %s (uuid %s)' % (port, uuid)) machines[uuid].port = port ports[port] = uuid log('connecting %s to port %s' % (uuid, port)) machines[uuid].call('connect', ['admin', port, [chr(x) for x in run_id]], {}, lambda success, ret: None) else: log('accepting unknown machine on port %s' % port) # Close detect port so it doesn't interfere. machine.close() #log('machines: %s' % repr(tuple(machines.keys()))) process = subprocess.Popen((fhs.read_data('driver.py', opened = False), '--uuid', uuid if uuid is not None else '', '--allow-system', config['allow-system']) + (('--system',) if fhs.is_system else ()) + (('--arc', 'False') if not config['arc'] else ()), stdin = subprocess.PIPE, stdout = subprocess.PIPE, close_fds = True) new_machine = Machine(port, process, run_id, send = False) def finish(): log('finish detect %s' % repr(uuid)) ports[port] = uuid machines[uuid] = new_machine log('connecting new machine %s to port %s' % (uuid, port)) new_machine.call('connect', ['admin', port, [chr(x) for x in run_id]], {}, lambda success, ret: None) if uuid is None: def prefinish(success, uuid): assert success new_machine.uuid = uuid new_machine.finish(finish) new_machine.call('reset_uuid', ['admin'], {}, prefinish) else: new_machine.finish(finish) return False def boot_machine_error(): log('error during machine detection on port %s.' % port) websocketd.remove_timeout(timeout_handle[0]) machine.close() ports[port] = None broadcast(None, 'port_state', port, 0) return False machine.write(protocol.single['ID']) timeout_handle = [websocketd.add_timeout(time.time() + .5, timeout)] watcher = websocketd.add_read(machine, boot_machine_input, boot_machine_error) def cancel(): websocketd.remove_timeout(timeout_handle[0]) websocketd.remove_read(watcher) machine.close() ports[port] = None ports[port] = cancel # Wait at least a second before sending anything, otherwise the bootloader thinks we might be trying to reprogram it. handle = websocketd.add_timeout(time.time() + 1.5, part2) def cancel(): websocketd.remove_timeout(handle) ports[port] = None ports[port] = cancel
def Game(): # Main function to start a game. {{{ global server, title_game, have_2d, have_3d, _num_players # Set up the game name. if not hasattr(__main__, 'name') or __main__.name is None: __main__.name = os.path.basename(sys.argv[0]).capitalize() # Initialize fhs module. if not fhs.initialized: fhs.init({}, packagename = __main__.name.lower(), game = True) # Set up other constants. if not hasattr(__main__, 'autokill'): __main__.autokill = True have_2d = fhs.read_data(os.path.join('html', '2d'), dir = True, opened = False) is not None have_3d = fhs.read_data(os.path.join('html', '3d'), dir = True, opened = False) is not None or not have_2d # Fill in min and max if not specified. assert hasattr(__main__, 'num_players') if isinstance(__main__.num_players, int): _num_players = (__main__.num_players, __main__.num_players) else: _num_players = __main__.num_players assert 1 <= _num_players[0] and (_num_players[1] is None or _num_players[0] <= _num_players[1]) # Build asset string for inserting in js. for subdir, use_3d in (('2d', False), ('3d', True)): targets = [] for base in ('img', 'jta', 'gani', 'audio', 'text'): for d in (os.path.join('html', base), os.path.join('html', subdir, base)): for p in fhs.read_data(d, dir = True, multiple = True, opened = False): targets.extend(f.encode('utf-8') for f in os.listdir(p) if not f.startswith('.') and not os.path.isdir(os.path.join(p, f))) if len(targets) > 0: loader_js[use_3d] = b'\n'.join(b"\tplease.load('" + f + b"');" for f in targets) else: # Nothing to load, but force the "finished loading" event to fire anyway. loader_js[use_3d] = b'\twindow.dispatchEvent(new CustomEvent("mgrl_media_ready"));' # Set up commands. cmds['leave'] = {None: leave} if hasattr(__main__, 'commands'): for c in __main__.commands: cmds[c] = {None: __main__.commands[c]} # Start up websockets server. config = fhs.module_get_config('webgame') httpdirs = [fhs.read_data(x, opened = False, multiple = True, dir = True) for x in ('html', os.path.join('html', '2d'), os.path.join('html', '3d'))] server = websocketd.RPChttpd(config['port'], Connection, tls = config['tls'], httpdirs = httpdirs[0] + httpdirs[1] + httpdirs[2]) server.handle_ext('png', 'image/png') server.handle_ext('jpg', 'image/jpeg') server.handle_ext('jpeg', 'image/jpeg') server.handle_ext('gif', 'image/gif') server.handle_ext('gani', 'text/plain') server.handle_ext('wav', 'audio/wav') server.handle_ext('ogg', 'audio/ogg') server.handle_ext('mp3', 'audio/mp3') server.handle_ext('jta', 'application/octet-stream') server.handle_ext('txt', 'text/plain') server.handle_ext('frag', 'text/plain') server.handle_ext('vert', 'text/plain') server.handle_ext('glsl', 'text/plain') # Set up title page. if hasattr(__main__, 'Title'): title_game = Instance(__main__.Title, '') else: title_game = Instance(Title, '') log('Game "%s" started' % __main__.name) # Main loop. websocketd.fgloop()
'allow-system': '^$', 'admin': '', 'expert': '', 'user': '', 'done': '', 'local': '', 'driver': '', 'cdriver': '', 'log': '', 'tls': 'True', 'avrdudeconfig': '/usr/lib/franklin/avrdude.conf' }) if config['audiodir'] == '': config['audiodir'] = fhs.write_cache(name = 'audio', dir = True), if config['driver'] == '': config['driver'] = fhs.read_data('driver.py', opened = False) if config['cdriver'] == '': config['cdriver'] = fhs.read_data('cdriver', opened = False) # }}} # Global variables. {{{ httpd = None default_printer = (None, None) ports = {} autodetect = config['autodetect'].lower() == 'true' tls = config['tls'].lower() == 'true' orphans = {} scripts = {} # }}} # Load scripts. {{{