예제 #1
0
    def stopkill(self):
        """Called to stop all scheduled RepeatedFunctions"""
        try:
            self.stop_event.set()  #no more scheduling new events

            while len(self.running_rep_functions[:]) != 0:

                for repfunc_thread in self.running_rep_functions[:]:
                    repfunc_thread.stop_event.set(
                    )  #stop this RepeatedFunction

                for repfunc_thread in self.running_rep_functions[:]:
                    if not repfunc_thread.is_alive():
                        self.running_rep_functions.remove(repfunc_thread)
                        logger.debug("removed {}".format(repfunc_thread))

            # now self.running_rep_functions is empty
            self.stop_event.clear()

        except Exception as e:
            logger.error("Unexpected Error, sending to cloud {}".format(e))
            formatted_publish_message(
                topic=TOPIC_STATE,
                payload=
                "Error scheduling old timing events, consider restarting device: {} "
                .format(e),
                c_queue=pub_queue)
    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 on_disconnect(self, unused_client, unused_userdata, rc):
        """Paho callback for when a device disconnects."""
        try:
            logger.warning('on_disconnect callback reason:  {}: {}'.format(
                rc, mqtt.error_string(rc)))

            # Since a disconnect occurred, the next loop iteration will wait with
            # exponential backoff.
            self.should_backoff = True
        except Exception as e:
            logger.error("Error in Paho Callback on_disconnect{}", format(e))
예제 #5
0
    def connect_serial(self):
        "connect the serial port and modbus rtu master over serial port"
        logger.debug("calling connect serial")
        try:
            if self.serial_port != "undefined":  #if initialisized
                self.serial_port.close()
                logger.debug("serial port closed successfully")
        except:
            pass

        try:
            self.serial_connected = False
            self.master_status = self.max_master_attemps

            self.serial_port = serial.Serial(
                port=self.port_config["port"],
                baudrate=self.port_config["baudrate"],
                bytesize=self.port_config["databits"],
                parity=self.port_config["parity"],
                stopbits=self.port_config["stopbits"],
                xonxoff=0)

            logger.debug("connected serial")

            self.master = modbus_rtu.RtuMaster(self.serial_port)
            self.master.set_timeout(self.port_config["timeout_connection"]
                                    )  #max waittime for modbus answers
            self.master.set_verbose(
                False)  #if True, log additional modbus info

            #Successfull, set flags
            logger.debug("connected rtu master")

            self.serial_connected = True
            self.master_status = 0

            with self.timing_queue.mutex:  #reset timing events queue
                self.timing_queue.queue.clear()

        except Exception as e:
            time.sleep(3)
            logger.error(
                "ERROR Serial Port connection not successful{}".format(e))
            self.serial_connected = False
    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))
예제 #7
0
    def run(self):
        self.alive = True

        while True:  #Endless Loop of reconfigure, connect_serial and read_modbus_event
            logger.debug("RESTART the Modbus reader")

            try:
                self.reconfigure()

                while self.alive == True:  # do until stopkill is callled

                    self.connect_serial()  #serial connected
                    self.read_modbus_event()

                while self.alive == False:  # do until startup is callled
                    logger.debug("Modbus reader haltering")
                    time.sleep(2)
            except Exception as ex:
                logger.error("Unexpected run modbus error {}".format(ex))
                time.sleep(1)
