class AIoTBroker(object):
    def __init__(self, device):
        self.logger = logging.getLogger("Plugin.aIoTBroker")
        self.logger.setLevel(logging.DEBUG)
        streamHandler = logging.StreamHandler()
        formatter = logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        streamHandler.setFormatter(formatter)
        self.logger.addHandler(streamHandler)

        self.deviceID = device.id

        address = device.pluginProps.get('address', "")
        port = int(device.pluginProps.get('port', ""))
        ca_bundle = indigo.server.getInstallFolderPath(
        ) + '/' + device.pluginProps.get('ca_bundle', "")
        cert_file = indigo.server.getInstallFolderPath(
        ) + '/' + device.pluginProps.get('cert_file', "")
        private_key = indigo.server.getInstallFolderPath(
        ) + '/' + device.pluginProps.get('private_key', "")

        self.logger.debug(
            f"{device.name}: Broker __init__ address = {address}:{port}, ca_bundle = {ca_bundle}, cert_file = {cert_file}, private_key = {private_key}"
        )

        device.updateStateOnServer(key="status", value="Not Connected")
        device.updateStateImageOnServer(indigo.kStateImageSel.SensorOff)

        try:
            self.aIoTClient = AWSIoTMQTTClient("indigo-mqtt-{}".format(
                device.id),
                                               useWebsocket=False)
            self.aIoTClient.configureEndpoint(address, port)
            self.aIoTClient.configureCredentials(ca_bundle, private_key,
                                                 cert_file)

            self.aIoTClient.configureAutoReconnectBackoffTime(1, 64, 20)
            self.aIoTClient.configureOfflinePublishQueueing(
                -1)  # Infinite offline Publish queueing
            self.aIoTClient.configureDrainingFrequency(2)  # Draining: 2 Hz
            self.aIoTClient.configureConnectDisconnectTimeout(10)  # 10 sec
            self.aIoTClient.configureMQTTOperationTimeout(5)  # 5 sec

            self.aIoTClient.disableMetricsCollection()

            self.aIoTClient.onOnline = self.onOnline
            self.aIoTClient.onOffline = self.onOffline
            self.aIoTClient.onMessage = self.onMessage

            self.aIoTClient.connectAsync(ackCallback=self.onConnect)

        except (Exception, ):
            self.logger.exception(
                f"{device.name}: Exception while creating Broker object")

    def disconnect(self):
        device = indigo.devices[self.deviceID]
        self.aIoTClient.disconnect()
        device.updateStateOnServer(key="status", value="Not Connected")
        device.updateStateImageOnServer(indigo.kStateImageSel.SensorOff)

    def publish(self, topic, payload=None, qos=0, retain=False):
        device = indigo.devices[self.deviceID]
        self.logger.debug(u"{}: Publishing to: {} ({}), payload = {}".format(
            device.name, topic, qos, payload))
        self.aIoTClient.publishAsync(str(topic),
                                     str(payload),
                                     qos,
                                     ackCallback=self.onPublish)

    def subscribe(self, topic, qos=0):
        device = indigo.devices[self.deviceID]
        self.logger.info(u"{}: Subscribing to: {} ({})".format(
            device.name, topic, qos))
        self.aIoTClient.subscribeAsync(str(topic),
                                       int(qos),
                                       ackCallback=self.onSubscribe)

    def unsubscribe(self, topic):
        device = indigo.devices[self.deviceID]
        self.logger.info(u"{}: Unsubscribing from: {}".format(
            device.name, topic))
        self.aIoTClient.unsubscribeAsync(str(topic),
                                         ackCallback=self.onUnsubscribe)

    ################################################################################
    # Callbacks
    ################################################################################

    def onConnect(self, mid, rc):
        device = indigo.devices[self.deviceID]
        self.logger.debug(u"{}: Client Connected, mid = {}, rc = {}".format(
            device.name, mid, rc))
        device.updateStateOnServer(key="status", value="OnLine")
        device.updateStateImageOnServer(indigo.kStateImageSel.SensorOn)

        # Subscribing in onConnect() means that if we lose the connection and reconnect then subscriptions will be renewed.
        subs = device.pluginProps.get(u'subscriptions', None)
        if subs:
            for s in subs:
                qos = int(s[0:1])
                topic = s[2:]
                self.subscribe(topic, qos)

    def onOnline(self):
        device = indigo.devices[self.deviceID]
        self.logger.debug(u"{}: Client is Online".format(device.name))

    def onOffline(self):
        device = indigo.devices[self.deviceID]
        self.logger.debug(u"{}: Client is OffLine".format(device.name))
        device.updateStateOnServer(key="status", value="OffLine")
        device.updateStateImageOnServer(indigo.kStateImageSel.SensorTripped)

    def onTopicMessage(self, client, userdata, msg):
        device = indigo.devices[self.deviceID]
        self.logger.debug(
            u"{}: onTopicMessage - client: {}, userdata: {} message: '{}', payload: {}"
            .format(device.name, client, userdata, msg.topic, msg.payload))

    def onMessage(self, msg):
        device = indigo.devices[self.deviceID]
        self.logger.debug(u"{}: Message received: {}, payload: {}".format(
            device.name, msg.topic, msg.payload))
        indigo.activePlugin.processReceivedMessage(device.id, msg.topic,
                                                   msg.payload)

    def onPublish(self, mid):
        device = indigo.devices[self.deviceID]
        self.logger.debug(u"{}: Message published: {}".format(
            device.name, mid))

    def onSubscribe(self, mid, data):
        device = indigo.devices[self.deviceID]
        self.logger.debug(u"{}: Subscribe complete: {}, {}".format(
            device.name, mid, data))

    def onUnsubscribe(self, mid):
        device = indigo.devices[self.deviceID]
        self.logger.debug(u"{}: Unsubscribe complete: {}".format(
            device.name, mid))
    print("MQTT client disconnected and offline")
    # also send a MQTT message to the sensors/info topic
    myClient.publishAsync(
        "sensors/info",
        '{{"sensor":"{:s}","timestamp":"{:s}","info":"MQTT client offline"}}'.
        format(sensor_id, get_local_timestamp()), 1)
    time.sleep(10)


# Register the function defined above to be called when the MQTT  goes online
myClient.onOffline = myOnOfflineCallback
# tell the client to connect with AWS
# use 2400 seconds for the keep-alive which is much longer than the
# time between data sends, so the connection doesn't drop between
# each data send (publish)
myClient.connectAsync(keepAliveIntervalSecond=2400)
# seems to need a few seconds before the connection is ready to use
time.sleep(10)


# define a message publish acknowledgement callback function,
# called automatically when an acknowledgement of successful message
# publication is received when quality of service QoS is 1 (which requests ACKs)
def myPubackCallback(mid):
    print("Message ID {:d} sent and acknowledged".format(mid))


##################################################
# set up particulate sensor device
##################################################