Ejemplo n.º 1
0
    def __init__(self, config):
        self.config = config
        self.devices = {}
        self.subjects = {}
        self.trials = {
            TRIAL_ID: Trial()
        }

        self.storage = Storage(self.config['datafile'], STORAGE_HEADERS)

        # set up the routes manually
        bottle.route('/static/<filepath:path>', method='GET')(self.static)
        bottle.route('/', method='GET')(self.index)
        bottle.route('/log', method='GET')(self.get_log)

        bottle.route('/devices', method='GET')(self.get_devices)
        bottle.route('/devices', method='POST')(self.register_device_xml)

        bottle.route('/tasks', method='GET')(self.get_tasks)
        bottle.route('/tasks', method='POST')(self.register_tasks)
        bottle.route('/tasks/<id>/start', method='POST')(self.start_task)
        bottle.route('/tasks/<id>/stop', method='POST')(self.stop_task)
        bottle.route('/tasks/<id>/answer', method='POST')(self.task_answer)

        bottle.route('/markers', method='GET')(self.get_markers)
        bottle.route('/markers', method='POST')(self.register_markers)

        bottle.route('/subjects', method='GET')(self.get_subjects)
        bottle.route('/subjects', method='POST')(self.create_subject)
        bottle.route('/subjects/<id>', method='GET')(self.get_subject)
        bottle.route('/subjects/<id>', method='PUT')(self.update_subject)
        bottle.route('/subjects/<id>', method='DELETE')(self.delete_subject)
