def __init__(self, settings, logger=None, **kwargs): self.logger = logger or logging.getLogger(__name__) self.logger.info("Version is: %s", transport_version) super(TransportService, self).__init__( logger=logger, c_extension=(settings.full_python is False), ignored_ep_filter=settings.ignored_endpoints_filter, **kwargs ) self.gw_id = settings.gateway_id self.gw_model = settings.gateway_model self.gw_version = settings.gateway_version self.whitened_ep_filter = settings.whitened_endpoints_filter last_will_topic = TopicGenerator.make_status_topic(self.gw_id) last_will_message = wirepas_messaging.gateway.api.StatusEvent( self.gw_id, GatewayState.OFFLINE ).payload self.mqtt_wrapper = MQTTWrapper( settings, self.logger, self._on_mqtt_wrapper_termination_cb, self._on_connect, last_will_topic, last_will_message, ) self.mqtt_wrapper.start() self.logger.info("Gateway started with id: %s", self.gw_id)
def _on_get_gateway_info_cmd_received(self, client, userdata, message): # pylint: disable=unused-argument """ This function doesn't need the decorator @deferred_thread as request is handled without I/O """ self.logger.info("Gateway info request received") try: request = wirepas_messaging.gateway.api.GetGatewayInfoRequest.from_payload( message.payload) except GatewayAPIParsingException as e: self.logger.error(str(e)) return response = wirepas_messaging.gateway.api.GetGatewayInfoResponse( request.req_id, self.gw_id, GatewayResultCode.GW_RES_OK, current_time_s_epoch=int(time()), gateway_model=self.gw_model, gateway_version=self.gw_version, implemented_api_version=IMPLEMENTED_API_VERSION, ) topic = TopicGenerator.make_get_gateway_info_response_topic(self.gw_id) self.mqtt_wrapper.publish(topic, response.payload, qos=2)
def _on_otap_process_scratchpad_request_received(self, client, userdata, message): # pylint: disable=unused-argument self.logger.info("OTAP process request received") try: request = wirepas_messaging.gateway.api.ProcessScratchpadRequest.from_payload( message.payload ) except GatewayAPIParsingException as e: self.logger.error(str(e)) return sink = self.sink_manager.get_sink(request.sink_id) if sink is not None: res = sink.process_scratchpad() else: res = GatewayResultCode.GW_RES_INVALID_SINK_ID response = wirepas_messaging.gateway.api.ProcessScratchpadResponse( request.req_id, self.gw_id, res, request.sink_id ) topic = TopicGenerator.make_otap_process_scratchpad_response_topic( self.gw_id, request.sink_id ) self.mqtt_wrapper.publish(topic, response.payload, qos=2)
def _on_set_config_cmd_received(self, client, userdata, message): # pylint: disable=unused-argument self.logger.info("Set config request received") try: request = wirepas_messaging.gateway.api.SetConfigRequest.from_payload( message.payload ) except GatewayAPIParsingException as e: self.logger.error(str(e)) return self.logger.debug("Set sink config: %s", request) sink = self.sink_manager.get_sink(request.sink_id) if sink is not None: res = sink.write_config(request.new_config) new_config = sink.read_config() else: res = GatewayResultCode.GW_RES_INVALID_SINK_ID new_config = None response = wirepas_messaging.gateway.api.SetConfigResponse( request.req_id, self.gw_id, res, request.sink_id, new_config ) topic = TopicGenerator.make_set_config_response_topic( self.gw_id, request.sink_id ) self.mqtt_wrapper.publish(topic, response.payload, qos=2)
def _on_otap_set_target_scratchpad_request_received( self, client, userdata, message): # pylint: disable=unused-argument res = wmm.GatewayResultCode.GW_RES_OK self.logger.info("OTAP set target request received") try: request = wmm.SetScratchpadTargetAndActionRequest.from_payload( message.payload) action = request.target["action"] except wmm.GatewayAPIParsingException as e: self.logger.error(str(e)) return except KeyError: self.logger.error("Action is mandatory") res = wmm.GatewayResultCode.GW_RES_INVALID_PARAM if res == wmm.GatewayResultCode.GW_RES_OK: # Get optional params (None if not present) seq = request.target.get("target_sequence") crc = request.target.get("target_crc") delay = request.target.get("delay") if delay is not None: # Convert predefined delay to right format if delay is wmm.ProcessingDelay.DELAY_TEN_MINUTES: param = 0x4A elif delay is wmm.ProcessingDelay.DELAY_THIRTY_MINUTES: param = 0x5E elif delay is wmm.ProcessingDelay.DELAY_ONE_HOUR: param = 0x81 elif delay is wmm.ProcessingDelay.DELAY_SIX_HOURS: param = 0x86 elif delay is wmm.ProcessingDelay.DELAY_ONE_DAY: param = 0xC1 elif delay is wmm.ProcessingDelay.DELAY_TWO_DAYS: param = 0xC2 elif delay is wmm.ProcessingDelay.DELAY_FIVE_DAYS: param = 0xC5 else: # Unknown value, set to 0, will generate an error later param = 0 else: param = request.target.get("param") # no error so far sink = self.sink_manager.get_sink(request.sink_id) if sink is not None: res = sink.set_target_scratchpad(action=action, target_seq=seq, target_crc=crc, param=param) else: res = wmm.GatewayResultCode.GW_RES_INVALID_SINK_ID response = wmm.SetScratchpadTargetAndActionResponse( request.req_id, self.gw_id, res, request.sink_id) topic = TopicGenerator.make_otap_set_target_scratchpad_response_topic( self.gw_id, request.sink_id) self.mqtt_wrapper.publish(topic, response.payload, qos=2)
def _set_status(self): event_online = wirepas_messaging.gateway.api.StatusEvent( self.gw_id, GatewayState.ONLINE ) topic = TopicGenerator.make_status_topic(self.gw_id) self.mqtt_wrapper.publish(topic, event_online.payload, qos=1, retain=True)
def __init__(self, settings, logger=None, **kwargs): self.logger = logger or logging.getLogger(__name__) self.logger.info("Version is: %s", transport_version) super(TransportService, self).__init__( logger=logger, c_extension=(settings.full_python is False), ignored_ep_filter=settings.ignored_endpoints_filter, **kwargs) self.gw_id = settings.gateway_id self.gw_model = settings.gateway_model self.gw_version = settings.gateway_version self.whitened_ep_filter = settings.whitened_endpoints_filter last_will_topic = TopicGenerator.make_status_topic(self.gw_id) last_will_message = wirepas_messaging.gateway.api.StatusEvent( self.gw_id, GatewayState.OFFLINE).payload self.mqtt_wrapper = MQTTWrapper( settings, self.logger, self._on_mqtt_wrapper_termination_cb, self._on_connect, last_will_topic, last_will_message, ) self.mqtt_wrapper.start() self.logger.info("Gateway started with id: %s", self.gw_id) self.monitoring_thread = None self.minimum_sink_cost = settings.buffering_minimal_sink_cost if settings.buffering_max_buffered_packets > 0: self.logger.info( " Black hole detection enabled: max_packets=%s packets, max_delay=%s", settings.buffering_max_buffered_packets, settings.buffering_max_delay_without_publish, ) # Create and start a monitoring thread for black hole issue self.monitoring_thread = ConnectionToBackendMonitorThread( self.logger, self.MONITORING_BUFFERING_PERIOD_S, self.mqtt_wrapper, self.sink_manager, settings.buffering_minimal_sink_cost, settings.buffering_max_buffered_packets, settings.buffering_max_delay_without_publish, ) self.monitoring_thread.start() if settings.debug_incr_data_event_id: self.data_event_id = 0 else: self.data_event_id = None
def on_data_received( self, sink_id, timestamp, src, dst, src_ep, dst_ep, travel_time, qos, hop_count, data, ): if self.whitened_ep_filter is not None and dst_ep in self.whitened_ep_filter: # Only publish payload size but not the payload self.logger.debug("Filtering payload data") data_size = data.__len__() data = None else: data_size = None event = wirepas_messaging.gateway.api.ReceivedDataEvent( gw_id=self.gw_id, sink_id=sink_id, rx_time_ms_epoch=timestamp, src=src, dst=dst, src_ep=src_ep, dst_ep=dst_ep, travel_time_ms=travel_time, qos=qos, data=data, data_size=data_size, hop_count=hop_count, ) sink = self.sink_manager.get_sink(sink_id) if sink is None: # It can happen at sink connection as messages can be received # before sinks are identified self.logger.info( "Message received from unknown sink at the moment %s", sink_id ) return network_address = sink.get_network_address() topic = TopicGenerator.make_received_data_topic( self.gw_id, sink_id, network_address, src_ep, dst_ep ) self.logger.debug("Sending data to: %s", topic) # Set qos to 1 to avoid loading too much the broker # unique id in event header can be used for duplicate filtering in # backends self.mqtt_wrapper.publish(topic, event.payload, qos=1)
def on_stack_started(self, name): sink = self.sink_manager.get_sink(name) if sink is None: self.logger.error("Sink started %s error: unknown sink", name) return # Generate a setconfig answer with req_id of 0 response = wirepas_messaging.gateway.api.SetConfigResponse( 0, self.gw_id, GatewayResultCode.GW_RES_OK, sink.sink_id, sink.read_config() ) topic = TopicGenerator.make_set_config_response_topic(self.gw_id, sink.sink_id) self.mqtt_wrapper.publish(topic, response.payload, qos=2)
def _on_connect(self): # Register for get gateway info topic = TopicGenerator.make_get_gateway_info_request_topic(self.gw_id) self.mqtt_wrapper.subscribe(topic, self._on_get_gateway_info_cmd_received) # Register for get configs request topic = TopicGenerator.make_get_configs_request_topic(self.gw_id) self.mqtt_wrapper.subscribe(topic, self._on_get_configs_cmd_received) # Register for set config request for any sink topic = TopicGenerator.make_set_config_request_topic(self.gw_id) self.mqtt_wrapper.subscribe(topic, self._on_set_config_cmd_received) # Register for send data request for any sink on the gateway topic = TopicGenerator.make_send_data_request_topic(self.gw_id) self.logger.debug("Subscribing to: %s", topic) # It is important to have a qos of 2 and also from the publisher as 1 could generate # duplicated packets and we don't know the consequences on end # application self.mqtt_wrapper.subscribe(topic, self._on_send_data_cmd_received, qos=2) # Register for otap commands for any sink on the gateway topic = TopicGenerator.make_otap_status_request_topic(self.gw_id) self.mqtt_wrapper.subscribe(topic, self._on_otap_status_request_received) topic = TopicGenerator.make_otap_load_scratchpad_request_topic( self.gw_id) self.mqtt_wrapper.subscribe( topic, self._on_otap_upload_scratchpad_request_received) topic = TopicGenerator.make_otap_process_scratchpad_request_topic( self.gw_id) self.mqtt_wrapper.subscribe( topic, self._on_otap_process_scratchpad_request_received) topic = TopicGenerator.make_otap_set_target_scratchpad_request_topic( self.gw_id) self.mqtt_wrapper.subscribe( topic, self._on_otap_set_target_scratchpad_request_received) self._set_status() self.logger.info("MQTT connected!")
def _send_asynchronous_get_configs_response(self): # Create a list of different sink configs configs = [] for sink in self.sink_manager.get_sinks(): config = sink.read_config() if config is not None: configs.append(config) # Generate a setconfig answer with req_id of 0 as not from # a real request response = wirepas_messaging.gateway.api.GetConfigsResponse( 0, self.gw_id, GatewayResultCode.GW_RES_OK, configs) topic = TopicGenerator.make_get_configs_response_topic(self.gw_id) self.mqtt_wrapper.publish(topic, response.payload, qos=2)
def _on_otap_status_request_received(self, client, userdata, message): # pylint: disable=unused-argument self.logger.info("OTAP status request received") try: request = wmm.GetScratchpadStatusRequest.from_payload( message.payload) except wmm.GatewayAPIParsingException as e: self.logger.error(str(e)) return sink = self.sink_manager.get_sink(request.sink_id) if sink is not None: d = sink.get_scratchpad_status() target_and_action = {} try: target_and_action["action"] = d["target_action"] target_and_action["target_sequence"] = d["target_seq"] target_and_action["target_crc"] = d["target_crc"] target_and_action["param"] = d["target_param"] except KeyError: # If not present, just an old node target_and_action = None response = wmm.GetScratchpadStatusResponse( request.req_id, self.gw_id, wmm.GatewayResultCode.GW_RES_OK, request.sink_id, d["stored_scartchpad"], d["stored_status"], d["stored_type"], d["processed_scartchpad"], d["firmware_area_id"], target_and_action, ) else: response = wmm.GetScratchpadStatusResponse( request.req_id, self.gw_id, wmm.GatewayResultCode.GW_RES_INVALID_SINK_ID, request.sink_id, ) topic = TopicGenerator.make_otap_status_response_topic( self.gw_id, request.sink_id) self.mqtt_wrapper.publish(topic, response.payload, qos=2)
def _on_send_data_cmd_received(self, client, userdata, message): # pylint: disable=unused-argument self.logger.info("Request to send data") try: request = wirepas_messaging.gateway.api.SendDataRequest.from_payload( message.payload ) except GatewayAPIParsingException as e: self.logger.error(str(e)) return # Get the sink-id from topic _, sink_id = TopicParser.parse_send_data_topic(message.topic) self.logger.debug("Request for sink %s", sink_id) sink = self.sink_manager.get_sink(sink_id) if sink is not None: if request.hop_limit > self.MAX_HOP_LIMIT: res = GatewayResultCode.GW_RES_INVALID_MAX_HOP_COUNT else: res = sink.send_data( request.destination_address, request.source_endpoint, request.destination_endpoint, request.qos, request.initial_delay_ms, request.data_payload, request.is_unack_csma_ca, request.hop_limit, ) else: self.logger.warning("No sink with id: %s", sink_id) # No sink with this id res = GatewayResultCode.GW_RES_INVALID_SINK_ID # Answer to backend response = wirepas_messaging.gateway.api.SendDataResponse( request.req_id, self.gw_id, res, sink_id ) topic = TopicGenerator.make_send_data_response_topic(self.gw_id, sink_id) self.mqtt_wrapper.publish(topic, response.payload, qos=2)
def _on_get_configs_cmd_received(self, client, userdata, message): # pylint: disable=unused-argument self.logger.info("Config request received") try: request = wirepas_messaging.gateway.api.GetConfigsRequest.from_payload( message.payload) except GatewayAPIParsingException as e: self.logger.error(str(e)) return # Create a list of different sink configs configs = [] for sink in self.sink_manager.get_sinks(): config = sink.read_config() if config is not None: configs.append(config) response = wirepas_messaging.gateway.api.GetConfigsResponse( request.req_id, self.gw_id, GatewayResultCode.GW_RES_OK, configs) topic = TopicGenerator.make_get_configs_response_topic(self.gw_id) self.mqtt_wrapper.publish(topic, response.payload, qos=2)
def _on_otap_status_request_received(self, client, userdata, message): # pylint: disable=unused-argument self.logger.info("OTAP status request received") try: request = wirepas_messaging.gateway.api.GetScratchpadStatusRequest.from_payload( message.payload ) except GatewayAPIParsingException as e: self.logger.error(str(e)) return sink = self.sink_manager.get_sink(request.sink_id) if sink is not None: d = sink.get_scratchpad_status() response = wirepas_messaging.gateway.api.GetScratchpadStatusResponse( request.req_id, self.gw_id, GatewayResultCode.GW_RES_OK, request.sink_id, d["stored_scartchpad"], d["stored_status"], d["stored_type"], d["processed_scartchpad"], d["firmware_area_id"], ) else: response = wirepas_messaging.gateway.api.GetScratchpadStatusResponse( request.req_id, self.gw_id, GatewayResultCode.GW_RES_INVALID_SINK_ID, request.sink_id, ) topic = TopicGenerator.make_otap_status_response_topic( self.gw_id, request.sink_id ) self.mqtt_wrapper.publish(topic, response.payload, qos=2)