示例#1
0
    def __init__(self, request, client_address, server):
        #
        self.logger = server.logger or logging.getLogger(__name__)
        self.http_tx_queue = server.http_tx_queue
        self.status_observer = server.status_observer
        self.mqtt_topics = Topics()

        self.debug_comms = False  # if true communication details are logged
        self.http_api_test_mode = False  # When on, does not send MQTT messages

        super(wbcHTTPRequestHandler, self).__init__(request, client_address,
                                                    server)
示例#2
0
    def __init__(
        self,
        shared_state,
        tx_queue,
        rx_queue,
        settings,
        data_queue=None,
        event_queue=None,
        timeout=10,
        histfile_size=1000,
        exit_signal=None,
        logger=None,
    ):

        self._prompt_base = "cli"
        self._prompt_format = "{} | {} > "
        self._reply_greeting = "<< message from"

        self._bstr_as_hex = True
        self._pretty_prints = False
        self._minimal_prints = False
        self._silent_loop = False
        self._max_queue_size = 1000
        self._max_data_queue_size = 1  # Data queues contains list messges

        self._file = None
        self._histfile = os.path.expanduser("~/.wm-shell-history")
        self._histfile_size = histfile_size

        self._tracking_loop_timeout = 1
        self._tracking_loop_iterations = float("Inf")
        self._raise_errors = False
        self._skip_replies = True

        super().__init__()

        self.settings = settings
        self.intro = self.intro.format(**self.settings.to_dict())
        self.prompt = self._prompt_format.format(
            datetime.datetime.now().strftime("%H:%M.%S"), self._prompt_base)

        self.request_queue = tx_queue
        self.response_queue = rx_queue
        self.data_queue = data_queue
        self.event_queue = event_queue

        self.wait_api_lock = Lock()
        self.mqtt_topics = Topics()
        self.exit_signal = exit_signal
        self.timeout = timeout
        self.logger = logger or logging.getLogger(__name__)

        self.device_manager = shared_state["devices"]
        self._shared_state = shared_state
        self._flush_lock = threading.Lock()

        self.start_data_event_queue_peroidic_flush_timer()
 def __init__(self, deviceManager: MeshManagement):
     self._processingActive: bool = True
     self._networkIdToUse: int = 0
     self._diagnosticIntervalSecs: SetDiagnosticsIntervals = SetDiagnosticsIntervals.intervalOff
     self._deviceManager: MeshManagement = deviceManager
     self._mqttSendFn = None
     self._diagnosticActivationStatuses = dict()
     self.mqtt_topics = Topics()
     self._defaultPollTimeSec = 0.1
     self._operationStart = None
     self._sinksAddressInfo = None
     self._messageTxList = list()
     self._defaultOperationTimeOutSec = 30
