class TangoDevClassInfo(TangoInfo): def __init__(self, container, name=None, full_name=None): super(TangoDevClassInfo, self).__init__(container, name=name, full_name=full_name) self._devices = CaselessDict() def devices(self): return self._devices def addDevice(self, dev): self._devices[dev.name()] = dev def getDeviceNames(self): if not hasattr(self, "_device_name_list"): self._device_name_list = sorted(map(TangoDevInfo.name, self._devices.values())) return self._device_name_list
class BaseDoor(MacroServerDevice): """ Class encapsulating Door device functionality.""" On = PyTango.DevState.ON Running = PyTango.DevState.RUNNING Paused = PyTango.DevState.STANDBY Critical = 'Critical' Error = 'Error' Warning = 'Warning' Info = 'Info' Output = 'Output' Debug = 'Debug' Result = 'Result' RecordData = 'RecordData' BlockStart = '<BLOCK>' BlockFinish = '</BLOCK>' log_streams = (Error, Warning, Info, Output, Debug, Result) # maximum execution time without user interruption InteractiveTimeout = 0.1 def __init__(self, name, **kw): self._log_attr = CaselessDict() self._block_lines = 0 self._in_block = False self._macro_server = None self._running_macros = None self._running_macro = None self._last_running_macro = None self._user_xml = None self._ignore_logs = kw.get("ignore_logs", False) self._silent = kw.get("silent", True) self._debug = kw.get("debug", False) self._output_stream = kw.get("output", sys.stdout) self._writeLock = threading.Lock() self._input_handler = self.create_input_handler() self.call__init__(MacroServerDevice, name, **kw) self._old_door_state = PyTango.DevState.UNKNOWN try: self._old_sw_door_state = TaurusSWDevState.Uninitialized except RuntimeError: #TODO: For Taurus 4 compatibility from taurus.core import TaurusDevState self._old_sw_door_state = TaurusDevState.Undefined self.getStateObj().addListener(self.stateChanged) for log_name in self.log_streams: tg_attr = self.getAttribute(log_name) attr = LogAttr(self, log_name, None, tg_attr) if log_name == 'Result': attr.subscribeEvent(self.resultReceived, log_name) else: attr.subscribeEvent(self.logReceived, log_name) self._log_attr[log_name] = attr input_attr = self.getAttribute("Input") input_attr.addListener(self.inputReceived) record_data_attr = self.getAttribute('RecordData') record_data_attr.addListener(self.recordDataReceived) macro_status_attr = self.getAttribute('MacroStatus') macro_status_attr.addListener(self.macroStatusReceived) self._experiment_configuration = ExperimentConfiguration(self) def create_input_handler(self): return BaseInputHandler() def get_input_handler(self): return self._input_handler def get_color_mode(self): return "NoColor" #def macrosChanged(self, s, v, t): # pass @property def log_start(self): if not hasattr(self, "_log_start"): import taurus.core.util.console if self.get_color_mode() == "NoColor": kls = taurus.core.util.console.NoColors else: kls = taurus.core.util.console.TermColors self._log_start = { BaseDoor.Critical : kls.LightRed, BaseDoor.Error : kls.Red, BaseDoor.Info : kls.LightBlue, BaseDoor.Warning : kls.Brown, BaseDoor.Output : kls.Normal, BaseDoor.Debug : kls.DarkGray, BaseDoor.Result : kls.LightGreen } return self._log_start @property def log_stop(self): if not hasattr(self, "_log_stop"): import taurus.core.util.console if self.get_color_mode() == "NoColor": kls = taurus.core.util.console.NoColors else: kls = taurus.core.util.console.TermColors self._log_stop = { BaseDoor.Critical : kls.Normal, BaseDoor.Error : kls.Normal, BaseDoor.Info : kls.Normal, BaseDoor.Warning : kls.Normal, BaseDoor.Output : kls.Normal, BaseDoor.Debug : kls.Normal, BaseDoor.Result : kls.Normal } return self._log_stop def getStateAttr(self): return self._state_attr @property def macro_server(self): if self._macro_server is None: self._macro_server = self._get_macroserver_for_door() return self._macro_server def _get_macroserver_for_door(self): """Returns the MacroServer device object in the same DeviceServer as this door""" db = self.factory().getDatabase() door_name = self.dev_name() server_list = list(db.get_server_list('MacroServer/*')) server_list += list(db.get_server_list('Sardana/*')) server_devs = None for server in server_list: server_devs = db.get_device_class_list(server) devs, klasses = server_devs[0::2], server_devs[1::2] for dev in devs: if dev.lower() == door_name: for i, klass in enumerate(klasses): if klass == 'MacroServer': return self.factory().getDevice(devs[i]) else: return None def setDebugMode(self, state): self._debug = state def getDebugMode(self): return self._debug def setSilent(self, yesno): self._silent = yesno def isSilent(self): return self._silent def getLogObj(self, log_name='Debug'): return self._log_attr.get(log_name, None) def getRunningXML(self): return self._user_xml def getRunningMacro(self): return self._running_macro def getLastRunningMacro(self): return self._last_running_macro def abort(self, synch=True): if not synch: self.command_inout("AbortMacro") return evt_wait = AttributeEventWait(self.getAttribute("state")) evt_wait.lock() try: time_stamp = time.time() self.command_inout("AbortMacro") evt_wait.waitEvent(self.Running, equal=False, after=time_stamp, timeout=self.InteractiveTimeout) finally: evt_wait.unlock() evt_wait.disconnect() def stop(self, synch=True): if not synch: self.command_inout("StopMacro") return evt_wait = AttributeEventWait(self.getAttribute("state")) evt_wait.lock() try: time_stamp = time.time() self.command_inout("StopMacro") evt_wait.waitEvent(self.Running, equal=False, after=time_stamp, timeout=self.InteractiveTimeout) finally: evt_wait.unlock() evt_wait.disconnect() def _clearRunMacro(self): # Clear the log buffer map(LogAttr.clearLogBuffer, self._log_attr.values()) self._running_macros = None self._running_macro = None self._user_xml = None self._block_lines = 0 def _createMacroXml(self, macro_name, macro_params): """The best effort creation of the macro XML object. It tries to convert flat list of string parameter values to the correct macro XML object. The cases that can not be converted are: * repeat parameter containing repeat parameters * two repeat parameters * repeat parameter that is not the last parameter :param macro_name: (str) macro name :param macro_params: (sequence[str]) list of parameter values :return (lxml.etree._Element) macro XML element """ macro_info = self.macro_server.getMacroInfoObj(macro_name) params_def = macro_info.parameters macro_node = createMacroNode(macro_name, params_def, macro_params) return macro_node.toXml() def preRunMacro(self, obj, parameters): self._clearRunMacro() xml_root = None if isinstance(obj , (str, unicode)): if obj.startswith('<') and not parameters: xml_root = etree.fromstring(obj) else: macros = [] if len(parameters) == 0: macros_strs = obj.split('\n') for m in macros_strs: pars = m.split() macros.append((pars[0], pars[1:])) else: parameters = map(str, parameters) macros.append((obj, parameters)) xml_root = xml_seq = etree.Element('sequence') for m in macros: macro_name = m[0] macro_params = m[1] xml_macro = self._createMacroXml(macro_name, macro_params) xml_macro.set('id', str(uuid.uuid1())) xml_seq.append(xml_macro) elif etree.iselement(obj): xml_root = obj else: raise TypeError('obj must be a string or a etree.Element') self._running_macros = {} for macro_xml in xml_root.xpath('//macro'): id, name = macro_xml.get('id'), macro_xml.get('name') self._running_macros[id] = Macro(self, name, id, macro_xml) return xml_root def postRunMacro(self, result, synch): pass def runMacro(self, obj, parameters=[], synch=False): self._user_xml = self.preRunMacro(obj, parameters) result = self._runMacro(self._user_xml, synch=synch) return self.postRunMacro(result, synch) def _runMacro(self, xml, synch=False): if not synch: return self.command_inout("RunMacro", [etree.tostring(xml)]) timeout = self.InteractiveTimeout evt_wait = self._getEventWait() evt_wait.connect(self.getAttribute("state")) evt_wait.lock() try: evt_wait.waitEvent(self.Running, equal=False, timeout=timeout) ts = time.time() result = self.command_inout("RunMacro", [etree.tostring(xml)]) evt_wait.waitEvent(self.Running, after=ts, timeout=timeout) if synch: evt_wait.waitEvent(self.Running, equal=False, after=ts, timeout=timeout) finally: self._clearRunMacro() evt_wait.unlock() evt_wait.disconnect() return result def stateChanged(self, s, t, v): self._old_door_state = self.getState() try: self._old_sw_door_state = self.getSWState() except: # TODO: For Taurus 4 compatibility self._old_sw_door_state = self.state def resultReceived(self, log_name, result): """Method invoked by the arrival of a change event on the Result attribute""" if self._ignore_logs or self._running_macro is None: return self._running_macro.setResult(result) return result def putEnvironment(self, name, value): self.macro_server.putEnvironment(name, value) def putEnvironments(self, obj): self.macro_server.putEnvironments(obj) setEnvironment = putEnvironment setEnvironments = putEnvironments def getEnvironment(self, name=None): return self.macro_server.getEnvironment(name=name) def inputReceived(self, s, t, v): if t not in CHANGE_EVT_TYPES: return if v is None or self._running_macros is None: return input_data = CodecFactory().decode(('json', v.value)) self.processInput(input_data) def processInput(self, input_data): TaurusManager().addJob(self._processInput, None, input_data) def _processInput(self, input_data): input_type = input_data['type'] if input_type == 'input': result = self._input_handler.input(input_data) if result['input'] is '' and 'default_value' in input_data: result['input'] = input_data['default_value'] result = CodecFactory().encode('json', ('', result))[1] self.write_attribute('Input', result) elif input_type == 'timeout': self._input_handler.input_timeout(input_data) def recordDataReceived(self, s, t, v): if t not in CHANGE_EVT_TYPES: return return self._processRecordData(v) def _processRecordData(self, data): if data is None or data.value is None: return # make sure we get it as string since PyTango 7.1.4 returns a buffer # object and json.loads doesn't support buffer objects (only str) data = map(str, data.value) size = len(data[1]) if size == 0: return format = data[0] codec = CodecFactory().getCodec(format) data = codec.decode(data) return data def processRecordData(self, data): pass def macroStatusReceived(self, s, t, v): if v is None or self._running_macros is None: return if t not in CHANGE_EVT_TYPES: return # make sure we get it as string since PyTango 7.1.4 returns a buffer # object and json.loads doesn't support buffer objects (only str) v = map(str, v.value) if not len(v[1]): return format = v[0] codec = CodecFactory().getCodec(format) # make sure we get it as string since PyTango 7.1.4 returns a buffer # object and json.loads doesn't support buffer objects (only str) v[1] = str(v[1]) fmt, data = codec.decode(v) for macro_status in data: id = macro_status.get('id') macro = self._running_macros.get(id) self._last_running_macro = self._running_macro = macro # if we don't have the ID it's because the macro is running a submacro # or another client is connected to the same door (shame on him!) and # executing a macro we discard this event if macro is not None: macro.__dict__.update(macro_status) return data def logReceived(self, log_name, output): if not output or self._silent or self._ignore_logs: return if log_name == self.Debug and not self._debug: return o = self.log_start[log_name] for line in output: if not self._debug: if line == self.BlockStart: self._in_block = True for i in xrange(self._block_lines): o += '\x1b[2K\x1b[1A\x1b[2K' #erase current line, up one line, erase current line self._block_lines = 0 continue elif line == self.BlockFinish: self._in_block = False continue else: if self._in_block: self._block_lines += 1 else: self._block_lines = 0 o += "%s\n" % line o += self.log_stop[log_name] self.write(o) def write(self, msg, stream=None): if self.isSilent(): return msg = msg.encode('utf-8') self._output_stream = sys.stdout out = self._output_stream if not stream is None: start, stop = self.log_start.get(stream), self.log_stop.get(stream) if start is not None and stop is not None: out.write(start) out.write(msg) out.write(stop) out.flush() return out.write(msg) out.flush() def writeln(self, msg='', stream=None): self.write("%s\n" % msg, stream=stream) def getExperimentConfigurationObj(self): return self._experiment_configuration def getExperimentConfiguration(self): return self._experiment_configuration.get() def setExperimentConfiguration(self, config, mnt_grps=None): self._experiment_configuration.set(config, mnt_grps=mnt_grps)
class MacroServer(MSContainer, MSObject, SardanaElementManager, SardanaIDManager): All = "All" MaxParalellMacros = 5 logReportParams = dict(when='midnight', interval=1, backupCount=365) logReportKlass = NonOverlappingTimedRotatingFileHandler def __init__(self, full_name, name=None, macro_path=None, environment_db=None, recorder_path=None): # dict<str, Pool> # key - device name (case insensitive) # value - Pool object representing the device name self._pools = CaselessDict() self._max_parallel_macros = self.MaxParalellMacros self._path_id = None MSContainer.__init__(self) MSObject.__init__(self, full_name=full_name, name=name, id=InvalidId, macro_server=self, elem_type=ElementType.MacroServer) registerExtensions() self._type_manager = TypeManager(self) self._environment_manager = EnvironmentManager(self, environment_db=environment_db) self._macro_manager = MacroManager(self, macro_path=macro_path) self._recorder_manager = RecorderManager(self, recorder_path=recorder_path) def serialize(self, *args, **kwargs): kwargs = MSObject.serialize(self, *args, **kwargs) kwargs['type'] = self.__class__.__name__ kwargs['id'] = InvalidId kwargs['parent'] = None return kwargs def add_job(self, job, callback=None, *args, **kw): th_pool = get_thread_pool() th_pool.add(job, callback, *args, **kw) # -------------------------------------------------------------------------- # Environment DB related methods # -------------------------------------------------------------------------- def set_environment_db(self, environment_db): """Sets the environment database. :param env_db: environment database name :type env_db: :obj:`str` """ self.environment_manager.setEnvironmentDb(environment_db) # -------------------------------------------------------------------------- # Python related methods # -------------------------------------------------------------------------- def set_python_path(self, path): mod_man = ModuleManager() if self._path_id is not None: mod_man.remove_python_path(self._path_id) self._path_id = mod_man.add_python_path(path) # -------------------------------------------------------------------------- # Macro path related methods # -------------------------------------------------------------------------- def set_macro_path(self, macro_path): """Sets the macro path. :param macro_path: macro path :type macro_path: seq<str> """ self.macro_manager.setMacroPath([p.rstrip(os.sep) for p in macro_path]) # -------------------------------------------------------------------------- # Recorder path related methods # -------------------------------------------------------------------------- def set_recorder_path(self, recorder_path): """Sets the recorder path. :param recorder_path: recorder path :type recorder_path: seq<str> """ self.recorder_manager.setRecorderPath(recorder_path) # -------------------------------------------------------------------------- # Report related methods # -------------------------------------------------------------------------- def set_log_report(self, filename=None, format=None): log = self.get_report_logger() # first check that the handler has not been initialized yet. If it has # we remove previous handlers. We only allow one timed rotating file # handler at a time to_remove = [] for handler in log.handlers: if isinstance(handler, logging.handlers.TimedRotatingFileHandler): to_remove.append(handler) for handler in to_remove: handler.close() log.removeHandler(handler) if filename is None: return if format is None: format = Logger.DftLogMessageFormat formatter = logging.Formatter(format) self.info("Reports are being stored in %s", filename) klass = self.logReportKlass handler = klass(filename, **self.logReportParams) handler.setFormatter(formatter) log.addHandler(handler) def clear_log_report(self): self.set_log_report() def get_report_logger(self): return logging.getLogger("Sardana.Report") report_logger = property(get_report_logger) def report(self, msg, *args, **kwargs): """ Record a log message in the sardana report (if enabled) with default level **INFO**. The msg is the message format string, and the args are the arguments which are merged into msg using the string formatting operator. (Note that this means that you can use keywords in the format string, together with a single dictionary argument.) *kwargs* are the same as :meth:`logging.Logger.debug` plus an optional level kwargs which has default value **INFO** Example:: self.report("this is an official report!") :param msg: the message to be recorded :type msg: :obj:`str` :param args: list of arguments :param kwargs: list of keyword arguments""" level = kwargs.pop('level', logging.INFO) return self.report_logger.log(level, msg, *args, **kwargs) # -------------------------------------------------------------------------- # Pool related methods # -------------------------------------------------------------------------- def set_pool_names(self, pool_names): """Registers a new list of device pools in this manager :param pool_names: sequence of pool names :type pool_names: seq<str>""" for pool in self._pools.values(): elements_attr = pool.getAttribute("Elements") elements_attr.removeListener(self.on_pool_elements_changed) for name in pool_names: self.debug("Creating pool %s", name) pool = Device(name) if pool is None: self.error('Could not create Pool object for %s' % name) continue self._pools[name] = pool elements_attr = pool.getAttribute("Elements") elements_attr.addListener(self.on_pool_elements_changed) def get_pool_names(self): """Returns the list of names of the pools this macro server is connected to. :return: the list of names of the pools this macro server is connected to :rtype: seq<str>""" return self._pools.keys() def get_pool(self, pool_name): """Returns the device pool object corresponding to the given device name or None if no match is found. :param pool_name: device pool name :type pool_name: :obj:`str` :return: Pool object or None if no match is found""" return self._pools.get(pool_name) def get_pools(self): """Returns the list of pools this macro server is connected to. :return: the list of pools this macro server is connected to :rtype: seq<Pool>""" return self._pools.values() def on_pool_elements_changed(self, evt_src, evt_type, evt_value): if evt_type not in CHANGE_EVT_TYPES: return self.fire_event(EventType("PoolElementsChanged"), evt_value) # -------------------------------------------------------------------------- # Door related methods # -------------------------------------------------------------------------- def create_element(self, **kwargs): type = kwargs['type'] elem_type = ElementType[type] name = kwargs['name'] kwargs['macro_server'] = self td = TYPE_MAP_OBJ[elem_type] klass = td.klass auto_full_name = td.auto_full_name full_name = kwargs.get("full_name", auto_full_name.format(**kwargs)) self.check_element(name, full_name) id = kwargs.get('id') if id is None: kwargs['id'] = id = self.get_new_id() else: self.reserve_id(id) elem = klass(**kwargs) ret = self.add_element(elem) self.fire_event(EventType("ElementCreated"), elem) return ret def create_door(self, **kwargs): return self.create_element(type="Door", **kwargs) # -------------------------------------------------------------------------- # General access to elements # -------------------------------------------------------------------------- def get_elements_info(self): return self.get_remote_elements_info() + self.get_local_elements_info() def get_remote_elements_info(self): return [elem.serialize() for pool in self.get_pools() for elem in pool.getElements()] def get_local_elements_info(self): # fill macro library info ret = [macrolib.serialize() for macrolib in self.get_macro_libs().values()] # fill macro info ret += [macro.serialize() for macro in self.get_macros().values()] # fill parameter type info ret += [paramtype.serialize() for paramtype in self.get_data_types().values()] return ret # -------------------------------------------------------------------------- # macro execution # -------------------------------------------------------------------------- def set_max_parallel_macros(self, nb): assert nb > 0, "max parallel macros number must be > 0" th_pool = get_thread_pool() if th_pool.size + 5 < nb: th_pool.size = nb self._max_parallel_macros = nb def get_max_parallel_macros(self): return self._max_parallel_macros max_parallel_macros = property(get_max_parallel_macros, set_max_parallel_macros, doc="maximum number of macros which can " "execute at the same time") @property def macro_manager(self): return self._macro_manager @property def recorder_manager(self): return self._recorder_manager @property def environment_manager(self): return self._environment_manager @property def type_manager(self): return self._type_manager # -------------------------------------------------------------------------- # (Re)load code # -------------------------------------------------------------------------- def reload_lib(self, lib_name): return self.macro_manager.reloadLib(lib_name) def reload_macro_lib(self, lib_name): manager = self.macro_manager try: old_lib = manager.getMacroLib(lib_name) except UnknownMacroLibrary: old_lib = None new_elements, changed_elements, deleted_elements = [], [], [] new_lib = manager.reloadMacroLib(lib_name) if new_lib.has_errors(): return new_lib if old_lib is None: new_elements.extend(new_lib.get_macros()) new_elements.append(new_lib) else: changed_elements.append(new_lib) new_names = set([macro.name for macro in new_lib.get_macros()]) old_names = set([macro.name for macro in old_lib.get_macros()]) changed_names = set.intersection(new_names, old_names) deleted_names = old_names.difference(new_names) new_names = new_names.difference(old_names) for new_name in new_names: new_elements.append(new_lib.get_macro(new_name)) for changed_name in changed_names: changed_elements.append(new_lib.get_macro(changed_name)) for deleted_name in deleted_names: deleted_elements.append(old_lib.get_macro(deleted_name)) evt = {"new": new_elements, "change": changed_elements, "del": deleted_elements} self.fire_event(EventType("ElementsChanged"), evt) return new_lib reload_macro_lib.__doc__ = MacroManager.reloadMacroLib.__doc__ def reload_macro_libs(self, lib_names): for lib_name in lib_names: self.reload_macro_lib(lib_name) def reload_macro(self, macro_name): macro_info = self.macro_manager.getMacro(macro_name) lib_name = macro_info.module_name return self.reload_macro_lib(lib_name) def reload_macros(self, macro_names): lib_names = set() for macro_name in macro_names: macro_info = self.macro_manager.getMacro(macro_name) lib_names.add(macro_info.module_name) self.reload_macro_libs(lib_names) def get_macro_lib(self, lib_name): return self.macro_manager.getMacroLib(lib_name) get_macro_lib.__doc__ = MacroManager.getMacroLib.__doc__ def get_macro_libs(self, filter=None): return self.macro_manager.getMacroLibs(filter=filter) get_macro_libs.__doc__ = MacroManager.getMacroLibs.__doc__ def get_macro_lib_names(self): return self.macro_manager.getMacroLibNames() get_macro_lib_names.__doc__ = MacroManager.getMacroLibNames.__doc__ def get_macro(self, name): return self.macro_manager.getMacro(name) get_macro.__doc__ = MacroManager.getMacro.__doc__ def get_macros(self, filter=None): return self.macro_manager.getMacros(filter=filter) get_macros.__doc__ = MacroManager.getMacros.__doc__ def get_macro_names(self): return self.macro_manager.getMacroNames() get_macro_names.__doc__ = MacroManager.getMacroNames.__doc__ def get_macro_classes(self): return self.macro_manager.getMacroClasses() get_macro_classes.__doc__ = MacroManager.getMacroClasses.__doc__ def get_macro_functions(self): return self.macro_manager.getMacroFunctions() get_macro_functions.__doc__ = MacroManager.getMacroFunctions.__doc__ def get_macro_libs_summary_info(self): libs = self.get_macro_libs() ret = [] for module_name, macro_lib_info in libs.items(): elem = "%s (%s)" % (macro_lib_info.name, macro_lib_info.file_path) ret.append(elem) return ret def get_macro_classes_summary_info(self): macros = self.get_macros() ret = [] for macro_info in macros: elem = "%s (%s)" % (macro_info.name, macro_info.file_path) ret.append(elem) return ret def get_or_create_macro_lib(self, lib_name, macro_name=None): """Gets the exiting macro lib or creates a new macro lib file. If name is not None, a macro template code for the given macro name is appended to the end of the file. :param lib_name: module name, python file name, or full file name (with path) :type lib_name: :obj:`str` :param macro_name: an optional macro name. If given a macro template code is appended to the end of the file (default is None meaning no macro code is added) :type macro_name: :obj:`str` :return: a sequence with three items: full_filename, code, line number is 0 if no macro is created or n representing the first line of code for the given macro name. :rtype: seq<str, str, int>""" return self.macro_manager.getOrCreateMacroLib(lib_name, macro_name=macro_name) get_or_create_macro_lib.__doc__ = MacroManager.getOrCreateMacroLib.__doc__ def set_macro_lib(self, lib_name, code, auto_reload=True): module_name = self.macro_manager.setMacroLib(lib_name, code, auto_reload=False) if auto_reload: self.reload_macro_lib(module_name) set_macro_lib.__doc__ = MacroManager.setMacroLib.__doc__ # -------------------------------------------------------------------------- # Data types # -------------------------------------------------------------------------- def get_data_types(self): return self.type_manager.getTypes() get_data_types.__doc__ = TypeManager.getTypes.__doc__ def get_data_type(self, type_name): return self.type_manager.getTypeObj(type_name) get_data_type.__doc__ = TypeManager.getTypeObj.__doc__ def get_data_type_names(self): return self.type_manager.getTypeNames() get_data_type_names.__doc__ = TypeManager.getTypeNames.__doc__ def get_data_type_names_with_asterisc(self): return self.type_manager.getTypeListStr() get_data_type_names_with_asterisc.__doc__ = TypeManager.getTypeListStr.__doc__ # -------------------------------------------------------------------------- # Doors # -------------------------------------------------------------------------- def get_doors(self): return self.get_elements_by_type(ElementType.Door) def get_door_names(self): return [door.full_name for door in self.get_doors()] #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- # Environment access methods #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- def get_env(self, key=None, door_name=None, macro_name=None): """Gets the environment matching the given parameters: - door_name and macro_name define the context where to look for the environment. If both are None, the global environment is used. If door name is None but macro name not, the given macro environment is used and so on... - If key is None it returns the complete environment, otherwise key must be a string containing the environment variable name. :param key: environment variable name [default: None, meaning all environment] :type key: :obj:`str` :param door_name: local context for a given door [default: None, meaning no door context is used] :type door_name: :obj:`str` :param macro_name: local context for a given macro [default: None, meaning no macro context is used] :type macro_name: :obj:`str` :return: a :obj:`dict` containing the environment :rtype: :obj:`dict` :raises: UnknownEnv""" return self.environment_manager.getEnv(key=key, macro_name=macro_name, door_name=door_name) def set_env(self, key, value): """Sets the environment key to the new value and stores it persistently. :param key: the key for the environment :param value: the value for the environment :return: a tuple with the key and value objects stored""" env_man = self.environment_manager if env_man.hasEnv(key): evt_type = "change" else: evt_type = "new" k, v = self.environment_manager.setEnv(key, value) evt = {evt_type: {k: v}} self.fire_event(EventType("EnvironmentChanged"), evt) return k, v def set_env_obj(self, data): """Sets the environment key to the new value and stores it persistently. :param key: the key for the environment :param value: the value for the environment :return: a tuple with the key and value objects stored""" env_man = self.environment_manager new, change = {}, {} for key, value in data.items(): d = new if env_man.hasEnv(key): d = change d[key] = value ret = env_man.setEnvObj(data) evt = dict(new=new, change=change) self.fire_event(EventType("EnvironmentChanged"), evt) return ret def change_env(self, data): env_man = self.environment_manager new_change_env = data.get('new', {}) new_change_env.update(data.get('change', {})) del_env = data.get('del', []) new, change = {}, {} for key, value in new_change_env.items(): d = new if env_man.hasEnv(key): d = change d[key] = value del_keys = env_man.unsetEnv(del_env) env_man.setEnvObj(new_change_env) evt = dict(new=new, change=change) evt['del'] = del_keys self.fire_event(EventType("EnvironmentChanged"), evt) def unset_env(self, key): """Unsets the environment for the given key. :param key: the key for the environment to be unset""" ret = self.environment_manager.unsetEnv(key) # list is unhashable - convert to a tuple if isinstance(key, list): key = tuple(key) evt = {'del': {key: None}} self.fire_event(EventType("EnvironmentChanged"), evt) return ret def has_env(self, key, macro_name=None, door_name=None): return self.environment_manager.hasEnv(key, macro_name=macro_name, door_name=door_name) #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- # General object access methods #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- def get_object(self, name, type_class=All, subtype=All, pool=All): objs = self.find_objects(name, type_class, subtype, pool) if len(objs) == 0: return None if len(objs) > 1: raise AttributeError( 'More than one object named "%s" found' % name) return objs[0] def get_objects(self, names, type_class=All, subtype=All, pool=All): return self.find_objects(names, type_class=type_class, subtype=subtype, pool=pool) def find_objects(self, param, type_class=All, subtype=All, pool=All): if is_pure_str(param): param = param, if type_class == MacroServer.All: type_name_list = self.get_data_type_names() else: if is_pure_str(type_class): type_name_list = type_class, else: type_name_list = type_class obj_set = set() param = ['^%s$' % x for x in param] re_objs = map(re.compile, param, len(param) * (re.IGNORECASE,)) re_subtype = re.compile(subtype, re.IGNORECASE) for type_name in type_name_list: type_class_name = type_name if type_class_name.endswith('*'): type_class_name = type_class_name[:-1] type_inst = self.get_data_type(type_class_name) if not type_inst.hasCapability(ParamType.ItemList): continue if self.is_macroserver_interface(type_class_name): for name, obj in type_inst.getObjDict(pool=pool).items(): for re_obj in re_objs: if re_obj.match(name) is not None: obj_type = ElementType[obj.get_type()] if subtype is MacroServer.All or re_subtype.match(obj_type): obj_set.add(obj) else: for name, obj in type_inst.getObjDict(pool=pool).items(): for re_obj in re_objs: if re_obj.match(name) is not None: obj_type = obj.getType() if (subtype is MacroServer.All or re_subtype.match(obj.getType())) and \ obj_type != "MotorGroup": obj_set.add(obj) return list(obj_set) def get_motion(self, elems, motion_source=None, read_only=False, cache=True, decoupled=False): if motion_source is None: motion_source = self.get_pools() motion_klass = Motion if decoupled: # and len(elems)>1: motion_klass = MotionGroup return motion_klass(elems, motion_source) _LOCAL_INTERFACES = { Interface.MacroLibrary: get_macro_libs, Interface.MacroCode: get_macros, Interface.MacroClass: get_macro_classes, Interface.MacroFunction: get_macro_functions, } def is_macroserver_interface(self, interface): if is_pure_str(interface): interface = Interface[interface] return interface in self._LOCAL_INTERFACES def get_elements_with_interface(self, interface): ret = CaselessDict() if is_pure_str(interface): interface_str = interface interface = Interface[interface_str] else: interface_str = Interface[interface] if self.is_macroserver_interface(interface): ret.update(self._LOCAL_INTERFACES.get(interface)(self)) else: for pool in self.get_pools(): ret.update(pool.getElementsWithInterface(interface_str)) return ret def get_element_with_interface(self, name, interface): for pool in self.get_pools(): element = pool.getElementWithInterface(name, interface) if element is not None: return element def get_controllers(self): return self.get_elements_with_interface("Controller") def get_moveables(self): return self.get_elements_with_interface("Moveable") def get_motors(self): return self.get_elements_with_interface("Motor") def get_pseudo_motors(self): return self.get_elements_with_interface("PseudoMotor") def get_io_registers(self): return self.get_elements_with_interface("IORegister") def get_measurement_groups(self): return self.get_elements_with_interface("MeasurementGroup") def get_exp_channels(self): return self.get_elements_with_interface("ExpChannel") def get_counter_timers(self): return self.get_elements_with_interface("CTExpChannel") def get_0d_exp_channels(self): return self.get_elements_with_interface("ZeroDExpChannel") def get_1d_exp_channels(self): return self.get_elements_with_interface("OneDExpChannel") def get_2d_exp_channels(self): return self.get_elements_with_interface("TwoDExpChannel") def get_pseudo_counters(self): return self.get_elements_with_interface("PseudoCounter") def get_instruments(self): return self.get_elements_with_interface("Instrument") def get_controller(self, name): return self.get_element_with_interface(name, "Controller") def get_moveable(self, name): return self.get_element_with_interface(name, "Moveable") def get_motor(self, name): return self.get_element_with_interface(name, "Motor") def get_pseudo_motor(self, name): return self.get_element_with_interface(name, "PseudoMotor") def get_io_register(self, name): return self.get_element_with_interface(name, "IORegister") def get_measurement_group(self, name): return self.get_element_with_interface(name, "MeasurementGroup") def get_exp_channel(self, name): return self.get_element_with_interface(name, "ExpChannel") def get_counter_timer(self, name): return self.get_element_with_interface(name, "CTExpChannel") def get_0d_exp_channel(self, name): return self.get_element_with_interface(name, "ZeroDExpChannel") def get_1d_exp_channel(self, name): return self.get_element_with_interface(name, "OneDExpChannel") def get_2d_exp_channel(self, name): return self.get_element_with_interface(name, "TwoDExpChannel") def get_pseudo_counter(self, name): return self.get_element_with_interface(name, "PseudoCounter") def get_instrument(self, name): return self.get_element_with_interface(name, "Instrument")
class BaseDoor(MacroServerDevice): """ Class encapsulating Door device functionality.""" On = PyTango.DevState.ON Running = PyTango.DevState.RUNNING Paused = PyTango.DevState.STANDBY Critical = 'Critical' Error = 'Error' Warning = 'Warning' Info = 'Info' Output = 'Output' Debug = 'Debug' Result = 'Result' RecordData = 'RecordData' BlockStart = '<BLOCK>' BlockFinish = '</BLOCK>' log_streams = (Error, Warning, Info, Output, Debug, Result) # maximum execution time without user interruption InteractiveTimeout = 0.1 def __init__(self, name, **kw): self._log_attr = CaselessDict() self._block_lines = 0 self._in_block = False self._macro_server = None self._running_macros = None self._running_macro = None self._last_running_macro = None self._user_xml = None self._ignore_logs = kw.get("ignore_logs", False) self._silent = kw.get("silent", True) self._debug = kw.get("debug", False) self._output_stream = kw.get("output", sys.stdout) self._writeLock = threading.Lock() self._input_handler = self.create_input_handler() self.call__init__(MacroServerDevice, name, **kw) self._old_door_state = PyTango.DevState.UNKNOWN try: self._old_sw_door_state = TaurusSWDevState.Uninitialized except RuntimeError: # TODO: For Taurus 4 compatibility from taurus.core import TaurusDevState self._old_sw_door_state = TaurusDevState.Undefined self.getStateObj().addListener(self.stateChanged) for log_name in self.log_streams: tg_attr = self.getAttribute(log_name) attr = LogAttr(self, log_name, None, tg_attr) if log_name == 'Result': attr.subscribeEvent(self.resultReceived, log_name) else: attr.subscribeEvent(self.logReceived, log_name) self._log_attr[log_name] = attr input_attr = self.getAttribute("Input") input_attr.addListener(self.inputReceived) record_data_attr = self.getAttribute('RecordData') record_data_attr.addListener(self.recordDataReceived) macro_status_attr = self.getAttribute('MacroStatus') macro_status_attr.addListener(self.macroStatusReceived) self._experiment_configuration = ExperimentConfiguration(self) def create_input_handler(self): return BaseInputHandler() def get_input_handler(self): return self._input_handler def get_color_mode(self): return "NoColor" # def macrosChanged(self, s, v, t): # pass @property def log_start(self): if not hasattr(self, "_log_start"): import taurus.core.util.console if self.get_color_mode() == "NoColor": kls = taurus.core.util.console.NoColors else: kls = taurus.core.util.console.TermColors self._log_start = { BaseDoor.Critical: kls.LightRed, BaseDoor.Error: kls.Red, BaseDoor.Info: kls.LightBlue, BaseDoor.Warning: kls.Brown, BaseDoor.Output: kls.Normal, BaseDoor.Debug: kls.DarkGray, BaseDoor.Result: kls.LightGreen } return self._log_start @property def log_stop(self): if not hasattr(self, "_log_stop"): import taurus.core.util.console if self.get_color_mode() == "NoColor": kls = taurus.core.util.console.NoColors else: kls = taurus.core.util.console.TermColors self._log_stop = { BaseDoor.Critical: kls.Normal, BaseDoor.Error: kls.Normal, BaseDoor.Info: kls.Normal, BaseDoor.Warning: kls.Normal, BaseDoor.Output: kls.Normal, BaseDoor.Debug: kls.Normal, BaseDoor.Result: kls.Normal } return self._log_stop def getStateAttr(self): return self._state_attr @property def macro_server(self): if self._macro_server is None: self._macro_server = self._get_macroserver_for_door() return self._macro_server def _get_macroserver_for_door(self): """Returns the MacroServer device object in the same DeviceServer as this door""" db = self.factory().getDatabase() door_name = self.dev_name() server_list = list(db.get_server_list('MacroServer/*')) server_list += list(db.get_server_list('Sardana/*')) server_devs = None for server in server_list: server_devs = db.get_device_class_list(server) devs, klasses = server_devs[0::2], server_devs[1::2] for dev in devs: if dev.lower() == door_name: for i, klass in enumerate(klasses): if klass == 'MacroServer': return self.factory().getDevice(devs[i]) else: return None def setDebugMode(self, state): self._debug = state def getDebugMode(self): return self._debug def setSilent(self, yesno): self._silent = yesno def isSilent(self): return self._silent def getLogObj(self, log_name='Debug'): return self._log_attr.get(log_name, None) def getRunningXML(self): return self._user_xml def getRunningMacro(self): return self._running_macro def getLastRunningMacro(self): return self._last_running_macro def abort(self, synch=True): if not synch: self.command_inout("AbortMacro") return evt_wait = AttributeEventWait(self.getAttribute("state")) evt_wait.lock() try: time_stamp = time.time() self.command_inout("AbortMacro") evt_wait.waitEvent(self.Running, equal=False, after=time_stamp, timeout=self.InteractiveTimeout) finally: evt_wait.unlock() evt_wait.disconnect() def stop(self, synch=True): if not synch: self.command_inout("StopMacro") return evt_wait = AttributeEventWait(self.getAttribute("state")) evt_wait.lock() try: time_stamp = time.time() self.command_inout("StopMacro") evt_wait.waitEvent(self.Running, equal=False, after=time_stamp, timeout=self.InteractiveTimeout) finally: evt_wait.unlock() evt_wait.disconnect() def _clearRunMacro(self): # Clear the log buffer map(LogAttr.clearLogBuffer, self._log_attr.values()) self._running_macros = None self._running_macro = None self._user_xml = None self._block_lines = 0 def _createMacroXml(self, macro_name, macro_params): """The best effort creation of the macro XML object. It tries to convert flat list of string parameter values to the correct macro XML object. The cases that can not be converted are: * repeat parameter containing repeat parameters * two repeat parameters * repeat parameter that is not the last parameter :param macro_name: (str) macro name :param macro_params: (sequence[str]) list of parameter values :return (lxml.etree._Element) macro XML element """ macro_info = self.macro_server.getMacroInfoObj(macro_name) params_def = macro_info.parameters macro_node = createMacroNode(macro_name, params_def, macro_params) return macro_node.toXml() def preRunMacro(self, obj, parameters): self._clearRunMacro() xml_root = None if isinstance(obj, (str, unicode)): if obj.startswith('<') and not parameters: xml_root = etree.fromstring(obj) else: macros = [] if len(parameters) == 0: macros_strs = obj.split('\n') for m in macros_strs: pars = m.split() macros.append((pars[0], pars[1:])) else: parameters = recur_map(str, parameters) macros.append((obj, parameters)) xml_root = xml_seq = etree.Element('sequence') for m in macros: macro_name = m[0] macro_params = m[1] xml_macro = self._createMacroXml(macro_name, macro_params) xml_macro.set('id', str(uuid.uuid1())) xml_seq.append(xml_macro) elif etree.iselement(obj): xml_root = obj else: raise TypeError('obj must be a string or a etree.Element') self._running_macros = {} for macro_xml in xml_root.xpath('//macro'): id, name = macro_xml.get('id'), macro_xml.get('name') self._running_macros[id] = Macro(self, name, id, macro_xml) return xml_root def postRunMacro(self, result, synch): pass def runMacro(self, obj, parameters=[], synch=False): self._user_xml = self.preRunMacro(obj, parameters) result = self._runMacro(self._user_xml, synch=synch) return self.postRunMacro(result, synch) def _runMacro(self, xml, synch=False): if not synch: return self.command_inout("RunMacro", [etree.tostring(xml)]) timeout = self.InteractiveTimeout evt_wait = self._getEventWait() evt_wait.connect(self.getAttribute("state")) evt_wait.lock() try: evt_wait.waitEvent(self.Running, equal=False, timeout=timeout) ts = time.time() result = self.command_inout("RunMacro", [etree.tostring(xml)]) evt_wait.waitEvent(self.Running, after=ts, timeout=timeout) if synch: evt_wait.waitEvent(self.Running, equal=False, after=ts, timeout=timeout) finally: self._clearRunMacro() evt_wait.unlock() evt_wait.disconnect() return result def stateChanged(self, s, t, v): self._old_door_state = self.getState() try: self._old_sw_door_state = self.getSWState() except: # TODO: For Taurus 4 compatibility self._old_sw_door_state = self.state def resultReceived(self, log_name, result): """Method invoked by the arrival of a change event on the Result attribute""" if self._ignore_logs or self._running_macro is None: return self._running_macro.setResult(result) return result def putEnvironment(self, name, value): self.macro_server.putEnvironment(name, value) def putEnvironments(self, obj): self.macro_server.putEnvironments(obj) setEnvironment = putEnvironment setEnvironments = putEnvironments def getEnvironment(self, name=None): return self.macro_server.getEnvironment(name=name) def inputReceived(self, s, t, v): if t not in CHANGE_EVT_TYPES: return if v is None or self._running_macros is None: return input_data = CodecFactory().decode(('json', v.value)) self.processInput(input_data) def processInput(self, input_data): TaurusManager().addJob(self._processInput, None, input_data) def _processInput(self, input_data): input_type = input_data['type'] if input_type == 'input': result = self._input_handler.input(input_data) if result['input'] is '' and 'default_value' in input_data: result['input'] = input_data['default_value'] result = CodecFactory().encode('json', ('', result))[1] self.write_attribute('Input', result) elif input_type == 'timeout': self._input_handler.input_timeout(input_data) def recordDataReceived(self, s, t, v): if t not in CHANGE_EVT_TYPES: return return self._processRecordData(v) def _processRecordData(self, data): if data is None or data.value is None: return # make sure we get it as string since PyTango 7.1.4 returns a buffer # object and json.loads doesn't support buffer objects (only str) data = map(str, data.value) size = len(data[1]) if size == 0: return format = data[0] codec = CodecFactory().getCodec(format) data = codec.decode(data) return data def processRecordData(self, data): pass def macroStatusReceived(self, s, t, v): if v is None or self._running_macros is None: return if t not in CHANGE_EVT_TYPES: return # make sure we get it as string since PyTango 7.1.4 returns a buffer # object and json.loads doesn't support buffer objects (only str) v = map(str, v.value) if not len(v[1]): return format = v[0] codec = CodecFactory().getCodec(format) # make sure we get it as string since PyTango 7.1.4 returns a buffer # object and json.loads doesn't support buffer objects (only str) v[1] = str(v[1]) fmt, data = codec.decode(v) for macro_status in data: id = macro_status.get('id') macro = self._running_macros.get(id) self._last_running_macro = self._running_macro = macro # if we don't have the ID it's because the macro is running a submacro # or another client is connected to the same door (shame on him!) and # executing a macro we discard this event if macro is not None: macro.__dict__.update(macro_status) return data def logReceived(self, log_name, output): if not output or self._silent or self._ignore_logs: return if log_name == self.Debug and not self._debug: return o = self.log_start[log_name] for line in output: if not self._debug: if line == self.BlockStart: self._in_block = True for i in xrange(self._block_lines): # erase current line, up one line, erase current line o += '\x1b[2K\x1b[1A\x1b[2K' self._block_lines = 0 continue elif line == self.BlockFinish: self._in_block = False continue else: if self._in_block: self._block_lines += 1 else: self._block_lines = 0 o += "%s\n" % line o += self.log_stop[log_name] self.write(o) def write(self, msg, stream=None): if self.isSilent(): return msg = msg.encode('utf-8') self._output_stream = sys.stdout out = self._output_stream if not stream is None: start, stop = self.log_start.get(stream), self.log_stop.get(stream) if start is not None and stop is not None: out.write(start) out.write(msg) out.write(stop) out.flush() return out.write(msg) out.flush() def writeln(self, msg='', stream=None): self.write("%s\n" % msg, stream=stream) def getExperimentConfigurationObj(self): return self._experiment_configuration def getExperimentConfiguration(self): return self._experiment_configuration.get() def setExperimentConfiguration(self, config, mnt_grps=None): self._experiment_configuration.set(config, mnt_grps=mnt_grps)
class MacroServer(MSContainer, MSObject, SardanaElementManager, SardanaIDManager): All = "All" MaxParalellMacros = 5 logReportParams = dict(when='midnight', interval=1, backupCount=365) logReportKlass = NonOverlappingTimedRotatingFileHandler def __init__(self, full_name, name=None, macro_path=None, environment_db=None, recorder_path=None): # dict<str, Pool> # key - device name (case insensitive) # value - Pool object representing the device name self._pools = CaselessDict() self._max_parallel_macros = self.MaxParalellMacros self._path_id = None MSContainer.__init__(self) MSObject.__init__(self, full_name=full_name, name=name, id=InvalidId, macro_server=self, elem_type=ElementType.MacroServer) registerExtensions() self._type_manager = TypeManager(self) self._environment_manager = EnvironmentManager( self, environment_db=environment_db) self._macro_manager = MacroManager(self, macro_path=macro_path) self._recorder_manager = RecorderManager(self, recorder_path=recorder_path) def serialize(self, *args, **kwargs): kwargs = MSObject.serialize(self, *args, **kwargs) kwargs['type'] = self.__class__.__name__ kwargs['id'] = InvalidId kwargs['parent'] = None return kwargs def add_job(self, job, callback=None, *args, **kw): th_pool = get_thread_pool() th_pool.add(job, callback, *args, **kw) # -------------------------------------------------------------------------- # Environment DB related methods # -------------------------------------------------------------------------- def set_environment_db(self, environment_db): """Sets the environment database. :param env_db: environment database name :type env_db: str """ self.environment_manager.setEnvironmentDb(environment_db) # -------------------------------------------------------------------------- # Python related methods # -------------------------------------------------------------------------- def set_python_path(self, path): mod_man = ModuleManager() if self._path_id is not None: mod_man.remove_python_path(self._path_id) self._path_id = mod_man.add_python_path(path) # -------------------------------------------------------------------------- # Macro path related methods # -------------------------------------------------------------------------- def set_macro_path(self, macro_path): """Sets the macro path. :param macro_path: macro path :type macro_path: seq<str> """ self.macro_manager.setMacroPath([p.rstrip(os.sep) for p in macro_path]) # -------------------------------------------------------------------------- # Recorder path related methods # -------------------------------------------------------------------------- def set_recorder_path(self, recorder_path): """Sets the recorder path. :param recorder_path: recorder path :type recorder_path: seq<str> """ self.recorder_manager.setRecorderPath(recorder_path) # -------------------------------------------------------------------------- # Report related methods # -------------------------------------------------------------------------- def set_log_report(self, filename=None, format=None): log = self.get_report_logger() # first check that the handler has not been initialized yet. If it has # we remove previous handlers. We only allow one timed rotating file # handler at a time to_remove = [] for handler in log.handlers: if isinstance(handler, logging.handlers.TimedRotatingFileHandler): to_remove.append(handler) for handler in to_remove: handler.close() log.removeHandler(handler) if filename is None: return if format is None: format = Logger.DftLogMessageFormat formatter = logging.Formatter(format) self.info("Reports are being stored in %s", filename) klass = self.logReportKlass handler = klass(filename, **self.logReportParams) handler.setFormatter(formatter) log.addHandler(handler) def clear_log_report(self): self.set_log_report() def get_report_logger(self): return logging.getLogger("Sardana.Report") report_logger = property(get_report_logger) def report(self, msg, *args, **kwargs): """ Record a log message in the sardana report (if enabled) with default level **INFO**. The msg is the message format string, and the args are the arguments which are merged into msg using the string formatting operator. (Note that this means that you can use keywords in the format string, together with a single dictionary argument.) *kwargs* are the same as :meth:`logging.Logger.debug` plus an optional level kwargs which has default value **INFO** Example:: self.report("this is an official report!") :param msg: the message to be recorded :type msg: :obj:`str` :param args: list of arguments :param kwargs: list of keyword arguments""" level = kwargs.pop('level', logging.INFO) return self.report_logger.log(level, msg, *args, **kwargs) # -------------------------------------------------------------------------- # Pool related methods # -------------------------------------------------------------------------- def set_pool_names(self, pool_names): """Registers a new list of device pools in this manager :param pool_names: sequence of pool names :type pool_names: seq<str>""" for pool in self._pools.values(): elements_attr = pool.getAttribute("Elements") elements_attr.removeListener(self.on_pool_elements_changed) for name in pool_names: self.debug("Creating pool %s", name) pool = Device(name) if pool is None: self.error('Could not create Pool object for %s' % name) continue self._pools[name] = pool elements_attr = pool.getAttribute("Elements") elements_attr.addListener(self.on_pool_elements_changed) def get_pool_names(self): """Returns the list of names of the pools this macro server is connected to. :return: the list of names of the pools this macro server is connected to :rtype: seq<str>""" return self._pools.keys() def get_pool(self, pool_name): """Returns the device pool object corresponding to the given device name or None if no match is found. :param pool_name: device pool name :type pool_name: str :return: Pool object or None if no match is found""" return self._pools.get(pool_name) def get_pools(self): """Returns the list of pools this macro server is connected to. :return: the list of pools this macro server is connected to :rtype: seq<Pool>""" return self._pools.values() def on_pool_elements_changed(self, evt_src, evt_type, evt_value): if evt_type not in CHANGE_EVT_TYPES: return self.fire_event(EventType("PoolElementsChanged"), evt_value) # -------------------------------------------------------------------------- # Door related methods # -------------------------------------------------------------------------- def create_element(self, **kwargs): type = kwargs['type'] elem_type = ElementType[type] name = kwargs['name'] kwargs['macro_server'] = self td = TYPE_MAP_OBJ[elem_type] klass = td.klass auto_full_name = td.auto_full_name full_name = kwargs.get("full_name", auto_full_name.format(**kwargs)) self.check_element(name, full_name) id = kwargs.get('id') if id is None: kwargs['id'] = id = self.get_new_id() else: self.reserve_id(id) elem = klass(**kwargs) ret = self.add_element(elem) self.fire_event(EventType("ElementCreated"), elem) return ret def create_door(self, **kwargs): return self.create_element(type="Door", **kwargs) # -------------------------------------------------------------------------- # General access to elements # -------------------------------------------------------------------------- def get_elements_info(self): return self.get_remote_elements_info() + self.get_local_elements_info() def get_remote_elements_info(self): return [ elem.serialize() for pool in self.get_pools() for elem in pool.getElements() ] def get_local_elements_info(self): # fill macro library info ret = [ macrolib.serialize() for macrolib in self.get_macro_libs().values() ] # fill macro info ret += [macro.serialize() for macro in self.get_macros().values()] # fill parameter type info ret += [ paramtype.serialize() for paramtype in self.get_data_types().values() ] return ret # -------------------------------------------------------------------------- # macro execution # -------------------------------------------------------------------------- def set_max_parallel_macros(self, nb): assert nb > 0, "max parallel macros number must be > 0" th_pool = get_thread_pool() if th_pool.size + 5 < nb: th_pool.size = nb self._max_parallel_macros = nb def get_max_parallel_macros(self): return self._max_parallel_macros max_parallel_macros = property(get_max_parallel_macros, set_max_parallel_macros, doc="maximum number of macros which can " "execute at the same time") @property def macro_manager(self): return self._macro_manager @property def recorder_manager(self): return self._recorder_manager @property def environment_manager(self): return self._environment_manager @property def type_manager(self): return self._type_manager # -------------------------------------------------------------------------- # (Re)load code # -------------------------------------------------------------------------- def reload_lib(self, lib_name): return self.macro_manager.reloadLib(lib_name) def reload_macro_lib(self, lib_name): manager = self.macro_manager try: old_lib = manager.getMacroLib(lib_name) except UnknownMacroLibrary: old_lib = None new_elements, changed_elements, deleted_elements = [], [], [] new_lib = manager.reloadMacroLib(lib_name) if new_lib.has_errors(): return new_lib if old_lib is None: new_elements.extend(new_lib.get_macros()) new_elements.append(new_lib) else: changed_elements.append(new_lib) new_names = set([macro.name for macro in new_lib.get_macros()]) old_names = set([macro.name for macro in old_lib.get_macros()]) changed_names = set.intersection(new_names, old_names) deleted_names = old_names.difference(new_names) new_names = new_names.difference(old_names) for new_name in new_names: new_elements.append(new_lib.get_macro(new_name)) for changed_name in changed_names: changed_elements.append(new_lib.get_macro(changed_name)) for deleted_name in deleted_names: deleted_elements.append(old_lib.get_macro(deleted_name)) evt = { "new": new_elements, "change": changed_elements, "del": deleted_elements } self.fire_event(EventType("ElementsChanged"), evt) return new_lib reload_macro_lib.__doc__ = MacroManager.reloadMacroLib.__doc__ def reload_macro_libs(self, lib_names): for lib_name in lib_names: self.reload_macro_lib(lib_name) def reload_macro(self, macro_name): macro_info = self.macro_manager.getMacro(macro_name) lib_name = macro_info.module_name return self.reload_macro_lib(lib_name) def reload_macros(self, macro_names): lib_names = set() for macro_name in macro_names: macro_info = self.macro_manager.getMacro(macro_name) lib_names.add(macro_info.module_name) self.reload_macro_libs(lib_names) def get_macro_lib(self, lib_name): return self.macro_manager.getMacroLib(lib_name) get_macro_lib.__doc__ = MacroManager.getMacroLib.__doc__ def get_macro_libs(self, filter=None): return self.macro_manager.getMacroLibs(filter=filter) get_macro_libs.__doc__ = MacroManager.getMacroLibs.__doc__ def get_macro_lib_names(self): return self.macro_manager.getMacroLibNames() get_macro_lib_names.__doc__ = MacroManager.getMacroLibNames.__doc__ def get_macro(self, name): return self.macro_manager.getMacro(name) get_macro.__doc__ = MacroManager.getMacro.__doc__ def get_macros(self, filter=None): return self.macro_manager.getMacros(filter=filter) get_macros.__doc__ = MacroManager.getMacros.__doc__ def get_macro_names(self): return self.macro_manager.getMacroNames() get_macro_names.__doc__ = MacroManager.getMacroNames.__doc__ def get_macro_classes(self): return self.macro_manager.getMacroClasses() get_macro_classes.__doc__ = MacroManager.getMacroClasses.__doc__ def get_macro_functions(self): return self.macro_manager.getMacroFunctions() get_macro_functions.__doc__ = MacroManager.getMacroFunctions.__doc__ def get_macro_libs_summary_info(self): libs = self.get_macro_libs() ret = [] for module_name, macro_lib_info in libs.items(): elem = "%s (%s)" % (macro_lib_info.name, macro_lib_info.file_path) ret.append(elem) return ret def get_macro_classes_summary_info(self): macros = self.get_macros() ret = [] for macro_info in macros: elem = "%s (%s)" % (macro_info.name, macro_info.file_path) ret.append(elem) return ret def get_or_create_macro_lib(self, lib_name, macro_name=None): """Gets the exiting macro lib or creates a new macro lib file. If name is not None, a macro template code for the given macro name is appended to the end of the file. :param lib_name: module name, python file name, or full file name (with path) :type lib_name: str :param macro_name: an optional macro name. If given a macro template code is appended to the end of the file (default is None meaning no macro code is added) :type macro_name: str :return: a sequence with three items: full_filename, code, line number is 0 if no macro is created or n representing the first line of code for the given macro name. :rtype: seq<str, str, int>""" return self.macro_manager.getOrCreateMacroLib(lib_name, macro_name=macro_name) get_or_create_macro_lib.__doc__ = MacroManager.getOrCreateMacroLib.__doc__ def set_macro_lib(self, lib_name, code, auto_reload=True): module_name = self.macro_manager.setMacroLib(lib_name, code, auto_reload=False) if auto_reload: self.reload_macro_lib(module_name) set_macro_lib.__doc__ = MacroManager.setMacroLib.__doc__ # -------------------------------------------------------------------------- # Data types # -------------------------------------------------------------------------- def get_data_types(self): return self.type_manager.getTypes() get_data_types.__doc__ = TypeManager.getTypes.__doc__ def get_data_type(self, type_name): return self.type_manager.getTypeObj(type_name) get_data_type.__doc__ = TypeManager.getTypeObj.__doc__ def get_data_type_names(self): return self.type_manager.getTypeNames() get_data_type_names.__doc__ = TypeManager.getTypeNames.__doc__ def get_data_type_names_with_asterisc(self): return self.type_manager.getTypeListStr() get_data_type_names_with_asterisc.__doc__ = TypeManager.getTypeListStr.__doc__ # -------------------------------------------------------------------------- # Doors # -------------------------------------------------------------------------- def get_doors(self): return self.get_elements_by_type(ElementType.Door) def get_door_names(self): return [door.full_name for door in self.get_doors()] #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- # Environment access methods #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- def get_env(self, key=None, door_name=None, macro_name=None): """Gets the environment matching the given parameters: - door_name and macro_name define the context where to look for the environment. If both are None, the global environment is used. If door name is None but macro name not, the given macro environment is used and so on... - If key is None it returns the complete environment, otherwise key must be a string containing the environment variable name. :param key: environment variable name [default: None, meaning all environment] :type key: str :param door_name: local context for a given door [default: None, meaning no door context is used] :type door_name: str :param macro_name: local context for a given macro [default: None, meaning no macro context is used] :type macro_name: str :return: a :obj:`dict` containing the environment :rtype: :obj:`dict` :raises: UnknownEnv""" return self.environment_manager.getEnv(key=key, macro_name=macro_name, door_name=door_name) def set_env(self, key, value): """Sets the environment key to the new value and stores it persistently. :param key: the key for the environment :param value: the value for the environment :return: a tuple with the key and value objects stored""" env_man = self.environment_manager if env_man.hasEnv(key): evt_type = "change" else: evt_type = "new" k, v = self.environment_manager.setEnv(key, value) evt = {evt_type: {k: v}} self.fire_event(EventType("EnvironmentChanged"), evt) return k, v def set_env_obj(self, data): """Sets the environment key to the new value and stores it persistently. :param key: the key for the environment :param value: the value for the environment :return: a tuple with the key and value objects stored""" env_man = self.environment_manager new, change = {}, {} for key, value in data.items(): d = new if env_man.hasEnv(key): d = change d[key] = value ret = env_man.setEnvObj(data) evt = dict(new=new, change=change) self.fire_event(EventType("EnvironmentChanged"), evt) return ret def change_env(self, data): env_man = self.environment_manager new_change_env = data.get('new', {}) new_change_env.update(data.get('change', {})) del_env = data.get('del', []) new, change = {}, {} for key, value in new_change_env.items(): d = new if env_man.hasEnv(key): d = change d[key] = value del_keys = env_man.unsetEnv(del_env) env_man.setEnvObj(new_change_env) evt = dict(new=new, change=change) evt['del'] = del_keys self.fire_event(EventType("EnvironmentChanged"), evt) def unset_env(self, key): """Unsets the environment for the given key. :param key: the key for the environment to be unset""" ret = self.environment_manager.unsetEnv(key) # list is unhashable - convert to a tuple if isinstance(key, list): key = tuple(key) evt = {'del': {key: None}} self.fire_event(EventType("EnvironmentChanged"), evt) return ret def has_env(self, key, macro_name=None, door_name=None): return self.environment_manager.hasEnv(key, macro_name=macro_name, door_name=door_name) #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- # General object access methods #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- def get_object(self, name, type_class=All, subtype=All, pool=All): objs = self.find_objects(name, type_class, subtype, pool) if len(objs) == 0: return None if len(objs) > 1: raise AttributeError('More than one object named "%s" found' % name) return objs[0] def get_objects(self, names, type_class=All, subtype=All, pool=All): return self.find_objects(names, type_class=type_class, subtype=subtype, pool=pool) def find_objects(self, param, type_class=All, subtype=All, pool=All): if is_pure_str(param): param = param, if type_class == MacroServer.All: type_name_list = self.get_data_type_names() else: if is_pure_str(type_class): type_name_list = type_class, else: type_name_list = type_class obj_set = set() param = ['^%s$' % x for x in param] re_objs = map(re.compile, param, len(param) * (re.IGNORECASE, )) re_subtype = re.compile(subtype, re.IGNORECASE) for type_name in type_name_list: type_class_name = type_name if type_class_name.endswith('*'): type_class_name = type_class_name[:-1] type_inst = self.get_data_type(type_class_name) if not type_inst.hasCapability(ParamType.ItemList): continue if self.is_macroserver_interface(type_class_name): for name, obj in type_inst.getObjDict(pool=pool).items(): for re_obj in re_objs: if re_obj.match(name) is not None: obj_type = ElementType[obj.get_type()] if subtype is MacroServer.All or re_subtype.match( obj_type): obj_set.add(obj) else: for name, obj in type_inst.getObjDict(pool=pool).items(): for re_obj in re_objs: if re_obj.match(name) is not None: obj_type = obj.getType() if (subtype is MacroServer.All or re_subtype.match(obj.getType())) and \ obj_type != "MotorGroup": obj_set.add(obj) return list(obj_set) def get_motion(self, elems, motion_source=None, read_only=False, cache=True, decoupled=False): if motion_source is None: motion_source = self.get_pools() motion_klass = Motion if decoupled: # and len(elems)>1: motion_klass = MotionGroup return motion_klass(elems, motion_source) _LOCAL_INTERFACES = { Interface.MacroLibrary: get_macro_libs, Interface.MacroCode: get_macros, Interface.MacroClass: get_macro_classes, Interface.MacroFunction: get_macro_functions, } def is_macroserver_interface(self, interface): if is_pure_str(interface): interface = Interface[interface] return interface in self._LOCAL_INTERFACES def get_elements_with_interface(self, interface): ret = CaselessDict() if is_pure_str(interface): interface_str = interface interface = Interface[interface_str] else: interface_str = Interface[interface] if self.is_macroserver_interface(interface): ret.update(self._LOCAL_INTERFACES.get(interface)(self)) else: for pool in self.get_pools(): ret.update(pool.getElementsWithInterface(interface_str)) return ret def get_element_with_interface(self, name, interface): for pool in self.get_pools(): element = pool.getElementWithInterface(name, interface) if element is not None: return element def get_controllers(self): return self.get_elements_with_interface("Controller") def get_moveables(self): return self.get_elements_with_interface("Moveable") def get_motors(self): return self.get_elements_with_interface("Motor") def get_pseudo_motors(self): return self.get_elements_with_interface("PseudoMotor") def get_io_registers(self): return self.get_elements_with_interface("IORegister") def get_measurement_groups(self): return self.get_elements_with_interface("MeasurementGroup") def get_exp_channels(self): return self.get_elements_with_interface("ExpChannel") def get_counter_timers(self): return self.get_elements_with_interface("CTExpChannel") def get_0d_exp_channels(self): return self.get_elements_with_interface("ZeroDExpChannel") def get_1d_exp_channels(self): return self.get_elements_with_interface("OneDExpChannel") def get_2d_exp_channels(self): return self.get_elements_with_interface("TwoDExpChannel") def get_pseudo_counters(self): return self.get_elements_with_interface("PseudoCounter") def get_instruments(self): return self.get_elements_with_interface("Instrument") def get_controller(self, name): return self.get_element_with_interface(name, "Controller") def get_moveable(self, name): return self.get_element_with_interface(name, "Moveable") def get_motor(self, name): return self.get_element_with_interface(name, "Motor") def get_pseudo_motor(self, name): return self.get_element_with_interface(name, "PseudoMotor") def get_io_register(self, name): return self.get_element_with_interface(name, "IORegister") def get_measurement_group(self, name): return self.get_element_with_interface(name, "MeasurementGroup") def get_exp_channel(self, name): return self.get_element_with_interface(name, "ExpChannel") def get_counter_timer(self, name): return self.get_element_with_interface(name, "CTExpChannel") def get_0d_exp_channel(self, name): return self.get_element_with_interface(name, "ZeroDExpChannel") def get_1d_exp_channel(self, name): return self.get_element_with_interface(name, "OneDExpChannel") def get_2d_exp_channel(self, name): return self.get_element_with_interface(name, "TwoDExpChannel") def get_pseudo_counter(self, name): return self.get_element_with_interface(name, "PseudoCounter") def get_instrument(self, name): return self.get_element_with_interface(name, "Instrument")
class BaseSardanaElementContainer: def __init__(self): # dict<str, dict> where key is the type and value is: # dict<str, MacroServerElement> where key is the element full name # and value is the Element object self._type_elems_dict = CaselessDict() # dict<str, container> where key is the interface and value is the set # of elements which implement that interface self._interfaces_dict = {} def addElement(self, elem): elem_type = elem.getType() elem_full_name = elem.full_name # update type_elems type_elems = self._type_elems_dict.get(elem_type) if type_elems is None: self._type_elems_dict[elem_type] = type_elems = CaselessDict() type_elems[elem_full_name] = elem # update interfaces for interface in elem.interfaces: interface_elems = self._interfaces_dict.get(interface) if interface_elems is None: self._interfaces_dict[ interface] = interface_elems = CaselessDict() interface_elems[elem_full_name] = elem def removeElement(self, e): elem_type = e.getType() # update type_elems type_elems = self._type_elems_dict.get(elem_type) if type_elems: del type_elems[e.full_name] # update interfaces for interface in e.interfaces: interface_elems = self._interfaces_dict.get(interface) del interface_elems[e.full_name] def removeElementsOfType(self, t): for elem in self.getElementsOfType(t): self.removeElement(elem) def getElementsOfType(self, t): elems = self._type_elems_dict.get(t, {}) return elems def getElementNamesOfType(self, t): return [e.name for e in self.getElementsOfType(t).values()] def getElementsWithInterface(self, interface): elems = self._interfaces_dict.get(interface, {}) return elems def getElementsWithInterfaces(self, interfaces): ret = CaselessDict() for interface in interfaces: ret.update(self.getElementsWithInterface(interface)) return ret def getElementNamesWithInterface(self, interface): return [e.name for e in self.getElementsWithInterface(interface).values()] def hasElementName(self, elem_name): return self.getElement(elem_name) is not None def getElement(self, elem_name): elem_name = elem_name.lower() for elems in self._type_elems_dict.values(): elem = elems.get(elem_name) # full_name? if elem is not None: return elem for elem in elems.values(): if elem.name.lower() == elem_name: return elem def getElementWithInterface(self, elem_name, interface): elem_name = elem_name.lower() elems = self._interfaces_dict.get(interface, {}) if elem_name in elems: return elems[elem_name] for elem in elems.values(): if elem.name.lower() == elem_name: return elem def getElements(self): ret = set() for elems in self._type_elems_dict.values(): ret.update(elems.values()) return ret def getInterfaces(self): return self._interfaces_dict def getTypes(self): return self._type_elems_dict
class BaseDoor(MacroServerDevice): """ Class encapsulating Door device functionality.""" On = PyTango.DevState.ON Running = PyTango.DevState.RUNNING Paused = PyTango.DevState.STANDBY Critical = 'Critical' Error = 'Error' Warning = 'Warning' Info = 'Info' Output = 'Output' Debug = 'Debug' Result = 'Result' RecordData = 'RecordData' BlockStart = '<BLOCK>' BlockFinish = '</BLOCK>' log_streams = (Error, Warning, Info, Output, Debug, Result) # maximum execution time without user interruption # this also means a time window within door state events must arrive # 0.1 s was not enough on Windows (see sardana-ord/sardana#725) InteractiveTimeout = .3 def __init__(self, name, **kw): self._log_attr = CaselessDict() self._block_lines = 0 self._in_block = False self._macro_server = None self._running_macros = None self._running_macro = None self._last_running_macro = None self._user_xml = None self._ignore_logs = kw.get("ignore_logs", False) self._silent = kw.get("silent", True) self._debug = kw.get("debug", False) self._output_stream = kw.get("output", sys.stdout) self._writeLock = threading.Lock() self._input_handler = self.create_input_handler() self._len_last_data_line = 1 self.call__init__(MacroServerDevice, name, **kw) self._old_door_state = PyTango.DevState.UNKNOWN self._old_sw_door_state = TaurusDevState.Undefined self.stateObj.addListener(self.stateChanged) for log_name in self.log_streams: tg_attr = self.getAttribute(log_name) attr = LogAttr(self, log_name, None, tg_attr) if log_name == 'Result': attr.subscribeEvent(self.resultReceived, log_name) else: attr.subscribeEvent(self.logReceived, log_name) self._log_attr[log_name] = attr self.__input_attr = self.getAttribute("Input") self.__input_attr.addListener(self.inputReceived) self.__record_data_attr = self.getAttribute('RecordData') self.__record_data_attr.addListener(self.recordDataReceived) self.__macro_status_attr = self.getAttribute('MacroStatus') self.__macro_status_attr.addListener(self.macroStatusReceived) self._experiment_configuration = ExperimentConfiguration(self) def create_input_handler(self): return BaseInputHandler() def get_input_handler(self): return self._input_handler def get_color_mode(self): return "NoColor" # def macrosChanged(self, s, v, t): # pass @property def log_start(self): if not hasattr(self, "_log_start"): import taurus.core.util.console if self.get_color_mode() == "NoColor": kls = taurus.core.util.console.NoColors else: kls = taurus.core.util.console.TermColors self._log_start = { BaseDoor.Critical: kls.LightRed, BaseDoor.Error: kls.Red, BaseDoor.Info: kls.LightBlue, BaseDoor.Warning: kls.Brown, BaseDoor.Output: kls.Normal, BaseDoor.Debug: kls.DarkGray, BaseDoor.Result: kls.LightGreen } return self._log_start @property def log_stop(self): if not hasattr(self, "_log_stop"): import taurus.core.util.console if self.get_color_mode() == "NoColor": kls = taurus.core.util.console.NoColors else: kls = taurus.core.util.console.TermColors self._log_stop = { BaseDoor.Critical: kls.Normal, BaseDoor.Error: kls.Normal, BaseDoor.Info: kls.Normal, BaseDoor.Warning: kls.Normal, BaseDoor.Output: kls.Normal, BaseDoor.Debug: kls.Normal, BaseDoor.Result: kls.Normal } return self._log_stop def getStateAttr(self): return self._state_attr @property def macro_server(self): if self._macro_server is None: self._macro_server = self._get_macroserver_for_door() return self._macro_server def _get_macroserver_for_door(self): """Returns the MacroServer device object in the same DeviceServer as this door""" db = self.getParentObj() door_name = self.dev_name() server_list = list(db.get_server_list('MacroServer/*')) server_list += list(db.get_server_list('Sardana/*')) for server in server_list: server_devs = db.get_device_class_list(server) devs, klasses = server_devs[0::2], server_devs[1::2] for dev in devs: if dev.lower() == door_name: for i, klass in enumerate(klasses): if klass == 'MacroServer': full_name = db.getFullName() + "/" + devs[i] return self.factory().getDevice(full_name) else: return None def setDebugMode(self, state): self._debug = state def getDebugMode(self): return self._debug def setSilent(self, yesno): self._silent = yesno def isSilent(self): return self._silent def getLogObj(self, log_name='Debug'): return self._log_attr.get(log_name, None) def getRunningXML(self): return self._user_xml def getRunningMacro(self): return self._running_macro def getLastRunningMacro(self): return self._last_running_macro def abort(self, synch=True): if not synch: self.command_inout("AbortMacro") return evt_wait = AttributeEventWait(self.getAttribute("state")) evt_wait.lock() try: time_stamp = time.time() self.command_inout("AbortMacro") evt_wait.waitEvent(self.Running, equal=False, after=time_stamp, timeout=self.InteractiveTimeout) finally: evt_wait.unlock() evt_wait.disconnect() def release(self, synch=True): if not synch: try: self.command_inout("ReleaseMacro") except PyTango.DevFailed as df: # Macro already finished - no need to release if df.args[0].reason == "API_CommandNotAllowed": pass return evt_wait = AttributeEventWait(self.getAttribute("state")) evt_wait.lock() try: time_stamp = time.time() try: self.command_inout("ReleaseMacro") except PyTango.DevFailed as df: # Macro already finished - no need to release if df.args[0].reason == "API_CommandNotAllowed": return evt_wait.waitEvent(self.Running, equal=False, after=time_stamp, timeout=self.InteractiveTimeout) finally: evt_wait.unlock() evt_wait.disconnect() def stop(self, synch=True): if not synch: self.command_inout("StopMacro") return evt_wait = AttributeEventWait(self.getAttribute("state")) evt_wait.lock() try: time_stamp = time.time() self.command_inout("StopMacro") evt_wait.waitEvent(self.Running, equal=False, after=time_stamp, timeout=self.InteractiveTimeout) finally: evt_wait.unlock() evt_wait.disconnect() def _clearRunMacro(self): # Clear the log buffer list(map(LogAttr.clearLogBuffer, list(self._log_attr.values()))) self._running_macros = None self._running_macro = None self._user_xml = None self._block_lines = 0 def _createMacroXml(self, macro_name, macro_params): """Creation of the macro XML object. :param macro_name: (str) macro name :param macro_params: (sequence[str]) list of parameter values, if repeat parameters are used parameter values may be sequences itself. :return (lxml.etree._Element) macro XML element """ macro_info = self.macro_server.getMacroInfoObj(macro_name) params_def = macro_info.parameters macro_node = createMacroNode(macro_name, params_def, macro_params) return macro_node.toXml() def preRunMacro(self, obj, parameters): self._clearRunMacro() xml_root = None if isinstance(obj, str): if obj.startswith('<') and not parameters: xml_root = etree.fromstring(obj) else: macros = [] if len(parameters) == 0: macros_strs = obj.split('\n') for m in macros_strs: pars = m.split() macros.append((pars[0], pars[1:])) else: parameters = recur_map(str, parameters) macros.append((obj, parameters)) xml_root = xml_seq = etree.Element('sequence') for m in macros: macro_name = m[0] macro_params = m[1] xml_macro = self._createMacroXml(macro_name, macro_params) xml_macro.set('id', str(uuid.uuid1())) xml_seq.append(xml_macro) elif etree.iselement(obj): xml_root = obj else: raise TypeError('obj must be a string or a etree.Element') self._running_macros = {} for macro_xml in xml_root.xpath('//macro'): id, name = macro_xml.get('id'), macro_xml.get('name') self._running_macros[id] = Macro(self, name, id, macro_xml) return xml_root def postRunMacro(self, result, synch): pass def runMacro(self, obj, parameters=[], synch=False): self._user_xml = self.preRunMacro(obj, parameters) result = self._runMacro(self._user_xml, synch=synch) return self.postRunMacro(result, synch) def _runMacro(self, xml, synch=False): if not synch: return self.command_inout( "RunMacro", [etree.tostring(xml, encoding='unicode')]) timeout = self.InteractiveTimeout evt_wait = self._getEventWait() evt_wait.connect(self.getAttribute("state")) evt_wait.lock() try: evt_wait.waitEvent(self.Running, equal=False, timeout=timeout) # Clear event set to not confuse the value coming from the # connection with the event of of end of the macro execution # in the next wait event. This was observed on Windows where # the time stamp resolution is not better than 1 ms. evt_wait.clearEventSet() ts = time.time() result = self.command_inout( "RunMacro", [etree.tostring(xml, encoding='unicode')]) evt_wait.waitEvent(self.Running, after=ts, timeout=timeout) if synch: evt_wait.waitEvent(self.Running, equal=False, after=ts, timeout=timeout) finally: self._clearRunMacro() evt_wait.unlock() evt_wait.disconnect() return result def stateChanged(self, s, t, v): # In contrary to the Taurus3 the Taurus4 raises exceptions when the # device server is getting down and we try to retrieve the state. # In this case provide the same behavior as Taurus3 - assign None to # the old state try: self._old_door_state = self.stateObj.rvalue except PyTango.DevFailed: self._old_door_state = None self._old_sw_door_state = self.state def resultReceived(self, log_name, result): """Method invoked by the arrival of a change event on the Result attribute""" if self._ignore_logs or self._running_macro is None: return self._running_macro.setResult(result) return result def putEnvironment(self, name, value): self.macro_server.putEnvironment(name, value) def putEnvironments(self, obj): self.macro_server.putEnvironments(obj) setEnvironment = putEnvironment setEnvironments = putEnvironments def getEnvironment(self, name=None): return self.macro_server.getEnvironment(name=name) def inputReceived(self, s, t, v): if t not in CHANGE_EVT_TYPES: return if v is None or self._running_macros is None: return input_data = CodecFactory().decode(('json', v.value)) self.processInput(input_data) def processInput(self, input_data): TaurusManager().addJob(self._processInput, None, input_data) def _processInput(self, input_data): input_type = input_data['type'] if input_type == 'input': result = self._input_handler.input(input_data) if result['input'] == '' and 'default_value' in input_data: result['input'] = input_data['default_value'] result = CodecFactory().encode('json', ('', result))[1] self.write_attribute('Input', result) elif input_type == 'timeout': self._input_handler.input_timeout(input_data) def recordDataReceived(self, s, t, v): if t not in CHANGE_EVT_TYPES: return return self._processRecordData(v) def _processRecordData(self, data): if data is None or data.rvalue is None: return data = data.rvalue size = len(data[1]) if size == 0: return format = data[0] codec = CodecFactory().getCodec(format) data = codec.decode(data) return data def processRecordData(self, data): pass def macroStatusReceived(self, s, t, v): if v is None or self._running_macros is None: return if t not in CHANGE_EVT_TYPES: return v = v.value if not len(v[1]): return format = v[0] codec = CodecFactory().getCodec(format) fmt, data = codec.decode(v) for macro_status in data: id = macro_status.get('id') macro = self._running_macros.get(id) self._last_running_macro = self._running_macro = macro # if we don't have the ID it's because the macro is running a # submacro or another client is connected to the same door (shame # on him!) and executing a macro we discard this event if macro is not None: macro.__dict__.update(macro_status) return data def logReceived(self, log_name, output): term_size = get_terminal_size() max_chrs = term_size.columns if term_size else None if not output or self._silent or self._ignore_logs: return if log_name == self.Debug and not self._debug: return o = self.log_start[log_name] for line in output: if not self._debug: if line == self.BlockStart: self._in_block = True for i in range(self._block_lines): if max_chrs is None: nb_lines = 1 else: nb_lines = _get_nb_lines(self._len_last_data_line, max_chrs) # per each line: erase current line, # go up one line and erase current line o += '\x1b[2K\x1b[1A\x1b[2K' * nb_lines self._block_lines = 0 continue elif line == self.BlockFinish: self._in_block = False continue else: self._len_last_data_line = len(line) if self._in_block: self._block_lines += 1 else: self._block_lines = 0 o += "%s\n" % line o += self.log_stop[log_name] self.write(o) def write(self, msg, stream=None): if self.isSilent(): return self._output_stream = sys.stdout out = self._output_stream if stream is not None: start, stop = self.log_start.get(stream), self.log_stop.get(stream) if start is not None and stop is not None: out.write(start) out.write(msg) out.write(stop) out.flush() return out.write(msg) out.flush() def writeln(self, msg='', stream=None): self.write("%s\n" % msg, stream=stream) def getExperimentConfigurationObj(self): return self._experiment_configuration def getExperimentConfiguration(self): return self._experiment_configuration.get() def setExperimentConfiguration(self, config, mnt_grps=None): self._experiment_configuration.set(config, mnt_grps=mnt_grps)
class Pool(PoolContainer, PoolObject, SardanaElementManager, SardanaIDManager): """The central pool class.""" #: Default value representing the number of state reads per position #: read during a motion loop Default_MotionLoop_StatesPerPosition = 10 #: Default value representing the sleep time for each motion loop Default_MotionLoop_SleepTime = 0.01 #: Default value representing the number of state reads per value #: read during a motion loop Default_AcqLoop_StatesPerValue = 10 #: Default value representing the sleep time for each acquisition loop Default_AcqLoop_SleepTime = 0.01 Default_DriftCorrection = True def __init__(self, full_name, name=None): self._path_id = None self._motion_loop_states_per_position = self.Default_MotionLoop_StatesPerPosition self._motion_loop_sleep_time = self.Default_MotionLoop_SleepTime self._acq_loop_states_per_value = self.Default_AcqLoop_StatesPerValue self._acq_loop_sleep_time = self.Default_AcqLoop_SleepTime self._drift_correction = self.Default_DriftCorrection self._remote_log_handler = None # dict<str, dict<str, str>> # keys are acquisition channel names and value is a dict describing the # channel containing: # - 'name': with value being the channel name (given by user) # - 'full_name': acq channel full name (ex: tango attribute) # - 'origin': 'local' if local to this server or 'remote' if a remote # channel self._extra_acquisition_element_names = CaselessDict() PoolContainer.__init__(self) PoolObject.__init__(self, full_name=full_name, name=name, id=InvalidId, pool=self, elem_type=ElementType.Pool) self._monitor = PoolMonitor(self, "PMonitor", auto_start=False) # self.init_local_logging() ControllerManager().set_pool(self) # TODO: not ready to use. path must be the same as the one calculated in # sardana.tango.core.util:prepare_logging def init_local_logging(self): log = logging.getLogger("Controller") log.propagate = 0 path = os.path.join(os.sep, "tmp", "tango") log_file_name = os.path.join(path, 'controller.log.txt') try: if not os.path.exists(path): os.makedirs(path, 0777) f_h = logging.handlers.RotatingFileHandler(log_file_name, maxBytes=1E7, backupCount=5) f_h.setFormatter(self.getLogFormat()) log.addHandler(f_h) self.info("Controller logs stored in %s", log_file_name) except: self.warning("Controller logs could not be created!") self.debug("Details:", exc_info=1) def clear_remote_logging(self): rh = self._remote_log_handler if rh is None: return log = logging.getLogger("Controller") log.removeHandler(rh) self._remote_log_handler = None def init_remote_logging(self, host=None, port=None): """Initializes remote logging. :param host: host name [default: None, meaning use the machine host name as returned by :func:`socket.gethostname`]. :type host: str :param port: port number [default: None, meaning use :data:`logging.handlers.DEFAULT_TCP_LOGGING_PORT`""" log = logging.getLogger("Controller") # port 0 means no remote logging if port == 0: return # first check that the handler has not been initialized yet for handler in log.handlers: if isinstance(handler, logging.handlers.SocketHandler): return if host is None: import socket host = socket.gethostname() #host = socket.getfqdn() if port is None: port = logging.handlers.DEFAULT_TCP_LOGGING_PORT handler = logging.handlers.SocketHandler(host, port) if hasattr(handler, 'retryMax'): # default max retry is 30s which seems too much. Let's make it that # the pool tries to reconnect to a client every 10s (similar to the # tango event reconnection handler.retryMax = 10.0 log.addHandler(handler) self.info("Remote logging initialized for host '%s' on port %d", host, port) def serialize(self, *args, **kwargs): kwargs = PoolObject.serialize(self, *args, **kwargs) kwargs['type'] = self.__class__.__name__ kwargs['id'] = InvalidId kwargs['parent'] = None return kwargs def set_motion_loop_sleep_time(self, motion_loop_sleep_time): self._motion_loop_sleep_time = motion_loop_sleep_time def get_motion_loop_sleep_time(self): return self._motion_loop_sleep_time motion_loop_sleep_time = property(get_motion_loop_sleep_time, set_motion_loop_sleep_time, doc="motion sleep time (s)") def set_motion_loop_states_per_position(self, motion_loop_states_per_position): self._motion_loop_states_per_position = motion_loop_states_per_position def get_motion_loop_states_per_position(self): return self._motion_loop_states_per_position motion_loop_states_per_position = property( get_motion_loop_states_per_position, set_motion_loop_states_per_position, doc="Number of State reads done before doing a position read in the " "motion loop") def set_acq_loop_sleep_time(self, acq_loop_sleep_time): self._acq_loop_sleep_time = acq_loop_sleep_time def get_acq_loop_sleep_time(self): return self._acq_loop_sleep_time acq_loop_sleep_time = property(get_acq_loop_sleep_time, set_acq_loop_sleep_time, doc="acquisition sleep time (s)") def set_acq_loop_states_per_value(self, acq_loop_states_per_value): self._acq_loop_states_per_value = acq_loop_states_per_value def get_acq_loop_states_per_value(self): return self._acq_loop_states_per_value acq_loop_states_per_value = property( get_acq_loop_states_per_value, set_acq_loop_states_per_value, doc="Number of State reads done before doing a value read in the " "acquisition loop") def set_drift_correction(self, drift_correction): self._drift_correction = drift_correction def get_drift_correction(self): return self._drift_correction drift_correction = property(get_drift_correction, set_drift_correction, doc="drift correction") @property def monitor(self): return self._monitor @property def ctrl_manager(self): return ControllerManager() def set_python_path(self, path): mod_man = ModuleManager() if self._path_id is not None: mod_man.remove_python_path(self._path_id) self._path_id = mod_man.add_python_path(path) def set_path(self, path): self.ctrl_manager.setControllerPath(path, reload=False) def get_controller_libs(self): return self.ctrl_manager.getControllerLibs() def get_controller_lib_names(self): return self.ctrl_manager.getControllerLibNames() def get_controller_class_names(self): return self.ctrl_manager.getControllerNames() def get_controller_classes(self): return self.ctrl_manager.getControllers() def get_controller_class_info(self, name): return self.ctrl_manager.getControllerMetaClass(name) def get_controller_classes_info(self, names): return self.ctrl_manager.getControllerMetaClasses(names) def get_controller_libs_summary_info(self): libs = self.get_controller_libs() ret = [] for ctrl_lib_info in libs: elem = "%s (%s)" % (ctrl_lib_info.getName(), ctrl_lib_info.getFileName()) ret.append(elem) return ret def get_controller_classes_summary_info(self): ctrl_classes = self.get_controller_classes() ret = [] for ctrl_class_info in ctrl_classes: types = ctrl_class_info.getTypes() types_str = [ TYPE_MAP_OBJ[t].name for t in types if t != ElementType.Controller ] types_str = ", ".join(types_str) elem = "%s (%s) %s" % (ctrl_class_info.getName(), ctrl_class_info.getFileName(), types_str) ret.append(elem) return ret def get_elements_str_info(self, obj_type=None): if obj_type is None: objs = self.get_element_id_map().values() objs.extend(self.get_controller_classes()) objs.extend(self.get_controller_libs()) elif obj_type == ElementType.ControllerClass: objs = self.get_controller_classes() elif obj_type == ElementType.ControllerLibrary: objs = self.get_controller_libs() else: objs = self.get_elements_by_type(obj_type) name = self.full_name return [obj.str(pool=name) for obj in objs] def get_elements_info(self, obj_type=None): if obj_type is None: objs = self.get_element_id_map().values() objs.extend(self.get_controller_classes()) objs.extend(self.get_controller_libs()) objs.append(self) elif obj_type == ElementType.ControllerClass: objs = self.get_controller_classes() elif obj_type == ElementType.ControllerLibrary: objs = self.get_controller_libs() else: objs = self.get_elements_by_type(obj_type) name = self.full_name return [obj.serialize(pool=name) for obj in objs] def get_acquisition_elements_info(self): ret = [] for _, element in self.get_element_name_map().items(): if element.get_type() not in TYPE_ACQUIRABLE_ELEMENTS: continue acq_channel = element.get_default_acquisition_channel() full_name = "{0}/{1}".format(element.full_name, acq_channel) info = dict(name=element.name, full_name=full_name, origin='local') ret.append(info) ret.extend(self._extra_acquisition_element_names.values()) return ret def get_acquisition_elements_str_info(self): return map(self.str_object, self.get_acquisition_elements_info()) def create_controller(self, **kwargs): ctrl_type = kwargs['type'] lib = kwargs['library'] class_name = kwargs['klass'] name = kwargs['name'] elem_type = ElementType[ctrl_type] mod_name, _ = os.path.splitext(lib) kwargs['module'] = mod_name td = TYPE_MAP_OBJ[ElementType.Controller] klass_map = td.klass auto_full_name = td.auto_full_name kwargs['full_name'] = full_name = \ kwargs.get("full_name", auto_full_name.format(**kwargs)) self.check_element(name, full_name) ctrl_class_info = None ctrl_lib_info = self.ctrl_manager.getControllerLib(mod_name) if ctrl_lib_info is not None: ctrl_class_info = ctrl_lib_info.get_controller(class_name) kwargs['pool'] = self kwargs['class_info'] = ctrl_class_info kwargs['lib_info'] = ctrl_lib_info eid = kwargs.get('id') if eid is None: kwargs['id'] = eid = self.get_new_id() else: self.reserve_id(eid) # For pseudo controllers make sure 'role_ids' is given klass = klass_map.get(elem_type, PoolController) if elem_type in TYPE_PSEUDO_ELEMENTS: motor_roles = kwargs['role_ids'] # make sure the properties (that may have come from a case insensitive # environment like tango) are made case sensitive props = {} if ctrl_class_info is None: ctrl_prop_info = {} else: ctrl_prop_info = ctrl_class_info.ctrl_properties for k, v in kwargs['properties'].items(): info = ctrl_prop_info.get(k) if info is None: props[k] = v else: props[info.name] = v kwargs['properties'] = props ctrl = klass(**kwargs) ret = self.add_element(ctrl) self.fire_event(EventType("ElementCreated"), ctrl) return ret def create_element(self, **kwargs): etype = kwargs['type'] ctrl_id = kwargs['ctrl_id'] axis = kwargs['axis'] elem_type = ElementType[etype] name = kwargs['name'] try: ctrl = self.get_element(id=ctrl_id) except: raise Exception("No controller with id '%d' found" % ctrl_id) elem_axis = ctrl.get_element(axis=axis) if elem_axis is not None: raise Exception("Controller already contains axis %d (%s)" % (axis, elem_axis.get_name())) kwargs['pool'] = self kwargs['ctrl'] = ctrl kwargs['ctrl_name'] = ctrl.get_name() td = TYPE_MAP_OBJ[elem_type] klass = td.klass auto_full_name = td.auto_full_name full_name = kwargs.get("full_name", auto_full_name.format(**kwargs)) self.check_element(name, full_name) if ctrl.is_online(): ctrl_types, ctrl_id = ctrl.get_ctrl_types(), ctrl.get_id() if elem_type not in ctrl_types: ctrl_type_str = ElementType.whatis(ctrl_types[0]) raise Exception("Cannot create %s in %s controller" % (etype, ctrl_type_str)) # check if controller is online # check if axis is allowed # create the element in the controller eid = kwargs.get('id') if eid is None: kwargs['id'] = eid = self.get_new_id() else: self.reserve_id(eid) elem = klass(**kwargs) ctrl.add_element(elem) ret = self.add_element(elem) self.fire_event(EventType("ElementCreated"), elem) return ret def create_motor_group(self, **kwargs): name = kwargs['name'] elem_ids = kwargs["user_elements"] kwargs['pool'] = self kwargs["pool_name"] = self.name td = TYPE_MAP_OBJ[ElementType.MotorGroup] klass = td.klass auto_full_name = td.auto_full_name full_name = kwargs.get("full_name", auto_full_name.format(**kwargs)) kwargs.pop('pool_name') self.check_element(name, full_name) for elem_id in elem_ids: elem = self.pool.get_element(id=elem_id) if elem.get_type() not in (ElementType.Motor, ElementType.PseudoMotor): raise Exception("%s is not a motor" % elem.name) eid = kwargs.get('id') if eid is None: kwargs['id'] = eid = self.get_new_id() else: self.reserve_id(eid) elem = klass(**kwargs) ret = self.add_element(elem) self.fire_event(EventType("ElementCreated"), elem) return ret def create_measurement_group(self, **kwargs): name = kwargs['name'] elem_ids = kwargs["user_elements"] kwargs['pool'] = self kwargs["pool_name"] = self.name td = TYPE_MAP_OBJ[ElementType.MeasurementGroup] klass = td.klass auto_full_name = td.auto_full_name full_name = kwargs.get("full_name", auto_full_name.format(**kwargs)) kwargs.pop('pool_name') self.check_element(name, full_name) for elem_id in elem_ids: if isinstance(elem_id, int): self.pool.get_element(id=elem_id) else: tg_attr_validator = TangoAttributeNameValidator() params = tg_attr_validator.getParams(elem_id) if params is None: raise Exception("Invalid channel name %s" % elem_id) eid = kwargs.get('id') if eid is None: kwargs['id'] = eid = self.get_new_id() else: self.reserve_id(eid) elem = klass(**kwargs) ret = self.add_element(elem) self.fire_event(EventType("ElementCreated"), elem) return ret def rename_element(self, old_name, new_name): elem = self.get_element_by_name(old_name) elem.controller.rename_element(old_name, new_name) PoolContainer.rename_element(self, old_name, new_name) elem = self.get_element_by_name(new_name) self.fire_event(EventType("ElementChanged"), elem) def delete_element(self, name): try: elem = self.get_element(name=name) except: try: elem = self.get_element(full_name=name) except: raise Exception("There is no element with name '%s'" % name) elem_type = elem.get_type() if elem_type == ElementType.Controller: if len(elem.get_elements()) > 0: raise Exception("Cannot delete controller with elements. " "Delete elements first") elif elem_type == ElementType.Instrument: if elem.has_instruments(): raise Exception("Cannot delete instrument with instruments. " "Delete instruments first") if elem.has_elements(): raise Exception("Cannot delete instrument with elements") parent_instrument = elem.parent_instrument if parent_instrument is not None: parent_instrument.remove_instrument(elem) elif hasattr(elem, "get_controller"): ctrl = elem.get_controller() ctrl.remove_element(elem) instrument = elem.instrument if instrument is not None: instrument.remove_element(elem) self.remove_element(elem) self.fire_event(EventType("ElementDeleted"), elem) def create_instrument(self, full_name, klass_name, id=None): is_root = full_name.count('/') == 1 if is_root: parent_full_name, _ = '', full_name[1:] parent = None else: parent_full_name, _ = full_name.rsplit('/', 1) try: parent = self.get_element_by_full_name(parent_full_name) except: raise Exception("No parent instrument named '%s' found" % parent_full_name) if parent.get_type() != ElementType.Instrument: raise Exception("%s is not an instrument as expected" % parent_full_name) self.check_element(full_name, full_name) td = TYPE_MAP_OBJ[ElementType.Instrument] klass = td.klass if id is None: id = self.get_new_id() else: self.reserve_id(id) elem = klass(id=id, name=full_name, full_name=full_name, parent=parent, klass=klass_name, pool=self) if parent: parent.add_instrument(elem) ret = self.add_element(elem) self.fire_event(EventType("ElementCreated"), elem) return ret def stop(self): msg = "" controllers = self.get_elements_by_type(ElementType.Controller) for controller in controllers: if controller.is_pseudo(): continue elif ElementType.IORegister in controller.get_ctrl_types(): # Skip IOR since they are not stoppable continue error_elements = controller.stop_elements() if len(error_elements) > 0: element_names = "" for element in error_elements: element_names += element.name + " " msg += ("Controller %s -> %s\n" % (controller.name, element_names)) self.error("Unable to stop %s controller: " "Stop of elements %s failed" % (controller.name, element_names)) if msg: msg_init = "Elements which could not be stopped:\n" raise Exception(msg_init + msg) def abort(self): msg = "" controllers = self.get_elements_by_type(ElementType.Controller) for controller in controllers: if controller.is_pseudo(): continue elif ElementType.IORegister in controller.get_ctrl_types(): # Skip IOR since they are not stoppable continue error_elements = controller.abort_elements() if len(error_elements) > 0: element_names = "" for element in error_elements: element_names += element.name + " " msg += ("Controller %s -> %s\n" % (controller.name, element_names)) self.error("Unable to abort %s controller: " "Abort of elements %s failed" % (controller.name, element_names)) if msg: msg_init = "Elements which could not be aborted:\n" raise Exception(msg_init + msg) # -------------------------------------------------------------------------- # (Re)load code # -------------------------------------------------------------------------- def reload_controller_lib(self, lib_name): manager = self.ctrl_manager old_lib = manager.getControllerLib(lib_name) new_elements, changed_elements, deleted_elements = [], [], [] old_ctrl_classes = () if old_lib is not None: ctrl_infos = old_lib.get_controllers() pool_ctrls = self.get_elements_by_type(ElementType.Controller) init_pool_ctrls = [] for pool_ctrl in pool_ctrls: if pool_ctrl.get_ctrl_info() in ctrl_infos: init_pool_ctrls.append(pool_ctrl) old_ctrl_classes = ctrl_infos changed_elements.append(old_lib) new_lib = manager.reloadControllerLib(lib_name) if old_lib is None: new_elements.extend(new_lib.get_controllers()) new_elements.append(new_lib) else: new_names = set([ctrl.name for ctrl in new_lib.get_controllers()]) old_names = set([ctrl.name for ctrl in old_lib.get_controllers()]) changed_names = set.intersection(new_names, old_names) deleted_names = old_names.difference(new_names) new_names = new_names.difference(old_names) for new_name in new_names: new_elements.append(new_lib.get_controller(new_name)) for changed_name in changed_names: changed_elements.append(new_lib.get_controller(changed_name)) for deleted_name in deleted_names: deleted_elements.append(old_lib.get_controller(deleted_name)) evt = { "new": new_elements, "change": changed_elements, "del": deleted_elements } self.fire_event(EventType("ElementsChanged"), evt) if old_lib is not None: for pool_ctrl in init_pool_ctrls: pool_ctrl.re_init() def reload_controller_class(self, class_name): ctrl_info = self.ctrl_manager.getControllerMetaClass(class_name) lib_name = ctrl_info.module_name self.reload_controller_lib(lib_name) def get_element_id_graph(self): physical_elems_id_map = {} elem_type_map = self.get_element_type_map() for elem_type in TYPE_PHYSICAL_ELEMENTS: physical_elems_id_map.update(elem_type_map[elem_type]) # TODO def _build_element_id_dependencies(self, elem_id, graph=None): if graph is None: graph = Graph() elem = self.get_element_by_id(elem_id) if elem.get_id() in graph or elem.get_type() in TYPE_PHYSICAL_ELEMENTS: return graph graph[elem_id] = list(elem.get_user_element_ids()) return graph def get_moveable_id_graph(self): moveable_elems_id_map = {} elem_type_map = self.get_element_type_map() for elem_type in TYPE_MOVEABLE_ELEMENTS: moveable_elems_id_map.update(elem_type_map[elem_type]) graph = Graph() for moveable_id in moveable_elems_id_map: self._build_element_id_dependencies(moveable_id, graph) return graph def _build_element_dependencies(self, elem, graph=None): if graph is None: graph = Graph() if elem.get_id() in graph or elem.get_type() in TYPE_PHYSICAL_ELEMENTS: return graph graph[elem] = list(elem.get_user_elements()) return graph def get_moveable_graph(self): moveable_elems_map = {} elem_type_map = self.get_element_type_map() for elem_type in TYPE_MOVEABLE_ELEMENTS: moveable_elems_map.update(elem_type_map[elem_type]) graph = Graph() for moveable in moveable_elems_map.values(): self._build_element_dependencies(moveable, graph) return graph
class BaseSardanaElementContainer: def __init__(self): # dict<str, dict> where key is the type and value is: # dict<str, MacroServerElement> where key is the element full name # and value is the Element object self._type_elems_dict = CaselessDict() # dict<str, container> where key is the interface and value is the set # of elements which implement that interface self._interfaces_dict = {} def addElement(self, elem): elem_type = elem.getType() elem_full_name = elem.full_name # update type_elems type_elems = self._type_elems_dict.get(elem_type) if type_elems is None: self._type_elems_dict[elem_type] = type_elems = CaselessDict() type_elems[elem_full_name] = elem # update interfaces for interface in elem.interfaces: interface_elems = self._interfaces_dict.get(interface) if interface_elems is None: self._interfaces_dict[ interface] = interface_elems = CaselessDict() interface_elems[elem_full_name] = elem def removeElement(self, e): elem_type = e.getType() # update type_elems type_elems = self._type_elems_dict.get(elem_type) if type_elems: del type_elems[e.full_name] # update interfaces for interface in e.interfaces: interface_elems = self._interfaces_dict.get(interface) del interface_elems[e.full_name] def removeElementsOfType(self, t): for elem in self.getElementsOfType(t): self.removeElement(elem) def getElementsOfType(self, t): elems = self._type_elems_dict.get(t, {}) return elems def getElementNamesOfType(self, t): return [e.name for e in list(self.getElementsOfType(t).values())] def getElementsWithInterface(self, interface): elems = self._interfaces_dict.get(interface, {}) return elems def getElementsWithInterfaces(self, interfaces): ret = CaselessDict() for interface in interfaces: ret.update(self.getElementsWithInterface(interface)) return ret def getElementNamesWithInterface(self, interface): return [e.name for e in list(self.getElementsWithInterface(interface).values())] def hasElementName(self, elem_name): return self.getElement(elem_name) is not None def getElement(self, elem_name): elem_name = elem_name.lower() for elems in list(self._type_elems_dict.values()): elem = elems.get(elem_name) # full_name? if elem is not None: return elem for elem in list(elems.values()): if elem.name.lower() == elem_name: return elem def getElementWithInterface(self, elem_name, interface): elem_name = elem_name.lower() elems = self._interfaces_dict.get(interface, {}) if elem_name in elems: return elems[elem_name] for elem in list(elems.values()): if elem.name.lower() == elem_name: return elem def getElements(self): ret = set() for elems in list(self._type_elems_dict.values()): ret.update(list(elems.values())) return ret def getInterfaces(self): return self._interfaces_dict def getTypes(self): return self._type_elems_dict
class Pool(PoolContainer, PoolObject, SardanaElementManager, SardanaIDManager): """The central pool class.""" #: Default value representing the number of state reads per position #: read during a motion loop Default_MotionLoop_StatesPerPosition = 10 #: Default value representing the sleep time for each motion loop Default_MotionLoop_SleepTime = 0.01 #: Default value representing the number of state reads per value #: read during a motion loop Default_AcqLoop_StatesPerValue = 10 #: Default value representing the sleep time for each acquisition loop Default_AcqLoop_SleepTime = 0.01 Default_DriftCorrection = True def __init__(self, full_name, name=None): self._path_id = None self._motion_loop_states_per_position = self.Default_MotionLoop_StatesPerPosition self._motion_loop_sleep_time = self.Default_MotionLoop_SleepTime self._acq_loop_states_per_value = self.Default_AcqLoop_StatesPerValue self._acq_loop_sleep_time = self.Default_AcqLoop_SleepTime self._drift_correction = self.Default_DriftCorrection self._remote_log_handler = None # dict<str, dict<str, str>> # keys are acquisition channel names and value is a dict describing the # channel containing: # - 'name': with value being the channel name (given by user) # - 'full_name': acq channel full name (ex: tango attribute) # - 'origin': 'local' if local to this server or 'remote' if a remote # channel self._extra_acquisition_element_names = CaselessDict() PoolContainer.__init__(self) PoolObject.__init__(self, full_name=full_name, name=name, id=InvalidId, pool=self, elem_type=ElementType.Pool) self._monitor = PoolMonitor(self, "PMonitor", auto_start=False) # self.init_local_logging() ControllerManager().set_pool(self) # TODO: not ready to use. path must be the same as the one calculated in # sardana.tango.core.util:prepare_logging def init_local_logging(self): log = logging.getLogger("Controller") log.propagate = 0 path = os.path.join(os.sep, "tmp", "tango") log_file_name = os.path.join(path, 'controller.log.txt') try: if not os.path.exists(path): os.makedirs(path, 0777) f_h = logging.handlers.RotatingFileHandler(log_file_name, maxBytes=1E7, backupCount=5) f_h.setFormatter(self.getLogFormat()) log.addHandler(f_h) self.info("Controller logs stored in %s", log_file_name) except: self.warning("Controller logs could not be created!") self.debug("Details:", exc_info=1) def clear_remote_logging(self): rh = self._remote_log_handler if rh is None: return log = logging.getLogger("Controller") log.removeHandler(rh) self._remote_log_handler = None def init_remote_logging(self, host=None, port=None): """Initializes remote logging. :param host: host name [default: None, meaning use the machine host name as returned by :func:`socket.gethostname`]. :type host: :obj:`str` :param port: port number [default: None, meaning use :data:`logging.handlers.DEFAULT_TCP_LOGGING_PORT`""" log = logging.getLogger("Controller") # port 0 means no remote logging if port == 0: return # first check that the handler has not been initialized yet for handler in log.handlers: if isinstance(handler, logging.handlers.SocketHandler): return if host is None: import socket host = socket.gethostname() #host = socket.getfqdn() if port is None: port = logging.handlers.DEFAULT_TCP_LOGGING_PORT handler = logging.handlers.SocketHandler(host, port) if hasattr(handler, 'retryMax'): # default max retry is 30s which seems too much. Let's make it that # the pool tries to reconnect to a client every 10s (similar to the # tango event reconnection handler.retryMax = 10.0 log.addHandler(handler) self.info("Remote logging initialized for host '%s' on port %d", host, port) def serialize(self, *args, **kwargs): kwargs = PoolObject.serialize(self, *args, **kwargs) kwargs['type'] = self.__class__.__name__ kwargs['id'] = InvalidId kwargs['parent'] = None return kwargs def set_motion_loop_sleep_time(self, motion_loop_sleep_time): self._motion_loop_sleep_time = motion_loop_sleep_time def get_motion_loop_sleep_time(self): return self._motion_loop_sleep_time motion_loop_sleep_time = property(get_motion_loop_sleep_time, set_motion_loop_sleep_time, doc="motion sleep time (s)") def set_motion_loop_states_per_position(self, motion_loop_states_per_position): self._motion_loop_states_per_position = motion_loop_states_per_position def get_motion_loop_states_per_position(self): return self._motion_loop_states_per_position motion_loop_states_per_position = property(get_motion_loop_states_per_position, set_motion_loop_states_per_position, doc="Number of State reads done before doing a position read in the " "motion loop") def set_acq_loop_sleep_time(self, acq_loop_sleep_time): self._acq_loop_sleep_time = acq_loop_sleep_time def get_acq_loop_sleep_time(self): return self._acq_loop_sleep_time acq_loop_sleep_time = property(get_acq_loop_sleep_time, set_acq_loop_sleep_time, doc="acquisition sleep time (s)") def set_acq_loop_states_per_value(self, acq_loop_states_per_value): self._acq_loop_states_per_value = acq_loop_states_per_value def get_acq_loop_states_per_value(self): return self._acq_loop_states_per_value acq_loop_states_per_value = property(get_acq_loop_states_per_value, set_acq_loop_states_per_value, doc="Number of State reads done before doing a value read in the " "acquisition loop") def set_drift_correction(self, drift_correction): self._drift_correction = drift_correction def get_drift_correction(self): return self._drift_correction drift_correction = property(get_drift_correction, set_drift_correction, doc="drift correction") @property def monitor(self): return self._monitor @property def ctrl_manager(self): return ControllerManager() def set_python_path(self, path): mod_man = ModuleManager() if self._path_id is not None: mod_man.remove_python_path(self._path_id) self._path_id = mod_man.add_python_path(path) def set_path(self, path): self.ctrl_manager.setControllerPath(path, reload=False) def get_controller_libs(self): return self.ctrl_manager.getControllerLibs() def get_controller_lib_names(self): return self.ctrl_manager.getControllerLibNames() def get_controller_class_names(self): return self.ctrl_manager.getControllerNames() def get_controller_classes(self): return self.ctrl_manager.getControllers() def get_controller_class_info(self, name): return self.ctrl_manager.getControllerMetaClass(name) def get_controller_classes_info(self, names): return self.ctrl_manager.getControllerMetaClasses(names) def get_controller_libs_summary_info(self): libs = self.get_controller_libs() ret = [] for ctrl_lib_info in libs: elem = "%s (%s)" % (ctrl_lib_info.getName(), ctrl_lib_info.getFileName()) ret.append(elem) return ret def get_controller_classes_summary_info(self): ctrl_classes = self.get_controller_classes() ret = [] for ctrl_class_info in ctrl_classes: types = ctrl_class_info.getTypes() types_str = [TYPE_MAP_OBJ[ t].name for t in types if t != ElementType.Controller] types_str = ", ".join(types_str) elem = "%s (%s) %s" % (ctrl_class_info.getName(), ctrl_class_info.getFileName(), types_str) ret.append(elem) return ret def get_elements_str_info(self, obj_type=None): if obj_type is None: objs = self.get_element_id_map().values() objs.extend(self.get_controller_classes()) objs.extend(self.get_controller_libs()) elif obj_type == ElementType.ControllerClass: objs = self.get_controller_classes() elif obj_type == ElementType.ControllerLibrary: objs = self.get_controller_libs() else: objs = self.get_elements_by_type(obj_type) name = self.full_name return [obj.str(pool=name) for obj in objs] def get_elements_info(self, obj_type=None): if obj_type is None: objs = self.get_element_id_map().values() objs.extend(self.get_controller_classes()) objs.extend(self.get_controller_libs()) objs.append(self) elif obj_type == ElementType.ControllerClass: objs = self.get_controller_classes() elif obj_type == ElementType.ControllerLibrary: objs = self.get_controller_libs() else: objs = self.get_elements_by_type(obj_type) name = self.full_name return [obj.serialize(pool=name) for obj in objs] def get_acquisition_elements_info(self): ret = [] for _, element in self.get_element_name_map().items(): if element.get_type() not in TYPE_ACQUIRABLE_ELEMENTS: continue acq_channel = element.get_default_acquisition_channel() full_name = "{0}/{1}".format(element.full_name, acq_channel) info = dict(name=element.name, full_name=full_name, origin='local') ret.append(info) ret.extend(self._extra_acquisition_element_names.values()) return ret def get_acquisition_elements_str_info(self): return map(self.str_object, self.get_acquisition_elements_info()) def create_controller(self, **kwargs): ctrl_type = kwargs['type'] lib = kwargs['library'] class_name = kwargs['klass'] name = kwargs['name'] elem_type = ElementType[ctrl_type] mod_name, _ = os.path.splitext(lib) kwargs['module'] = mod_name td = TYPE_MAP_OBJ[ElementType.Controller] klass_map = td.klass auto_full_name = td.auto_full_name kwargs['full_name'] = full_name = \ kwargs.get("full_name", auto_full_name.format(**kwargs)) self.check_element(name, full_name) ctrl_class_info = None ctrl_lib_info = self.ctrl_manager.getControllerLib(mod_name) if ctrl_lib_info is not None: ctrl_class_info = ctrl_lib_info.get_controller(class_name) kwargs['pool'] = self kwargs['class_info'] = ctrl_class_info kwargs['lib_info'] = ctrl_lib_info eid = kwargs.get('id') if eid is None: kwargs['id'] = eid = self.get_new_id() else: self.reserve_id(eid) # For pseudo controllers make sure 'role_ids' is given klass = klass_map.get(elem_type, PoolController) if elem_type in TYPE_PSEUDO_ELEMENTS: motor_roles = kwargs['role_ids'] # make sure the properties (that may have come from a case insensitive # environment like tango) are made case sensitive props = {} if ctrl_class_info is None: ctrl_prop_info = {} else: ctrl_prop_info = ctrl_class_info.ctrl_properties for k, v in kwargs['properties'].items(): info = ctrl_prop_info.get(k) if info is None: props[k] = v else: props[info.name] = v kwargs['properties'] = props ctrl = klass(**kwargs) ret = self.add_element(ctrl) self.fire_event(EventType("ElementCreated"), ctrl) return ret def create_element(self, **kwargs): etype = kwargs['type'] ctrl_id = kwargs['ctrl_id'] axis = kwargs['axis'] elem_type = ElementType[etype] name = kwargs['name'] try: ctrl = self.get_element(id=ctrl_id) except: raise Exception("No controller with id '%d' found" % ctrl_id) elem_axis = ctrl.get_element(axis=axis) if elem_axis is not None: raise Exception("Controller already contains axis %d (%s)" % (axis, elem_axis.get_name())) kwargs['pool'] = self kwargs['ctrl'] = ctrl kwargs['ctrl_name'] = ctrl.get_name() td = TYPE_MAP_OBJ[elem_type] klass = td.klass auto_full_name = td.auto_full_name full_name = kwargs.get("full_name", auto_full_name.format(**kwargs)) self.check_element(name, full_name) if ctrl.is_online(): ctrl_types, ctrl_id = ctrl.get_ctrl_types(), ctrl.get_id() if elem_type not in ctrl_types: ctrl_type_str = ElementType.whatis(ctrl_types[0]) raise Exception("Cannot create %s in %s controller" % (etype, ctrl_type_str)) # check if controller is online # check if axis is allowed # create the element in the controller eid = kwargs.get('id') if eid is None: kwargs['id'] = eid = self.get_new_id() else: self.reserve_id(eid) elem = klass(**kwargs) ctrl.add_element(elem) ret = self.add_element(elem) self.fire_event(EventType("ElementCreated"), elem) return ret def create_motor_group(self, **kwargs): name = kwargs['name'] elem_ids = kwargs["user_elements"] kwargs['pool'] = self kwargs["pool_name"] = self.name td = TYPE_MAP_OBJ[ElementType.MotorGroup] klass = td.klass auto_full_name = td.auto_full_name full_name = kwargs.get("full_name", auto_full_name.format(**kwargs)) kwargs.pop('pool_name') self.check_element(name, full_name) for elem_id in elem_ids: elem = self.pool.get_element(id=elem_id) if elem.get_type() not in (ElementType.Motor, ElementType.PseudoMotor): raise Exception("%s is not a motor" % elem.name) eid = kwargs.get('id') if eid is None: kwargs['id'] = eid = self.get_new_id() else: self.reserve_id(eid) elem = klass(**kwargs) ret = self.add_element(elem) self.fire_event(EventType("ElementCreated"), elem) return ret def create_measurement_group(self, **kwargs): name = kwargs['name'] elem_ids = kwargs["user_elements"] kwargs['pool'] = self kwargs["pool_name"] = self.name td = TYPE_MAP_OBJ[ElementType.MeasurementGroup] klass = td.klass auto_full_name = td.auto_full_name full_name = kwargs.get("full_name", auto_full_name.format(**kwargs)) kwargs.pop('pool_name') self.check_element(name, full_name) for elem_id in elem_ids: if isinstance(elem_id, int): self.pool.get_element(id=elem_id) else: tg_attr_validator = TangoAttributeNameValidator() params = tg_attr_validator.getParams(elem_id) if params is None: raise Exception("Invalid channel name %s" % elem_id) eid = kwargs.get('id') if eid is None: kwargs['id'] = eid = self.get_new_id() else: self.reserve_id(eid) elem = klass(**kwargs) ret = self.add_element(elem) self.fire_event(EventType("ElementCreated"), elem) return ret def rename_element(self, old_name, new_name): elem = self.get_element_by_name(old_name) elem.controller.rename_element(old_name, new_name) PoolContainer.rename_element(self, old_name, new_name) elem = self.get_element_by_name(new_name) self.fire_event(EventType("ElementChanged"), elem) def delete_element(self, name): try: elem = self.get_element(name=name) except: try: elem = self.get_element(full_name=name) except: raise Exception("There is no element with name '%s'" % name) elem_type = elem.get_type() if elem_type == ElementType.Controller: if len(elem.get_elements()) > 0: raise Exception("Cannot delete controller with elements. " "Delete elements first") elif elem_type == ElementType.Instrument: if elem.has_instruments(): raise Exception("Cannot delete instrument with instruments. " "Delete instruments first") if elem.has_elements(): raise Exception("Cannot delete instrument with elements") parent_instrument = elem.parent_instrument if parent_instrument is not None: parent_instrument.remove_instrument(elem) elif hasattr(elem, "get_controller"): ctrl = elem.get_controller() ctrl.remove_element(elem) instrument = elem.instrument if instrument is not None: instrument.remove_element(elem) self.remove_element(elem) self.fire_event(EventType("ElementDeleted"), elem) def create_instrument(self, full_name, klass_name, id=None): is_root = full_name.count('/') == 1 if is_root: parent_full_name, _ = '', full_name[1:] parent = None else: parent_full_name, _ = full_name.rsplit('/', 1) try: parent = self.get_element_by_full_name(parent_full_name) except: raise Exception("No parent instrument named '%s' found" % parent_full_name) if parent.get_type() != ElementType.Instrument: raise Exception("%s is not an instrument as expected" % parent_full_name) self.check_element(full_name, full_name) td = TYPE_MAP_OBJ[ElementType.Instrument] klass = td.klass if id is None: id = self.get_new_id() else: self.reserve_id(id) elem = klass(id=id, name=full_name, full_name=full_name, parent=parent, klass=klass_name, pool=self) if parent: parent.add_instrument(elem) ret = self.add_element(elem) self.fire_event(EventType("ElementCreated"), elem) return ret def stop(self): msg = "" controllers = self.get_elements_by_type(ElementType.Controller) for controller in controllers: if controller.is_pseudo(): continue elif ElementType.IORegister in controller.get_ctrl_types(): # Skip IOR since they are not stoppable continue error_elements = controller.stop_elements() if len(error_elements) > 0: element_names = "" for element in error_elements: element_names += element.name + " " msg += ("Controller %s -> %s\n" % (controller.name, element_names)) self.error("Unable to stop %s controller: " "Stop of elements %s failed" % (controller.name, element_names)) if msg: msg_init = "Elements which could not be stopped:\n" raise Exception(msg_init + msg) def abort(self): msg = "" controllers = self.get_elements_by_type(ElementType.Controller) for controller in controllers: if controller.is_pseudo(): continue elif ElementType.IORegister in controller.get_ctrl_types(): # Skip IOR since they are not stoppable continue error_elements = controller.abort_elements() if len(error_elements) > 0: element_names = "" for element in error_elements: element_names += element.name + " " msg += ("Controller %s -> %s\n" % (controller.name, element_names)) self.error("Unable to abort %s controller: " "Abort of elements %s failed" % (controller.name, element_names)) if msg: msg_init = "Elements which could not be aborted:\n" raise Exception(msg_init + msg) # -------------------------------------------------------------------------- # (Re)load code # -------------------------------------------------------------------------- def reload_controller_lib(self, lib_name): manager = self.ctrl_manager old_lib = manager.getControllerLib(lib_name) new_elements, changed_elements, deleted_elements = [], [], [] old_ctrl_classes = () if old_lib is not None: ctrl_infos = old_lib.get_controllers() pool_ctrls = self.get_elements_by_type(ElementType.Controller) init_pool_ctrls = [] for pool_ctrl in pool_ctrls: if pool_ctrl.get_ctrl_info() in ctrl_infos: init_pool_ctrls.append(pool_ctrl) old_ctrl_classes = ctrl_infos changed_elements.append(old_lib) new_lib = manager.reloadControllerLib(lib_name) if old_lib is None: new_elements.extend(new_lib.get_controllers()) new_elements.append(new_lib) else: new_names = set([ctrl.name for ctrl in new_lib.get_controllers()]) old_names = set([ctrl.name for ctrl in old_lib.get_controllers()]) changed_names = set.intersection(new_names, old_names) deleted_names = old_names.difference(new_names) new_names = new_names.difference(old_names) for new_name in new_names: new_elements.append(new_lib.get_controller(new_name)) for changed_name in changed_names: changed_elements.append(new_lib.get_controller(changed_name)) for deleted_name in deleted_names: deleted_elements.append(old_lib.get_controller(deleted_name)) evt = {"new": new_elements, "change": changed_elements, "del": deleted_elements} self.fire_event(EventType("ElementsChanged"), evt) if old_lib is not None: for pool_ctrl in init_pool_ctrls: pool_ctrl.re_init() def reload_controller_class(self, class_name): ctrl_info = self.ctrl_manager.getControllerMetaClass(class_name) lib_name = ctrl_info.module_name self.reload_controller_lib(lib_name) def get_element_id_graph(self): physical_elems_id_map = {} elem_type_map = self.get_element_type_map() for elem_type in TYPE_PHYSICAL_ELEMENTS: physical_elems_id_map.update(elem_type_map[elem_type]) # TODO def _build_element_id_dependencies(self, elem_id, graph=None): if graph is None: graph = Graph() elem = self.get_element_by_id(elem_id) if elem.get_id() in graph or elem.get_type() in TYPE_PHYSICAL_ELEMENTS: return graph graph[elem_id] = list(elem.get_user_element_ids()) return graph def get_moveable_id_graph(self): moveable_elems_id_map = {} elem_type_map = self.get_element_type_map() for elem_type in TYPE_MOVEABLE_ELEMENTS: moveable_elems_id_map.update(elem_type_map[elem_type]) graph = Graph() for moveable_id in moveable_elems_id_map: self._build_element_id_dependencies(moveable_id, graph) return graph def _build_element_dependencies(self, elem, graph=None): if graph is None: graph = Graph() if elem.get_id() in graph or elem.get_type() in TYPE_PHYSICAL_ELEMENTS: return graph graph[elem] = list(elem.get_user_elements()) return graph def get_moveable_graph(self): moveable_elems_map = {} elem_type_map = self.get_element_type_map() for elem_type in TYPE_MOVEABLE_ELEMENTS: moveable_elems_map.update(elem_type_map[elem_type]) graph = Graph() for moveable in moveable_elems_map.values(): self._build_element_dependencies(moveable, graph) return graph