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))