示例#4
0
class wbcHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
    """A simple HTTP server class.

    Only overrides the do_GET from the HTTP server so it catches
    all the GET requests and processes them into commands.
    """

    class HTTP_response_fields(Enum):
        path = "path"
        params = "params"
        gw_and_sinks = "gateways_and_sinks"
        command = "command"
        text = "text"
        code = "code"

    class HTTP_server_commands(Enum):
        data_tx = "datatx"
        start = "start"
        stop = "stop"
        set_config = "setconfig"
        get_info = "info"

    class HTTP_server_response_codes(Enum):
        http_response_ok = 200
        http_response_code_unknown_command = 500

    # pylint: disable=locally-disabled, too-many-arguments, broad-except,
    # unused-argument, invalid-name
    # pylint: disable=locally-disabled, too-many-statements, too-many-locals,
    # too-many-branches, too-many-nested-blocks

    def __init__(self, request, client_address, server):
        #
        self.logger = server.logger or logging.getLogger(__name__)
        self.http_tx_queue = server.http_tx_queue
        self.status_observer = server.status_observer
        self.mqtt_topics = Topics()

        self.debug_comms = False  # if true communication details are logged
        self.http_api_test_mode = False  # When on, does not send MQTT messages

        super(wbcHTTPRequestHandler, self).__init__(
            request, client_address, server
        )

    def end_headers(self):
        self.send_my_headers()
        super().end_headers()

    def send_my_headers(self):
        self.send_header(
            "Cache-Control", "no-cache, no-store, must-revalidate"
        )
        self.send_header("Pragma", "no-cache")
        self.send_header("Expires", "0")

    def _process_request(self, verb):
        """ Decodes an incoming http request regardless of its verb"""
        __default_command = "info"

        # Parse into commands and parameters
        slitted = urllib.parse.urlsplit(self.path)
        params = dict(
            urllib.parse.parse_qsl(urllib.parse.urlsplit(self.path).query)
        )
        try:
            command = slitted.path.split("/")[1]
        except KeyError:
            command = __default_command

        if command == "":
            command = __default_command
        if self.debug_comms is True:
            self.logger.info(
                dict(
                    protocol="http",
                    verb=verb,
                    path=self.path,
                    params=str(params),
                    command=command,
                    gateways_and_sinks=str(
                        self.status_observer.gateways_and_sinks
                    ),
                )
            )

        self._mesh_control(command, params)

    # flake8: noqa
    def do_GET(self):
        """Process a single HTTP GET request.
        """
        self._process_request("GET")

    def do_POST(self):
        """Process a single HTTP POST request.
        """
        self._process_request("POST")

    def _mesh_control(self, command, params):
        """ Decodes an incoming payload and acts upon it """

        # By default assume that gateway configuration does not need
        # refreshing after command is executed
        refresh = False
        response = dict()

        # Create HTTP response header
        response[self.HTTP_response_fields.path.value] = self.path
        response[self.HTTP_response_fields.params.value] = str(params)
        response[self.HTTP_response_fields.gw_and_sinks.value] = str(
            self.status_observer.gateways_and_sinks
        )
        response[self.HTTP_response_fields.command.value] = command

        if len(command) > 0:

            self.logger.info("HTTP command '%s' received", command)

            response[self.HTTP_response_fields.text.value] = f"{command} ok!"
            response[
                self.HTTP_response_fields.code.value
            ] = self.HTTP_server_response_codes.http_response_ok.value

            config_messages = list()
            messages = list()

            # Go through all gateways and sinks that are currently known
            gateways_and_sinks = self.status_observer.gateways_and_sinks
            for gateway_id, sinks in gateways_and_sinks.items():

                # Sends the command towards all the discovered sinks
                for sink_id, sink in sinks.items():

                    command_was_ok = False

                    if command == self.HTTP_server_commands.data_tx.value:
                        # Handle transmit request.
                        (
                            command_was_ok,
                            new_messages,
                        ) = self._handle_datatx_command(
                            gateway_id,
                            refresh,
                            response,
                            sink,
                            sink_id,
                            command,
                            params,
                            gateways_and_sinks,
                        )
                        if command_was_ok is not True:
                            break
                        else:
                            if len(new_messages) > 0:
                                for msg in new_messages:
                                    messages.append(msg)

                    elif command == self.HTTP_server_commands.start.value:
                        (
                            command_was_ok,
                            refresh,
                            new_messages,
                        ) = self._handle_start_command(
                            gateway_id, refresh, sink_id
                        )
                        if len(new_messages) > 0:
                            for msg in new_messages:
                                messages.append(msg)
                    elif command == self.HTTP_server_commands.stop.value:
                        (
                            command_was_ok,
                            refresh,
                            new_messages,
                        ) = self._handle_stop_command(
                            gateway_id, refresh, sink_id
                        )
                        if len(new_messages) > 0:
                            for msg in new_messages:
                                messages.append(msg)
                    elif command == self.HTTP_server_commands.set_config.value:
                        (
                            command_was_ok,
                            refresh,
                            new_messages,
                        ) = self._handle_setconfig_command(
                            gateway_id, params, refresh, sink, sink_id
                        )
                        if len(new_messages) > 0:
                            for msg in new_messages:
                                messages.append(msg)
                    elif command == self.HTTP_server_commands.get_info.value:
                        (
                            command_was_ok,
                            refresh,
                            new_messages,
                        ) = self._handle_info_command(
                            command,
                            gateway_id,
                            refresh,
                            response,
                            sink,
                            sink_id,
                        )
                        if len(new_messages) > 0:
                            for msg in new_messages:
                                messages.append(msg)
                    else:
                        self._handle_unknown_command(response)
                        break
                    # Renews information about remote gateways
                    if command_was_ok is True:

                        if refresh:
                            refresh = False
                            self._send_get_config_request_to_gateways(
                                gateway_id, config_messages
                            )
                    else:
                        self.logger.error(
                            "HTTP command parsing (%s) failed", command
                        )

            # sends all messages
            if self.http_api_test_mode is False:
                if len(messages) > 0:
                    self.logger.info(
                        "Send %d MQTT data messages", len(messages)
                    )
                    self._send_messages_to_mqtt(messages)
                if len(config_messages) > 0:
                    self.logger.info(
                        "Send %d MQTT config messages", len(config_messages)
                    )
                    self._send_messages_to_mqtt(config_messages)
            else:
                self.logger.error(
                    "HTTP API test test mode. " "Not sending MQTT messages."
                )
        else:
            self._handle_empty_request(response)

        if (
            response[self.HTTP_response_fields.code.value]
            != self.HTTP_server_response_codes.http_response_ok.value
        ):
            self.logger.error(response)
        else:
            self.logger.info("HTTP command ok")

        # send code and response message
        self._send_http_response(response)

        if self.debug_comms is True:
            self.logger.info("HTTP response body: %s", response)

    def _send_http_response(self, response):
        self.send_response(
            code=response[self.HTTP_response_fields.code.value],
            message=response[self.HTTP_response_fields.text.value],
        )
        self.end_headers()

    def _handle_empty_request(self, response):

        self.logger.error("HTTP request was empty")

        response[self.HTTP_response_fields.text.value] = "Error: empty request"
        response[
            self.HTTP_response_fields.code.value
        ] = (
            self.HTTP_server_response_codes.http_response_code_unknown_command.value
        )

    def _send_get_config_request_to_gateways(self, gateway_id, messages):
        message = self.mqtt_topics.request_message(
            "get_configs", **dict(gw_id=gateway_id)
        )
        messages.append(message)

    def _send_messages_to_mqtt(self, messages):
        for message in messages:
            if len(message) > 0:
                if self.debug_comms is True:
                    self.logger.info({message["topic"]: str(message["data"])})
                self.http_tx_queue.put(message)
            else:
                self.logger.error("MQTT message size is 0")

    def _handle_unknown_command(self, response):
        response[
            self.HTTP_response_fields.code.value
        ] = (
            self.HTTP_server_response_codes.http_response_code_unknown_command.value
        )
        self.logger.error("HTTP request command was unknown")
        response[self.HTTP_response_fields.text.value] = "Unknown command"

    def _find_sink(self, sink_node_address: int, gateways: dict):

        sink_node_address_belongs_network = False
        for gateway_id, sinks in gateways.items():
            # Sends the command towards all the discovered sinks
            for sink_id, sink in sinks.items():
                if (
                    sink[App_config_keys.app_config_node_address_key.value]
                    == sink_node_address
                ):
                    sink_node_address_belongs_network = True
                    break
            if sink_node_address_belongs_network is True:
                break

        return sink_node_address_belongs_network

    def _handle_datatx_command(
        self,
        gateway_id,
        refresh,
        response,
        sink,
        sink_id,
        command,
        params,
        gateways: dict,
    ):

        command_was_ok: bool = True
        command_parse_was_ok: bool = False
        newMessages = list()
        message = None

        try:
            # When sending message to certain gateway/sink on network we need
            destination_node_address = int(params["destination"])
            src_ep = int(params["source_ep"])
            dst_ep = int(params["dest_ep"])

            # QOS passed by HTTP request (int(params["qos"])) is not used
            # from now on. MQTT QOS is fixed to
            # MQTT_QOS_options.exactly_once.value
            qos = MQTT_QOS_options.exactly_once.value

            payload = binascii.unhexlify(params["payload"])
            command_parse_was_ok = True
        except KeyError as error:
            response[
                self.HTTP_response_fields.code.value
            ] = (
                self.HTTP_server_response_codes.http_response_code_unknown_command.value
            )
            response[
                self.HTTP_response_fields.text
            ] = f"Missing field: {error}"
            command_was_ok = False
        except Exception as error:
            response[
                self.HTTP_response_fields.code.value
            ] = (
                self.HTTP_server_response_codes.http_response_code_unknown_command.value
            )
            response[
                self.HTTP_response_fields.text
            ] = f"Unknown error: {error}"
            command_was_ok = False

        if command_parse_was_ok is True:
            try:
                is_unack_csma_ca = params["fast"] in ["true", "1", "yes", "y"]
            except KeyError:
                is_unack_csma_ca = False

            try:
                hop_limit = int(params["hoplimit"])
            except KeyError:
                hop_limit = 0

            try:
                count = int(params["count"])
            except KeyError:
                count = 1

            # Expected behavior:
            # (1) If destination_node_address is any of gateway sink addresses,
            # send only to desired sink.

            # (2) If destination_node_address is not any of gateway sink
            # addresses, then send this to all sinks of gateways belonging
            # to this network

            # Assumptions
            # (1) each sink node address is unique to network

            send_message_to_sink: bool = False

            if self._find_sink(destination_node_address, gateways):
                if (
                    sink[App_config_keys.app_config_node_address_key.value]
                    == destination_node_address
                ):
                    # send only addressed sink
                    send_message_to_sink = True
                    if self.debug_comms is True:
                        self.logger.info("Node address is sink address")

            else:
                # send to all sinks on network
                send_message_to_sink = True

            if send_message_to_sink is True:
                # sends a or multiple messages according to the count
                # parameter in the request

                while count:

                    if self.debug_comms is True:
                        self.logger.info(
                            "Create message to be sent via %s/%s to "
                            "nodeaddress=%s dst ep=%s payload=%s",
                            gateway_id,
                            sink_id,
                            destination_node_address,
                            dst_ep,
                            binascii.hexlify(payload),
                        )

                    count -= 1
                    message = self.mqtt_topics.request_message(
                        "send_data",
                        **dict(
                            sink_id=sink_id,
                            gw_id=gateway_id,
                            dest_add=destination_node_address,
                            src_ep=src_ep,
                            dst_ep=dst_ep,
                            qos=qos,
                            payload=payload,
                            is_unack_csma_ca=is_unack_csma_ca,
                            hop_limit=hop_limit,
                        ),
                    )
                    newMessages.append(message)

        return command_was_ok, newMessages

    def _handle_info_command(
        self, command, gateway_id, refresh, response, sink, sink_id
    ):

        command_was_ok = True
        refresh = True
        newMessages = list()

        response[self.HTTP_response_fields.command.value] = command
        # Add rest of fields
        response["gateway"] = gateway_id
        response["sink"] = sink_id
        response["started"] = sink[
            App_config_keys.app_config_started_key.value
        ]
        response["app_config_seq"] = str(
            sink[App_config_keys.app_config_seq_key.value]
        )
        response["app_config_diag"] = str(
            sink[App_config_keys.app_config_diag_key.value]
        )
        response["app_config_data"] = str(
            sink[App_config_keys.app_config_data_key.value]
        )

        return command_was_ok, refresh, newMessages

    def _handle_setconfig_command(
        self, gateway_id, params, refresh, sink, sink_id
    ):

        command_was_ok = True
        refresh = True
        newMessages = list()

        try:
            seq = int(params["seq"])
        except KeyError:
            if sink[App_config_keys.app_config_seq_key.value] == 254:
                seq = 1
            else:
                seq = sink[App_config_keys.app_config_seq_key.value] + 1
        try:
            diag = int(params["diag"])
        except KeyError:
            diag = sink[App_config_keys.app_config_diag_key.value]
        try:
            data = bytes.fromhex(params["data"])
        except KeyError:
            data = sink[App_config_keys.app_config_data_key.value]
        new_config = dict(
            app_config_diag=diag, app_config_data=data, app_config_seq=seq
        )
        message = self.mqtt_topics.request_message(
            "set_config",
            **dict(sink_id=sink_id, gw_id=gateway_id, new_config=new_config),
        )
        newMessages.append(message)
        return command_was_ok, refresh, newMessages

    def _handle_stop_command(self, gateway_id, refresh, sink_id):

        command_was_ok = True
        refresh = True
        newMessages = list()

        new_config = dict(started=False)
        message = self.mqtt_topics.request_message(
            "set_config",
            **dict(sink_id=sink_id, gw_id=gateway_id, new_config=new_config),
        )
        newMessages.append(message)

        return command_was_ok, refresh, newMessages

    def _handle_start_command(self, gateway_id, refresh, sink_id):

        command_was_ok = True
        newMessages = list()

        new_config = dict(started=True)
        message = self.mqtt_topics.request_message(
            "set_config",
            **dict(sink_id=sink_id, gw_id=gateway_id, new_config=new_config),
        )
        newMessages.append(message)
        refresh = True
        return command_was_ok, refresh, newMessages
