Beispiel #1
0
class BotWrapper:
    def __init__(self,
                 url,
                 magic_phrase,
                 max_turns=10,
                 callback=None,
                 callback_params=1,
                 msg_q=False):
        print('starting service')
        self.start_proba = 1.0
        self.magic_phrase = magic_phrase
        self.url = replace_localhost(url)
        self.bot = Alice()
        self.max_turns = max_turns
        self.sending_message = False
        self._id = None
        self.use_msg_q = msg_q  # msg_q sets whether or not we are queueing messages
        self.websocket = 'ws://%s/websocket' % self.url
        self.client = MeteorClient(self.websocket)
        self.client.ddp_client.ddpsocket.extra_headers = [('Bot', 'true')]
        print(self.client.ddp_client.ddpsocket.handshake_headers)
        self.client.connect()

        self.idle_time = 3 * 60
        self.thread_time = 2
        self.max_retry = 3

    def restart_idler(self):
        ''' Restarts the idle watcher '''
        print('restarting idler')
        if hasattr(self, 'idler_thread') and self.idler_thread:
            self.idler_thread.cancel()
        self.idler_thread = threading.Timer(self.idle_time,
                                            self.idle_user_handler)
        self.idler_thread.start()

    def idle_user_handler(self):
        """ Handler that disconnects conversation in the event that a user leaves """

        print('user is idle disconnect')
        self.idler_thread = None
        self.end_convo()

    def login(self,
              user='******',
              pwd='botbot',
              callback=None,
              callback_params=0):
        print('logging in')

        def set_user(data):
            self.set_user_id(data['id'])
            print('user id set to', self._id)
            if callback and callback_params == 1:
                print('running callback with 1 parameter')
                callback(self)
            elif callback and callback_params == 0:
                callback()

        # TODO make this into threading timers.
        while not self._id:
            self.client.login(user,
                              pwd,
                              callback=func_wrap(set_user, params=1))
            time.sleep(0.5)

    def logout(self):
        self.client.logout()


#    def find_and_join_room(self):
#        """ Finds a room and joins it """
#        self.find_room(callback=(lambda roomId : self.join_room(roomId)))
#
#    def find_room(self, callback=None):
#        print('looking for an open room')
#        def room_callback():
#            print('looking for a room')
#            user = self.client.find_one('users')
#            print('user dict',user.items())
#            if user["in_convo"]:
#                roomObj = user["curConvo"]
#                print('roomid: ', roomObj)
#            else:
#                openrooms = self.client.find('convos') # {curSessions : {$lt  :2}}
#                roomObj = openrooms[0] if openrooms and len(openrooms) > 0 else -1
#
#            # TODO may have issues with room id when user is in convo
#            if roomObj != -1:
#                if type(roomObj) == str:
#                    print(roomObj, 'room')
#                    print('openrooms', openrooms)
#                callback(roomObj['_id'])
#                # Add user to room
#
#            else:
#                print('No rooms found. Back to the bat cave')
#        self.subscribe('currentUser',params=[], callback=func_wrap(
#            lambda : room_callback()
#            )
#        )

    def subscribe(self, collection, params=[], callback=None):
        """ Wrapper for subscribe to avoid issues with already subscribed rooms """
        try:
            print("subscribing to {}".format(collection))
            self.client.subscribe(collection, params, callback)
        except MeteorClientException:
            print(
                'Already subscribed to {}. Running callback with None'.format(
                    collection))
            if callback:
                callback(None)

    def join_room(self, roomId, otherUserId, callback=None):
        """ Join a room based on roomId """
        print('join room with id', roomId)
        self.roomId = roomId
        self.msg_queue = []
        self.available = False
        self.client.call(
            'convos.addUserToRoom',
            params=[roomId, self.magic_phrase],
            callback=func_wrap(lambda: self.subscribe(
                'chat', [roomId],
                func_wrap(lambda: self.subscribe(
                    'msgs', [roomId],
                    func_wrap(lambda: self.subscribe(
                        'currentUsers', [roomId],
                        func_wrap(lambda: self.watch_room(
                            roomId,
                            func_wrap(lambda: self.send_ready(
                                roomId, otherUserId, callback)))))))))))

    def send_ready(self, roomId, otherUserId, callback=None):
        self.client.call('convos.botReady',
                         params=[roomId, otherUserId, self.magic_phrase],
                         callback=callback)

    def unsubscribe(self, collection):
        """ Unsubscribe from the collection """
        try:
            self.client.unsubscribe(collection)
        except MeteorClientException:
            print('\t"{}" not subscribed to.'.format(collection))

    def end_convo(self):
        """ End the conversation """
        print('end conversation and unsubscribe from it all')
        self.client.remove_all_listeners('added')
        self.client.remove_all_listeners('changed')

        self.unsubscribe('chat')
        self.unsubscribe('msgs')
        self.unsubscribe('currentUsers')

        self.client.call('users.exitConvo', [])
        self.client.call('convos.updateRatings', [self.roomId, 'not'])
        self.available = True
        if hasattr(self, 'idler_thread') and self.idler_thread:
            self.idler_thread.cancel()

    def set_wpm(self):
        """ Set the words per minute of the bot """
        wpm = random.randint(150, 200)
        self.cps = 60 / (wpm * 5)
        print('Setting wpm : {} '.format(wpm))

    def prime_bot(self, convo_obj):
        """  the conversational bot """
        print('convo_obj', convo_obj)
        input_msg = 'hi'
        if 'msgs' in convo_obj and convo_obj['msgs']:
            topic_msg_id = convo_obj['msgs'][0]
            msg_obj = self.client.find_one('messages',
                                           selector={'_id': topic_msg_id})
            if msg_obj:
                input_msg = msg_obj['message']

        msg = self.bot.message(input_msg, self.roomId)
        if random.random() > self.start_proba:
            self.send_message(msg)

    def watch_room(self, roomId, callback=None):
        """
        Setup Event Listeneres for a room and checks to make sure that the room is updating
        """
        self.turns = 0
        convo_obj = self.client.find_one('convos', selector={'_id': roomId})
        self.room_closed = convo_obj['closed']
        self.set_wpm()

        self.last_message = ""
        self.confirmed_messages = [
        ]  # all messages sent by the user that have been confirmed
        self.thread = MessageHandlerThread(self)

        def message_added(collection, id, fields):
            """ callback for when a message is added """
            if (collection == 'messages' and 'message' in fields
                    and 'user' in fields):
                print(type(self._id), type(fields['user']), self._id,
                      fields['user'])
                if fields['user'] != self._id and self.last_message != fields[
                        'message']:
                    self.restart_idler()
                    self.receive_message(fields['message'])
                    self.last_message = fields['message']
                    self.thread.message_received = True
                elif fields['user'] == self._id:
                    print('\t messages from self detected')
                    self.confirmed_messages.append(fields['message'])

        self.client.on('added', message_added)

        def watch_convo(collection, id, fields, cleared):
            """ callback for when any part of the conversation is updated """
            if self.roomId and collection == "convos" and id == self.roomId:
                # print('\t',fields)
                if 'closed' in fields:
                    print('\tRoom is closed: ', fields['closed'])
                    self.room_closed = fields['closed']
                    self.end_convo()
                if 'msgs' in fields:
                    print('\tMessages updated in convo "{}"'.format(id))
                    # TODO this is bugggy
                    self.thread.convo_updated = True
                if 'turns' in fields:
                    print('\tTurns updated to "{}"'.format(fields['turns']))
                    self.turns = fields['turns']
            elif self.roomId == id:
                print(collection, id, fields)

        self.client.on('changed', watch_convo)
        # mark the bot as ready to talk
        self.restart_idler()
        self.prime_bot(convo_obj)
        print("before thread")
        self.thread.start()
        print("after thread")

        if callback:
            callback(None)

    def respond(self):
        """ Kind of a hacky way to respond to the conversation """
        print("responding")
        if self.msg_queue and self.use_msg_q:
            partner_msg = self.msg_queue[0]
            self.msg_queue = self.msg_queue[1:]
            msg = self.bot.message(partner_msg, self.roomId)
            print(msg)
            self.send_message(msg)

        if self.msg_queue and not self.sending_message:
            partner_msg = self.msg_queue[-1]
            self.msg_queue = self.msg_queue[:-1]
            msg = self.bot.message(partner_msg, self.roomId)
            print(msg)
            self.send_message(msg)

    def still_in_conv(self):
        """ Returns whether the conversation is still moving """
        in_conv = self.roomId != None and not self.client.find_one(
            'convos', selector={'_id': self.roomId})['closed']
        print('\tstill in conv', in_conv)
        if not in_conv:
            self.end_convo()
        print(
            '\tclosed: ',
            self.client.find_one('convos', selector={'_id':
                                                     self.roomId})['closed'])
        return in_conv

    def get_convo_dict(self):
        if self.roomId:
            return self.client.find_one('convos',
                                        selector={'_id': self.roomId})
        else:
            return {}

    def get_message(self, idx):
        ''' Returns the message at idx'''
        convo_dict = self.get_convo_dict()
        if convo_dict:
            topic_msg_id = convo_dict['msgs'][idx]
            msg_dict = self.client.find_one('messages',
                                            selector={'_id': topic_msg_id})
            # print(msg_dict)
            if msg_dict:
                return msg_dict['message']
        return ''

    def received_message(self, message):
        """ Checks whether the bot actually sent the message """
        # TODO add handler that removes a confirmed message to save memory
        return message in self.confirmed_messages

    def retry_message(self, message, retry=0, callback=None):
        """ Handler that makes attempts to connect a user back into a conversation """
        # TODO set as properties
        if retry == 0 or not self.received_message(
                message) and retry < self.max_retry:
            self.update_conversation(message, callback)

            if retry != 0:
                print('\t\tRetry {} of sending "{}"'.format(retry, message))

            t = threading.Timer(self.thread_time,
                                lambda: self.retry_message(message, retry + 1))
            t.start()
        elif retry >= self.max_retry:
            print(
                '\tMax retries reached - couldn\'t verify whether {} was received'
                .format(message))
        else:
            print('\t"{}" successfully received'.format(message))

    def update_conversation(self, message, callback=None):
        self.client.call('convos.updateChat', [message, self.roomId], callback)

    def _send_message(self, message, callback=None):
        self.last_message_sent = message
        if self.still_in_conv():
            self.retry_message(message, callback=callback)
        else:
            print('Not responding - conversation is OVER')
        self.sending_message = False

    def send_message(self, message, callback=None):
        # calculates typing speed based on rough cps for user
        sleep_time = self.cps * len(message)
        print("Preparing to send '{}' Waiting '{}' seconds.".format(
            message, sleep_time))
        t = threading.Timer(sleep_time,
                            lambda: self._send_message(message, callback))
        t.start()

    def receive_message(self, message):
        """ Called whenever the bot receives a message """
        print('Received "{}"'.format(message))
        self.msg_queue.append(message)
        # message = 'sup then' # self.bot.message(message)

        # self.send_message(message)

    def set_user_id(self, id):
        self.available = True
        print('set user id to ', id)
        self._id = id
Beispiel #2
0
from MeteorClient import MeteorClient

UNAME = os.environ.get('DICECLOUD_USER', '')
PWD = os.environ.get('DICECLOUD_PWD', '').encode()

client = MeteorClient('ws://dicecloud.com/websocket', debug=True)
client.connect()
print("Connected")
while not client.connected:
    time.sleep(0.1)
client.login(UNAME, PWD)
print("Logged in")

time.sleep(1)  # wait until users collection has updated

USER_ID = client.find_one('users', selector={'username': UNAME}).get('_id')
print("User ID: " + USER_ID)

char_id = 'Mtx98jb3c3wWcrWPj'


def main():
    # check_char()
    test_id()


def test_id():
    client.insert('characters', {
        'name': 'DELETABLETESTCHAR',
        'owner': USER_ID
    })
