def handle_node_check(self, data): """Handle alive message received from coap node.""" node_id = data['id'] node = MQTTNode(node_id) node.check_time = time.time() if node not in self.nodes: resources_topic = 'node/{}/resources'.format(node_id) yield from self.mqtt_client.subscribe([(resources_topic, QOS_1)]) logger.debug("Subscribed to topic: {}".format(resources_topic)) node_uid = str(uuid.uuid4()) self.nodes.update( {node: { 'uid': node_uid, 'data': { 'protocol': PROTOCOL } }}) logger.debug("Available nodes: {}".format(self.nodes)) self._on_message_cb(Msg.new_node(node_uid)) self._on_message_cb(Msg.update_node(node_uid, "protocol", PROTOCOL)) discover_topic = 'gateway/{}/discover'.format(node_id) yield from self.mqtt_client.publish(discover_topic, b"resources", qos=QOS_1) logger.debug("Published '{}' to topic: {}".format( "resources", discover_topic)) else: data = self.nodes.pop(node) self.nodes.update({node: data}) logger.debug("Available nodes: {}".format(self.nodes))
def fetch_nodes_cache(self, source): """Send cached nodes information.""" logger.debug("Fetching cached information of registered nodes.") for _, node in self.nodes.items(): self._on_message_cb(Msg.new_node(node['uid'], dst=source)) for endpoint, value in node['data'].items(): yield self._on_message_cb( Msg.update_node(node['uid'], endpoint, value, dst=source))
def add_node(self, node): """Add a new node to the list of nodes and notify the broker.""" node.set_resource_value('protocol', self.PROTOCOL) self.nodes.update({node.uid: node}) self.send_to_broker(Message.new_node(node.uid)) for res, value in node.resources.items(): self.send_to_broker(Message.update_node(node.uid, res, value)) yield self.discover_node(node)
def fetch_nodes_cache(self, source): """Send cached nodes information.""" logger.debug( "Fetching cached information of registered nodes '{}'.".format( self.nodes)) for _, value in self.nodes.items(): self._on_message_cb(Msg.new_node(value['uid'], dst=source)) for resource, data in value['data'].items(): self._on_message_cb( Msg.update_node(value['uid'], resource, data, dst=source))
def fetch_nodes_cache(self, source): """Send cached nodes information.""" logger.debug("Fetching cached information of registered nodes.") for ws, node in self.nodes.items(): self.send_to_broker(Message.new_node(node['uid'], dst=source)) for endpoint, value in node['data'].items(): self.send_to_broker( Message.update_node(node['uid'], endpoint, value, dst=source))
def fetch_nodes_cache(self, client): """Send cached nodes information to a given client. :param client: the ID of the client """ logger.debug( "Fetching cached information of registered nodes '{}'.".format( self.nodes)) for node in self.nodes.values(): self.send_to_broker(Message.new_node(node.uid, dst=client)) for resource, value in node.resources.items(): self.send_to_broker( Message.update_node(node.uid, resource, value, dst=client))
def test_new_node(): serialized = Message.new_node('1234') assert serialized == Message.serialize({ 'type': 'new', 'uid': '1234', 'dst': 'all' }) serialized = Message.new_node('1234', '5678') assert serialized == Message.serialize({ 'type': 'new', 'uid': '1234', 'dst': '5678' })
def send_data_to_node(self, data): """Forward received message data to the destination node. The message should be JSON and contain 'uid', 'path' and 'payload' keys. - 'uid' corresponds to the node uid (uuid) - 'path' corresponds to the CoAP resource on the node - 'payload' corresponds to the new payload for the CoAP resource. """ uid = data['uid'] endpoint = data['endpoint'] payload = data['payload'] logger.debug("Translating message ('{}') received to CoAP PUT " "request".format(data)) for node, _ in self.nodes.items(): if self.nodes[node]['uid'] == uid: address = self.nodes[node]['data']['ip'] logger.debug("Updating CoAP node '{}' resource '{}'".format( self.nodes[node]['data']['ip'], endpoint)) code, p = yield _coap_resource('coap://[{0}]/{1}'.format( address, endpoint), method=PUT, payload=payload.encode('ascii')) if code == Code.CHANGED: self.nodes[node]['data'][endpoint] = payload yield self._on_message_cb( Msg.update_node(uid, endpoint, payload)) break
def discover_node(self, node, uid): """Discover resources available on a node.""" coap_node_url = 'coap://[{}]'.format(node.address) if len(node.endpoints) == 0: logger.debug("Discovering CoAP node {}".format(node.address)) code, payload = yield _coap_resource( '{0}/.well-known/core'.format(coap_node_url), method=GET) node.endpoints = _coap_endpoints(payload) endpoints = [ endpoint for endpoint in node.endpoints if 'well-known/core' not in endpoint ] logger.debug("Fetching CoAP node resources: {}".format(endpoints)) for endpoint in endpoints: elems = endpoint.split(';') path = elems.pop(0).replace('<', '').replace('>', '') try: code, payload = yield _coap_resource('{0}{1}'.format( coap_node_url, path), method=GET) except: logger.debug("Cannot discover ressource {} on node {}".format( endpoint, node.address)) return # Remove '/' from path path = path[1:] self._on_message_cb(Msg.update_node(uid, path, payload)) self.nodes[node]['data'].update({path: payload}) logger.debug( "CoAP node resources '{}' sent to broker".format(endpoints))
def handle_coap_post(self, address, endpoint, value): """Handle CoAP post message sent from coap node.""" node = CoapNode(address) if node in self.nodes and endpoint in self.nodes[node]['data']: self.nodes[node]['data'][endpoint] = value self._on_message_cb( Msg.update_node(self.nodes[node]['uid'], endpoint, value))
def forward_data_from_node(self, node, resource, value): """Send data received from a node to the broker via the gateway.""" logger.debug( "Sending data received from node '{}': '{}', '{}'.".format( node, resource, value)) node.set_resource_value(resource, value) self.send_to_broker(Message.update_node(node.uid, resource, value))
def open(self): """Discover nodes on each opened connection.""" self.set_nodelay(True) logger.debug("New node websocket opened") self.application.nodes.update( {self: { 'uid': str(uuid.uuid4()), 'data': { 'protocol': PROTOCOL } }}) node_uid = self.application.nodes[self]['uid'] self.application.send_to_broker(Message.new_node(node_uid)) yield self.write_message(Message.discover_node()) self.application.send_to_broker( Message.update_node(node_uid, 'protocol', PROTOCOL))
def on_message(self, raw): """Triggered when a message is received from the broker child.""" message, reason = Message.check_message(raw) if message is not None: self.application.on_gateway_message(self, message) else: logger.debug("Invalid message, closing websocket") self.close(code=1003, reason="{}.".format(reason))
def reset_node(self, node, default_resources={}): """Reset a node: clear the current resource and reinitialize them.""" node.clear_resources() node.set_resource_value('protocol', self.PROTOCOL) for resource, value in default_resources.items(): node.set_resource_value(resource, value) self.send_to_broker(Message.reset_node(node.uid)) self.discover_node(node)
def remove_ws(self, ws): """Remove websocket that has been closed.""" if ws in self.clients: self.clients.pop(ws) elif ws in self.gateways.keys(): # Notify clients that the nodes behind the closed gateway are out. for node_uid in self.gateways[ws]: self.broadcast(Message.out_node(node_uid)) self.gateways.pop(ws)
def on_message(self, raw): """Triggered when a message is received from the web client.""" message, reason = Message.check_message(raw) if message is not None: message.update({'src': self.uid}) self.application.on_client_message(self, message) else: logger.debug("Invalid message, closing websocket") self.close(code=1003, reason="{}.".format(reason))
def test_update_node(value): serialized = Message.update_node('1234', 'test', 'value') assert serialized == Message.serialize({ 'type': 'update', 'uid': '1234', 'endpoint': 'test', 'data': 'value', 'dst': 'all' }) serialized = Message.update_node('1234', 'test', value, '5678') assert serialized == Message.serialize({ 'type': 'update', 'uid': '1234', 'endpoint': 'test', 'data': value, 'dst': '5678' })
def check_dead_nodes(self): """Check and remove nodes that are not alive anymore.""" to_remove = [ node for node in self.nodes.keys() if int(time.time()) > node.check_time + self.max_time ] for node in to_remove: uid = self.nodes[node]['uid'] self.nodes.pop(node) logger.debug("Removing inactive node {}".format(uid)) self._on_message_cb(Msg.out_node(uid))
def handle_coap_check(self, address, reset=False): """Handle check message received from coap node.""" node = CoapNode(address) node.check_time = time.time() if node not in self.nodes: # This is a totally new node: create uid, initialized cached node # send 'new' node notification, 'update' notification. node_uid = str(uuid.uuid4()) self.nodes.update({ node: { 'uid': node_uid, 'data': { 'ip': address, 'protocol': PROTOCOL } } }) self._on_message_cb(Msg.new_node(node_uid)) self._on_message_cb(Msg.update_node(node_uid, "ip", address)) self._on_message_cb(Msg.update_node(node_uid, "protocol", PROTOCOL)) self.discover_node(node, node_uid) elif reset: # The data of the node need to be reset without removing it. This # is particularly the case after a reboot of the node or a # firmware update of the node that triggered the reboot. node_uid = self.nodes[node]['uid'] self.nodes[node]['data'] = {} self.nodes[node]['data'].update({ 'ip': address, 'protocol': PROTOCOL }) self._on_message_cb(Msg.reset_node(node_uid)) self.discover_node(node, node_uid) else: # The node simply sent a check message to notify that it's still # online. data = self.nodes.pop(node) self.nodes.update({node: data})
def handle_coap_alive(self, address): """Handle alive message received from coap node.""" node = CoapNode(address) node.check_time = time.time() if node not in self.nodes: node_uid = str(uuid.uuid4()) self.nodes.update({ node: { 'uid': node_uid, 'data': { 'ip': address, 'protocol': 'coap' } } }) self._on_message_cb(Msg.new_node(node_uid)) self._on_message_cb(Msg.update_node(node_uid, "ip", address)) self._on_message_cb(Msg.update_node(node_uid, "protocol", 'coap')) self.discover_node(node, node_uid) else: data = self.nodes.pop(node) self.nodes.update({node: data})
def on_node_message(self, ws, message): """Handle a message received from a node websocket.""" if message['type'] == "update": logger.debug("New update message received from node websocket") for key, value in message['data'].items(): if key in self.nodes[ws]['data']: self.nodes[ws]['data'][key] = value else: self.nodes[ws]['data'].update({key: value}) self.send_to_broker( Message.update_node(self.nodes[ws]['uid'], key, value)) else: logger.debug("Invalid message received from node websocket")
def check_dead_nodes(self): """Check and remove nodes that are not alive anymore.""" to_remove = [node for node in self.nodes.keys() if int(time.time()) > node.check_time + self.max_time] for node in to_remove: asyncio.get_event_loop().create_task( self._disconnect_from_node(node)) for resource in node.resources: pass uid = self.nodes[node]['uid'] self.nodes.pop(node) logger.info("Removing inactive node {}".format(uid)) logger.debug("Available nodes {}".format(self.nodes)) self._on_message_cb(Msg.out_node(uid))
def on_client_message(self, ws, message): """Handle a message received from a client.""" logger.debug("Handling message '{}' received from client websocket." .format(message)) if message['type'] == "new": logger.info("New client connected: {}".format(ws.uid)) if ws.uid not in self.clients.keys(): self.clients.update({ws.uid: ws}) elif message['type'] == "update": logger.debug("New message from client: {}".format(ws.uid)) # Simply forward this message to satellite gateways logger.debug("Forwarding message {} to gateways".format(message)) for gw in self.gateways: gw.write_message(Message.serialize(message))
def handle_node_update(self, topic_name, data): """Handle CoAP post message sent from coap node.""" _, node_id, resource = topic_name.split("/") node = MQTTNode(node_id) value = data['value'] if node in self.nodes: if resource in self.nodes[node]['data']: # Add updated information to cache self.nodes[node]['data'][resource] = value else: self.nodes[node]['data'].update({resource: value}) # Send update to broker self._on_message_cb( Msg.update_node(self.nodes[node]['uid'], resource, value))
def on_gateway_message(self, ws, message): """Handle a message received from a gateway. This method redirect messages from gateways to the right destinations: - for freshly new information initiated by nodes => broadcast - for replies to new client connection => only send to this client """ logger.debug( "Handling message '{}' received from gateway.".format(message)) if message['type'] == "new": # Received when notifying clients of a new node available if not message['uid'] in self.gateways[ws]: self.gateways[ws].append(message['uid']) if message['dst'] == "all": # Occurs when an unknown new node arrived self.broadcast(Message.serialize(message)) elif message['dst'] in self.clients.keys(): # Occurs when a single client has just connected self.send_to_client(message['dst'], Message.serialize(message)) elif (message['type'] == "out" and message['uid'] in self.gateways[ws]): # Node disparition are always broadcasted to clients self.gateways[ws].remove(message['uid']) self.broadcast(Message.serialize(message)) elif (message['type'] == "update" and message['uid'] in self.gateways[ws]): if message['dst'] == "all": # Occurs when a new update was pushed by a node: # require broadcast self.broadcast(Message.serialize(message)) elif message['dst'] in self.clients.keys(): # Occurs when a new client has just connected: # Only the cached information of a node are pushed to this # specific client self.send_to_client(message['dst'], Message.serialize(message))
def on_message(self, raw): """Triggered when a message is received from the broker child.""" if not self.authentified: if verify_auth_token(raw, self.application.keys): logger.info("Gateway websocket authentication verified") self.authentified = True self.application.gateways.update({self: []}) else: logger.info("Gateway websocket authentication failed, " "closing.") self.close() else: message, reason = Message.check_message(raw) if message is not None: self.application.on_gateway_message(self, message) else: logger.debug("Invalid message, closing websocket") self.close(code=1003, reason="{}.".format(reason))
def main(args): """Main function.""" try: ws = websocket.create_connection("ws://{}:{}/node".format( args.host, args.port)) except ConnectionRefusedError: print("Cannot connect to ws://{}:{}".format(args.host, args.port)) return init_node(ws) while True: try: msg = ws.recv() except: print("Connection closed") break else: print(msg) if msg == Message.discover_node(): init_node(ws) else: msg = json.loads(msg) if msg['payload'] == '1': ws.send( json.dumps({ 'type': 'update', 'data': { 'led': '1' } })) else: ws.send( json.dumps({ 'type': 'update', 'data': { 'led': '0' } }))
async def discover_node(self, node): for ws, uid in self.node_mapping.items(): if node.uid == uid: await ws.write_message(Message.discover_node()) break
def send_alive(self): self.send_to_broker(Message.gateway_alive())
def remove_node(self, node): """Remove the given node from known nodes and notify the broker.""" self.nodes.pop(node.uid) logger.debug("Remaining nodes {}".format(self.nodes)) self.send_to_broker(Message.out_node(node.uid))