예제 #8
0
    def read_modbus_event(self):
        """read operations of the timing_queue with the Modbus RTU Master and Execute them with """

        logger.debug("calling read_modbus_event")

        while self.serial_connected and self.alive and self.master_status < self.max_master_attemps - 1:

            request = self.timing_queue.get(
            )  #blocking call until new request is received
            if self.alive == False:  #discontinue if function is wished to be stopped
                self.timing_queue.task_done()
                break

            slave_id = request["slave_id"]
            startadress = request["startadress"]
            function_code = request["function_code"]
            display_name = request["display_name"]

            result = (99999, "modbus_request_not_possible")  #default
            try:
                if "quantity_of_x" in request:
                    result = self.master.execute(
                        slave=slave_id,
                        function_code=function_code,
                        starting_address=startadress,
                        quantity_of_x=request["quantity_of_x"])  #read
                elif "output_value" in request:
                    result = self.master.execute(
                        slave=slave_id,
                        function_code=function_code,
                        starting_address=startadress,
                        output_value=request["output_value"])  #write

                logger.debug(
                    "slave no {}, starting_adress {} with name {} and {} ".
                    format(slave_id, startadress, display_name, str(result)))

                self.master_status = 0  #successfull read, reset to 0

            except ModbusError as exc:
                self.master_status = self.master_status + 1  #unsuccessfull, increase the status
                logger.warning(
                    "ModbusError in read_modbus_event {}".format(ex))

            except Exception as ex:  #other error, like serial port etc.
                logger.warning(
                    "Unexpected error in read_modbus_event {}".format(ex))
                self.master_status = self.master_status + 5  #unsuccessfull, increase the status
                self.serial_connected = False

            payload = {
                "na": display_name,
                "res": result,
                "sl": slave_id,
                "time": time.time()
            }
            formatted_publish_message(topic=TOPIC_EVENT,
                                      payload=payload,
                                      c_queue=self.publishing_queue)

            self.timing_queue.task_done()

        logger.warning("Disconnected the Modbus, restarting")
        try:
            if self.serial_port != "undefined":  #if not just initialisized
                self.serial_port.close()
        except Exception as ex:
            logger.error("error closing serial port: {}".format(ex))
예제 #9
0
    def startup(self):
        """Start or Restart the Scheduling events"""

        #Step 1: Figure out what current setup_modbus.json is and its slaveconfig
        unused_port_config, slaveconfig = read_setup(
            self.publishing_queue)  #wait to get newest setup

        try:
            while not self.stop_event.is_set():

                #Step 2: For every operation in slaveconfig: find out details
                for slave_name in slaveconfig:  #looping through all exisiting slaves
                    operations = slaveconfig[slave_name]["operations"]
                    slave_id = slaveconfig[slave_name]["slave_id"]

                    for operation in operations:  #looping for all operations of a specific slave

                        #[Start get Information of Operation]
                        op = operations[operation]
                        interval = (op["sampling_interval"]
                                    )  #interval for Scheduling events

                        process_request = {
                            "slave_id": slave_id,
                            "startadress": op["startadress"],
                            "function_code": op["function_code"],
                            "display_name": op["display_name"],
                        }

                        if "quantity_of_x" in op:
                            process_request["quantity_of_x"] = op[
                                "quantity_of_x"]
                        elif "output_value" in op:
                            process_request["output_value"] = op[
                                "output_value"]
                        else:
                            logger.error(
                                "FATAL ERROR, no output value or quantity_of_x {}"
                                .format(process_request))
                        #[End get Information of Operation]

                        self.stop_event.wait(
                            self.timeout_between_functions
                        )  #timeout, so differet request will be executed at different times

                        if self.stop_event.is_set():
                            logger.warning(
                                "Error: unwanted break in scheduling new RepeatedFunctions"
                            )
                            return
                        #Step 3: For a specific operation schedule a specific RepeatedFunction every interval seconds
                        #[Start Schedule new RepeatedFunction]
                        rf = RepeatedFunction(interval, self.query_task,
                                              (self, process_request))
                        rf.setDaemon = True
                        rf.start()
                        self.running_rep_functions.append(rf)

                        logger.debug(
                            "scheduleded {} \t at interval {} ".format(
                                process_request,
                                (str(interval) +
                                 "s") if interval != 0 else "once occuring"))

                        #[End Schedule new RepeatedFunction]

                logger.debug("ending this scheduler thread {}".format(
                    self.running_rep_functions))
                return

        except Exception as e:  #should in no case occur
            logger.error(
                "Error scheduling new timing events, consider restarting device: {}"
                .format(e))
            formatted_publish_message(
                topic=TOPIC_STATE,
                payload=
                "Error scheduling new timing events, consider restarting device: {} "
                .format(e),
                c_queue=pub_queue)
    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))