def config_on_message(self, unused_client, unused_userdata, message):
        """Callback when the device receives a config message on the config subscription."""
        try:
            config_payload = str(message.payload.decode('utf-8'))

            if config_payload not in self.last_messages_payloads:
                """if config message received that was not received earlier"""
                logger.info("new config message received")
                self.last_messages_payloads.append(config_payload)

                self.last_messages_payloads = self.last_messages_payloads[
                    -5:]  #keep only most recent messages

                #[Start Config Update]
                #check_configuration_message(config_payload, self.scheduler_obj, self.modbus_reader_obj, self.publishing_queue) #gets final result update implemented [True/False] and log
                #[End Config Update]

                t = threading.Thread(target=check_configuration_message,
                                     args=(
                                         config_payload,
                                         self.scheduler_obj,
                                         self.modbus_reader_obj,
                                         self.publishing_queue,
                                     ))
                t.setDaemon = True
                t.start()

            else:
                """if config message received that was received earlier, e.g. through QoS1 MQTT Subscription"""
                logger.info("Existing config message received again")
        except Exception as e:
            logger.error("Error in Paho Callback config_on_message {}",
                         format(e))
    def on_message(self, unused_client, unused_userdata, message):
        """Callback when the device receives a message on a subscription."""
        try:
            payload = str(message.payload.decode('utf-8'))
            logger.info(
                ' Received message \'{}\' on topic \'{}\' with Qos {}'.format(
                    payload, message.topic, str(message.qos)))

        except Exception as e:
            logger.error("Error in Paho Callback on_message {}", format(e))
    def do_exponential_backoff(self):
        """How long to wait with exponential backoff before publishing, when backoff"""
        if self.minimum_backoff_time <= 1 or self.maximum_backoff_time < self.minimum_backoff_time:  #no exponential backoff possible
            self.minimum_backoff_time = 2

        # If backoff time is too large, give up.
        if self.minimum_backoff_time > self.maximum_backoff_time:
            logger.warning('Exceeded maximum backoff time.')
            self.should_backoff = False
            self.minimum_backoff_time = 16

        # Otherwise, wait and connect again.
        delay = self.minimum_backoff_time + random.randint(0, 1000) / 1000.0
        logger.info(
            'Waiting for {} seconds before reconnecting.'.format(delay))
        time.sleep(max(0, delay))
        self.minimum_backoff_time *= 2
    def on_connect(self, unused_client, unused_userdata, unused_flags, rc):
        try:
            """Callback for when a device connects."""
            logger.info('on_connect:{}'.format(mqtt.connack_string(rc)))

            # After a successful connect, reset backoff time and stop backing off.
            self.should_backoff = False
            self.minimum_backoff_time = 2
            logger.info('Subscribing to {} and {}'.format(
                self.mqtt_command_topic, self.mqtt_config_topic))

            # Subscribe to the config topic, QoS 1 enables message acknowledgement.
            self.client.subscribe(self.mqtt_config_topic, qos=1)

            # Subscribe to the commands topic, QoS not used
            self.client.subscribe(self.mqtt_command_topic, qos=0)

        except Exception as e:
            logger.error("Error in Paho Callback on_connect{}", format(e))
    def initial_start_client(self):
        #set up client

        #create unique client identifier in google cloud
        client_id = 'projects/{}/locations/{}/registries/{}/devices/{}'.format(
            PROJECT_ID, CLOUD_REGION, REGISTRY_ID, DEVICE_ID)
        logger.info('Device client_id is \'{}\''.format(client_id))
        self.client = mqtt.Client(
            client_id=client_id, clean_session=False
        )  #broker remembers subscriptions, once connected

        # Enable SSL/TLS support.
        self.client.tls_set(ca_certs=CA_CERTS,
                            tls_version=ssl.PROTOCOL_TLSv1_2)

        # enables maximum messages for publishing queued, further messages dropped
        self.client.max_queued_messages_set(queue_size=200)

        # This is the topic that the device will receive configuration updates on.
        self.mqtt_config_topic = '/devices/{}/config'.format(DEVICE_ID)

        # The topic that the device will receive commands on.
        self.mqtt_command_topic = '/devices/{}/commands/#'.format(DEVICE_ID)