示例#5
0
    def __init__(
        self,
        mqtt_settings,
        shared_state=None,
        data_queue=None,
        event_queue=None,
        gateway_id: str = "+",
        sink_id: str = "+",
        network_id: str = "+",
        source_endpoint: str = "+",
        destination_endpoint: str = "+",
        message_subscribe_handlers=None,
        publish_cb=None,
        network_parameters=None,
        **kwargs,
    ):

        try:
            tx_queue = kwargs["tx_queue"]
        except KeyError:
            tx_queue = None

        try:
            rx_queue = kwargs["rx_queue"]
        except KeyError:
            rx_queue = None

        try:
            logger = kwargs["logger"]
        except KeyError:
            logger = logging.getLogger(__name__)

        try:
            exit_signal = kwargs["exit_signal"]
        except KeyError:
            exit_signal = Signal(False)

        try:
            start_signal = kwargs["start_signal"]
        except KeyError:
            start_signal = Signal(True)

        try:
            allowed_endpoints = kwargs["allowed_endpoints"]
        except KeyError:
            allowed_endpoints = None

        # create subscription list for MQTT API
        self.mqtt_settings = mqtt_settings
        self.mqtt_topics = Topics()

        if network_parameters:
            self.network_parameters = network_parameters
        else:
            self.network_parameters = dict(
                gw_id=str(gateway_id),
                sink_id=str(sink_id),
                network_id=str(network_id),
                src_ep=str(source_endpoint),
                dst_ep=str(destination_endpoint),
            )

        if message_subscribe_handlers:
            self.message_subscribe_handlers = message_subscribe_handlers
        else:
            self.message_subscribe_handlers = self.build_subscription()

        super(NetworkDiscovery, self).__init__(
            mqtt_settings=mqtt_settings,
            start_signal=start_signal,
            exit_signal=exit_signal,
            tx_queue=tx_queue,
            rx_queue=rx_queue,
            allowed_endpoints=allowed_endpoints,
            message_subscribe_handlers=self.message_subscribe_handlers,
            publish_cb=publish_cb,
            logger=logger,
        )

        # This is to mimic the API style in terms of having a data, event
        # and request response path
        self.response_queue = self.tx_queue
        self.request_queue = self.rx_queue
        self.data_queue = data_queue
        self.event_queue = event_queue

        self.shared_state = shared_state
        self.device_manager = MeshManagement()
        self._debug_comms = False
        self._perioidicTimer = None  # Set on notify where context is right
        self._timerRunning: bool = False  # picklable
        self.data_event_flush_timer_interval_sec: float = 1.0

        self._data_event_tx_queue = Queue()
