def on_message(client, userdata, msg): # pylint: disable=unused-argument # print('MQTT: %s %s' % (msg.topic, msg.payload.decode())) message_struct = json.loads(msg.payload.decode()) message_type = message_struct['type'] if message_type == 'update_sequence': controller = find_resource('/' + msg.topic) # for now we assume these messages are published on controller channels if controller and controller.type == Resource.CONTROLLER_FOLDER: parameters = message_struct['parameters'] seq_name = parameters['sequence'] if not seq_name.startswith('/'): # handle relative sequence names resource = Resource.query.filter(Resource.id == controller.id).one() # this is ok for now since .. doesn't have special meaning in resource path (no way to escape controller folder) seq_name = resource.path() + '/' + seq_name timestamp = parameters.get('timestamp', '') # fix(soon): need to convert to datetime if not timestamp: timestamp = datetime.datetime.utcnow() value = parameters['value'] if 'encoded' in parameters: value = base64.b64decode(value) # remove this; require clients to use REST POST for images resource = find_resource(seq_name) if not resource: return system_attributes = json.loads(resource.system_attributes) if resource.system_attributes else None if system_attributes and system_attributes['data_type'] == Resource.IMAGE_SEQUENCE: value = base64.b64decode(value) else: value = str(value) # don't emit message since the message is already in the system update_sequence_value(resource, seq_name, timestamp, value, emit_message=False) db.session.commit() # update controller watchdog status elif message_type == 'watchdog': controller = find_resource('/' + msg.topic) # for now we assume these messages are published on controller channels if controller and controller.type == Resource.CONTROLLER_FOLDER: controller_status = ControllerStatus.query.filter(ControllerStatus.id == controller.id).one() controller_status.last_watchdog_timestamp = datetime.datetime.utcnow() db.session.commit() # send emails elif message_type == 'send_email': controller = find_resource('/' + msg.topic) # for now we assume these messages are published on controller channels if controller and controller.type == Resource.CONTROLLER_FOLDER: print('sending email') handle_send_email(controller.id, message_struct['parameters']) # send SMS messages elif message_type == 'send_sms' or message_type == 'send_text_message': controller = find_resource('/' + msg.topic) # for now we assume these messages are published on controller channels if controller and controller.type == Resource.CONTROLLER_FOLDER: handle_send_text_message(controller.id, message_struct['parameters'])
def process_web_socket_message(message_struct, ws_conn): message_type = message_struct['type'] message_debug = False # handle new connection; no longer used; websocket should be authenticated using HTTP basic auth if message_type == 'connect': pass # handle watchdog message (updates controller status record) elif message_type == 'watchdog': if ws_conn.controller_id: controller_status = ControllerStatus.query.filter( ControllerStatus.id == ws_conn.controller_id).one() controller_status.last_watchdog_timestamp = datetime.datetime.utcnow( ) db.session.commit() # handle ping (does nothing; used to keep connection active) elif message_type == 'ping': pass # handle subscription (used to subscribe to messages from one or more folders) elif message_type == 'subscribe': # process the subscription parameters = message_struct['parameters'] subscriptions = parameters.get('subscriptions', []) for subscription in subscriptions: # fix(clean): remove support for folder IDs and old message args folder_path = subscription.get('folder', subscription.get('folderId', None)) message_type = subscription.get( 'message_type', subscription.get('messageType', None)) include_children = subscription.get( 'include_children', subscription.get('includeChildren', False)) # fix(clean): remove "[self]" option if folder_path == 'self' or folder_path == '[self]': folder_id = ws_conn.controller_id elif hasattr(folder_path, 'strip'): resource = find_resource(folder_path) if not resource: print('unable to find subscription folder: %s' % folder_path) return folder_id = resource.id else: folder_id = folder_path # if subscription is allowed, store it # fix(later): send a message back if not allowed if ws_conn.access_level(folder_id) >= ACCESS_LEVEL_READ: if message_debug: print('subscribe folder: %s (%d), message type: %s' % (folder_path, folder_id, message_type)) ws_conn.subscriptions.append( MessageSubscription(folder_id, message_type, include_children=include_children)) # fix(soon): remove this case after clients are updated elif message_type == 'setNode' or message_type == 'updateSequence' or message_type == 'update_sequence': if ws_conn.controller_id: parameters = message_struct['parameters'] if message_type == 'setNode': # fix(soon): remove this case seq_name = parameters['node'] else: seq_name = parameters['sequence'] if not seq_name.startswith('/'): # handle relative sequence names resource = Resource.query.filter( Resource.id == ws_conn.controller_id).one() # this is ok for now since .. doesn't have special meaning in resource path (no way to escape controller folder) seq_name = resource.path() + '/' + seq_name timestamp = parameters.get( 'timestamp', '') # fix(soon): need to convert to datetime if not timestamp: timestamp = datetime.datetime.utcnow() value = parameters['value'] if 'encoded' in parameters: value = base64.b64decode(value) # remove this; require clients to use REST POST for images resource = find_resource(seq_name) if not resource: return system_attributes = json.loads( resource.system_attributes ) if resource.system_attributes else None if system_attributes and system_attributes[ 'data_type'] == Resource.IMAGE_SEQUENCE: value = base64.b64decode(value) else: value = str(value) update_sequence_value(resource, seq_name, timestamp, value) db.session.commit() # update a resource elif message_type == 'write_resource': parameters = message_struct['parameters'] if 'path' and 'data' in parameters: path = parameters['path'] if not path.startswith( '/'): # fix(soon): remove this after clients updated path = '/' + path data = parameters['data'] resource = find_resource(path) if resource: if ws_conn.access_level(resource.id) >= ACCESS_LEVEL_WRITE: timestamp = datetime.datetime.utcnow() update_sequence_value(resource, path, timestamp, data) db.session.commit() else: socket_sender.send_error(ws_conn, 'permission error: %s' % path) else: socket_sender.send_error(ws_conn, 'resource not found: %s' % path) else: socket_sender.send_error( ws_conn, 'expected data and path parameters for write_resource message') # handle request for detailed message logging elif message_type == 'debug_messaging': parameters = message_struct['parameters'] enable = bool(parameters['enable']) level = logging.DEBUG if enable else logging.INFO logging.getLogger().setLevel(level) # handle other action messages elif message_type in ('sendEmail', 'sendTextMessage', 'send_email', 'send_text_message'): if ws_conn.controller_id: # only support these messages from controllers, not browsers if message_type == 'sendEmail' or message_type == 'send_email': handle_send_email(ws_conn.controller_id, message_struct['parameters']) elif message_type == 'sendTextMessage' or message_type == 'send_text_message': handle_send_text_message(ws_conn.controller_id, message_struct['parameters']) # for other types, assume that we want to create a message record else: # figure out target folder if 'folder' in message_struct: folder_name = message_struct['folder'] if message_debug: print('message to folder: %s' % folder_name) if hasattr(folder_name, 'startswith') and folder_name.startswith('/'): if message_debug: print('message to folder name: %s' % folder_name) folder = find_resource(folder_name) # assumes leading slash if folder: folder_id = folder.id if message_debug: print('message to folder id: %d' % folder_id) else: print('message to unknown folder (%s)' % folder_name) return else: folder_id = folder_name # fix(soon): remove this case elif ws_conn.controller_id: folder_id = ws_conn.controller_id else: print('message (%s) without folder or controller; discarding' % message_type) return # if allowed, create a message for the folder if ws_conn.access_level(folder_id) >= ACCESS_LEVEL_WRITE: parameters = message_struct['parameters'] # fix(soon): can we move this spawn above access level check (might require request context) Thread(target=message_queue.add, daemon=True, args=[folder_id, None, message_type, parameters], kwargs={ 'sender_controller_id': ws_conn.controller_id, 'sender_user_id': ws_conn.user_id }).start()
def on_message(client, userdata, msg): # pylint: disable=unused-argument payload = msg.payload.decode() # handle full (JSON) messages if payload.startswith('{'): message_struct = json.loads(payload) for message_type, parameters in message_struct.items(): # update sequence values; doesn't support image sequence; should use REST API for image sequences if message_type == 'update': folder = find_resource( '/' + msg.topic ) # for now we assume these messages are published on controller channels if folder and folder.type in (Resource.BASIC_FOLDER, Resource.ORGANIZATION_FOLDER, Resource.CONTROLLER_FOLDER): timestamp = parameters.get('$t', '') if timestamp: timestamp = parse_json_datetime( timestamp ) # fix(soon): handle conversion errors else: timestamp = datetime.datetime.utcnow() for name, value in parameters.items(): if name != '$t': seq_name = '/' + msg.topic + '/' + name resource = find_resource(seq_name) if resource: # don't emit new message since UI will receive this message update_sequence_value(resource, seq_name, timestamp, value, emit_message=False) db.session.commit() # update controller watchdog status elif message_type == 'watchdog': controller = find_resource( '/' + msg.topic ) # for now we assume these messages are published on controller channels if controller and controller.type == Resource.CONTROLLER_FOLDER: controller_status = ControllerStatus.query.filter( ControllerStatus.id == controller.id).one() controller_status.last_watchdog_timestamp = datetime.datetime.utcnow( ) db.session.commit() # send emails elif message_type == 'send_email': controller = find_resource( '/' + msg.topic ) # for now we assume these messages are published on controller channels if controller and controller.type == Resource.CONTROLLER_FOLDER: print('sending email') handle_send_email(controller.id, parameters) # send SMS messages elif message_type == 'send_sms' or message_type == 'send_text_message': controller = find_resource( '/' + msg.topic ) # for now we assume these messages are published on controller channels if controller and controller.type == Resource.CONTROLLER_FOLDER: handle_send_text_message(controller.id, parameters) # handle short (non-JSON) messages else: # print('MQTT: %s %s' % (msg.topic, payload)) if payload.startswith( 's,' ): # type 's' is "store and display new sequence value" parts = payload.split(',', 3) if len(parts) == 4: seq_name = '/' + msg.topic + '/' + parts[1] timestamp = parse_json_datetime( parts[2]) # fix(soon): handle conversion errors value = parts[3] resource = find_resource(seq_name) if resource and resource.type == Resource.SEQUENCE: # don't emit new message since UI will receive this message update_sequence_value(resource, seq_name, timestamp, value, emit_message=False) db.session.commit()
def process_web_socket_message(message_struct, ws_conn): type = message_struct['type'] message_debug = False # handle new connection (updates controller status record) if type == 'connect': # fix(soon): remove this parameters = message_struct['parameters'] print 'connect message' # clients/controllers should send authCode in connect message if 'authCode' in parameters: auth_code = parameters['authCode'] key = find_key_by_code(auth_code) if key and key.access_as_controller_id: controller_resource = Resource.query.filter( Resource.id == key.access_as_controller_id).one() # handle child controller if 'name' in parameters: key_resource = controller_resource controller_resource = None # look for a resource with the given name that is a child of the controller referenced by the key candidate_resources = Resource.query.filter( Resource.name == parameters['name'], Resource.deleted == False) for resource in candidate_resources: if resource.is_descendent_of(key_resource.id): controller_resource = resource break if not controller_resource: ws_conn.ws.close() print('unable to find child controller: %s' % parameters['name'] ) # fix(soon): what should we do in this case? return ws_conn.controller_id = controller_resource.id ws_conn.auth_method = 'authCode' try: controller_status = ControllerStatus.query.filter( ControllerStatus.id == ws_conn.controller_id).one() controller_status.last_connect_timestamp = datetime.datetime.utcnow( ) controller_status.client_version = parameters.get( 'version', None) db.session.commit() except NoResultFound: pass else: ws_conn.ws.close() print('invalid auth code' ) # fix(soon): what should we do in this case? # handle watchdog message (updates controller status record) elif type == 'watchdog': if ws_conn.controller_id: controller_status = ControllerStatus.query.filter( ControllerStatus.id == ws_conn.controller_id).one() controller_status.last_watchdog_timestamp = datetime.datetime.utcnow( ) db.session.commit() # handle ping (does nothing; used to keep connection active) elif type == 'ping': pass # handle subscription (used to subscribe to messages from one or more folders) elif type == 'subscribe': # process the subscription parameters = message_struct['parameters'] subscriptions = parameters.get('subscriptions', []) for subscription in subscriptions: folder_path = subscription.get( 'folder', subscription.get('folderId', None) ) # fix(clean): remove support for folder IDs and old message args message_type = subscription.get( 'message_type', subscription.get('messageType', None)) include_children = subscription.get( 'include_children', subscription.get('includeChildren', False)) # fix(clean): remove "[self]" option if folder_path == 'self' or folder_path == '[self]': folder_id = ws_conn.controller_id elif hasattr(folder_path, 'strip'): resource = find_resource(folder_path) if not resource: print('unable to find subscription folder: %s' % folder_path) return folder_id = resource.id else: folder_id = folder_path # if subscription is allowed, store it # fix(later): send a message back if not allowed if ws_conn.access_level(folder_id) >= ACCESS_LEVEL_READ: if message_debug: print('subscribe folder: %s (%d), message type: %s' % (folder_path, folder_id, message_type)) ws_conn.subscriptions.append( MessageSubscription(folder_id, message_type, include_children=include_children)) # fix(soon): remove this case after clients are updated elif type == 'setNode' or type == 'updateSequence' or type == 'update_sequence': if ws_conn.controller_id: parameters = message_struct['parameters'] if type == 'setNode': # fix(soon): remove this case seq_name = parameters['node'] else: seq_name = parameters['sequence'] if not seq_name.startswith('/'): # handle relative sequence names resource = Resource.query.filter( Resource.id == ws_conn.controller_id).one() seq_name = resource.path( ) + '/' + seq_name # this is ok for now since .. doesn't have special meaning in resource path (no way to escape controller folder) timestamp = parameters.get( 'timestamp', '') # fix(soon): need to convert to datetime if not timestamp: timestamp = datetime.datetime.utcnow() value = parameters['value'] if 'encoded' in parameters: value = base64.b64decode(value) # remove this; require clients to use REST POST for images resource = find_resource(seq_name) if not resource: return system_attributes = json.loads( resource.system_attributes ) if resource.system_attributes else None if system_attributes and system_attributes[ 'data_type'] == Resource.IMAGE_SEQUENCE: value = base64.b64decode(value) else: value = str(value) update_sequence_value(resource, seq_name, timestamp, value) db.session.commit() # update a resource elif type == 'write_resource': if 'path' and 'data' in parameters: path = parameters['path'] if not path.startswith( '/'): # fix(soon): remove this after clients updated path = '/' + path data = parameters['data'] resource = find_resource(path) if resource: if ws_conn.access_level(resource.id) >= ACCESS_LEVEL_WRITE: timestamp = datetime.datetime.utcnow() update_sequence_value(resource, path, timestamp, data) db.session.commit() else: socket_sender.send_error(ws_conn, 'permission error: %s' % path) else: socket_sender.send_error(ws_conn, 'resource not found: %s' % path) else: socket_sender.send_error( ws_conn, 'expected data and path parameters for write_resource message') # handle other action messages elif type in ('sendEmail', 'sendTextMessage', 'send_email', 'send_text_message'): if ws_conn.controller_id: # only support these messages from controllers, not browsers if type == 'sendEmail' or type == 'send_email': handle_send_email(ws_conn.controller_id, message_struct['parameters']) elif type == 'sendTextMessage' or type == 'send_text_message': handle_send_text_message(ws_conn.controller_id, message_struct['parameters']) # for other types, assume that we want to create a message record else: # figure out target folder if 'folder' in message_struct: folder_name = message_struct['folder'] if message_debug: print('message to folder: %s' % folder_name) if hasattr(folder_name, 'startswith') and folder_name.startswith('/'): if message_debug: print('message to folder name: %s' % folder_name) folder = find_resource(folder_name) # assumes leading slash if folder: folder_id = folder.id if message_debug: print('message to folder id: %d' % folder_id) else: print('message to unknown folder (%s)' % folder_name) return else: folder_id = folder_name # fix(soon): remove this case elif ws_conn.controller_id: folder_id = ws_conn.controller_id else: print('message (%s) without folder or controller; discarding' % type) return # if allowed, create a message for the folder if ws_conn.access_level(folder_id) >= ACCESS_LEVEL_WRITE: parameters = message_struct['parameters'] # fix(soon): can we move this spawn above access level check (might require request context) gevent.spawn(message_queue.add, folder_id, type, parameters, sender_controller_id=ws_conn.controller_id, sender_user_id=ws_conn.user_id)