def create_jwt(PROJECT_ID, PRIVATE_KEY_FILE, ALGORITHM):
    """Creates a JWT (https://jwt.io) to establish an MQTT connection.
        
         PROJECT_ID: Cloud Project ID
         PRIVATE_KEY_FILE: A path to the RSA256 or ES256 private key file.
         ALGORITHM: Either 'RS256' or 'ES256'

        Returns:
            A JWT generated from the given PROJECT_ID and private key, which
            expires after JWT_EXPIRES_MINUTES. After this time, the client will be
            disconnected.
        Raises:
            ValueError: If the PRIVATE_KEY_FILE does not contain a known key.
        """

    token = {
        # The time that the token was issued at
        'iat':
        datetime.datetime.utcnow(),
        # The time the token expires.
        'exp':
        datetime.datetime.utcnow() +
        datetime.timedelta(minutes=int(JWT_EXPIRES_MINUTES)),
        # The audience field should always be set to the GCP project id.
        'aud':
        PROJECT_ID
    }

    # Read the private key file.
    with open(PRIVATE_KEY_FILE, 'r') as f:
        private_key = f.read()

    logger.info('Creating JWT using {} from private key file {}'.format(
        ALGORITHM, PRIVATE_KEY_FILE))

    return jwt.encode(token, private_key, algorithm=ALGORITHM)
    def publish_data(self):
        """Publish data from the publishing queue via MQTT

        start_new_connection and initial_start_client must be run first. 
        Will do nothing if self.connection_working and self.run_publish are not set to true beforehand.
        """

        try:
            while self.connection_working == True and self.run_publish == True:

                # [Start Queue message from queue]
                while True:
                    try:
                        #[Start Do Exponential Backoff and Reconnect]
                        if self.should_backoff and (
                                datetime.datetime.utcnow() -
                                self.last_client_restart).seconds > 5:
                            logger.info("Backoff detected, reconnect")
                            self.do_exponential_backoff()
                            self.start_new_connection()

                        #[End Do Exponential Backoff and Reconnect]

                        publish_request = self.publishing_queue.get(
                            timeout=1
                        )  #wait for newest message from publishing_queue that is queued to be puplished
                        break  #if element was in queue

                    except queue.Empty:
                        pass

                sub_topic = publish_request["sub_topic"]
                payload = publish_request["payload"]
                qos = int(publish_request["qos"])

                mqtt_topic = "/devices/{}/{}".format(
                    DEVICE_ID, sub_topic)  #where message is published
                # [End message from queue]

                # [START jwt_refresh]
                seconds_since_issue = (datetime.datetime.utcnow() -
                                       self.jwt_iat).seconds
                if seconds_since_issue + 60 >= int(60 * JWT_EXPIRES_MINUTES):
                    logger.info(
                        "Refreshing JWT token and connection after {}s".format(
                            seconds_since_issue))
                    self.start_new_connection()
                # [END jwt_refresh]

                # [START Precaution before publish on recent opened connection]
                elapsed_seconds_since_restart = (
                    datetime.datetime.utcnow() -
                    self.last_client_restart).seconds
                if elapsed_seconds_since_restart < 20:  #if paho mqtt not ready
                    if elapsed_seconds_since_restart < 5:
                        logger.debug(
                            "MQTT just after connection restart, pausing message send for 5 seconds"
                        )
                        time.sleep(max(0, 5 - elapsed_seconds_since_restart)
                                   )  #up to 5 seconds sleep
                    if qos == 0:
                        logger.debug("Set message from qos 0 to qos 1")
                        qos = 1  #set to qos 1 until stable connection and fully set up
                # [END Precaution before publish on recent opened connection]

                # [Start State messages delay]
                if sub_topic == TOPIC_STATE:
                    #TOPIC_STATE can be refreshed no more than once per second & 6000 times per minute per project in the Google Cloud, optimum once each 5 or 10 seconds max.
                    time_difference_state = (
                        datetime.datetime.utcnow() -
                        self.last_state_message_queued).seconds

                    if 0 <= time_difference_state < 5:
                        logger.info("State message, Sleeping for {}".format(
                            5 - time_difference_state))
                        time.sleep(max(0, 5 - time_difference_state))

                    self.last_state_message_queued = datetime.datetime.utcnow()
                # [End State messages delay]

                self.client.publish(
                    mqtt_topic, payload, qos=qos,
                    retain=True)  # Publish payload to the MQTT topic.

                self.publishing_queue.task_done()

        except Exception as e:
            logger.error(
                'An error occured during publishing data {}'.format(e))
            self.connection_working == False
 def on_publish(self, unused_client, unused_userdata, unused_mid):
     """Paho callback when a message is sent to the broker."""
     try:
         logger.info('on_publish')
     except Exception as e:
         logger.error("Error in Paho Callback on_publish {}", format(e))