diff --git a/devices/clients/ini_client.py b/devices/clients/ini_client.py new file mode 100644 index 0000000000000000000000000000000000000000..701bd7a9096fe59e5b02f4d4ec4f947789031c9f --- /dev/null +++ b/devices/clients/ini_client.py @@ -0,0 +1,161 @@ +from src.comms_client import CommClient +import configparser +import numpy + + +numpy_to_ini_dict = { + numpy.int64: int, + numpy.double: float, + numpy.bool_: bool, + str: str +} +ini_to_numpy_dict = { + int: numpy.int64, + float: numpy.double, + bool: numpy.bool_, + str: str +} + +import os + + +class ini_client(CommClient): + """ + this class provides an example implementation of a comms_client. + Durirng initialisation it creates a correctly shaped zero filled value. on read that value is returned and on write its modified. + """ + + def start(self): + super().start() + + def __init__(self, filename, fault_func, streams, try_interval=2): + """ + initialises the class and tries to connect to the client. + """ + self.config = configparser.ConfigParser() + self.filename = filename + + if not filename.endswith(".ini"): + filename = filename + ".ini" + + + super().__init__(fault_func, streams, try_interval) + + # Explicitly connect + if not self.connect(): + # hardware or infra is down -- needs fixing first + fault_func() + return + + def connect(self): + files_path = [os.path.abspath(x) for x in os.listdir()] + self.streams.debug_stream(" %s", files_path) + self.config_file = open(self.filename, "rw") + + self.connected = True # set connected to true + return True # if succesfull, return true. otherwise return false + + def disconnect(self): + self.connected = False # always force a reconnect, regardless of a successful disconnect + self.streams.debug_stream("disconnected from the 'client' ") + + def _setup_annotation(self, annotation): + """ + this function gives the client access to the comm client annotation data given to the attribute wrapper. + The annotation data can be used to provide whatever extra data is necessary in order to find/access the monitor/control point. + + the annotation can be in whatever format may be required. it is up to the user to handle its content + example annotation may include: + - a file path and file line/location + - COM object path + """ + + # as this is an example, just print the annotation + self.streams.debug_stream("annotation: {}".format(annotation)) + name = annotation.get('name') + if name is None: + AssertionError("ini client requires a variable name to set/get") + section = annotation.get('section') + if section is None: + AssertionError("requires a section to open") + + return section, name + + + def _setup_value_conversion(self, attribute): + """ + gives the client access to the attribute_wrapper object in order to access all + necessary data such as dimensionality and data type + """ + + if attribute.dim_y > 1: + dims = (attribute.dim_y, attribute.dim_x) + else: + dims = (attribute.dim_x,) + + dtype = attribute.numpy_type + + return dims, dtype + + def _setup_mapping(self, name, section, dtype): + """ + takes all gathered data to configure and return the correct read and write functions + """ + + def read_function(): + value = self.config.get(section, name) + value = ini_to_numpy_dict[dtype](value) + return value + + def write_function(write_value): + self.config.set(section, name, write_value) + fp = open(self.filename, 'w') + self.config.write(fp) + + return read_function, write_function + + def setup_attribute(self, annotation=None, attribute=None): + """ + MANDATORY function: is used by the attribute wrapper to get read/write functions. + must return the read and write functions + """ + + # process the comms_annotation + section, name = self._setup_annotation(annotation) + + # get all the necessary data to set up the read/write functions from the attribute_wrapper + dims, dtype = self._setup_value_conversion(attribute) + + # configure and return the read/write functions + read_function, write_function = self._setup_mapping(name, section, dtype) + + # return the read/write functions + return read_function, write_function + + +def write_config(): + config = configparser.ConfigParser() + config['scalar'] = {} + config['scalar']['double_scalar'] = '1.2' + config['scalar']['double_scalar'] = '3.4' + config['scalar']['bool_scalar'] = 'True' + config['scalar']['bool_scalar'] = 'False' + config['scalar']['int_scalar'] = '5' + config['scalar']['int_scalar'] = '6' + config['scalar']['str_scalar'] = 'this is' + config['scalar']['str_scalar'] = 'a test' + + config['spectrum'] = {} + config['spectrum']['double_scalar'] = '[1.2, 2.3, 3.4]' + config['spectrum']['double_scalar'] = '[5.6, 6.7, 7.8]' + config['spectrum']['bool_scalar'] = '[True, True, False]' + config['spectrum']['bool_scalar'] = '[False, False, True]' + config['spectrum']['int_scalar'] = '[5' + config['spectrum']['int_scalar'] = '[6,7,8,9]' + config['spectrum']['str_scalar'] = '["a", "b", "c"]' + config['spectrum']['str_scalar'] = '["D", "E", "F"]' + + + + with open('example.ini', 'w') as configfile: + config.write(configfile) diff --git a/devices/ini_device.py b/devices/ini_device.py new file mode 100644 index 0000000000000000000000000000000000000000..5b11d236017c19bea6bd951e694518a08ec1900c --- /dev/null +++ b/devices/ini_device.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +# +# This file wraps around a tango device class and provides a number of abstractions useful for hardware devices. It works together +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" + +""" + +# PyTango imports +from tango.server import run +from tango.server import device_property +from tango import AttrWriteType +from tango import DevState +# Additional import +from src.attribute_wrapper import attribute_wrapper +from src.hardware_device import hardware_device + + + +from clients.ini_client import * + + +__all__ = ["ini_device"] + + +class ini_device(hardware_device): + """ + This class is the minimal (read empty) implementation of a class using 'hardware_device' + """ + + # ---------- + # Attributes + # ---------- + """ + attribute wrapper objects can be declared here. All attribute wrapper objects will get automatically put in a list (attr_list) for easy access + + example = attribute_wrapper(comms_annotation="this is an example", datatype=numpy.double, dims=(8, 2), access=AttrWriteType.READ_WRITE) + ... + + """ + double_scalar_RW = attribute_wrapper(comms_annotation={"section": "scalar", "name": "double_scalar"}, datatype=numpy.double, access=AttrWriteType.READ_WRITE) + double_scalar_R = attribute_wrapper(comms_annotation={"section": "scalar", "name": "double_scalar"}, datatype=numpy.double) + bool_scalar_RW = attribute_wrapper(comms_annotation={"section": "scalar", "name": "bool_scalar"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + bool_scalar_R = attribute_wrapper(comms_annotation={"section": "scalar", "name": "bool_scalar"}, datatype=numpy.bool_) + int_scalar_RW = attribute_wrapper(comms_annotation={"section": "scalar", "name": "int_scalar"}, datatype=numpy.int64, access=AttrWriteType.READ_WRITE) + int_scalar_R = attribute_wrapper(comms_annotation={"section": "scalar", "name": "int_scalar"}, datatype=numpy.int64) + str_scalar_RW = attribute_wrapper(comms_annotation={"section": "scalar", "name": "str_scalar"}, datatype=numpy.str, access=AttrWriteType.READ_WRITE) + str_scalar_R = attribute_wrapper(comms_annotation={"section": "scalar", "name": "str_scalar"}, datatype=numpy.str) + + double_spectrum_RW = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "double_spectrum"}, datatype=numpy.double, dims=(4,), access=AttrWriteType.READ_WRITE) + double_spectrum_R = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "double_spectrum"}, datatype=numpy.double, dims=(4,)) + bool_spectrum_RW = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "bool_spectrum"}, datatype=numpy.bool_, dims=(4,), access=AttrWriteType.READ_WRITE) + bool_spectrum_R = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "bool_spectrum"}, datatype=numpy.bool_, dims=(4,)) + int_spectrum_RW = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "int_spectrum"}, datatype=numpy.int64, dims=(4,), access=AttrWriteType.READ_WRITE) + int_spectrum_R = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "int_spectrum"}, datatype=numpy.int64, dims=(4,)) + str_spectrum_RW = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "str_spectrum"}, datatype=numpy.str, dims=(4,), access=AttrWriteType.READ_WRITE) + str_spectrum_R = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "str_spectrum"}, datatype=numpy.str, dims=(4,)) + + double_image_RW = attribute_wrapper(comms_annotation={"section": "image", "name": "double_image"}, datatype=numpy.double, dims=(3, 2), access=AttrWriteType.READ_WRITE) + double_image_R = attribute_wrapper(comms_annotation={"section": "image", "name": "double_image"}, datatype=numpy.double, dims=(3, 2)) + bool_image_RW = attribute_wrapper(comms_annotation={"section": "image", "name": "bool_image"}, datatype=numpy.bool_, dims=(3, 2), access=AttrWriteType.READ_WRITE) + bool_image_R = attribute_wrapper(comms_annotation={"section": "image", "name": "bool_image"}, datatype=numpy.bool_, dims=(3, 2)) + int_image_RW = attribute_wrapper(comms_annotation={"section": "image", "name": "int_image"}, datatype=numpy.int64, dims=(3, 2), access=AttrWriteType.READ_WRITE) + int_image_R = attribute_wrapper(comms_annotation={"section": "image", "name": "int_image"}, datatype=numpy.int64, dims=(3, 2)) + str_image_RW = attribute_wrapper(comms_annotation={"section": "image", "name": "str_image"}, datatype=numpy.str, dims=(3, 2), access=AttrWriteType.READ_WRITE) + str_image_R = attribute_wrapper(comms_annotation={"section": "image", "name": "str_image"}, datatype=numpy.str, dims=(3, 2)) + + + def always_executed_hook(self): + """Method always executed before any TANGO command is executed.""" + pass + + def delete_device(self): + """Hook to delete resources allocated in init_device. + + This method allows for any memory or other resources allocated in the + init_device method to be released. This method is called by the device + destructor and by the device Init command (a Tango built-in). + """ + self.debug_stream("Shutting down...") + + self.Off() + self.debug_stream("Shut down. Good bye.") + + # -------- + # overloaded functions + # -------- + def initialise(self): + """ user code here. is called when the sate is set to INIT """ + """Initialises the attributes and properties of the PCC.""" + + self.set_state(DevState.INIT) + + # set up the OPC ua client + self.ini_client = ini_client("example/example.ini", self.Fault, self) + + # map an access helper class + for i in self.attr_list(): + i.set_comm_client(self.ini_client) + + self.ini_client.start() + + +# ---------- +# Run server +# ---------- +def main(args=None, **kwargs): + """Main function of the hardware device module.""" + return run((ini_device,), args=args, **kwargs) + + +if __name__ == '__main__': + main()