Skip to content
Snippets Groups Projects
comms_client.py 4.68 KiB
Newer Older
from threading import Thread
import time

class CommClient(Thread):
Thomas Juerges's avatar
Thomas Juerges committed
    """
    The ProtocolHandler class is the generic interface class between the tango attribute_wrapper and the outside world
    """

    def __init__(self, fault_func, streams, try_interval=2):
Thomas Juerges's avatar
Thomas Juerges committed
        """

        """
        self.fault_func = fault_func
        self.try_interval = try_interval
        self.streams = streams
        self.stopping = False
        self.connected = False

        super().__init__(daemon=True)

    def connect(self):
        """
        Function used to connect to the client.
        """
        self.connected = True
        return True

    def disconnect(self):
        """
        Function used to connect to the client.
        """
        self.connected = False

    def run(self):

        # Explicitly connect
        if not self.connect():
            # hardware or infra is down -- needs fixing first
            self.fault_func()
            return

        self.stopping = False
        while not self.stopping:
            # keep trying to connect
            if not self.connected:
                if self.connect():
Thomas Juerges's avatar
Thomas Juerges committed
                else:
                    # we retry only once, to catch exotic network issues. if the infra or hardware is down,
                    # our device cannot help, and must be reinitialised after the infra or hardware is fixed.
                    self.fault_func()
                    return

            # keep checking if the connection is still alive
            try:
                while not self.stopping:
                    self.ping()
                    time.sleep(self.try_interval)
            except Exception as e:
                self.streams.error_stream("Fault condition in communication detected.", e)

                # technically, we may not have dropped the connection, but encounter a different error. so explicitly disconnect.
                self.disconnect()

                # signal that we're disconnected
                self.fault_func()

    def ping(self):
Taya Snijder's avatar
Taya Snijder committed
        return
Thomas Juerges's avatar
Thomas Juerges committed

    def stop(self):
        """
          Stop connecting & disconnect. Can take a few seconds for the timeouts to hit.
        """

        if not self.ident:
            # have not yet been started, so nothing to do
            return

        self.stopping = True
        self.join()

        self.disconnect()

    def setup_attribute(self, annotation, attribute):
        """
        This function is responsible for providing the attribute_wrapper with a read/write function
        How this is done is implementation specific.
        The setup-attribute has access to the comms_annotation provided to the attribute wrapper to pass along to the comms client
        as well as a reference to the attribute itself.

        It should do this by first calling: _setup_annotation and setup_value_conversion to get all data necceacry to configure the read/write functions.
        It should then return the read and write functions to the attribute.

        MANDATORY:
        annotation_outputs = _setup_annotation(annotation)
        attribute_outputs = _setup_annotation(attribute)
        (note: outputs are up to the user)

        REQUIRED: provide read and write functions to return, there are no restrictions on how these should be provided,
        except that the read function takes a single input value and the write function returns a single value

        MANDATORY:
        return read_function, write_function

        Examples:
        - File system:  get_mapping returns functions that read/write a fixed
        number of bytes at a fixed location in a file. (SEEK)
        - OPC-UA:  traverse the OPC-UA tree until the node is found.
        Then return the read/write functions for that node which automatically
        convert values between Python and OPC-UA.
        """
        raise NotImplementedError("the setup_attribute must be implemented and provide return a valid read/write function for the attribute")

    def _setup_annotation(self, annotation):
        """
        This function is responsible for handling the annotation data provided by the attribute to configure the read/write function the client must provide.
        This function should be called by setup_attribute
        """
        raise NotImplementedError("the _setup_annotation must be implemented, content and outputs are up to the user")

    def setup_value_conversion(self, attribute):
        """
        this function is responsible for setting up the value conversion between the client and the attribute.
        This function should be called by setup_attribute
        """
        raise NotImplementedError("the setup_value_conversion must be implemented, content and outputs are up to the user")