Beispiel #3
0
class MWorker:
    multiprocessPluginManager = ""  # yapsy manager
    meteorClient = ""  # python-meteor client
    # session=requests.Session()               #http client
    meteorUrl = "ws://127.0.0.1:3000/websocket"
    CYCLE_DEFAULT_DELAY = 20  # 10 seconds
    SYNCHRO_TIMEOUT = 10  # meteor functions are asynchron. this is delay for how long i should wait till
    # it is considered as fail... 2 sec are default
    subscription_status = {}  # this will assure, subscription is ready...    "subscription_name":true/false
    workername = "localhostworker"  # this is worker name - this must be initialised at moment of start, and
    # must by uniqued...

    data = ""
    error = ""
    syncwaitdone = False

    def __init__(self, meteorUrl):
        """
        constructor
        :return:
        """

        self.meteorUrl = meteorUrl
        self.logm("MWorker:constructor", "python meteor client initialisation:")
        self.initMeteorConnect()
        self.logm("MWorker:constructor", "getting unassigned")
        # self.infiniteCycle()
        self.getUnassigned()

    def infiniteCycle(self):
        """
        this is to keep thread running - it can be interupted by ctrl+c


        :return:
        """
        while True:
            try:
                time.sleep(1)
            except KeyboardInterrupt:
                break

    def initMeteorConnect(self):
        """
        this will use library python-meteor in order to escablish session to selected meteor

        :return:
        """
        self.logm("MWorkerLib:initMeteorConnect:", "meteor python lib client iniciaisation...")
        self.meteorClient = MeteorClient('ws://127.0.0.1:3000/websocket', auto_reconnect=True)
        self.meteorClient.connect()
        self.subscribeCollectionSynchron(['unassigned', 'active', 'trash'], self.SYNCHRO_TIMEOUT)

        self.logm("MWorkerLib:initMeteorConnect:", "meteor python lib client iniciaisation done...")

    def meteorCollectionSubscribed(self, subscription):
        self.subscription_status[subscription] = True;
        self.logm("MWorkerLib:subscribed:", 'SUBSCRIBED {}'.format(subscription))

    def meteorConnected(self):
        self.logm("MWorkerLib:connected:", ' CONNECTED')

    def initYapsy(self):
        """
        inicialisation of yapsi subsystems...
        :return:
        """
        self.multiprocessPluginManager = MultiprocessPluginManager()
        self.multiprocessPluginManager.setPluginPlaces(["plugins"])
        self.multiprocessPluginManager.collectPlugins()
        self.logm("MWorkerLib:initYapsy:", "following pluggins ready")
        for pluginInfo in self.multiprocessPluginManager.getAllPlugins():
            self.logm("MWorkerLib:initYapsy:", ">" + pluginInfo.name)

    def isSubscriptionProcessDone(self):
        """
        this will check, if in self.subscription_status   all collections are set to true
        :return: true if all requested collections are subscribed, orthervice false..
        """
        kk = self.subscription_status.keys()

        for col in kk:
            if self.subscription_status[col] == False:
                self.logm("MWorkerLib:isSubscriptionProcessDone:", self.subscription_status)
                return False
        return True

    def subscribeCollectionSynchron(self, collections, timeout):
        """
        this is synchron method. this means, there is loop for "timeout" seconds,
        so subscription can be estalished...
        :param: timeout  - number of seconds for cycle this function will have
        :param: collections   - array of collections to be subscribed with self.meteorClient...
                                ['unassigned', 'active', 'trash']
        :return: will return true, or collection if some is false
        """
        self.logm("MWorkerLib:subscribeCollectionsynchron:", "begining with subscription")
        self.meteorClient.on('subscribed', self.meteorCollectionSubscribed)

        self.meteorClient.on('connected', self.meteorConnected)

        for col in collections:
            self.subscription_status[col] = False
            self.meteorClient.subscribe(col)
        self.logm("MWorkerLib:subscribeCollectionSynchron",
                  "subscription init done, now waiting...:" + str(self.meteorClient.subscriptions.keys()))

        epoch = int(time.time())
        while (((int(time.time()) - epoch) < timeout) and (
                (self.isSubscriptionProcessDone() == False))):
            try:
                time.sleep(1)
                self.logm("MWorkerLib:subscribeCollectionSynchron",
                          "inside waiting cycle :" + str(self.meteorClient.subscriptions.keys()))
            except KeyboardInterrupt:
                break
        if (self.isSubscriptionProcessDone() == False):
            self.logm("MWorkerLib:subscribeCollectionSynchron",
                      "some requested subscription failed...:" + str(self.subscription_status))
            return False
        else:
            self.logm("MWorkerLib:subscribeCollectionSynchron", "requested subscription successfuly subscribed...")
            return True

    def getUnassigned(self):
        """
        get list of unassigned tasks
        :return: list of unassigned tasks
        """

        self.logm("MWorkerLib:getUnassigned", "geting all unassigned")
        all_posts = self.meteorClient.find('unassigned')

        self.logm("MWorkerLib:getUnassigned", "found following unassigned:" + str(all_posts))

    def subscription_callback(self, error):
        if error:
            self.logm("MWorkerLib:subscription_callback", "subsribing to unassigned failed")
            return
        self.logm("MWorkerLib:subscription_callback", "subsribing to unassigned succeed")

    def logm(self, tag="undefined tag", text="undefined text"):
        """
         logger wrapper ....
        :param tag:  tag
        :param level:   level ( 1..10 )
        :param text:   dscription of logging
        :return:
        """
        timestamp = self.getTimestamp()
        print("{d} t:{a}   t:{c}   ".format(a=tag, c=text, d=timestamp))

    ####################################### support methods
    def getTimestamp(self):
        ts = time.time()
        return datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')

    def callViewToolkit(self, method, parameters, callback):
        """
        this will execute call...
        please note, for simple geting collection data, no need to create method...


        :param method:    name of function
        :param parameters: what to give to viewtoolkit
        :callback  -  as all meteor calls are async, callback is required
            def callback_function(error, result):
                if error:
                    print(error)
                    return
                print(result)


        :return:
        """
        self.logm("MWorkerLib:callViewToolkit", "method:" + method + " with parameters:" + parameters + " called")
        self.meteorClient.call(method, parameters, callback)

    def isPluginActiveInPluginManager(self, pluginname):
        """
        this is to check if plugin which is parameter exist in worker, and if yes, if it is activated...
        :param pluginname:
        :return: true in case plugin is activated
        """

        for pluginInfo in self.multiprocessPluginManager.getAllPlugins():
            self.logm("MWorkerLib:isPlugginActiveInPluginManager:",
                      "checking plugin " + pluginInfo.name + "against " + pluginname)
            if (pluginname == pluginInfo.name):
                self.logm("MWorkerLib:isPlugginActiveInPluginManager:",
                          "plugin was found, let see if it is activated " + str(pluginInfo.is_activated))
                if pluginInfo.is_activated:
                    self.logm("MWorkerLib:isPlugginActiveInPluginManager:",
                              "yes, plugin exist and was activated. returning true")
                    return True
        self.logm("MWorkerLib:isPlugginActiveInPluginManager:",
                  "could not found plugin you uare asking for, or it is not activated, returning False")
        return False

    def activatePlugin(self, pluginname):
        """
        plugin activation itself
        :param pluginname:
        :return:
        """
        self.logm("MWorkerLib:isPlugginActiveInPluginManager:activatePlugin",
                  "plugin {a} will be activated now...".format(a=pluginname))
        self.multiprocessPluginManager.activatePluginByName(pluginname)
        self.logm("MWorkerLib:isPlugginActiveInPluginManager:activatePlugin",
                  "plugin {a} was activated...".format(a=pluginname))

    def moveJobFromActiveToTrash(self,jobname):
        """
        will move job by its name  from active to trash
        :param jobname:
        :return:
        """
        #kontrola, zda trash ma v sobe vytvoreneho workera:
        if(self.isWorkerInTrash()==False):
            self.createWorkerRecordInTrash()

        job=self.getJobInActiveByName(jobname)
        if (job==None):
            return False
        if self.createJobInTrashSynchronised(job):
            self.deleteJobFromActiveSynchronised(jobname)

    def deleteJobFromActiveSynchronised(self,jobname):
        """
        delete job from active
        :param jobname:
        :return:
        """
        result = False
        self.syncwaitdone = False  # zacatek locku
        self.logm("MWorkerLib:deleteJobFromActiveSynchronised",
                  "worker:" + self.workername + " is going to remove job from active " + str(jobname))
        self.meteorClient.remove( 'active',{'_id':self.workername,'jobs._id':jobname}, callback=self.callback_synchro)
        res = self.syncWait()  # konec locku
        self.logm("MWorkerLib:deleteJobFromActiveSynchronised",
                  "worker:" + self.workername + " removed " + str(jobname)+ "result is "+str(res))
        return res




    def createJobInTrashSynchronised(self,job):
        """
        this will create job in trash collection under worker name...

        :param job:
        :return:
        """

        #inseritng job
        self.logm("MWorkerLib:createJobInTrash",
                  "worker:" + self.workername + " is going to insert to trash following: " + str(job))
        self.syncwaitdone = False  # zacatek locku
        self.meteorClient.update('trash', {'_id': self.workername}, {'$push': {"jobs":job}},
                                 callback=self.callback_synchro)
        res = self.syncWait()  # konec locku
        return res

    def     getJobInActiveByName(self,jobname):
        """
        this will get job back
        :param jobname:  name of job in active ...
        :return:  return job, or None
        """
        res = self.meteorClient.find_one('active',{'_id':self.workername,'jobs.jobname':jobname})
        if res==[]:
            return None
        return res

    def moveJobFromUnassignedToActiveSynchronised(self, jobname):
        """
        this will shift job from unassigned to active... based on job name, which must be unique in hwhile system
        :param jobname:
        :return:
        """
        #kontrola, zda existuje worker v active, a kdyz ne, tak se vlozi podle template
        if(self.isWorkerInActive()==False):
            self.logm("MWorkerLib:moveJobFromUnassignedToActiveSynchronised",
                  " dont see record for worker {w} in active !! creating it ".format(w=self.workername))
            self.createWorkerRecordInActive()


        resulttmp = self.findJobinUnassigned(jobname)
        if(len(resulttmp)!=1):
            raise NoJobFound(" given job name "+jobname+" doesnt exist")
        job=resulttmp[0]
        self.logm("MWorkerLib:moveJobFromUnassignedToActiveSynchronised",
                  " job  {a} will be activated now...".format(a=str(job)))
        if (self.createMyJobInActiveSynchronised(job)):
            if(self.deleteJobFromUnassignedSynchronised(job)):
                self.logm("MWorkerLib:moveJobFromUnassignedToActiveSynchronised",
                  " job inserting to db is ok, job deleted from unassigned ")
                return True
        self.logm("MWorkerLib:moveJobFromUnassignedToActiveSynchronised",
                  "job inserting to db failed")
        return False


    def isWorkerInTrash(self):
        """
        will check is self.workername can be found in trash
        :return: true if it can be found, false if not
        """
        result = self.meteorClient.find('trash',{"_id":self.workername})
        self.logm("MWorkerLib:isWorkerInTrash",
                  "checking, if i see my worker name in trash "+str(result))
        if len(result)==1:
            self.logm("MWorkerLib:isWorkerInTrash",
                  " worker with my name found in trash, erturning true")
            return True
        else:
            self.logm("MWorkerLib:isWorkerInTrash",
                  "found nothing, returning False")
            return False


    def isWorkerInActive(self):
        """
        will check is self.workername can be found in active
        :return: true if it can be found, false if not
        """
        result = self.meteorClient.find('active',{"_id":self.workername})
        self.logm("MWorkerLib:isWorkerInActive",
                  "checking, if i see my worker name in active "+str(result))
        if len(result)==1:
            self.logm("MWorkerLib:isWorkerInActive",
                  " worker with my name found in active, erturning true")
            return True
        else:
            self.logm("MWorkerLib:isWorkerInActive",
                  "found nothing, returning False")
            return False

    def createWorkerRecordInActive(self):
        """
        will create new worker - it will not be checking if there is already worker created in active...
        :return:
        """
        template={"_id":self.workername,"worker":self.workername,"workerusername":"******","jobs":[]}
        self.logm("MWorkerLib:createWorkerRecordInActive",
                  "worker:" + self.workername + " is going to by added to collection active ")
        self.syncwaitdone = False  # zacatek locku
        self.meteorClient.insert('active',   template,
                                 callback=self.callback_synchro)
        res = self.syncWait()  # konec locku
        return res


    def createWorkerRecordInTrash(self):
        """
        will create new worker - it will not be checking if there is already worker created in active...
        :return:
        """
        template={"_id":self.workername,"worker":self.workername,"workerusername":"******","jobs":[]}
        self.logm("MWorkerLib:createWorkerRecordInTrash",
                  "worker:" + self.workername + " is going to by added to collection trash ")
        self.syncwaitdone = False  # zacatek locku
        self.meteorClient.insert('trash',   template,
                                 callback=self.callback_synchro)
        res = self.syncWait()  # konec locku
        return res


    def findJobinUnassigned(self, jobname):
        """
        will return job from unassigned by jobname
        this is synchron method... it will wait for ( synchron_timeout ) seconds, and return false
        :param job:
        :return:  false on timeout ( synchron_timeout ) or if return is different from 1.
        """
        return self.meteorClient.find('unassigned', {"_id": jobname})

    def createMyJobInActiveSynchronised(self, job):
        """
        this will take as input dict of job... and add it to jobs[] in proper worker...
        :param job:  dict of job...
        :return: false on timeout ( synchron_timeout ) or if return is different from 1.
        """
        template = {
            'pluginname': job['pluginname'],
            'jobname': job['jobname'],
            
            'starttime': int(time.time()),  # epocha
            'ttl': job['expectedttl'],
            'inputparams': job['inputparams'],
            'progress': 0,
            'result': "undefined"
        }

        self.logm("MWorkerLib:createMyJobInActive",
                  "worker:" + self.workername + " is going to insert to active following: " + str(job))
        self.syncwaitdone = False  # zacatek locku
        self.meteorClient.update('active', {'_id': self.workername}, {'$push': {'jobs': template}},
                                 callback=self.callback_synchro)
        res = self.syncWait()  # konec locku
        return res

    def callback_synchro(self, error, data):

        self.data = data
        self.error = error
        if error:
            self.logm("MWorkerLib:update_callback_synchro",
                      "worker:" + self.workername + " failed error is " + str(error))
            self.syncwaitdone = True
            return

        self.logm("MWorkerLib:update_callback_synchro", "is ok. number of updates is " + str(data))
        self.syncwaitdone = True

    def syncWait(self):
        """
        this will wait till data of error are not empty.
        it will do for self.SYNCHRO_TIMEOUT seconds. then it will return False

        also it will check what is return value from request. if update is 0,
        false, othervice true
        """

        epoch = int(time.time())
        while (((int(time.time()) - epoch) < self.SYNCHRO_TIMEOUT) and (self.syncwaitdone == False)):
            try:
                time.sleep(1)
                self.logm("MWorkerLib:syncWait",
                          "inside waiting cycle :")
            except KeyboardInterrupt:
                break

        if (self.syncwaitdone == False):
            # cycle was broken before timeou
            return False

        try:
            # zkouska, zda se updatoval aspon jeden radek
            if (int(self.data) == 0):
                # mongo changed nothing, but there was no error
                return False
            else:
                return True
        except:
            pass

        # nothing found, cykluus end on timeout
        return False

    def deleteJobFromUnassignedSynchronised(self, job):
        """
        this will delete selected job from unassigned
        this is synchron method... it will wait for ( synchron_timeout ) seconds, and return false

        :param job:
        :return: false on timeout ( synchron_timeout ) or if return is different from 1.
        """
        self.syncwaitdone = False  # zacatek locku
        self.logm("MWorkerLib:createMyJobInActive",
                  "worker:" + self.workername + " is going to remove job from unassigned " + str(job))
        self.meteorClient.remove('unassigned', {'_id': job['_id']}, callback=self.callback_synchro)
        res = self.syncWait()  # konec locku
        self.logm("MWorkerLib:createMyJobInActive",
                  "worker:" + self.workername + " removed " + str(job)+ "result is "+str(res))
        return res
    #########################testing


    def testScenario1(self):
        """
        this is scenario 1...
        this will move job from unassigned to active, then from active to trash...
        also it will list unassigned tasks
        :return:



        self.logm("MWorkerLib:testScenario1", "start test scenario 1")
        self.logm("MWorkerLib:testScenario1", "moving from unassigned to active")
        all_posts = self.meteorClient.find('unassigned')
        #job = (all_posts).keys()

        #get first unassigned:
        firstUnassigned=self.meteorClient.find('unassigned')[0]
        ttltmp=100
        #pro testovaci ucely jsem vytvoril v active workera:
        # db.active.insert({"_id":"localhostworker","worker":"localhostworker","workerusername":"******","jobs":[]})

        template={
             'pluginname':firstUnassigned['pluginname'],
             'starttime':int(time.time()),    #epocha
             'ttl':ttltmp,
             'inputparams':firstUnassigned['inputparams'],
             'progress':0,
             'result':"undefined"
        }
        #insert to active:
        #musim pouzit id_ protoze klient rve ze kod je insecure a musi se pouzit id
        #s _id je dalsi problem. protoze interne mongodb pouziva objekt, ne stringu
        #tak bude vyhodnejsi nahradit pri vytvoreni workeru v active _id s nejakym retezcem  _
        worker=self.meteorClient.find_one('active',{"worker" : "localhostworker"})
        #worker["workerusername"]="******"
        self.logm("MWorkerLib:testScenario1", "worker:"+str(worker)+" "+worker['_id'])
        worker1=self.meteorClient.find_one('active',{"_id":worker["_id"]})

        self.logm("MWorkerLib:testScenario1", "worker:"+str(worker1)+" "+worker1['_id'])
        self.meteorClient.update('active',{'_id':worker['_id']},{'$push':{'jobs':template}},callback=update_callback)

        #self.infiniteCycle()
        self.logm("MWorkerLib:testScenario1", "deleting job from unassigned")
        self.meteorClient.remove('unassigned',{'_id':firstUnassigned['_id']},callback=remove_callback)




        time.sleep(5)
        """
        job = {'_id': "asdasdsad", 'jobname': "asdasd", 'pluginname': "asdasda", 'inputparams': "assad",
               'expectedttl': "100"}


        #otestovane a funkcni 11.2.2016:
        #tohle presune job s danym jmenem do active pod jmeno mistniho workera...
        #self.moveJobFromUnassignedToActiveSynchronised("blabla")

        self.moveJobFromActiveToTrash("blabla")