Ejemplo n.º 2
0
class HttpServer(object):
    def __init__(self, config):
        self.config = config
        self.devices = {}
        self.subjects = {}
        self.trials = {
            TRIAL_ID: Trial()
        }

        self.storage = Storage(self.config['datafile'], STORAGE_HEADERS)

        # set up the routes manually
        bottle.route('/static/<filepath:path>', method='GET')(self.static)
        bottle.route('/', method='GET')(self.index)
        bottle.route('/log', method='GET')(self.get_log)

        bottle.route('/devices', method='GET')(self.get_devices)
        bottle.route('/devices', method='POST')(self.register_device_xml)

        bottle.route('/tasks', method='GET')(self.get_tasks)
        bottle.route('/tasks', method='POST')(self.register_tasks)
        bottle.route('/tasks/<id>/start', method='POST')(self.start_task)
        bottle.route('/tasks/<id>/stop', method='POST')(self.stop_task)
        bottle.route('/tasks/<id>/answer', method='POST')(self.task_answer)

        bottle.route('/markers', method='GET')(self.get_markers)
        bottle.route('/markers', method='POST')(self.register_markers)

        bottle.route('/subjects', method='GET')(self.get_subjects)
        bottle.route('/subjects', method='POST')(self.create_subject)
        bottle.route('/subjects/<id>', method='GET')(self.get_subject)
        bottle.route('/subjects/<id>', method='PUT')(self.update_subject)
        bottle.route('/subjects/<id>', method='DELETE')(self.delete_subject)


    def start(self):
        logging.info("Http control server started on port %s." % self.config['http_port'])
        bottle.run(host=self.config['http_host'], port=self.config['http_port'], server='cherrypy', debug=False, quiet=True)


    def index(self):
        return static_file('index.html', root=STATIC_ROOT)


    def static(self, filepath):
        if 'latest' in filepath:
            response.set_header('Cache-Control', 'No-store')

        return static_file(filepath, root=STATIC_ROOT)


    def get_log(self):
        lines = request.query.n or 10
        stdin,stdout = os.popen2("tail -n %s %s" % (lines, self.config['logfile']))
        stdin.close()
        log = stdout.readlines()
        stdout.close()
        ret = [{'log': l} for l in log] 
        return json.dumps(ret)


    def get_devices(self):
        ret =  self.devices.values()
        return json.dumps(ret)


    def register_device_xml(self):
        impl = minidom.getDOMImplementation()
        ret = impl.createDocument(None, "ContextML", None)

        # parse the input xml
        deviceIn = request.body.read()
        logging.debug("register_device_xml: got: %s" % deviceIn)

        try:
            deviceIn = minidom.parseString(deviceIn)

            device_id = deviceIn.documentElement.attributes["device"].value
            device_type = deviceIn.documentElement.attributes["type"].value
            server_timestamp = time.time() * 1000

            device = {
                "id": device_id,
                "type": device_type,
                "server_timestamp": server_timestamp
            }
            
            if not device_id in self.devices:
                device["first_server_timestamp"] = server_timestamp
                device["start_task_id"] = None
                device["stop_task_id"] = None
                self.devices[device_id] = device
            else:
                self.devices[device_id].update(device)

            ret = self._get_device_response_xml(device_id, ret)
            logging.info("register_device_xml: registered device: %s", device["id"])
        except:
            ret = self._get_device_error_response_xml(traceback.format_exc(), ret)
            logging.error(traceback.format_exc())

        logging.debug("register_device_xml: sending xml: %s" % ret.toxml())
        return ret.toxml()


    def get_tasks(self):
        trial_id = request.query.get('trial_id', TRIAL_ID)
        if trial_id in self.trials:
            ret = self.trials[trial_id].tasks
        else:
            response.status = 500
            ret = {"status":"ERROR", "body":"No such trial id"}
            logging.error("get_tasks: no such trial id: %s" % trial_id)

        return json.dumps(ret)


    def register_tasks(self):
        # parse the input txt
        tasks = []
        cur_id = None
        try:
            tasks_spec = request.files.get('tasks-spec[]').file
            for l in tasks_spec:
                l = l.rstrip()
                if l.startswith(COMMENT_CHAR):
                    continue
                elif re.match('^\s*$', l):
                    # Blank line, ignore
                    pass
                elif re.match('^\s*(\d+)\s*$', l):
                    # Number of records. Ignore for now.
                    pass
                else:
                    # split a record, tab delimited
                    fields = re.split('\s+', l)
                    task = {
                        'id': fields[0],
                        'f1': fields[1],
                        'f2': fields[2],
                        'f3': fields[3],
                        'f4': fields[4],
                        'f5': fields[5],
                        'f6': fields[6],
                        'f7': fields[7],
                        'f8': fields[8],
                        'f9': fields[9]
                    }
                    tasks.append(task)
        except:
            response.status = 500
            ret = {"status": "ERROR", "body": traceback.format_exc()}
            logging.error(traceback.format_exc())
            return json.dumps(ret)

        # NOTE: currently there is only 1 trial.
        # Re-uploading overwrites existing one, if any.
        self.trials[TRIAL_ID].set_tasks(tasks)

        ret = {"status": "OK", "body": "Registered trial with %s tasks" % len(tasks)}
        logging.info("register_tasks: registered trial with %s tasks" % len(tasks))
        return json.dumps(ret)

    
    def start_task(self, id):
        trial_id = request.forms.get('trial_id', TRIAL_ID)
        device_id = request.forms.get('device_id', None)

        if device_id == None:
            response.status = 500
            ret = {"status":"ERROR", "body":"Bad parameters"}
            logging.error("start_task: bad parameters: %s" % device_id)

        elif not trial_id in self.trials:
            response.status = 500
            ret = {"status":"ERROR", "body":"No such trial id"}
            logging.error("start_task: no such trial id: %s" % trial_id)

        elif not device_id in self.devices:
            response.status = 500
            ret = {"status":"ERROR", "body":"No such device id"}
            logging.error("start_task: no such device id: %s" % device_id)

        elif not id in self.trials[trial_id].index:
            response.status = 500
            ret = {"status":"ERROR", "body":"No such task id"}
            logging.error("start_task: no such task id: %s" % id)

        else:
            self.devices[device_id]['start_task_id'] = id
            self.devices[device_id]['stop_task_id'] = None
            ret = {"status": "OK", "body": "Started task %s" % id}
            logging.info("start_task: started task %s" % id)

        return json.dumps(ret)

    
    def stop_task(self, id):
        trial_id = request.forms.get('trial_id', TRIAL_ID)
        device_id = request.forms.get('device_id', None)

        if device_id == None:
            response.status = 500
            ret = {"status":"ERROR", "body":"Bad parameters"}
            logging.error("stop_task: bad parameters: %s" % device_id)

        elif not trial_id in self.trials:
            response.status = 500
            ret = {"status":"ERROR", "body":"No such trial id"}
            logging.error("stop_task: no such trial id: %s" % trial_id)

        elif not device_id in self.devices:
            response.status = 500
            ret = {"status":"ERROR", "body":"No such device id"}
            logging.error("stop_task: no such device id: %s" % device_id)

        elif not id in self.trials[trial_id].index:
            response.status = 500
            ret = {"status":"ERROR", "body":"No such task id"}
            logging.error("stop_task: no such task id: %s" % id)

        else:
            self.devices[device_id]['start_task_id'] = None
            self.devices[device_id]['stop_task_id'] = id
            ret = {"status": "OK", "body": "Stopped task %s" % id}
            logging.info("stop_task: stopped task %s" % id)

        return json.dumps(ret)

    
    def task_answer(self, id):
        trial_id = request.forms.get('trial_id', TRIAL_ID)
        device_id = request.forms.get('device_id', None)
        subject_id = request.forms.get('subject_id', None)
        timestamp = request.forms.get('timestamp', None)
        time_secs = request.forms.get('time_secs', None)
        absanswer = request.forms.get('absanswer', None)
        answer = request.forms.get('answer', None)

        if device_id == None or subject_id == None or time_secs == None or timestamp == None or (absanswer == None and answer == None):
            response.status = 500
            ret = {"status":"ERROR", "body":"Bad parameters"}
            logging.error("task_answer: bad parameters: %s" % device_id)

        elif trial_id not in self.trials:
            response.status = 500
            ret = {"status":"ERROR", "body":"No such trial id"}
            logging.error("task_answer: no such trial id: %s" % trial_id)

        elif not device_id in self.devices:
            response.status = 500
            ret = {"status":"ERROR", "body":"No such device id"}
            logging.error("task_answer: no such device id: %s" % device_id)

        elif not subject_id in self.subjects:
            response.status = 500
            ret = {"status":"ERROR", "body":"No such subject id"}
            logging.error("task_answer: no such subject id: %s" % id)

        else:
            #[TODO: parse answer]
            answer_relabs, answer_raw = self._parse_answer(trial_id, answer)
            actual_answer_abs, actual_answer_rel = self._get_actual_answers(trial_id, id, answer)

            # write answer to data file
            record = [timestamp, trial_id, device_id, self.subjects[subject_id]['name'], self.subjects[subject_id]['height'],
                      id, actual_answer_abs, actual_answer_rel, absanswer, answer_relabs, answer_raw, time_secs] 
            self.storage.write_array(record)
            logging.debug("task_answer: wrote record: %s" % record)

            ret = {"status": "OK", "body": "Task %s answered" % id}
            logging.info("task_answer: answered task %s" % id)

        return json.dumps(ret)


    def get_markers(self):
        trial_id = request.query.get('trial_id', TRIAL_ID)
        if trial_id in self.trials:
            ret = []
            print self.trials[trial_id].markers
            for id,m in self.trials[trial_id].markers.iteritems():
                ret.append(m)
        else:
            response.status = 500
            ret = {"status":"ERROR", "body":"No such trial id"}
            logging.error("get_markers: no such trial id: %s" % trial_id)

        return json.dumps(ret)


    def register_markers(self):
        trial_id = request.query.get('trial_id', TRIAL_ID)
        if not trial_id in self.trials:
            response.status = 500
            ret = {"status":"ERROR", "body":"No such trial id"}
        else:
            # parse the input txt
            markers = {}
            try:
                markers_spec = request.files.get('markers-spec[]').file
                for l in markers_spec:
                    l = l.rstrip()
                    if l.startswith(COMMENT_CHAR):
                        continue
                    else:
                        # split a record, tab delimited
                        fields = re.split('\s+', l)
                        marker = {
                            'id': fields[0],
                            'x': float(fields[1]),
                            'y': float(fields[2]),
                            'color': fields[3]
                        }
                        markers[marker['id']] = marker
            except:
                response.status = 500
                ret = {"status": "ERROR", "body": traceback.format_exc()}
                logging.error(traceback.format_exc())
                return json.dumps(ret)

            # NOTE: currently there is only 1 trial.
            # Re-uploading markers overwrites existing spec, if any.
            print markers
            self.trials[trial_id].markers = markers

            ret = {"status": "OK", "body": "Registered %s markers" % len(markers)}
            logging.info("register_markers: registered %s markers" % len(markers))

        return json.dumps(ret)


    def get_subjects(self):
        ret =  self.subjects.values()
        return json.dumps(ret)


    def create_subject(self):
        subject = request.body.read()
        try:
            subject = json.loads(subject)
            #[FIXME: validate]
        except:
            response.status = 500
            ret = {"status": "ERROR", "body": traceback.format_exc()}
            logging.error(traceback.format_exc())
            return json.dumps(ret)

        if subject['name'] == None or subject['height'] == None:
            response.status = 500
            ret = {"status":"ERROR", "body":"Bad parameters"}
            logging.error("create_subject: bad parameters: %s" % subject)

        else:
            #[XXX: only one subject at a time]
            subject_id = SUBJECT_ID
            subject = {
                'id': subject_id,
                'name': subject['name'],
                'height': subject['height'],
                'notes': subject['notes']
            }
            self.subjects[subject_id] = subject
            ret = subject
            logging.info("create_subject: created subject: %s" % subject)

        return json.dumps(ret)


    def get_subject(self, id):
        if not id in self.subjects:
            response.status = 404
            ret = {"status":"ERROR", "body":"Subject not found"}
            logging.error("get_subject: subject not found: %s" % id)
        else:
            ret = self.subjects[id]

        return json.dumps(ret)


    def update_subject(self, id):
        subject = request.body.read()
        try:
            subject = json.loads(subject)
            #[FIXME: validate]
        except:
            response.status = 500
            ret = {"status": "ERROR", "body": traceback.format_exc()}
            logging.error(traceback.format_exc())
            return json.dumps(ret)

        if not id in self.subjects:
            response.status = 404
            ret = {"status":"ERROR", "body":"Subject not found"}
            logging.error("get_subject: subject not found: %s" % id)
        else:
            self.subjects[id]['name'] = subject['name']
            self.subjects[id]['height'] = subject['height']
            self.subjects[id]['notes'] = subject['notes']

            ret = self.subjects[id]
            logging.info("update_subject: updated subject: %s" % subject)

        return json.dumps(ret)


    def delete_subject(self, id):
        if not id in self.subjects:
            response.status = 404
            ret = {"status":"ERROR", "body":"Subject not found"}
            logging.error("delete_subject: subject not found: %s" % id)
        else:
            del self.subjects[id]
            ret = {"status":"OK", "body":"Item deleted"}
            logging.info("delete_subject: subject deleted: %s" % id)

        return json.dumps(ret)

    
    def _parse_answer(self, trial_id, answer):
        relabs = -1
        if '<' in answer:
            rel, marker = answer.split('<')
            if marker in self.trials[trial_id].markers:
                y = self.trials[trial_id].markers[marker]['y']
                relabs = y - float(rel)

        elif '>' in answer:
            rel, marker = answer.split('>')
            if marker in self.trials[trial_id].markers:
                y = self.trials[trial_id].markers[marker]['y']
                relabs = y + float(rel)

        else:
            relabs = answer

        return relabs, answer

    def _get_actual_answers(self, trial_id, task_id, answer):
        actual_abs = None
        actual_rel = None

        if task_id in self.trials[trial_id].index:
            idx = self.trials[trial_id].index[task_id]
            actual_abs = float(self.trials[trial_id].tasks[idx]['f6'])

            if '<' in answer:
                rel, marker = answer.split('<')
                if marker in self.trials[trial_id].markers:
                    y = self.trials[trial_id].markers[marker]['y']
                    actual_rel = "%s<%s" % ((y - actual_abs), marker)

            elif '>' in answer:
                rel, marker = answer.split('>')
                if marker in self.trials[trial_id].markers:
                    y = self.trials[trial_id].markers[marker]['y']
                    actual_rel = "%s>%s" % ((actual_abs - y), marker)

        return actual_abs, actual_rel

    
    # Helpers
    def _get_device_error_response_xml(self, error, ret):
        ctxEL = ret.createElement('ctxEl')

        errorEl = ret.createElement('error')
        errorEl.appendChild(ret.createTextNode(error))

        ctxEl.appendChild(errorEl)

        ret.documentElement.appendChild(ctxEl)
        return ret


    def _get_device_response_xml(self, device_id, ret):
        ctxEL = ret.createElement('ctxEl')
        start_task_id = self.devices[device_id]['start_task_id']
        stop_task_id = self.devices[device_id]['stop_task_id']

        if start_task_id or start_task_id:
            taskEl = ret.createElement('task')
            taskEl.appendChild(ret.createTextNode(self.devices[device_id]['start_task_id'][4:]))
            ctxEL.appendChild(taskEl)

            # [XXX: only one subject at a time]
            userHeightEl = ret.createElement('userHeight')
            userHeightEl.appendChild(ret.createTextNode(self.subjects[SUBJECT_ID]['height']))
            ctxEL.appendChild(userHeightEl)

            operationEl = ret.createElement('operation')
            if start_task_id:
                operationEl.appendChild(ret.createTextNode('start'))
                self.devices[device_id]['start_task_id'] = None
            else:
                operationEl.appendChild(ret.createTextNode('stop'))
                self.devices[device_id]['stop_task_id'] = None

            ctxEL.appendChild(operationEl)


        ret.documentElement.appendChild(ctxEL)
        return ret