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

from tango import DevState


class CommClient(Thread):
	"""
	The ProtocolHandler class is the generic interface class between the tango attribute_wrapper and the outside world
	"""

	def __init__(self, standby_func, fault_func, streams, try_interval=2):
		"""

		"""
		self.standby_func = standby_func
		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.standby_func()

		self.stopping = False
		while not self.stopping:
			# keep trying to connect
			if not self.connected:
				if self.connect():
					self.standby_func()
				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):
		pass

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