Beispiel #4
0
class DDP(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.meteor = conf.get('ddp', 'meteor')

        self.client = MeteorClient(self.meteor, debug=False)
        self.client.on('added', self.on_added)
        self.client.on('changed', self.on_changed)
        self.client.on('subscribed', self.on_subscribed)
        self.client.on('connected', self.on_connected)
        self.client.on('removed', self.on_removed)
        self.client.on('closed', self.on_closed)
        self.client.on('logged_in', self.on_logged_in)

        self.displayName = conf.get('ddp', 'room_name')
        self.vu_min = -50
        self.vu_range = 50
        self.vu_data = 0
        self.last_vu = None
        self.ip = conf.get('ingest', 'address')
        self.id = conf.get('ingest', 'hostname')
        self._user = conf.get('ddp', 'user')
        self._password = conf.get('ddp', 'password')
        self._http_host = conf.get('ddp', 'http_host')
        self._audiostream_port = conf.get('audiostream', 'port') or 31337
        self.store_audio = conf.get_boolean('ddp', 'store_audio')
        self.screenshot_file = conf.get('ddp', 'existing_screenshot')
        self.high_quality = conf.get_boolean('ddp', 'hq_snapshot')
        self.paused = False
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.has_disconnected = False
        screen = Gdk.Screen.get_default()
        self._screen_width = screen.get_width()
        self._screen_height = screen.get_height()
        self.cardindex = None

        cam_available = conf.get('ddp', 'cam_available') or 0
        if cam_available in ('True', 'true', True, '1', 1):
            self.cam_available = 1
        elif cam_available in ('False', 'false', False, '0', 0):
            self.cam_available = 0
        else:
            self.cam_available = int(cam_available)
        # Getting audiostream params. either using existing audiostreaming server like icecast or the audiostream plugin
        if conf.get('ddp', 'existing_stream_host'):
            self._stream_host = conf.get('ddp', 'existing_stream_host')
        else:
            self._stream_host = self.ip

        if conf.get_int('ddp', 'existing_stream_port'):
            self._audiostream_port = conf.get_int('ddp',
                                                  'existing_stream_port')
        else:
            self._audiostream_port = conf.get_int('audiostream',
                                                  'port') or 31337

        if conf.get('ddp', 'existing_stream_key'):
            self.stream_key = conf.get('ddp', 'existing_stream_key')
        else:
            self.stream_key = uuid.uuid4().get_hex()

        if conf.get('ddp', 'extra_params'):
            self.extra_params_list = conf.get('ddp', 'extra_params').split(';')
        else:
            self.extra_params_list = []
        logger.info(
            'audiostream URI: {}'.format('http://' + self._stream_host + ':' +
                                         str(self._audiostream_port) + '/' +
                                         self.stream_key))

        dispatcher.connect('init', self.on_init)
        dispatcher.connect('recorder-vumeter', self.vumeter)
        dispatcher.connect('timer-short', self.update_vu)
        dispatcher.connect('timer-short', self.heartbeat)
        dispatcher.connect('recorder-started', self.on_start_recording)
        dispatcher.connect('recorder-stopped', self.on_stop_recording)
        dispatcher.connect('recorder-status', self.on_rec_status_update)

    def run(self):
        self.connect()

    def connect(self):
        if not self.has_disconnected:
            try:
                self.client.connect()
            except Exception:
                logger.warn('DDP connection failed')

    def update(self, collection, query, update):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.update(collection,
                                   query,
                                   update,
                                   callback=self.update_callback)
            except Exception:
                logger.warn("Error updating document "
                            "{collection: %s, query: %s, update: %s}" %
                            (collection, query, update))

    def insert(self, collection, document):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.insert(collection,
                                   document,
                                   callback=self.insert_callback)
            except Exception:
                logger.warn(
                    "Error inserting document {collection: %s, document: %s}" %
                    (collection, document))

    def heartbeat(self, element):
        if self.client.connected:
            self.update_images()
        else:
            self.connect()

    def on_start_recording(self, sender, id):
        self.recording = True
        self.currentMediaPackage = self.media_package_metadata(id)
        self.currentProfile = conf.get_current_profile().name
        self.update('rooms', {'_id': self.id}, {
            '$set': {
                'currentMediaPackage': self.currentMediaPackage,
                'currentProfile': self.currentProfile,
                'recording': self.recording
            }
        })

    def on_stop_recording(self, mpid, sender=None):
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.update('rooms', {'_id': self.id}, {
            '$unset': {
                'currentMediaPackage': '',
                'currentProfile': ''
            },
            '$set': {
                'recording': self.recording
            }
        })
        self.update_images(1.5)

    def on_init(self, data):
        self.update_images(1.5)

    def update_images(self, delay=0.0):
        worker = Thread(target=self._update_images, args=(delay, ))
        worker.start()

    def _update_images(self, delay):
        time.sleep(delay)
        files = {}

        if not self.screenshot_file:
            # take a screenshot with pyscreenshot
            im = ImageGrab.grab(bbox=(0, 0, self._screen_width,
                                      self._screen_height),
                                backend='imagemagick')
        else:
            try:
                # used if screenshot already exists
                im = Image.open(self.screenshot_file)
            except IOError as e:
                logger.warn("Unable to open screenshot file {0}".format(
                    self.screenshot_file))
                return
        output = cStringIO.StringIO()
        image_format = 'JPEG'
        if not self.high_quality:
            im.thumbnail((640, 360), Image.ANTIALIAS)
        else:
            image_format = 'PNG'

        if im.mode != "RGB":
            im = im.convert("RGB")
        im.save(output, format=image_format
                )  # to reduce jpeg size use param: optimize=True
        files['galicaster'] = ('galicaster.jpg', output.getvalue(),
                               'image/jpeg')
        try:
            # add verify=False for testing self signed certs
            requests.post(
                "%s/image/%s" % (self._http_host, self.id),
                files=files,
                auth=(self._user, self._password
                      ))  # to ignore ssl verification, use param: verify=False
        except Exception:
            logger.warn('Unable to post images')

    def vumeter(self, element, data, data_chan2, vu_bool):
        if data == "Inf":
            data = 0
        else:
            if data < -self.vu_range:
                data = -self.vu_range
            elif data > 0:
                data = 0
        self.vu_data = int(
            ((data + self.vu_range) / float(self.vu_range)) * 100)

    def update_vu(self, element):
        if self.vu_data != self.last_vu:
            update = {'vumeter': self.vu_data}
            self.update('rooms', {'_id': self.id}, {'$set': update})
            self.last_vu = self.vu_data

    def on_rec_status_update(self, element, data):
        if data == 'paused':
            is_paused = True
        else:
            is_paused = False
        if is_paused:
            self.update_images(.75)
        if self.paused == is_paused:
            self.update('rooms', {'_id': self.id},
                        {'$set': {
                            'paused': is_paused
                        }})
            self.paused = is_paused
        if data == 'recording':
            self.update_images(.75)

    def media_package_metadata(self, id):
        mp = context.get('recorder').current_mediapackage
        line = mp.metadata_episode
        duration = mp.getDuration()
        line["duration"] = long(duration / 1000) if duration else None
        # FIXME Does series_title need sanitising as well as duration?
        created = mp.getDate()
        # line["created"] = calendar.timegm(created.utctimetuple())
        for key, value in mp.metadata_series.iteritems():
            line["series_" + key] = value
        for key, value in line.iteritems():
            if value in [None, []]:
                line[key] = ''
        # return line
        return line

    def subscription_callback(self, error):
        if error:
            logger.warn("Subscription callback returned error: %s" % error)

    def insert_callback(self, error, data):
        if error:
            logger.warn("Insert callback returned error: %s" % error)

    def update_callback(self, error, data):
        if error:
            logger.warn("Update callback returned error: %s" % error)

    def on_subscribed(self, subscription):
        if (subscription == 'GalicasterControl'):
            me = self.client.find_one('rooms')
            # Data to push when inserting or updating
            data = {
                'displayName': self.displayName,
                'ip': self.ip,
                'paused': self.paused,
                'recording': self.recording,
                'heartbeat': int(time.time()),
                'camAvailable': self.cam_available,
                'inputs': self.inputs(),
                'stream': {
                    'host': self._stream_host,
                    'port': self._audiostream_port,
                    'key': self.stream_key
                }
            }
            # Parse extra Meteor Mongodb collection elements and append
            for params in self.extra_params_list:
                param = params.split(':')
                data[param[0]] = param[1]

            if self.currentMediaPackage:
                data['currentMediaPackage'] = self.currentMediaPackage
            if self.currentProfile:
                data['currentProfile'] = self.currentProfile

            if me:
                # Items to unset
                unset = {}
                if not self.currentMediaPackage:
                    unset['currentMediaPackage'] = ''
                if not self.currentProfile:
                    unset['currentProfile'] = ''

                # Update to push
                update = {'$set': data}

                if unset:
                    update['$unset'] = unset
                self.update('rooms', {'_id': self.id}, update)
            else:
                data['_id'] = self.id
                self.insert('rooms', data)

    def inputs(self):
        inputs = {'presentations': ['Presentation']}
        inputs['cameras'] = []
        labels = conf.get('ddp', 'cam_labels')
        cam_labels = []
        if labels:
            cam_labels = [l.strip() for l in labels.split(',')]
        for i in range(0, self.cam_available):
            label = cam_labels[i] if i < len(cam_labels) else "Camera %d" % (
                i + 1)
            inputs['cameras'].append(label)
        return inputs

    def on_added(self, collection, id, fields):
        pass

    def on_changed(self, collection, id, fields, cleared):
        me = self.client.find_one('rooms')
        if self.paused != me['paused']:
            self.set_paused(me['paused'])

        if context.get('recorder').is_recording() != me['recording']:
            self.set_recording(me)

    def on_removed(self, collection, id):
        self.on_subscribed(None)

    def set_paused(self, new_status):
        if not self.paused:
            self.paused = new_status
            context.get('recorder').pause()
        else:
            self.paused = False
            context.get('recorder').resume()

    def set_recording(self, me):
        self.recording = me['recording']
        if self.recording:
            # FIXME: Metadata isn't passed to recorder
            meta = me.get('currentMediaPackage', {}) or {}
            profile = me.get('currentProfile', 'nocam')
            series = (meta.get('series_title', ''), meta.get('isPartOf', ''))
            user = {
                'user_name': meta.get('creator', ''),
                'user_id': meta.get('rightsHolder', '')
            }
            title = meta.get('title', 'Unknown')
            context.get('recorder').record()
        else:
            context.get('recorder').stop()

    def on_connected(self):
        logger.info('Connected to Meteor')
        token = conf.get('ddp', 'token')
        self.client.login(self._user, self._password, token=token)

    def on_logged_in(self, data):
        conf.set('ddp', 'token', data['token'])
        conf.update()
        try:
            self.client.subscribe('GalicasterControl',
                                  params=[self.id],
                                  callback=self.subscription_callback)
        except Exception:
            logger.warn('DDP subscription failed')

    def on_closed(self, code, reason):
        self.has_disconnected = True
        logger.error('Disconnected from Meteor: err %d - %s' % (code, reason))

    def subscribedTo(self, publication):
        return self.client.subscriptions.get(publication) != None
Beispiel #5
0
client.on('unsubscribed', unsubscribed)
client.on('logged_in', logged_in)
client.on('logged_out', logged_out)

client.connect()
client.logout()
client.login(configuration.USER, configuration.PASSWORD)

client.subscribe('configuration', callback=subscription_callback)
client.subscribe('queue', callback=subscription_callback)
client.subscribe('ingredients', callback=subscription_callback)
client.subscribe('cocktails', callback=subscription_callback)

sleep(1)

mixer_configuration = client.find_one('configuration',
                                      selector={"name": "mixer"})
mixer_status = client.find_one('configuration', selector={'name': 'status'})


def start_pump(valve):
    valvepin = configuration.VALVE_PINS[valve]
    arduino.digitalWrite(valvepin, arduino.HIGH)
    arduino.digitalWrite(configuration.PUMP_PIN, arduino.HIGH)


def stop_pump(valve):
    valvepin = configuration.VALVE_PINS[valve]
    arduino.digitalWrite(configuration.PUMP_PIN, arduino.LOW)
    arduino.digitalWrite(valvepin, arduino.LOW)

class DisplayMinion(App):
    action_map = {
        'media': MediaAction,
        'playlist': PlaylistAction,
        'song': SongAction,
        'presentation': PresentationAction,
        'presentationslide': PresentationAction,
        'timer': TimerAction,
        'camera': GStreamerAction,
        'clear-layer': Action
    }
    
    def __init__(self, **kwargs):
        self._id = None
        self.server = None
        self.ready = False

        self.state = 'disconnected' # 'disconnected' => 'connecting' => 'connected' => 'registering' => 'registered'
        self.binds = {}
        
        self.layers = {}

        self.sections = []
        self.last_blocks = None
        
        self.fullscreen = False
        
        self.defaults = json.load(open('common/default_settings.json'))
        
        super(DisplayMinion, self).__init__(**kwargs)

    def debug(self, *args):
        print(*args)
#        if self.config.get('debug'):
#            print(*args)

    def bind(self, event, function):
        if self.binds.get(event):
            self.binds[event].append(function)
            
        else:
            self.binds[event] = [function]
            
    def trigger_event(self, event):
        event_data = {'event': event, 'client': self}

        for function in self.binds.get(event, []):
            function(event_data)

    def connect(self, server):
        self.server = server
        
        self.meteor = MeteorClient('ws://{}/websocket'.format(self.server))
        self.meteor.on('connected', self.connected)
        self.meteor.connect()

        self.state = 'connecting'
        self.trigger_event('connecting')

    def connected(self):
        self.state = 'connected'
        self.trigger_event('connected')

        self.debug('Connected to server')

        self.time = MeteorTime(self.meteor)
        Clock.schedule_interval(self.time.update, 0.5)

        self.collections = 0
        self.collections_ready = 0

        for collection in ['settings', 'stages', 'minions',
                           'media', 'mediaplaylists',
                           'songs', 'songarrangements', 'songsections',
                           'presentations', 'presentationslides']:

            self.collections += 1
            self.meteor.subscribe(collection, callback=self.subscription_ready)

    def subscription_ready(self, err):
        if err: self.debug(err)
        self.collections_ready += 1

        if self.collections_ready >= self.collections:
            self.trigger_event('loaded')
            self.debug('All subscriptions ready')

    def register(self, _id):
        self._id = _id
        self.meteor.call('minionConnect', [_id], self.prep)

        self.state = 'registering'
        self.trigger_event('registering')
        
    def prep(self, e, r):
        self.state = 'registered'
        self.trigger_event('registered')
    
        self.debug('Registered')
        
        self.meteor.on('added', self.added)
        self.meteor.on('changed', self.changed)
        
        self.minion = self.meteor.find_one('minions', selector = {'_id': self._id});
        self.stage = self.meteor.find_one('stages', selector = {'_id': self.minion['stage']})

        self.update_minion_settings(self.minion)

        Clock.create_trigger(self.update_layers)()
        Clock.schedule_once(self.update_minion_blocks, 0)
        
        self.ready = True
            
    def added(self, collection, _id, fields):
        self.changed(collection, _id, fields, None)
        
    def changed(self, collection, _id, fields, cleared):
        if not self.ready: return
        
        if collection == 'minions' and _id == self._id:
            self.minion = self.meteor.find_one('minions', selector = {'_id': self._id});
            self.update_minion_settings(self.minion)
        
        if collection == 'stages' and _id == self.minion['stage']:
            self.stage = self.meteor.find_one('stages', selector = {'_id': self.minion['stage']})
            Clock.create_trigger(self.update_layers)()
            
    def update_minion_settings(self, minion):
        if not minion['settings']['blocks'] == self.last_blocks:
            self.last_blocks = minion['settings']['blocks']
            Clock.schedule_once(self.update_minion_blocks, 0)
        
        if not minion['settings'].get('mediaminion_width', 0) == self.source.disp_size[0] or \
           not minion['settings'].get('mediaminion_height', 0) == self.source.disp_size[1]:
            self.source.disp_size = (int(minion['settings'].get('mediaminion_width') or 0), int(minion['settings'].get('mediaminion_height') or 0))
            self.source.resize()
        
    def update_minion_blocks(self, dt):
        # Note: Sections were originally named "blocks", so far I've been too lazy to rewrite all the cedarserver code to reflect the new name. -IHS
        start_length = len(self.sections)
        block_delta = len(self.minion['settings']['blocks']) - start_length

        if block_delta > 0:
            for n in range(block_delta):
                config = self.minion['settings']['blocks'][start_length + n]
                
                section = Section(
                    source = self.source,
                    block = config,
                    client = self
                )
                
                self.layout.add_widget(section)
                self.sections.append(section)
                
        elif block_delta < 0:
            for n in range(abs(block_delta)):
                section = self.sections.pop()
                self.layout.remove_widget(section)
        
        for index, section in enumerate(self.sections):
            config = self.minion['settings']['blocks'][index]
            if not section.block == config: # TODO add brightness etc.
                section.block = config
                section.recalc()
    
    def update_layers(self, dt = None):
        layers = self.stage.get('layers', [])

        for layer, action in layers.items():
            if not layer in self.minion['layers']: continue
        
            if action and self.layers.get(layer):
                # Test if new action is the same as the current one
                current = self.layers.get(layer).action

                if action['_id'] == current['_id']:
                    if action['type'] == 'song':
                        if action.get('args') and current.get('args') and \
                           action['args']['section'] == current['args']['section'] and \
                           action['args']['index'] == current['args']['index']:
                            continue
                    
                    elif action['type'] == 'presentation':
                        if action.get('args') and current.get('args') and \
                           action['args']['order'] == current['args']['order'] and \
                           action['args']['fillin'] == current['args']['fillin']:
                            continue
                            
                    else: continue
            
            if action and self.action_map.get(action['type']):
                self.layers[layer] = self.action_map[action['type']](action, self.layers.get(layer) or None, self)

                self.layers[layer].show()
                
            elif action == None and self.layers.get(layer):
                self.layers[layer].hide()
                self.layers[layer].remove()
                self.layers[layer] = None
            
                
    def get_layer_index(self, target_layer):
        layers = self.stage['settings']['layers']
        layer_index = layers.index(target_layer)

        higher_layers = layers[layer_index + 1:]
        widget_index = 0

        for layer in higher_layers:
            higher_action = self.layers.get(layer)
 
            if higher_action:
                higher_index = higher_action.get_current_widget_index()
                
                if not higher_index == None:
                    widget_index = higher_index + 1
                    break
                
        return widget_index
        

    def add_layer_widget(self, new_widget, layer):
        # TODO switch to this behavior once https://github.com/kivy/kivy/issues/4293 is resolved
        # self.source.add_widget(widget, index = self.get_layer_index(layer))
        
        widgets = self.source.children[:]
        new_index = self.get_layer_index(layer)
        
        for widget in widgets:
            self.source.remove_widget(widget)
        
        widgets.insert(new_index, new_widget)
        widgets.reverse()
        
        for widget in widgets:
            self.source.add_widget(widget)

    
    def remove_widget(self, widget):
        self.source.remove_widget(widget)

    def get_application_config(self):
        return super(DisplayMinion, self).get_application_config('~/.%(appname)s.ini')

    def build_config(self, config):
        config.setdefaults('connection', {
            'server': 'localhost:3000',
            '_id': '',
            'autoconnect': 'no',
        })
        
        config.setdefaults('window', {
            'fullscreen': 'no'
        })
        
        config.setdefaults('outputs', {
            'shmsink': 'no'
        })
   
    def toggle_fullscreen(self, thing, touch):        
        if not self.ui.layout.collide_point(*touch.pos):
            if self.fullscreen: Window.fullscreen = 0
            else: Window.fullscreen = 'auto'
            self.fullscreen = not self.fullscreen
    
    def on_stop(self):
        self.source.stop()
        
    def build(self):
        self.title = 'Cedar Display Client'
        
        if self.config.get('window', 'fullscreen') == 'yes':
            Window.fullscreen = 'auto'
        
        if kivy.utils.platform is 'windows':
            self.icon = 'logo/logo-128x128.png'
        else:
            self.icon = 'logo/logo-1024x1024.png'

        self.source = DisplaySource(self, pos_hint = {'x': 1, 'y': 1}, size_hint = [None, None])
        self.source.bind(on_touch_down = self.toggle_fullscreen)

        self.layout = FloatLayout()
        self.layout.add_widget(self.source)
                
        self.ui = UserInterface(self)
        
        return self.layout
Beispiel #7
0
                    "emergencyName": line[8],
                    "emergencyPhone": line[9],
                    "uniform": line[10],
                    "allergy": line[11],
                    "previousParticipation": line[12]
                }
            except:
                print("Failed to Parse Data...")

                #this really only happens for this last line in the file...
                student = {
                    "season": season,
                    "program": program,
                    "site": "over-10",
                    "firstName": "Jonathan",
                    "lastName": "Del Rosario"
                }

        client.insert("students", student, callback=new_student_callback)


