def main(url): character = {} client = DDPClient('ws://dicecloud.com/websocket', auto_reconnect=False) client.is_connected = False client.connect() def connected(): client.is_connected = True client.on('connected', connected) while not client.is_connected: time.sleep(1) client.subscribe('singleCharacter', [url]) def update_character(collection, _id, fields): if character.get(collection) is None: character[collection] = [] fields['id'] = _id character.get(collection).append(fields) client.on('added', update_character) time.sleep(10) client.close() character['id'] = url return character
class DDPCiaoClient: def __init__(self, ddp_params, ciao_queue): # validate params - START missing_params = [] required_params = ["host", "port"] for p in required_params: if not p in ddp_params: missing_params.append(p) if len(missing_params) > 0: raise RuntimeError("DDP configuration error, missing: %s" % ",".join(missing_params)) # validate params - END # reference to Queue for exchanging data with CiaoCore self.ciao_queue = ciao_queue # local instance of DDP Client self.handle = DDPClient("ws://" + ddp_params["host"] + ":" + str(ddp_params["port"]) + "/websocket") self.handle.on("connected", self.on_connect) self.host = ddp_params["host"] self.port = ddp_params["port"] self.logger = logging.getLogger("ddp.client") def on_connect(): self.logger.info("Connected to DDP Server %s" % self.host) """ TODO from METEOR to SKETCH def on_message(self, client, userdata, msg): self.logger.debug("Got new message. Topic: %s Message: %s" % (str(msg.topic), str(msg.payload))) entry = { "data" : [str(msg.topic), str(msg.payload)] } self.ciao_queue.put(entry) """ def connect(self): try: self.handle.connect() return True except: return False def disconnect(self): self.handle.close() def callback_function(a, b): self.logger.debug("Callback Function. Param1: %s Param2: %s" % (str(a), str(b))) def publish(self, function, parameter): self.logger.debug("Call function: %s Parameter: %s" % (function, str(parameter))) try: self.handle.call(function, json.loads(parameter), None) except Exception, e: self.logger.error("Failed handle call: " + str(e))
class MeteorClient(EventEmitter): def __init__(self, url, auto_reconnect=True, auto_reconnect_timeout=0.5, debug=False): EventEmitter.__init__(self) self.collection_data = CollectionData() self.ddp_client = DDPClient( url, auto_reconnect=auto_reconnect, auto_reconnect_timeout=auto_reconnect_timeout, debug=debug) self.ddp_client.on('connected', self.connected) self.ddp_client.on('socket_closed', self.closed) self.ddp_client.on('failed', self.failed) self.ddp_client.on('added', self.added) self.ddp_client.on('changed', self.changed) self.ddp_client.on('removed', self.removed) self.ddp_client.on('reconnected', self._reconnected) self.connected = False self.subscriptions = {} self._login_data = None self._login_token = None def connect(self): """Connect to the meteor server""" self.ddp_client.connect() def close(self): """Close connection with meteor server""" self.ddp_client.close() def _reconnected(self): """Reconnect Currently we get a new session every time so we have to clear all the data an resubscribe""" if self._login_data or self._login_token: def reconnect_login_callback(error, result): if error: if self._login_token: self._login_token = None self._login(self._login_data, callback=reconnect_login_callback) return else: raise MeteorClientException( 'Failed to re-authenticate during reconnect') self.connected = True self._resubscribe() if self._login_token: self._resume(self._login_token, callback=reconnect_login_callback) else: self._login(self._login_data, callback=reconnect_login_callback) else: self._resubscribe() def _resubscribe(self): self.collection_data.data = {} cur_subs = self.subscriptions.items() self.subscriptions = {} for name, value in cur_subs: self.subscribe(name, value['params']) self.emit('reconnected') # # Account Management # def login(self, user, password, token=None, callback=None): """Login with a username and password Arguments: user - username or email address password - the password for the account Keyword Arguments: token - meteor resume token callback - callback function containing error as first argument and login data""" # TODO: keep the tokenExpires around so we know the next time # we need to authenticate # hash the password hashed = hashlib.sha256(password).hexdigest() # handle username or email address if '@' in user: user_object = {'email': user} else: user_object = {'username': user} password_object = {'algorithm': 'sha-256', 'digest': hashed} self._login_token = token self._login_data = {'user': user_object, 'password': password_object} if token: self._resume(token, callback=callback) else: self._login(self._login_data, callback=callback) def _resume(self, token, callback=None): login_data = {'resume': token} self._login(login_data, callback=callback) def _login(self, login_data, callback=None): self.emit('logging_in') def logged_in(error, data): if error: if self._login_token: self._login_token = None self._login(self._login_data, callback=callback) return if callback: callback(error, None) return self._login_token = data['token'] if callback: callback(None, data) self.emit('logged_in', data) self.ddp_client.call('login', [login_data], callback=logged_in) def logout(self, callback=None): """Logout a user Keyword Arguments: callback - callback function called when the user has been logged out""" self.ddp_client.call('logout', [], callback=callback) self.emit('logged_out') # # Meteor Method Call # def call(self, method, params, callback=None): """Call a remote method Arguments: method - remote method name params - remote method parameters Keyword Arguments: callback - callback function containing return data""" self._wait_for_connect() self.ddp_client.call(method, params, callback=callback) # # Subscription Management # def subscribe(self, name, params=[], callback=None): """Subscribe to a collection Arguments: name - the name of the publication params - the subscription parameters Keyword Arguments: callback - a function callback that returns an error (if exists)""" self._wait_for_connect() def subscribed(error, sub_id): if error: self._remove_sub_by_id(sub_id) if callback: callback(error.get('reason')) return if callback: callback(None) self.emit('subscribed', name) if name in self.subscriptions: raise MeteorClientException('Already subcribed to {}'.format(name)) sub_id = self.ddp_client.subscribe(name, params, subscribed) self.subscriptions[name] = {'id': sub_id, 'params': params} def unsubscribe(self, name): """Unsubscribe from a collection Arguments: name - the name of the publication""" self._wait_for_connect() if name not in self.subscriptions: raise MeteorClientException('No subscription for {}'.format(name)) self.ddp_client.unsubscribe(self.subscriptions[name]['id']) del self.subscriptions[name] self.emit('unsubscribed', name) # # Collection Management # def find(self, collection, selector={}): """Find data in a collection Arguments: collection - collection to search Keyword Arguments: selector - the query (default returns all items in a collection)""" results = [] for _id, doc in self.collection_data.data.get(collection, {}).items(): doc.update({'_id': _id}) if selector == {}: results.append(doc) for key, value in selector.items(): if key in doc and doc[key] == value: results.append(doc) return results def find_one(self, collection, selector={}): """Return one item from a collection Arguments: collection - collection to search Keyword Arguments: selector - the query (default returns first item found)""" for _id, doc in self.collection_data.data.get(collection, {}).items(): doc.update({'_id': _id}) if selector == {}: return doc for key, value in selector.items(): if key in doc and doc[key] == value: return doc return None def insert(self, collection, doc, callback=None): """Insert an item into a collection Arguments: collection - the collection to be modified doc - The document to insert. May not yet have an _id attribute, in which case Meteor will generate one for you. Keyword Arguments: callback - Optional. If present, called with an error object as the first argument and, if no error, the _id as the second.""" self.call("/" + collection + "/insert", [doc], callback=callback) def update(self, collection, selector, modifier, callback=None): """Insert an item into a collection Arguments: collection - the collection to be modified selector - specifies which documents to modify modifier - Specifies how to modify the documents Keyword Arguments: callback - Optional. If present, called with an error object as the first argument and, if no error, the number of affected documents as the second.""" self.call("/" + collection + "/update", [selector, modifier], callback=callback) def remove(self, collection, selector, callback=None): """Remove an item from a collection Arguments: collection - the collection to be modified selector - Specifies which documents to remove Keyword Arguments: callback - Optional. If present, called with an error object as its argument.""" self.call("/" + collection + "/remove", [selector], callback=callback) # # Event Handlers # def connected(self): self.connected = True self.emit('connected') def closed(self, code, reason): self.connected = False self.emit('closed', code, reason) def failed(self, data): self.emit('failed', str(data)) def added(self, collection, id, fields): self.collection_data.add_data(collection, id, fields) self.emit('added', collection, id, fields) def changed(self, collection, id, fields, cleared): self.collection_data.change_data(collection, id, fields, cleared) self.emit('changed', collection, id, fields, cleared) def removed(self, collection, id): self.collection_data.remove_data(collection, id) self.emit('removed', collection, id) # # Helper functions # def _time_from_start(self, start): now = datetime.datetime.now() return now - start def _remove_sub_by_id(self, sub_id): for name, cur_sub_id in self.subscriptions.items(): if cur_sub_id == sub_id: del self.subscriptions[name] def _wait_for_connect(self): start = datetime.datetime.now() while not self.connected and self._time_from_start(start).seconds < 5: time.sleep(0.1) if not self.connected: raise MeteorClientException( 'Could not subscribe because a connection has not been established' )
class MeteorClient(EventEmitter): def __init__(self, url, auto_reconnect=True, auto_reconnect_timeout=0.5, debug=False, headers=None): EventEmitter.__init__(self) self.collection_data = CollectionData() self.ddp_client = DDPClient(url, auto_reconnect=auto_reconnect, auto_reconnect_timeout=auto_reconnect_timeout, debug=debug, headers=headers) self.ddp_client.on('connected', self.connected) self.ddp_client.on('socket_closed', self.closed) self.ddp_client.on('failed', self.failed) self.ddp_client.on('added', self.added) self.ddp_client.on('changed', self.changed) self.ddp_client.on('removed', self.removed) self.ddp_client.on('reconnected', self._reconnected) self.connected = False self.subscriptions = {} self._login_data = None self._login_token = None def connect(self): """Connect to the meteor server""" self.ddp_client.connect() def close(self): """Close connection with meteor server""" self.ddp_client.close() def _reconnected(self): """Reconnect Currently we get a new session every time so we have to clear all the data an resubscribe""" if self._login_data or self._login_token: def reconnect_login_callback(error, result): if error: if self._login_token: self._login_token = None self._login(self._login_data, callback=reconnect_login_callback) return else: raise MeteorClientException( 'Failed to re-authenticate during reconnect') self.connected = True self._resubscribe() if self._login_token: self._resume(self._login_token, callback=reconnect_login_callback) else: self._login(self._login_data, callback=reconnect_login_callback) else: self._resubscribe() def _resubscribe(self): self.collection_data.data = {} cur_subs = self.subscriptions.items() self.subscriptions = {} for name, value in cur_subs: self.subscribe(name, value['params']) self.emit('reconnected') # # Account Management # def login(self, user, password, token=None, callback=None): """Login with a username and password Arguments: user - username or email address password - the password for the account Keyword Arguments: token - meteor resume token callback - callback function containing error as first argument and login data""" # TODO: keep the tokenExpires around so we know the next time # we need to authenticate # hash the password hashed = hashlib.sha256(password).hexdigest() # handle username or email address if '@' in user: user_object = { 'email': user } else: user_object = { 'username': user } password_object = { 'algorithm': 'sha-256', 'digest': hashed } self._login_token = token self._login_data = {'user': user_object, 'password': password_object} if token: self._resume(token, callback=callback) else: self._login(self._login_data, callback=callback) def _resume(self, token, callback=None): login_data = {'resume': token} self._login(login_data, callback=callback) def _login(self, login_data, callback=None): self.emit('logging_in') def logged_in(error, data): if error: if self._login_token: self._login_token = None self._login(self._login_data, callback=callback) return if callback: callback(error, None) return self._login_token = data['token'] if callback: callback(None, data) self.emit('logged_in', data) self.ddp_client.call('login', [login_data], callback=logged_in) def logout(self, callback=None): """Logout a user Keyword Arguments: callback - callback function called when the user has been logged out""" self.ddp_client.call('logout', [], callback=callback) self.emit('logged_out') # # Meteor Method Call # def call(self, method, params, callback=None): """Call a remote method Arguments: method - remote method name params - remote method parameters Keyword Arguments: callback - callback function containing return data""" self._wait_for_connect() self.ddp_client.call(method, params, callback=callback) # # Subscription Management # def subscribe(self, name, params=[], callback=None): """Subscribe to a collection Arguments: name - the name of the publication params - the subscription parameters Keyword Arguments: callback - a function callback that returns an error (if exists)""" self._wait_for_connect() def subscribed(error, sub_id): if error: self._remove_sub_by_id(sub_id) if callback: callback(error.get('reason')) return if callback: callback(None) self.emit('subscribed', name) if name in self.subscriptions: raise MeteorClientException('Already subcribed to {}'.format(name)) sub_id = self.ddp_client.subscribe(name, params, subscribed) self.subscriptions[name] = { 'id': sub_id, 'params': params } def unsubscribe(self, name): """Unsubscribe from a collection Arguments: name - the name of the publication""" self._wait_for_connect() if name not in self.subscriptions: raise MeteorClientException('No subscription for {}'.format(name)) self.ddp_client.unsubscribe(self.subscriptions[name]['id']) del self.subscriptions[name] self.emit('unsubscribed', name) # # Collection Management # def find(self, collection, selector={}): """Find data in a collection Arguments: collection - collection to search Keyword Arguments: selector - the query (default returns all items in a collection)""" results = [] for _id, doc in self.collection_data.data.get(collection, {}).items(): doc.update({'_id': _id}) if selector == {}: results.append(doc) for key, value in selector.items(): if key in doc and doc[key] == value: results.append(doc) return results def find_one(self, collection, selector={}): """Return one item from a collection Arguments: collection - collection to search Keyword Arguments: selector - the query (default returns first item found)""" for _id, doc in self.collection_data.data.get(collection, {}).items(): doc.update({'_id': _id}) if selector == {}: return doc for key, value in selector.items(): if key in doc and doc[key] == value: return doc return None def insert(self, collection, doc, callback=None): """Insert an item into a collection Arguments: collection - the collection to be modified doc - The document to insert. May not yet have an _id attribute, in which case Meteor will generate one for you. Keyword Arguments: callback - Optional. If present, called with an error object as the first argument and, if no error, the _id as the second.""" self.call("/" + collection + "/insert", [doc], callback=callback) def update(self, collection, selector, modifier, callback=None): """Insert an item into a collection Arguments: collection - the collection to be modified selector - specifies which documents to modify modifier - Specifies how to modify the documents Keyword Arguments: callback - Optional. If present, called with an error object as the first argument and, if no error, the number of affected documents as the second.""" self.call("/" + collection + "/update", [selector, modifier], callback=callback) def remove(self, collection, selector, callback=None): """Remove an item from a collection Arguments: collection - the collection to be modified selector - Specifies which documents to remove Keyword Arguments: callback - Optional. If present, called with an error object as its argument.""" self.call("/" + collection + "/remove", [selector], callback=callback) # # Event Handlers # def connected(self): self.connected = True self.emit('connected') def closed(self, code, reason): self.connected = False self.emit('closed', code, reason) def failed(self, data): self.emit('failed', str(data)) def added(self, collection, id, fields): self.collection_data.add_data(collection, id, fields) self.emit('added', collection, id, fields) def changed(self, collection, id, fields, cleared): self.collection_data.change_data(collection, id, fields, cleared) self.emit('changed', collection, id, fields, cleared) def removed(self, collection, id): self.collection_data.remove_data(collection, id) self.emit('removed', collection, id) # # Helper functions # def _time_from_start(self, start): now = datetime.datetime.now() return now - start def _remove_sub_by_id(self, sub_id): for name, cur_sub_id in self.subscriptions.items(): if cur_sub_id == sub_id: del self.subscriptions[name] def _wait_for_connect(self): start = datetime.datetime.now() while not self.connected and self._time_from_start(start).seconds < 5: time.sleep(0.1) if not self.connected: raise MeteorClientException('Could not subscribe because a connection has not been established')
class Hoduino(): """ A listener for the arduino """ latest_hash = None def __init__(self, ddp_server_url, debug=False): self.shutting_down = False self.server_url = ddp_server_url self.debug = debug self.board_interface = HoduinoBoardInterface(port=ARDUINO_PORT) # Setup connection to the DDP server self.ddp_connect() def ddp_connect(self): print '++ DDP connection attempt...' self.ddp_client = DDPClient(self.server_url) self.ddp_client.debug = self.debug # Some events to handle from DDP self.ddp_client.on('added', self.added) self.ddp_client.on('connected', self.connected) self.ddp_client.on('socket_closed', self.closed) self.ddp_client.on('failed', self.failed) # Connect to DDP server self.ddp_client.connect() def exit(self): self.shutting_down = True # Close connect to Arduino if self.board_interface: self.board_interface.close_arduino() # Close connection to self.ddp_client.close() def connected(self): print '++ DDP Connected' self.ddp_connected = True # Subscribe to messages stream. Limit messages to 1 as we # only want the latest message. sub_id = self.ddp_client.subscribe('messages', [{'limit': 1}]) def closed(self, code, reason): print '++ DDP connection closed {0} {1}'.format(code, reason) self.ddp_connected = False if not self.shutting_down: while not self.ddp_connected: self.ddp_connect() time.sleep(30) def failed(self, data): print '++ DDP failed - data: {0}'.format(str(data)) def added(self, collection, id, fields): print '++ DDP added {0} {1}'.format(len(collection), id) if id <> self.latest_hash: print '++ New messages!' self.board_interface.donation_reaction() self.latest_hash = id else: print '++ No new messages'