示例#6
0
class NetworkDiscovery(MQTTObserver):
    """
    NetworkDiscovery

    Tracks the MQTT topics and generates an object representation of the
    devices present in a given network.

    It builds a map of gateways, sinks and devices.

    """
    def __init__(
        self,
        mqtt_settings,
        shared_state=None,
        data_queue=None,
        event_queue=None,
        gateway_id: str = "+",
        sink_id: str = "+",
        network_id: str = "+",
        source_endpoint: str = "+",
        destination_endpoint: str = "+",
        message_subscribe_handlers=None,
        publish_cb=None,
        network_parameters=None,
        **kwargs,
    ):

        try:
            tx_queue = kwargs["tx_queue"]
        except KeyError:
            tx_queue = None

        try:
            rx_queue = kwargs["rx_queue"]
        except KeyError:
            rx_queue = None

        try:
            logger = kwargs["logger"]
        except KeyError:
            logger = logging.getLogger(__name__)

        try:
            exit_signal = kwargs["exit_signal"]
        except KeyError:
            exit_signal = Signal(False)

        try:
            start_signal = kwargs["start_signal"]
        except KeyError:
            start_signal = Signal(True)

        try:
            allowed_endpoints = kwargs["allowed_endpoints"]
        except KeyError:
            allowed_endpoints = None

        # create subscription list for MQTT API
        self.mqtt_settings = mqtt_settings
        self.mqtt_topics = Topics()

        if network_parameters:
            self.network_parameters = network_parameters
        else:
            self.network_parameters = dict(
                gw_id=str(gateway_id),
                sink_id=str(sink_id),
                network_id=str(network_id),
                src_ep=str(source_endpoint),
                dst_ep=str(destination_endpoint),
            )

        if message_subscribe_handlers:
            self.message_subscribe_handlers = message_subscribe_handlers
        else:
            self.message_subscribe_handlers = self.build_subscription()

        super(NetworkDiscovery, self).__init__(
            mqtt_settings=mqtt_settings,
            start_signal=start_signal,
            exit_signal=exit_signal,
            tx_queue=tx_queue,
            rx_queue=rx_queue,
            allowed_endpoints=allowed_endpoints,
            message_subscribe_handlers=self.message_subscribe_handlers,
            publish_cb=publish_cb,
            logger=logger,
        )

        # This is to mimic the API style in terms of having a data, event
        # and request response path
        self.response_queue = self.tx_queue
        self.request_queue = self.rx_queue
        self.data_queue = data_queue
        self.event_queue = event_queue

        self.shared_state = shared_state
        self.device_manager = MeshManagement()
        self._debug_comms = False
        self._perioidicTimer = None  # Set on notify where context is right
        self._timerRunning: bool = False  # picklable
        self.data_event_flush_timer_interval_sec: float = 1.0

        self._data_event_tx_queue = Queue()

    def __data_event_perioid_flush_timeout(self):

        txList: list = []
        while self._data_event_tx_queue.empty() is False:
            msg = self._data_event_tx_queue.get(True)
            txList.append(msg)
            self._data_event_tx_queue.task_done()
        if len(txList) > 0:
            self.data_queue.put(txList)

        self._perioidicTimer = Timer(
            self.data_event_flush_timer_interval_sec,
            self.__data_event_perioid_flush_timeout,
        ).start()

    def notify(self, message, path="response"):
        """ Puts the device on the queue"""

        if self.shared_state:
            self.shared_state["devices"] = self.device_manager

        if message:
            if "response" in path:
                self.response_queue.put(message)

            elif "data" in path and self.data_queue:
                # Data message rate is huge compared others. Handle it
                # different way

                # Put data to internal queue first.
                self._data_event_tx_queue.put(message)

                # Start on this call context.
                if self._timerRunning is False:
                    self._timerRunning = True
                    self._perioidicTimer = Timer(
                        self.data_event_flush_timer_interval_sec,
                        self.__data_event_perioid_flush_timeout,
                    ).start()

            elif "event" in path and self.event_queue:
                self.event_queue.put(message)

    def build_subscription(self):
        """
            Uses the network parameters to build a dictionary with
            topics as keys and callbacks as handlers.
        """

        # track gateway events
        event_status = self.mqtt_topics.event("status",
                                              **self.network_parameters)
        event_received_data = self.mqtt_topics.event("received_data",
                                                     **self.network_parameters)

        response_get_configs = self.mqtt_topics.response(
            "get_configs", **self.network_parameters)
        response_set_config = self.mqtt_topics.response(
            "set_config", **self.network_parameters)
        response_send_data = self.mqtt_topics.response(
            "send_data", **self.network_parameters)
        response_otap_status = self.mqtt_topics.response(
            "otap_status", **self.network_parameters)
        response_otap_load_scratchpad = self.mqtt_topics.response(
            "otap_load_scratchpad", **self.network_parameters)
        response_otap_process_scratchpad = self.mqtt_topics.response(
            "otap_process_scratchpad", **self.network_parameters)

        message_subscribe_handlers = {
            event_status:
            self.generate_gateway_status_event_cb(),
            event_received_data:
            self.generate_gateway_data_event_cb(),
            response_get_configs:
            self.generate_gateway_response_get_configs_cb(),
            response_set_config:
            self.generate_gateway_response_set_config_cb(),
            response_send_data:
            self.generate_gateway_data_response_cb(),
            response_otap_status:
            self.generate_gateway_otap_status_response_cb(),
            response_otap_load_scratchpad:
            self.generate_gateway_load_scratchpad_response_cb(),
            response_otap_process_scratchpad:
            self.generate_gateway_process_scratchpad_response_cb(),
        }

        return message_subscribe_handlers

    # Publishing
    def send_data(self, timeout: int, block: bool):
        """ Callback provided by the interface's cb generator
            Args:
        """

        ret = super(NetworkDiscovery, self).send_data(timeout=timeout,
                                                      block=block)
        if ret is not None:
            try:
                if self.shared_state:
                    if self.shared_state["devices"] is not None:
                        self.device_manager = self.shared_state["devices"]
            except KeyError:
                pass

    # Subscribing
    def generate_gateway_status_event_cb(self) -> callable:
        """ Returns a callback to handle a gateway status event """
        @topic_message
        def on_gateway_status_event_cb(payload, topic: list):
            """ Decodes an incoming gateway status event """

            message = self.mqtt_topics.constructor(
                "event", "status").from_payload(payload)
            gateway = self.device_manager.add(message.gw_id)
            gateway.state = message.state
            self.notify(message=message, path="event")

        return on_gateway_status_event_cb

    def generate_gateway_data_event_cb(self) -> callable:
        """ Returns a callback to handle a gateway data event """
        @decode_topic_message
        def on_gateway_data_event_cb(data_message, topic: list):
            """ Decodes an incoming data event callback """
            if self._debug_comms:
                self.logger.debug("data event: %s", data_message)

            self.device_manager.add_from_mqtt_topic(
                topic, data_message.source_address)
            self.notify(message=data_message, path="data")

        return on_gateway_data_event_cb

    def generate_gateway_response_get_configs_cb(self) -> callable:
        """ Returns a callback to handle a
        response with gateway configurations """
        @topic_message
        def on_gateway_get_configs_cb(payload, topic: list):
            """ Decodes and incoming configuration response """

            if self._debug_comms:
                self.logger.debug("configs response: %s", payload)

            message = self.mqtt_topics.constructor(
                "response", "get_configs").from_payload(payload)

            self.device_manager.add_from_mqtt_topic(topic)
            self.device_manager.update(message.gw_id, message.configs)
            self.notify(message, path="response")

        return on_gateway_get_configs_cb

    def generate_gateway_otap_status_response_cb(self) -> callable:
        """ Returns a callback to handle otap status responses """
        @topic_message
        def on_gateway_otap_status_cb(payload, topic: list):
            """ Decodes an otap status response """
            if self._debug_comms:
                self.logger.debug("otap status response: %s", payload)

            message = self.mqtt_topics.constructor(
                "response", "otap_status").from_payload(payload)
            self.notify(message, path="response")

        return on_gateway_otap_status_cb

    def generate_gateway_response_set_config_cb(self) -> callable:
        """ Returns a callback to handle
        responses to configuration set requests """
        @topic_message
        def on_gateway_set_config_response_cb(payload, topic: list):
            """ Decodes a set config response """
            if self._debug_comms:
                self.logger.debug("set config response: %s", payload)

            message = self.mqtt_topics.constructor(
                "response", "set_config").from_payload(payload)
            self.notify(message, path="response")

        return on_gateway_set_config_response_cb

    def generate_gateway_data_response_cb(self) -> callable:
        """ Returns a callback to handle data responses """
        @topic_message
        def on_gateway_data_response_cb(payload, topic: list):
            """ Decodes a data response """
            if self._debug_comms:
                self.logger.debug("send data response: %s", payload)

            self.device_manager.add_from_mqtt_topic(topic)
            message = self.mqtt_topics.constructor(
                "response", "send_data").from_payload(payload)

            self.notify(message, path="response")

        return on_gateway_data_response_cb

    def generate_gateway_load_scratchpad_response_cb(self) -> callable:
        """ Returns a callback to handle the
        loading of a scratchpad into the target sink """
        @topic_message
        def on_gateway_load_scratchpad_response_cb(payload, topic: list):
            """ """
            if self._debug_comms:
                self.logger.debug("load scratchpad response: %s", payload)

            message = self.mqtt_topics.constructor(
                "response", "otap_load_scratchpad").from_payload(payload)
            self.notify(message, path="response")

        return on_gateway_load_scratchpad_response_cb

    def generate_gateway_process_scratchpad_response_cb(self) -> callable:
        """ Returns a callback to handle a processed scratchpad response """
        @topic_message
        def on_gateway_process_scratchpad_cb(payload, topic: list):
            """ """
            if self._debug_comms:
                self.logger.debug("process scratchpad response: %s", payload)
            message = self.mqtt_topics.constructor(
                "response", "otap_process_scratchpad").from_payload(payload)
            self.notify(message, path="response")

        return on_gateway_process_scratchpad_cb