if __name__ == "__main__":
    print("Starting...")
    s = client.find_one('students')
    print(s)
    # client.remove("students", {}, callback=remove_callback)
    # client.remove("registrations", {}, callback=remove_callback)
    # client.remove("volunteers", {}, callback=remove_callback)
    # client.remove("attendance", {}, callback=remove_callback)
    # run()
Beispiel #8
0
class DDP(Thread):

    def __init__(self):
        Thread.__init__(self)
        self.meteor = conf.get('ddp', 'meteor')

        self.client = MeteorClient(self.meteor, debug=False)
        self.client.on('added', self.on_added)
        self.client.on('changed', self.on_changed)
        self.client.on('subscribed', self.on_subscribed)
        self.client.on('connected', self.on_connected)
        self.client.on('removed', self.on_removed)
        self.client.on('closed', self.on_closed)
        self.client.on('logged_in', self.on_logged_in)

        self.displayName = conf.get('sussexlogin', 'room_name')
        self.vu_min = -70
        self.vu_range = 40
        self.do_vu = 0
        self.last_vu = None
        self.ip = socket.gethostbyname(socket.gethostname())
        self.id = conf.get('ingest', 'hostname')
        self._user = conf.get('ddp', 'user')
        self._password = conf.get('ddp', 'password')
        self._http_host = conf.get('ddp', 'http_host')
        self._audiostream_port = conf.get('audiostream', 'port') or 31337
        self.netreg_id = conf.get('ddp', 'netreg_id')
        self.store_audio = conf.get_boolean('ddp', 'store_audio')
        self.paused = False
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.has_disconnected = False

        cam_available = conf.get(
            'sussexlogin',
            'cam_available') or cam_available
        if cam_available in ('True', 'true', True, '1', 1):
            self.cam_available = 1
        elif cam_available in ('False', 'false', False, '0', 0):
            self.cam_available = 0
        else:
            self.cam_available = int(cam_available)

        self.audiofaders = []
        faders = conf.get('ddp', 'audiofaders').split()
        for fader in faders:
            audiofader = {}
            fader = 'audiofader-' + fader
            audiofader['name'] = conf.get(fader, 'name')
            audiofader['display'] = conf.get(fader, 'display')
            audiofader['min'] = conf.get_int(fader, 'min')
            audiofader['max'] = conf.get_int(fader, 'max')
            audiofader['type'] = conf.get(fader, 'type')
            audiofader['setrec'] = conf.get_boolean(fader, 'setrec')
            audiofader['mute'] = conf.get_boolean(fader, 'mute')
            audiofader['unmute'] = conf.get_boolean(fader, 'unmute')
            audiofader['setlevel'] = conf.get_int(fader, 'setlevel')
            try:
                audiofader['control'] = alsaaudio.Mixer(
                    control=audiofader['name'])
                self.audiofaders.append(audiofader)
            except Exception as e:
                logger.warn(e)
        fd, eventmask = self.audiofaders[0]['control'].polldescriptors()[0]
        self.watchid = gobject.io_add_watch(fd, eventmask, self.mixer_changed)

        dispatcher.connect('galicaster-init', self.on_init)
        dispatcher.connect('update-rec-vumeter', self.vumeter)
        dispatcher.connect('galicaster-notify-timer-short', self.heartbeat)
        dispatcher.connect('start-before', self.on_start_recording)
        dispatcher.connect('restart-preview', self.on_stop_recording)
        dispatcher.connect('update-rec-status', self.on_rec_status_update)

    def run(self):
        self.connect()

    def connect(self):
        if not self.has_disconnected:
            try:
                self.client.connect()
            except Exception:
                logger.warn('DDP connection failed')

    def update(self, collection, query, update):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.update(
                    collection,
                    query,
                    update,
                    callback=self.update_callback)
            except Exception:
                logger.warn(
                    "Error updating document "
                    "{collection: %s, query: %s, update: %s}" %
                    (collection, query, update))

    def insert(self, collection, document):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.insert(
                    collection,
                    document,
                    callback=self.insert_callback)
            except Exception:
                logger.warn(
                    "Error inserting document {collection: %s, document: %s}" %
                    (collection, document))

    def heartbeat(self, element):
        if self.client.connected:
            self.update_images()
        else:
            self.connect()

    def on_start_recording(self, sender, id):
        self.recording = True
        self.currentMediaPackage = self.media_package_metadata(id)
        self.currentProfile = context.get_state().profile.name
        self.update(
            'rooms', {
                '_id': self.id
            }, {
                '$set': {
                    'currentMediaPackage': self.currentMediaPackage,
                    'currentProfile': self.currentProfile,
                    'recording': self.recording
                }
            })

    def on_stop_recording(self, sender=None):
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.update(
            'rooms', {
                '_id': self.id
            }, {
                '$unset': {
                    'currentMediaPackage': '',
                    'currentProfile': ''
                }, '$set': {
                    'recording': self.recording
                }
            })
        self.update_images(1.5)

    def on_init(self, data):
        self.update_images(1.5)

    def update_images(self, delay=0):
        worker = Thread(target=self._update_images, args=(delay,))
        worker.start()

    def _update_images(self, delay):
        time.sleep(delay)
        files = {}
        audio_devices = ['audiotest', 'autoaudio', 'pulse']
        for track in context.get_state().profile.tracks:
            if track.device not in audio_devices:
                file = os.path.join('/tmp', track.file + '.jpg')
                try:
                    if(os.path.getctime(file) > time.time() - 3):
                        files[track.flavor] = (track.flavor + '.jpg',
                                               open(file, 'rb'),
                                               'image/jpeg')
                except Exception:
                    logger.warn("Unable to check date of or open file (%s)"
                                % file)
        im = ImageGrab.grab(bbox=(10, 10, 1280, 720), backend='imagemagick')
        im.thumbnail((640, 360))
        output = cStringIO.StringIO()
        if im.mode != "RGB":
            im = im.convert("RGB")
        im.save(output, format="JPEG")
        files['galicaster'] = ('galicaster.jpg', output.getvalue(),
                               'image/jpeg')
        try:
            # add verify=False for testing self signed certs
            requests.post(
                "%s/image/%s" %
                (self._http_host, self.id), files=files, auth=(
                    self._user, self._password))
        except Exception:
            logger.warn('Unable to post images')

    def mixer_changed(self, source=None, condition=None, reopen=True):
        if reopen:
            for audiofader in self.audiofaders:
                audiofader['control'] = alsaaudio.Mixer(
                    control=audiofader['name'])
        self.update_audio()
        return True

    def vumeter(self, element, data):
        if self.do_vu == 0:
            if data == "Inf":
                data = 0
            else:
                if data < -self.vu_range:
                    data = -self.vu_range
                elif data > 0:
                    data = 0
            data = int(((data + self.vu_range) / float(self.vu_range)) * 100)
            if data != self.last_vu:
                update = {'vumeter': data}
                self.update('rooms', {'_id': self.id}, {'$set': update})
                self.last_vu = data
        self.do_vu = (self.do_vu + 1) % 20

    def on_rec_status_update(self, element, data):
        is_paused = data == 'Paused'
        if is_paused:
            self.update_images(.75)
        if self.paused != is_paused:
            self.update(
                'rooms', {
                    '_id': self.id}, {
                    '$set': {
                        'paused': is_paused}})
            self.paused = is_paused
        if data == '  Recording  ':
            subprocess.call(['killall', 'maliit-server'])
            self.update_images(.75)

    def media_package_metadata(self, id):
        mp = context.get_repository().get(id)
        line = mp.metadata_episode.copy()
        duration = mp.getDuration()
        line["duration"] = long(duration / 1000) if duration else None
        # Does series_title need sanitising as well as duration?
        created = mp.getDate()
        line["created"] = calendar.timegm(created.utctimetuple())
        for key, value in mp.metadata_series.iteritems():
            line["series_" + key] = value
        for key, value in line.iteritems():
            if value in [None, []]:
                line[key] = ''
        return line

    def subscription_callback(self, error):
        if error:
            logger.warn("Subscription callback returned error: %s" % error)

    def insert_callback(self, error, data):
        if error:
            logger.warn("Insert callback returned error: %s" % error)

    def update_callback(self, error, data):
        if error:
            logger.warn("Update callback returned error: %s" % error)

    def on_subscribed(self, subscription):
        if(subscription == 'GalicasterControl'):
            me = self.client.find_one('rooms')
            stream_key = uuid.uuid4().get_hex()

            # Data to push when inserting or updating
            data = {
                'displayName': self.displayName,
                'ip': self.ip,
                'paused': self.paused,
                'recording': self.recording,
                'heartbeat': int(time.time()),
                'camAvailable': self.cam_available,
                'netregId': self.netreg_id,
                'inputs': self.inputs(),
                'stream': {
                    'port': self._audiostream_port,
                    'key': stream_key
                },
                'galicasterVersion': galicaster.__version__
            }
            if self.currentMediaPackage:
                data['currentMediaPackage'] = self.currentMediaPackage
            if self.currentProfile:
                data['currentProfile'] = self.currentProfile

            if me:
                # Items to unset
                unset = {}
                if not self.currentMediaPackage:
                    unset['currentMediaPackage'] = ''
                if not self.currentProfile:
                    unset['currentProfile'] = ''

                # Update to push
                update = {
                    '$set': data
                }

                if unset:
                    update['$unset'] = unset
                self.update('rooms', {'_id': self.id}, update)
            else:
                audio = self.read_audio_settings()
                data['_id'] = self.id
                data['audio'] = audio
                self.insert('rooms', data)

    def inputs(self):
        inputs = {
            'presentations': ['Presentation']
        }
        inputs['cameras'] = []
        labels = conf.get('sussexlogin', 'matrix_cam_labels')
        cam_labels = []
        if labels:
            cam_labels = [l.strip() for l in labels.split(',')]
        for i in range(0, self.cam_available):
            label = cam_labels[i] if i < len(
                cam_labels) else "Camera %d" % (i + 1)
            inputs['cameras'].append(label)
        return inputs

    def set_audio(self, fields):
        faders = fields.get('audio')
        if faders:
            for fader in faders:
                mixer = None
                level = fader.get('level')
                for audiofader in self.audiofaders:
                    if audiofader['name'] == fader['name']:
                        mixer = audiofader['control']
                        break
                if mixer:
                    l, r = mixer.getvolume(fader['type'])
                    if level >= 0 and l != level:
                        mixer.setvolume(level, 0, fader['type'])
                        mixer.setvolume(level, 1, fader['type'])
            if self.store_audio:
                # Relies on no password sudo access for current user to alsactl
                subprocess.call(['sudo', 'alsactl', 'store'])

    def on_added(self, collection, id, fields):
        self.set_audio(fields)
        self.update_audio()

    def on_changed(self, collection, id, fields, cleared):
        self.set_audio(fields)
        me = self.client.find_one('rooms')
        if self.paused != me['paused']:
            self.set_paused(me['paused'])
        if context.get_state().is_recording != me['recording']:
            self.set_recording(me)

    def on_removed(self, collection, id):
        self.on_subscribed(None)

    def set_paused(self, new_status):
        self.paused = new_status
        dispatcher.emit("toggle-pause-rec")

    def set_recording(self, me):
        self.recording = me['recording']
        if self.recording:
            meta = me.get('currentMediaPackage', {}) or {}
            profile = me.get('currentProfile', 'nocam')
            series = (meta.get('series_title', ''), meta.get('isPartOf', ''))
            user = {'user_name': meta.get('creator', ''),
                    'user_id': meta.get('rightsHolder', '')}
            title = meta.get('title', 'Unknown')
            dispatcher.emit('sussexlogin-record',
                            (user, title, series, profile))
        else:
            dispatcher.emit("stop-record", '')

    def on_connected(self):
        logger.info('Connected to Meteor')
        token = conf.get('ddp', 'token')
        self.client.login(self._user, self._password, token=token)

    def on_logged_in(self, data):
        conf.set('ddp', 'token', data['token'])
        conf.update()
        try:
            self.client.subscribe(
                'GalicasterControl',
                params=[
                    self.id],
                callback=self.subscription_callback)
        except Exception:
            logger.warn('DDP subscription failed')

    def on_closed(self, code, reason):
        self.has_disconnected = True
        logger.error('Disconnected from Meteor: err %d - %s' % (code, reason))

    def update_audio(self):
        me = self.client.find_one('rooms')
        audio = self.read_audio_settings()
        update = False
        if me:
            mAudio = me.get('audio')
            mAudioNames = [x['name'] for x in mAudio]
            audioNames = [x['name'] for x in audio]
            if set(mAudioNames) != set(audioNames):
                update = True
            if not update:
                for key, fader in enumerate(audio):
                    if mAudio[key].get('level') != fader.get('level'):
                        update = True
            if update:
                self.update(
                    'rooms', {
                        '_id': self.id}, {
                        '$set': {
                            'audio': audio}})

    def read_audio_settings(self):
        audio_settings = []
        for audiofader in self.audiofaders:
            if audiofader['display']:
                audio_settings.append(
                    self.control_values(audiofader)
                )
            # ensure fixed values
            mixer = audiofader['control']
            if audiofader['setrec']:
                mixer.setrec(1)
            if audiofader['mute']:
                mixer.setmute(1)
            if audiofader['unmute']:
                mixer.setmute(0)
            if audiofader['setlevel'] >= 0:
                mixer.setvolume(audiofader['setlevel'], 0, audiofader['type'])
                if 'Joined Playback Volume' not in mixer.volumecap():
                    mixer.setvolume(audiofader['setlevel'],
                                    1, audiofader['type'])
        return audio_settings

    def control_values(self, audiofader):
        controls = {}
        left, right = audiofader['control'].getvolume(audiofader['type'])
        controls['min'] = audiofader['min']
        controls['max'] = audiofader['max']
        controls['level'] = left
        controls['type'] = audiofader['type']
        controls['name'] = audiofader['name']
        controls['display'] = audiofader['display']
        return controls

    def subscribedTo(self, publication):
        return self.client.subscriptions.get(publication) is not None
Beispiel #9
0
class ConnectionManager():

    def __init__(self, server_url='ws://localhost:3000/websocket', worker=None):
        self.server_url = server_url
        self.client = MeteorClient(server_url)
        self.client.on('subscribed', self.subscribed)
        self.client.on('unsubscribed', self.unsubscribed)
        self.client.on('added', self.added)
        self.client.on('changed', self.changed)
        self.client.on('removed', self.removed)
        self.client.on('connected', self.connected)
        self.client.on('logged_in', self.logged_in)
        self.client.on('logged_out', self.logged_out)
        self.worker = worker
        self.connected = False
        self.ready = False

    def connect(self):
        self.client.connect()

    def connected(self):
        self.connected = True
        print('connected to ' + self.server_url)
        #self.client.login('test', '*****')
        if not 'workers.worker' in self.client.subscriptions:
            self.client.subscribe(
                'workers.worker', [self.worker.id, self.worker.token])

    def logged_in(self, data):
        self.userId = data['id']
        print('* LOGGED IN {}'.format(data))

    def subscribed(self, subscription):
        print('* SUBSCRIBED {}'.format(subscription))
        self.ready = True
        if subscription == 'workers.worker':
            if self.client.find_one('workers', selector={'_id': self.worker.id}):
                print('-----Worker {} found-----'.format(self.worker.id))
                if not 'widgets.worker' in self.client.subscriptions:
                    self.client.subscribe(
                        'widgets.worker', [self.worker.id, self.worker.token])
            else:
                raise Exception('Failed to find the worker with id:{} token{}'.format(
                    self.worker.id, self.worker.token))

        if subscription == 'widgets.worker':
            print('widgets of this worker SUBSCRIBED-')

        elif subscription == 'tasks.worker':
            print('* tasks of this worker SUBSCRIBED-')

    def added(self, collection, id, fields):
        print('* ADDED {} {}'.format(collection, id))
        # for key, value in fields.items():
        #    print('  - FIELD {} {}'.format(key, value))
        if collection == 'tasks':
            if not self.worker.workTasks.has_key(id):
                if fields.has_key('worker') and fields['worker'] == self.worker.id:
                    taskDoc = self.client.find_one('tasks', selector={'_id': id})
                    widget = self.worker.get_registered_widget(taskDoc['widgetId'])
                    if widget:
                        task = Task(taskDoc, self.worker, self.client)
                        if task and task.id:
                            self.worker.add_task(task)
                        else:
                            # remove task if widget is not registered
                            self.client.call('tasks.update.worker', [
                                                   id, self.worker.id, self.worker.token, {'$set': {'visible2worker': False}}])
                    else:
                        # remove task if widget is not registered
                        self.client.call('tasks.update.worker', [
                                               id, self.worker.id, self.worker.token, {'$set': {'visible2worker': False}}])

        elif collection == 'users':
            self.userName = fields['username']
        elif collection == 'widgets':
            # widget = fields#self.client.find_one('widgets', selector={'name':
            widget_ = Widget(self.client.find_one(
                'widgets', selector={'_id': id}), self.worker, self.client)
            if widget_.id:
                self.worker.register_widget(widget_)
                if not 'tasks.worker' in self.client.subscriptions:
                    self.client.subscribe(
                        'tasks.worker', [self.worker.id, self.worker.token])

    def changed(self, collection, id, fields, cleared):
        #print('* CHANGED {} {}'.format(collection, id))
        # for key, value in fields.items():
        #    print('  - FIELD {} {}'.format(key, value))
        # for key, value in cleared.items():
        #    print('  - CLEARED {} {}'.format(key, value))
        if collection == 'tasks':
            if self.worker.workTasks.has_key(id):
                task = self.worker.workTasks[id]
                for key, value in fields.items():
                    if key == 'cmd':
                        self.worker.execute_task_cmd(task, key, value)
                    elif key == 'worker':
                        self.worker.task_worker_changed(task, key, value)

                    if task.processor.changeCallbackDict.has_key(key):
                        for changeCallback in task.processor.changeCallbackDict[key]:
                            try:
                                changeCallback(task, key, value)
                            except Exception as e:
                                traceback.print_exc()
                                task.set('status.error', traceback.format_exc())

                for key, value in cleared.items():
                    if key == 'cmd':
                        self.worker.execute_task_cmd(task, key, value)
                    elif key == 'worker':
                        self.worker.task_worker_changed(task, key, value)

                    if task.processor.changeCallbackDict.has_key(key):
                        for changeCallback in task.processor.changeCallbackDict[key]:
                            try:
                                changeCallback(task, key, value)
                            except Exception as e:
                                traceback.print_exc()
                                task.set('status.error', traceback.format_exc())

            else:
                if fields.has_key('worker') and fields['worker'] == self.worker.id:
                    self.worker.add_task(id)

                #print('task is not in worktask list: ' + id)
        if collection == 'widgets':
            widget_ = Widget(self.client.find_one(
                'widgets', selector={'_id': id}), self.worker, self.client)
            if widget_.id:
                self.worker.register_widget(widget_)

            if fields.has_key('workers'):
                if fields['workers'].has_key(self.worker.id):
                    #print('worker config changed')
                    worker = fields['workers'][self.worker.id]
                    if worker.has_key('cmd'):
                        self.worker.execute_worker_cmd(worker['cmd'])

    def removed(self, collection, id):
        print('* REMOVED {} {}'.format(collection, id))
        if collection == 'tasks':
            if self.worker.workTasks.has_key(id):
                task = self.worker.workTasks[id]
                self.worker.remove_task(task)
                for cb in task.processor.removeCallbackList:
                    cb(task)

    def unsubscribed(self, subscription):
        print('* UNSUBSCRIBED {}'.format(subscription))

    def logged_out():
        self.userId = None
        print('* LOGGED OUT')

    def subscription_callback(self, error):
        if error:
            print(error)

    def run(self):
        # (sort of) hacky way to keep the client alive
        # ctrl + c to kill the script
        try:
            while True:
                time.sleep(1)
        except:
            traceback.print_exc()
        finally:
            self.stop()
            print('server exited')

    def stop(self):
        try:
            for task in self.worker.workTasks:
                if task.processor:
                    task.processor.stop()
        except Exception as e:
            pass
        self.worker['status'] = 'stopped'
        for subscription in self.client.subscriptions.copy():
            self.client.unsubscribe(subscription)
        return
    print(data)
def update_callback(error, data):
    if error:
        print(error)
        return
    print(data)
def remove_callback(error, data):
    if error:
        print(error)
        return
    print(data)

client.subscribe('snowDroneTest4', callback=subscription_callback)
#all_posts = client.find('snowDroneTest3')
#print all_posts
boolTrue = True
while(1):
    vehicle1 = client.find_one('snowDroneTest4', selector={'name': 'Vehicle 3'})
    #vehicle1['speed'] = incomingSpeed
    print vehicle1['current_location']
    vehicle1['current_location'][0] = vehicle1['current_location'][0] + 0.0001
    if boolTrue:
        vehicle1['status'] = 'ON'
        boolTrue = False
    else:
        vehicle1['status'] = 'OFF'
        boolTrue = True
    client.update('snowDroneTest4', {'_id': vehicle1['_id']}, vehicle1, callback=insert_callback)
    time.sleep(10)
Beispiel #11
0
class DDP(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.meteor = conf.get('ddp', 'meteor')

        self.client = MeteorClient(self.meteor, debug=False)
        self.client.on('added', self.on_added)
        self.client.on('changed', self.on_changed)
        self.client.on('subscribed', self.on_subscribed)
        self.client.on('connected', self.on_connected)
        self.client.on('removed', self.on_removed)
        self.client.on('closed', self.on_closed)
        self.client.on('logged_in', self.on_logged_in)

        self.displayName = conf.get('sussexlogin', 'room_name')
        self.vu_min = -70
        self.vu_range = 40
        self.do_vu = 0
        self.last_vu = None
        self.ip = socket.gethostbyname(socket.gethostname())
        self.id = conf.get('ingest', 'hostname')
        self._user = conf.get('ddp', 'user')
        self._password = conf.get('ddp', 'password')
        self._http_host = conf.get('ddp', 'http_host')
        self._audiostream_port = conf.get('audiostream', 'port') or 31337
        self.netreg_id = conf.get('ddp', 'netreg_id')
        self.store_audio = conf.get_boolean('ddp', 'store_audio')
        self.paused = False
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.has_disconnected = False

        cam_available = conf.get('sussexlogin',
                                 'cam_available') or cam_available
        if cam_available in ('True', 'true', True, '1', 1):
            self.cam_available = 1
        elif cam_available in ('False', 'false', False, '0', 0):
            self.cam_available = 0
        else:
            self.cam_available = int(cam_available)

        self.audiofaders = []
        faders = conf.get('ddp', 'audiofaders').split()
        for fader in faders:
            audiofader = {}
            fader = 'audiofader-' + fader
            audiofader['name'] = conf.get(fader, 'name')
            audiofader['display'] = conf.get(fader, 'display')
            audiofader['min'] = conf.get_int(fader, 'min')
            audiofader['max'] = conf.get_int(fader, 'max')
            audiofader['type'] = conf.get(fader, 'type')
            audiofader['setrec'] = conf.get_boolean(fader, 'setrec')
            audiofader['mute'] = conf.get_boolean(fader, 'mute')
            audiofader['unmute'] = conf.get_boolean(fader, 'unmute')
            audiofader['setlevel'] = conf.get_int(fader, 'setlevel')
            try:
                audiofader['control'] = alsaaudio.Mixer(
                    control=audiofader['name'])
                self.audiofaders.append(audiofader)
            except Exception as e:
                logger.warn(e)
        fd, eventmask = self.audiofaders[0]['control'].polldescriptors()[0]
        self.watchid = gobject.io_add_watch(fd, eventmask, self.mixer_changed)

        dispatcher.connect('galicaster-init', self.on_init)
        dispatcher.connect('update-rec-vumeter', self.vumeter)
        dispatcher.connect('galicaster-notify-timer-short', self.heartbeat)
        dispatcher.connect('start-before', self.on_start_recording)
        dispatcher.connect('restart-preview', self.on_stop_recording)
        dispatcher.connect('update-rec-status', self.on_rec_status_update)

    def run(self):
        self.connect()

    def connect(self):
        if not self.has_disconnected:
            try:
                self.client.connect()
            except Exception:
                logger.warn('DDP connection failed')

    def update(self, collection, query, update):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.update(collection,
                                   query,
                                   update,
                                   callback=self.update_callback)
            except Exception:
                logger.warn("Error updating document "
                            "{collection: %s, query: %s, update: %s}" %
                            (collection, query, update))

    def insert(self, collection, document):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.insert(collection,
                                   document,
                                   callback=self.insert_callback)
            except Exception:
                logger.warn(
                    "Error inserting document {collection: %s, document: %s}" %
                    (collection, document))

    def heartbeat(self, element):
        if self.client.connected:
            self.update_images()
        else:
            self.connect()

    def on_start_recording(self, sender, id):
        self.recording = True
        self.currentMediaPackage = self.media_package_metadata(id)
        self.currentProfile = context.get_state().profile.name
        self.update('rooms', {'_id': self.id}, {
            '$set': {
                'currentMediaPackage': self.currentMediaPackage,
                'currentProfile': self.currentProfile,
                'recording': self.recording
            }
        })

    def on_stop_recording(self, sender=None):
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.update('rooms', {'_id': self.id}, {
            '$unset': {
                'currentMediaPackage': '',
                'currentProfile': ''
            },
            '$set': {
                'recording': self.recording
            }
        })
        self.update_images(1.5)

    def on_init(self, data):
        self.update_images(1.5)

    def update_images(self, delay=0):
        worker = Thread(target=self._update_images, args=(delay, ))
        worker.start()

    def _update_images(self, delay):
        time.sleep(delay)
        files = {}
        audio_devices = ['audiotest', 'autoaudio', 'pulse']
        for track in context.get_state().profile.tracks:
            if track.device not in audio_devices:
                file = os.path.join('/tmp', track.file + '.jpg')
                try:
                    if (os.path.getctime(file) > time.time() - 3):
                        files[track.flavor] = (track.flavor + '.jpg',
                                               open(file, 'rb'), 'image/jpeg')
                except Exception:
                    logger.warn("Unable to check date of or open file (%s)" %
                                file)
        im = ImageGrab.grab(bbox=(10, 10, 1280, 720), backend='imagemagick')
        im.thumbnail((640, 360))
        output = cStringIO.StringIO()
        if im.mode != "RGB":
            im = im.convert("RGB")
        im.save(output, format="JPEG")
        files['galicaster'] = ('galicaster.jpg', output.getvalue(),
                               'image/jpeg')
        try:
            # add verify=False for testing self signed certs
            requests.post("%s/image/%s" % (self._http_host, self.id),
                          files=files,
                          auth=(self._user, self._password))
        except Exception:
            logger.warn('Unable to post images')

    def mixer_changed(self, source=None, condition=None, reopen=True):
        if reopen:
            for audiofader in self.audiofaders:
                audiofader['control'] = alsaaudio.Mixer(
                    control=audiofader['name'])
        self.update_audio()
        return True

    def vumeter(self, element, data):
        if self.do_vu == 0:
            if data == "Inf":
                data = 0
            else:
                if data < -self.vu_range:
                    data = -self.vu_range
                elif data > 0:
                    data = 0
            data = int(((data + self.vu_range) / float(self.vu_range)) * 100)
            if data != self.last_vu:
                update = {'vumeter': data}
                self.update('rooms', {'_id': self.id}, {'$set': update})
                self.last_vu = data
        self.do_vu = (self.do_vu + 1) % 20

    def on_rec_status_update(self, element, data):
        is_paused = data == 'Paused'
        if is_paused:
            self.update_images(.75)
        if self.paused != is_paused:
            self.update('rooms', {'_id': self.id},
                        {'$set': {
                            'paused': is_paused
                        }})
            self.paused = is_paused
        if data == '  Recording  ':
            subprocess.call(['killall', 'maliit-server'])
            self.update_images(.75)

    def media_package_metadata(self, id):
        mp = context.get_repository().get(id)
        line = mp.metadata_episode.copy()
        duration = mp.getDuration()
        line["duration"] = long(duration / 1000) if duration else None
        # Does series_title need sanitising as well as duration?
        created = mp.getDate()
        line["created"] = calendar.timegm(created.utctimetuple())
        for key, value in mp.metadata_series.iteritems():
            line["series_" + key] = value
        for key, value in line.iteritems():
            if value in [None, []]:
                line[key] = ''
        return line

    def subscription_callback(self, error):
        if error:
            logger.warn("Subscription callback returned error: %s" % error)

    def insert_callback(self, error, data):
        if error:
            logger.warn("Insert callback returned error: %s" % error)

    def update_callback(self, error, data):
        if error:
            logger.warn("Update callback returned error: %s" % error)

    def on_subscribed(self, subscription):
        if (subscription == 'GalicasterControl'):
            me = self.client.find_one('rooms')
            stream_key = uuid.uuid4().get_hex()

            # Data to push when inserting or updating
            data = {
                'displayName': self.displayName,
                'ip': self.ip,
                'paused': self.paused,
                'recording': self.recording,
                'heartbeat': int(time.time()),
                'camAvailable': self.cam_available,
                'netregId': self.netreg_id,
                'inputs': self.inputs(),
                'stream': {
                    'port': self._audiostream_port,
                    'key': stream_key
                },
                'galicasterVersion': galicaster.__version__
            }
            if self.currentMediaPackage:
                data['currentMediaPackage'] = self.currentMediaPackage
            if self.currentProfile:
                data['currentProfile'] = self.currentProfile

            if me:
                # Items to unset
                unset = {}
                if not self.currentMediaPackage:
                    unset['currentMediaPackage'] = ''
                if not self.currentProfile:
                    unset['currentProfile'] = ''

                # Update to push
                update = {'$set': data}

                if unset:
                    update['$unset'] = unset
                self.update('rooms', {'_id': self.id}, update)
            else:
                audio = self.read_audio_settings()
                data['_id'] = self.id
                data['audio'] = audio
                self.insert('rooms', data)

    def inputs(self):
        inputs = {'presentations': ['Presentation']}
        inputs['cameras'] = []
        labels = conf.get('sussexlogin', 'matrix_cam_labels')
        cam_labels = []
        if labels:
            cam_labels = [l.strip() for l in labels.split(',')]
        for i in range(0, self.cam_available):
            label = cam_labels[i] if i < len(cam_labels) else "Camera %d" % (
                i + 1)
            inputs['cameras'].append(label)
        return inputs

    def set_audio(self, fields):
        faders = fields.get('audio')
        if faders:
            for fader in faders:
                mixer = None
                level = fader.get('level')
                for audiofader in self.audiofaders:
                    if audiofader['name'] == fader['name']:
                        mixer = audiofader['control']
                        break
                if mixer:
                    l, r = mixer.getvolume(fader['type'])
                    if level >= 0 and l != level:
                        mixer.setvolume(level, 0, fader['type'])
                        mixer.setvolume(level, 1, fader['type'])
            if self.store_audio:
                # Relies on no password sudo access for current user to alsactl
                subprocess.call(['sudo', 'alsactl', 'store'])

    def on_added(self, collection, id, fields):
        self.set_audio(fields)
        self.update_audio()

    def on_changed(self, collection, id, fields, cleared):
        self.set_audio(fields)
        me = self.client.find_one('rooms')
        if self.paused != me['paused']:
            self.set_paused(me['paused'])
        if context.get_state().is_recording != me['recording']:
            self.set_recording(me)

    def on_removed(self, collection, id):
        self.on_subscribed(None)

    def set_paused(self, new_status):
        self.paused = new_status
        dispatcher.emit("toggle-pause-rec")

    def set_recording(self, me):
        self.recording = me['recording']
        if self.recording:
            meta = me.get('currentMediaPackage', {}) or {}
            profile = me.get('currentProfile', 'nocam')
            series = (meta.get('series_title', ''), meta.get('isPartOf', ''))
            user = {
                'user_name': meta.get('creator', ''),
                'user_id': meta.get('rightsHolder', '')
            }
            title = meta.get('title', 'Unknown')
            dispatcher.emit('sussexlogin-record',
                            (user, title, series, profile))
        else:
            dispatcher.emit("stop-record", '')

    def on_connected(self):
        logger.info('Connected to Meteor')
        token = conf.get('ddp', 'token')
        self.client.login(self._user, self._password, token=token)

    def on_logged_in(self, data):
        conf.set('ddp', 'token', data['token'])
        conf.update()
        try:
            self.client.subscribe('GalicasterControl',
                                  params=[self.id],
                                  callback=self.subscription_callback)
        except Exception:
            logger.warn('DDP subscription failed')

    def on_closed(self, code, reason):
        self.has_disconnected = True
        logger.error('Disconnected from Meteor: err %d - %s' % (code, reason))

    def update_audio(self):
        me = self.client.find_one('rooms')
        audio = self.read_audio_settings()
        update = False
        if me:
            mAudio = me.get('audio')
            mAudioNames = [x['name'] for x in mAudio]
            audioNames = [x['name'] for x in audio]
            if set(mAudioNames) != set(audioNames):
                update = True
            if not update:
                for key, fader in enumerate(audio):
                    if mAudio[key].get('level') != fader.get('level'):
                        update = True
            if update:
                self.update('rooms', {'_id': self.id},
                            {'$set': {
                                'audio': audio
                            }})

    def read_audio_settings(self):
        audio_settings = []
        for audiofader in self.audiofaders:
            if audiofader['display']:
                audio_settings.append(self.control_values(audiofader))
            # ensure fixed values
            mixer = audiofader['control']
            if audiofader['setrec']:
                mixer.setrec(1)
            if audiofader['mute']:
                mixer.setmute(1)
            if audiofader['unmute']:
                mixer.setmute(0)
            if audiofader['setlevel'] >= 0:
                mixer.setvolume(audiofader['setlevel'], 0, audiofader['type'])
                if 'Joined Playback Volume' not in mixer.volumecap():
                    mixer.setvolume(audiofader['setlevel'], 1,
                                    audiofader['type'])
        return audio_settings

    def control_values(self, audiofader):
        controls = {}
        left, right = audiofader['control'].getvolume(audiofader['type'])
        controls['min'] = audiofader['min']
        controls['max'] = audiofader['max']
        controls['level'] = left
        controls['type'] = audiofader['type']
        controls['name'] = audiofader['name']
        controls['display'] = audiofader['display']
        return controls

    def subscribedTo(self, publication):
        return self.client.subscriptions.get(publication) is not None
Beispiel #12
0
class DDP(Thread):

    def __init__(self):
        Thread.__init__(self)
        self.meteor = conf.get('ddp', 'meteor')

        self.client = MeteorClient(self.meteor, debug=False)
        self.client.on('added', self.on_added)
        self.client.on('changed', self.on_changed)
        self.client.on('subscribed', self.on_subscribed)
        self.client.on('connected', self.on_connected)
        self.client.on('removed', self.on_removed)
        self.client.on('closed', self.on_closed)
        self.client.on('logged_in', self.on_logged_in)

        self.displayName = conf.get('ddp', 'room_name')
        self.vu_min = -50
        self.vu_range = 50
        self.vu_data = 0
        self.last_vu = None
        self.ip = conf.get('ingest', 'address')
        self.id = conf.get('ingest', 'hostname')
        self._user = conf.get('ddp', 'user')
        self._password = conf.get('ddp', 'password')
        self._http_host = conf.get('ddp', 'http_host')
        self._audiostream_port = conf.get('audiostream', 'port') or 31337
        self.store_audio = conf.get_boolean('ddp', 'store_audio')
        self.screenshot_file = conf.get('ddp', 'existing_screenshot')
        self.high_quality = conf.get_boolean('ddp', 'hq_snapshot')
        self.paused = False
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.has_disconnected = False
        screen = Gdk.Screen.get_default()
        self._screen_width = screen.get_width()
        self._screen_height = screen.get_height()
        self.cardindex = None

        cam_available = conf.get(
            'ddp',
            'cam_available') or 0
        if cam_available in ('True', 'true', True, '1', 1):
            self.cam_available = 1
        elif cam_available in ('False', 'false', False, '0', 0):
            self.cam_available = 0
        else:
            self.cam_available = int(cam_available)
        # Getting audiostream params. either using existing audiostreaming server like icecast or the audiostream plugin
        if conf.get('ddp', 'existing_stream_host'):
            self._stream_host = conf.get('ddp', 'existing_stream_host')
        else:
            self._stream_host = self.ip

        if conf.get_int('ddp', 'existing_stream_port'):
            self._audiostream_port = conf.get_int('ddp', 'existing_stream_port')
        else:
            self._audiostream_port = conf.get_int('audiostream', 'port') or 31337

        if conf.get('ddp', 'existing_stream_key'):
            self.stream_key = conf.get('ddp', 'existing_stream_key')
        else:
            self.stream_key = uuid.uuid4().get_hex()

        if conf.get('ddp', 'extra_params'):
            self.extra_params_list = conf.get('ddp', 'extra_params').split(';')
        else:
            self.extra_params_list = []
        logger.info('audiostream URI: {}'.format('http://' + self._stream_host + ':' + str(self._audiostream_port) + '/' + self.stream_key))

        dispatcher.connect('init', self.on_init)
        dispatcher.connect('recorder-vumeter', self.vumeter)
        dispatcher.connect('timer-short', self.update_vu)
        dispatcher.connect('timer-short', self.heartbeat)
        dispatcher.connect('recorder-started', self.on_start_recording)
        dispatcher.connect('recorder-stopped', self.on_stop_recording)
        dispatcher.connect('recorder-status', self.on_rec_status_update)

    def run(self):
        self.connect()

    def connect(self):
        if not self.has_disconnected:
            try:
                self.client.connect()
            except Exception:
                logger.warn('DDP connection failed')

    def update(self, collection, query, update):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.update(
                    collection,
                    query,
                    update,
                    callback=self.update_callback)
            except Exception:
                logger.warn(
                    "Error updating document "
                    "{collection: %s, query: %s, update: %s}" %
                    (collection, query, update))

    def insert(self, collection, document):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.insert(
                    collection,
                    document,
                    callback=self.insert_callback)
            except Exception:
                logger.warn(
                    "Error inserting document {collection: %s, document: %s}" %
                    (collection, document))

    def heartbeat(self, element):
        if self.client.connected:
            self.update_images()
        else:
            self.connect()

    def on_start_recording(self, sender, id):
        self.recording = True
        self.currentMediaPackage = self.media_package_metadata(id)
        self.currentProfile = conf.get_current_profile().name
        self.update(
            'rooms', {
                '_id': self.id
            }, {
                '$set': {
                    'currentMediaPackage': self.currentMediaPackage,
                    'currentProfile': self.currentProfile,
                    'recording': self.recording
                }
            })

    def on_stop_recording(self, mpid, sender=None):
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.update(
            'rooms', {
                '_id': self.id
            }, {
                '$unset': {
                    'currentMediaPackage': '',
                    'currentProfile': ''
                }, '$set': {
                    'recording': self.recording
                }
            })
        self.update_images(1.5)

    def on_init(self, data):
        self.update_images(1.5)

    def update_images(self, delay=0.0):
        worker = Thread(target=self._update_images, args=(delay,))
        worker.start()

    def _update_images(self, delay):
        time.sleep(delay)
        files = {}

        if not self.screenshot_file:
            # take a screenshot with pyscreenshot
            im = ImageGrab.grab(bbox=(0, 0, self._screen_width, self._screen_height), backend='imagemagick')
        else:
            try:
                # used if screenshot already exists
                im = Image.open(self.screenshot_file)
            except IOError as e:
                logger.warn("Unable to open screenshot file {0}".format(self.screenshot_file))
                return
        output = cStringIO.StringIO()
        image_format = 'JPEG'
        if not self.high_quality:
            im.thumbnail((640, 360), Image.ANTIALIAS)
        else:
            image_format = 'PNG'

        if im.mode != "RGB":
            im = im.convert("RGB")
        im.save(output, format=image_format) # to reduce jpeg size use param: optimize=True
        files['galicaster'] = ('galicaster.jpg', output.getvalue(),
                               'image/jpeg')
        try:
            # add verify=False for testing self signed certs
            requests.post(
                "%s/image/%s" %
                (self._http_host, self.id), files=files, auth=(
                    self._user, self._password)) # to ignore ssl verification, use param: verify=False
        except Exception:
            logger.warn('Unable to post images')

    def vumeter(self, element, data, data_chan2, vu_bool):
        if data == "Inf":
            data = 0
        else:
            if data < -self.vu_range:
                data = -self.vu_range
            elif data > 0:
                data = 0
        self.vu_data = int(((data + self.vu_range) / float(self.vu_range)) * 100)

    def update_vu(self, element):
        if self.vu_data != self.last_vu:
                update = {'vumeter': self.vu_data}
                self.update('rooms', {'_id': self.id}, {'$set': update})
                self.last_vu = self.vu_data

    def on_rec_status_update(self, element, data):
        if data == 'paused':
            is_paused = True
        else:
            is_paused = False
        if is_paused:
            self.update_images(.75)
        if self.paused == is_paused:
            self.update(
                'rooms', {
                    '_id': self.id}, {
                    '$set': {
                        'paused': is_paused}})
            self.paused = is_paused
        if data == 'recording':
            self.update_images(.75)

    def media_package_metadata(self, id):
        mp = context.get('recorder').current_mediapackage
        line = mp.metadata_episode
        duration = mp.getDuration()
        line["duration"] = long(duration / 1000) if duration else None
        # FIXME Does series_title need sanitising as well as duration?
        created = mp.getDate()
        # line["created"] = calendar.timegm(created.utctimetuple())
        for key, value in mp.metadata_series.iteritems():
            line["series_" + key] = value
        for key, value in line.iteritems():
            if value in [None, []]:
                line[key] = ''
        # return line
        return line

    def subscription_callback(self, error):
        if error:
            logger.warn("Subscription callback returned error: %s" % error)

    def insert_callback(self, error, data):
        if error:
            logger.warn("Insert callback returned error: %s" % error)

    def update_callback(self, error, data):
        if error:
            logger.warn("Update callback returned error: %s" % error)

    def on_subscribed(self, subscription):
        if(subscription == 'GalicasterControl'):
            me = self.client.find_one('rooms')
            # Data to push when inserting or updating
            data = {
                'displayName': self.displayName,
                'ip': self.ip,
                'paused': self.paused,
                'recording': self.recording,
                'heartbeat': int(time.time()),
                'camAvailable': self.cam_available,
                'inputs': self.inputs(),
                'stream': {
                    'host': self._stream_host,
                    'port': self._audiostream_port,
                    'key': self.stream_key
                }
            }
            # Parse extra Meteor Mongodb collection elements and append
            for params in self.extra_params_list:
                param = params.split(':')
                data[param[0]] = param[1]

            if self.currentMediaPackage:
                data['currentMediaPackage'] = self.currentMediaPackage
            if self.currentProfile:
                data['currentProfile'] = self.currentProfile

            if me:
                # Items to unset
                unset = {}
                if not self.currentMediaPackage:
                    unset['currentMediaPackage'] = ''
                if not self.currentProfile:
                    unset['currentProfile'] = ''

                # Update to push
                update = {
                    '$set': data
                }

                if unset:
                    update['$unset'] = unset
                self.update('rooms', {'_id': self.id}, update)
            else:
                data['_id'] = self.id
                self.insert('rooms', data)

    def inputs(self):
        inputs = {
            'presentations': ['Presentation']
        }
        inputs['cameras'] = []
        labels = conf.get('ddp', 'cam_labels')
        cam_labels = []
        if labels:
            cam_labels = [l.strip() for l in labels.split(',')]
        for i in range(0, self.cam_available):
            label = cam_labels[i] if i < len(
                cam_labels) else "Camera %d" % (i + 1)
            inputs['cameras'].append(label)
        return inputs

    def on_added(self, collection, id, fields):
        pass

    def on_changed(self, collection, id, fields, cleared):
        me = self.client.find_one('rooms')
        if self.paused != me['paused']:
            self.set_paused(me['paused'])

        if context.get('recorder').is_recording() != me['recording']:
            self.set_recording(me)

    def on_removed(self, collection, id):
        self.on_subscribed(None)

    def set_paused(self, new_status):
        if not self.paused:
            self.paused = new_status
            context.get('recorder').pause()
        else:
            self.paused = False
            context.get('recorder').resume()


    def set_recording(self, me):
        self.recording = me['recording']
        if self.recording:
            # FIXME: Metadata isn't passed to recorder
            meta = me.get('currentMediaPackage', {}) or {}
            profile = me.get('currentProfile', 'nocam')
            series = (meta.get('series_title', ''), meta.get('isPartOf', ''))
            user = {'user_name': meta.get('creator', ''),
                    'user_id': meta.get('rightsHolder', '')}
            title = meta.get('title', 'Unknown')
            context.get('recorder').record()
        else:
            context.get('recorder').stop()

    def on_connected(self):
        logger.info('Connected to Meteor')
        token = conf.get('ddp', 'token')
        self.client.login(self._user, self._password, token=token)

    def on_logged_in(self, data):
        conf.set('ddp', 'token', data['token'])
        conf.update()
        try:
            self.client.subscribe(
                'GalicasterControl',
                params=[
                    self.id],
                callback=self.subscription_callback)
        except Exception:
            logger.warn('DDP subscription failed')

    def on_closed(self, code, reason):
        self.has_disconnected = True
        logger.error('Disconnected from Meteor: err %d - %s' % (code, reason))

    def subscribedTo(self, publication):
        return self.client.subscriptions.get(publication) != None
Beispiel #13
0
#!/usr/bin/env python                                                                                    
import sys
from sense_hat import SenseHat
from MeteorClient import MeteorClient


print(sys.argv);

value = sys.argv[1];
client = MeteorClient('ws://127.0.0.1:3000/websocket')
client.connect()

def subscription_callback(error):
    if error:
        print(error)

client.subscribe('sensehat', callback=subscription_callback)

result = client.find_one('sensehat',selector={ '_id' : value})

sense = SenseHat()

sense.set_pixels(result['grid'])


class LightingMinion:
    def __init__(self, config):
        self.config = config
        self.universes = {}
        self.fades = {}
        
        self.last = 0
        self.ready = False
        
        self.meteor = MeteorClient(self.config['server'])
        self.meteor.on('connected', self.connect_cb)
        self.meteor.connect()
        
    def connect_cb(self):
        self.debug('Connection to Meteor established.')

        if not self.config.get('id'):
            self.debug('No id in settings, registering as new.')
            self.meteor.call('minionNew', [self.config['type']], self.register)
        
        else:
            self.register(None, self.config['id'])
    
    def register(self, err, id):
        self.config['id'] = id
        self.debug('Connecting with id ' + id)
        self.meteor.call('minionConnect', [id], self.prep)

    def prep(self, e, r):
        self.meteortime = MeteorTime(self.meteor)
        
        self.meteor.subscribe('lights')
        self.meteor.on('added', self.added)
        self.meteor.on('changed', self.changed)
        
        self.ola = OlaClient.OlaClient()
        self.olasock = self.ola.GetSocket()
        self.olasock.setblocking(False)
        self.selectargs = ([self.olasock], [], [], 0)
        
        self.ready = True
        
        print('Connected to server.')
        
    def debug(self, *args):
        if self.config.get('debug'):
            print(*args)
            
    def added(self, collection, id, fields):
        self.changed(collection, id, fields, None)
        
    def changed(self, collection, id, fields, cleared):
        light = self.meteor.find_one('lights', selector={'_id': id})
        settings = light.get('settings')
        if not settings: return
        
        if light['minion'] == self.config['id']:
            self.debug('light changed: ', light['title'], fields)

            for channel in light['channels']:
                uni_num = channel['universe']

                if not self.universes.get(uni_num):
                    self.universes[uni_num] = array.array('B', [0] * 512)
                    self.fades[uni_num] = {}
                
                uni = self.universes[uni_num]

                addr = channel['address'] - 1
                curr = uni[addr]

                try:
                    value = light['values'][light['channels'].index(channel)] * 255
                except IndexError:
                    continue

                if not value == curr:
                    starttime = self.meteortime.now() - time.time() + settings['time'] # Calculate offset of server's time vs local system's time
                    self.fades[uni_num][addr] = Fade(uni[addr], value, starttime, settings['fade'], uni, addr)

    def run(self):
        while True:
            start = time.time()

            if self.ready:
                r, w, e = select.select(*self.selectargs)
                if len(r) > 0: self.ola.SocketReady()

                if start - self.last >= 1:
                    self.meteortime.update()
                    self.last = time.time()
            
                for uni, fades in self.fades.items():
                    for addr, fade in tuple(fades.items()):
                        fade.tick()
                        if fade.finished: del self.fades[uni][addr]
            
                for num, uni in self.universes.items():
                    self.ola.SendDmx(num, uni, None)
            
            try: time.sleep(rate - (time.time() - start))
            except ValueError: continue
            except IOError: continue