various fixes, state as of 2026-05-18
instead of removing setup files, they are changed to lowlevel, so no longer visible, but not creating any errors when loaded
This commit is contained in:
@@ -39,9 +39,12 @@ from frappy_sinq.secop.devices import SecNodeDevice, SecopDevice, DefunctDevice,
|
||||
from nicos.core.utils import createThread
|
||||
from nicos.utils.comparestrings import compare
|
||||
from nicos.devices.secop.devices import get_attaching_devices
|
||||
from nicos.utils import loggers
|
||||
from nicos.utils import loggers, printTable
|
||||
from nicos.services.daemon.script import ScriptRequest
|
||||
from nicos.commands import helparglist, usercommand
|
||||
from nicos.commands.basic import RemoveSetup, AddSetup
|
||||
from linsetools.frappy import FrappyControl
|
||||
from linsetools.base import write_content
|
||||
|
||||
|
||||
SETUP_TEMPLATE = """description = '%(desc)s'
|
||||
@@ -134,8 +137,7 @@ def shorten(value):
|
||||
return result[:-1] if result.endswith(',') else result
|
||||
|
||||
|
||||
def get_meanings(modname, moddesc):
|
||||
meaning = moddesc['properties'].get('meaning')
|
||||
def get_meanings(modname, meaning, hastarget):
|
||||
result = {}
|
||||
if meaning:
|
||||
meaning_name, importance = meaning
|
||||
@@ -146,7 +148,7 @@ def get_meanings(modname, moddesc):
|
||||
if meaning_name == 'temperature_regulation':
|
||||
# add temperature_regulation to temperature list, with very low importance
|
||||
result.setdefault('temperature', []).append((importance - 100, modname))
|
||||
elif meaning_name == 'temperature' and moddesc['parameters'].get('target'):
|
||||
elif meaning_name == 'temperature' and hastarget:
|
||||
result.setdefault('temperature_regulation', []).append((importance, modname))
|
||||
return result
|
||||
|
||||
@@ -171,12 +173,13 @@ class FrappyManager(Readable):
|
||||
_box2uri = None
|
||||
_uri2box = None
|
||||
_secnodes = None # a dict like Secnodes instance
|
||||
_do_cleanup = False
|
||||
|
||||
def doInit(self, mode):
|
||||
if SETUPDIR not in session._setup_paths:
|
||||
session.log.error('can not use frappy as %r is not in the setup_subdirs', SETUPDIR)
|
||||
expt = session.experiment
|
||||
if 'persistent_environment' in expt.parameters:
|
||||
if 'persistent_environment' in expt.parameters and mode != SIMULATION:
|
||||
expt.persistent_environment = [v for k, v in MEANINGS.items() if k not in SKIP_ENV]
|
||||
else:
|
||||
cls = type(expt)
|
||||
@@ -189,6 +192,10 @@ class FrappyManager(Readable):
|
||||
self._secnodes = secnodes_from_string(value)
|
||||
self._value = shorten(value)
|
||||
|
||||
def _update_secnodes(self, secnodes):
|
||||
self._secnodes = secnodes
|
||||
self.secnodes = secnodes_to_string(self._secnodes)
|
||||
|
||||
def doRead(self, maxage=0):
|
||||
return self._value
|
||||
|
||||
@@ -205,10 +212,6 @@ class FrappyManager(Readable):
|
||||
secnodes = secnodes_from_string(self.secnodes)
|
||||
else:
|
||||
secnodes = {k: v for k, v in self._secnodes.items() if k in self._running or not v.startswith('localhost:')}
|
||||
for name, uri in self._secnodes:
|
||||
if name not in self._running and uri.startswith('localhost.'):
|
||||
continue
|
||||
secnodes[name] = uri
|
||||
if check_connection:
|
||||
for name, uri in list(secnodes.items()):
|
||||
if not uri.startswith('localhost:'):
|
||||
@@ -219,12 +222,7 @@ class FrappyManager(Readable):
|
||||
secopclient.disconnect()
|
||||
self._box2uri[name] = uri
|
||||
self._uri2box[uri] = name
|
||||
self._secnodes = secnodes
|
||||
self.secnodes = secnodes_to_string(secnodes)
|
||||
self._value = shorten(self.secnodes)
|
||||
|
||||
def doPoll(self, maxage=0):
|
||||
self.doRead()
|
||||
self._update_secnodes(secnodes)
|
||||
|
||||
def doStatus(self, maxage=0):
|
||||
return status.OK, ''
|
||||
@@ -259,87 +257,110 @@ class FrappyManager(Readable):
|
||||
self._check_secnodes(False)
|
||||
fc = self._frappy_control
|
||||
|
||||
# determine which servers to stop/start
|
||||
tostop = []
|
||||
tostart = {}
|
||||
# determine which nodes to add (and start) or remove (and stop)
|
||||
to_remove = {}
|
||||
to_add = {}
|
||||
main_item = None
|
||||
stick_item = None
|
||||
for name, uri in self._secnodes:
|
||||
for name, uri in self._secnodes.items():
|
||||
if isinstance(uri, Main):
|
||||
main_item = name
|
||||
elif isinstance(uri, Stick):
|
||||
stick_item = name
|
||||
elif not isinstance(uri, Addons):
|
||||
self.log.warning('%r is no service', uri)
|
||||
if main is not None:
|
||||
if main_item:
|
||||
tostop.append(main_item)
|
||||
self._secnodes.pop(main_item)
|
||||
to_remove[main_item] = self._secnodes.pop(main_item)
|
||||
if main:
|
||||
tostart[main] = Main
|
||||
# auto stick:
|
||||
if main_item != main and stick is None:
|
||||
try:
|
||||
cfg = f'{main}stick'
|
||||
service, cfgfile = fc.cfg_file(None, 'stick', cfg)
|
||||
if service == 'stick':
|
||||
stick = cfg
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
to_add[main] = Main
|
||||
if main != main_item:
|
||||
if main in self._secnodes:
|
||||
to_remove[main] = self._secnodes.pop(main)
|
||||
# auto stick:
|
||||
if stick is None:
|
||||
try:
|
||||
cfg = f'{main}stick'
|
||||
service, cfgfile = fc.cfg_file(None, 'stick', cfg)
|
||||
if service == 'stick':
|
||||
stick = cfg
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
if stick is not None:
|
||||
if stick_item:
|
||||
tostop.append(stick_item)
|
||||
self._secnodes.pop(stick_item)
|
||||
to_remove[stick_item] = self._secnodes.pop(stick_item)
|
||||
if stick:
|
||||
tostart[stick] = Stick
|
||||
to_add[stick] = Stick
|
||||
if stick in self._secnodes:
|
||||
to_remove[stick] = self._secnodes.pop(stick)
|
||||
for arglist in (addons,) + extra:
|
||||
if arglist == '':
|
||||
tostop.extend(self._secnodes)
|
||||
to_remove.update(self._secnodes)
|
||||
self._secnodes.clear()
|
||||
elif arglist is not None:
|
||||
# allow legacy '<cfg1>,<cfg2>'
|
||||
for arg in arglist.split(','):
|
||||
if arg:
|
||||
tostart[arg] = None
|
||||
to_add[arg] = Addons
|
||||
prev = self._secnodes.get(arg)
|
||||
if isinstance(prev, (Main, Stick)):
|
||||
to_remove[arg] = self._secnodes.pop(arg)
|
||||
|
||||
toremove = []
|
||||
for cfg in self._running:
|
||||
if cfg is None:
|
||||
continue
|
||||
# TODO: when using nodename for setup, this may not work here ...
|
||||
setup = self.get_setup_name(cfg)
|
||||
if setup in session.loaded_setups:
|
||||
toremove.append(setup)
|
||||
if ':' in cfg:
|
||||
continue
|
||||
if toremove:
|
||||
RemoveSetup(*toremove)
|
||||
for cfg in tostop:
|
||||
fc.stop(cfg)
|
||||
if cfg not in tostart:
|
||||
fc.delete_frappy_service(cfg)
|
||||
# remove_setups = {self.get_setup_name(cfg) for cfg in to_remove} & set(session.loaded_setups)
|
||||
# if remove_setups:
|
||||
# RemoveSetup(*remove_setups)
|
||||
for cfg in to_remove:
|
||||
secnode_dev = session.devices.get(f'secnode_{cfg}')
|
||||
if secnode_dev:
|
||||
secnode_dev.doShutdown()
|
||||
for cfg, uri in to_remove.items():
|
||||
if uri.startswith('localhost:'):
|
||||
fc.stop(cfg)
|
||||
fc.delete_frappy_service(cfg)
|
||||
|
||||
toadd = []
|
||||
add_setups = []
|
||||
# add and start new servers
|
||||
for arg, service in tostart.items():
|
||||
uri, name = self.get_uri_name(arg, service)
|
||||
for arg, servicecls in to_add.items():
|
||||
uri, name = self.get_uri_name(arg, servicecls)
|
||||
cfginfo = ''
|
||||
if isinstance(uri, Service):
|
||||
if uri.startswith('localhost:'): # isinstance(uri, Service):
|
||||
port = fc.get_port(uri.name)
|
||||
uri = fc.add_frappy_service(uri.name, name, port, session.log)
|
||||
uri = servicecls(fc.add_frappy_service(servicecls.name, name, port, session.log))
|
||||
cfginfo = f' ({name})'
|
||||
fc.start(name)
|
||||
session.log.info('wait for startup of %r%s', uri, cfginfo)
|
||||
session.log.info('wait for startup of %r%s', uri, cfginfo)
|
||||
else:
|
||||
session.log.info('connect to %r%s', uri, cfginfo)
|
||||
secopclient = self.connect_secnode(uri)
|
||||
if secopclient:
|
||||
self.write_setup_file(secopclient, uri, name, arg)
|
||||
secopclient.disconnect()
|
||||
toadd.append(self.get_setup_name(name))
|
||||
add_setups.append(self.get_setup_name(name))
|
||||
self._secnodes[name] = uri
|
||||
else:
|
||||
self._secnodes.pop(name, None)
|
||||
session.log.exception('cannot connect to %r%s', uri, cfginfo)
|
||||
self._update_secnodes(self._secnodes)
|
||||
remove_setups = {self.get_setup_name(cfg) for cfg in to_remove} & set(session.loaded_setups)
|
||||
noadd_set = session.loaded_setups - set(remove_setups)
|
||||
add_setups = [v for v in add_setups if v not in noadd_set]
|
||||
|
||||
if remove_setups:
|
||||
RemoveSetup(*remove_setups)
|
||||
if add_setups:
|
||||
AddSetup(*add_setups)
|
||||
session.readSetups()
|
||||
if toadd:
|
||||
AddSetup(*toadd)
|
||||
self._running = self._frappy_control.running()
|
||||
return self.read()
|
||||
unused_setups = {f for f in Path(SETUPDIR).glob('se_*.py')
|
||||
if f.stem not in session.loaded_setups and f.stem[3:] not in self._secnodes}
|
||||
if unused_setups:
|
||||
for setup_file in unused_setups:
|
||||
content = setup_file.read_text()
|
||||
newcontent = re.sub("\ngroup = '(optional|plugplay)'", "\ngroup = 'lowlevel'", content)
|
||||
if newcontent != content:
|
||||
self.log.info('hide unused setup %s', setup_file.stem)
|
||||
write_content(setup_file, newcontent, group='+rw')
|
||||
session.readSetups()
|
||||
return self.read(0)
|
||||
|
||||
def get_setup_name(self, cfg):
|
||||
cfg = cfg.replace(':', '_')
|
||||
@@ -359,30 +380,17 @@ class FrappyManager(Readable):
|
||||
depending on their meaning
|
||||
"""
|
||||
modules = secopclient.modules
|
||||
result = {} # dict <meaning name> of list of (<importance>, <target>)
|
||||
meaning_info = {} # dict <meaning name> of list of (<importance>, <target>)
|
||||
device_mapping = {}
|
||||
reserved_names = {v.lower() for v in MEANINGS.values()}
|
||||
for modname, moddesc in modules.items():
|
||||
if modname.lower() in reserved_names:
|
||||
if self.prefix == '' and modname.lower() in reserved_names:
|
||||
device_mapping[modname] = {'name': f'{modname}_'}
|
||||
# meanings = self.get_meanings(modname, moddesc)
|
||||
|
||||
|
||||
meaning = moddesc['properties'].get('meaning')
|
||||
if meaning:
|
||||
meaning_name, importance = meaning
|
||||
if meaning_name not in MEANINGS:
|
||||
self.log.warning('%s: meaning %r is unknown', modname, meaning_name)
|
||||
continue
|
||||
result.setdefault(meaning_name, []).append((importance, modname))
|
||||
if meaning_name == 'temperature_regulation':
|
||||
# add temperature_regulation to temperature list, with very low importance
|
||||
result.setdefault('temperature', []).append((importance - 100, modname))
|
||||
elif meaning_name == 'temperature' and moddesc['parameters'].get('target'):
|
||||
result.setdefault('temperature_regulation', []).append((importance, modname))
|
||||
meaning_info.update(get_meanings(modname, moddesc['properties'].get('meaning'),
|
||||
'target' in moddesc['parameters']))
|
||||
envlist = []
|
||||
alias_config = {}
|
||||
for meaning_name, info in result.items():
|
||||
for meaning_name, info in meaning_info.items():
|
||||
importance, modname = sorted(info)[-1]
|
||||
devname = self._get_device_name(device_mapping, modname)
|
||||
target = MEANINGS.get(meaning_name)
|
||||
@@ -395,16 +403,15 @@ class FrappyManager(Readable):
|
||||
|
||||
def write_setup_file(self, secopclient, uri, name, origname):
|
||||
setup = self.get_setup_name(name)
|
||||
setup_file = Path(SETUPDIR) / f'{setup}.py'
|
||||
if name != origname:
|
||||
# sanitize name: replace sequences of non-alphanumeric characters by a single '_'
|
||||
# also prefix '_' when starting with a digit
|
||||
# and remove trailing .psi.ch
|
||||
name = IDSUB.sub(secopclient.nodename.replace('.psi.ch', ''), '_')
|
||||
self._box2uri[name] = uri
|
||||
envlist, alias_config, devmap = self.node_setup_info(secopclient)
|
||||
nodeargs = f'device_mapping={devmap!r}' if devmap else ''
|
||||
additions = []
|
||||
if envlist:
|
||||
aliassetups = [f'{k}_alias' for k in envlist]
|
||||
# additions.append(f'includes = {aliassetups!r}')
|
||||
if alias_config:
|
||||
additions.append(f'alias_config = {alias_config!r}')
|
||||
desc = secopclient.properties.get('description') or name
|
||||
@@ -415,18 +422,24 @@ class FrappyManager(Readable):
|
||||
'uri': repr(uri),
|
||||
'nodeargs': nodeargs,
|
||||
'additions': '\n'.join(additions),}
|
||||
setup_file.write_text(setup_content)
|
||||
prev_mode = setup_file.stat().st_mode
|
||||
if not prev_mode & 0x20:
|
||||
setup_file.chmod(prev_mode | 0x20)
|
||||
write_content(Path(SETUPDIR) / f'{setup}.py', setup_content, group='+rw')
|
||||
return envlist
|
||||
|
||||
def adjustEnvironment(self):
|
||||
if self._mode == SIMULATION:
|
||||
return
|
||||
self._do_cleanup = True
|
||||
session.readSetups()
|
||||
# user = type('User', (), {'name': 'ghost'})()
|
||||
# session.daemon_device._controller.new_request(ScriptRequest('frappy.cleanup_setups()', '', user))
|
||||
samenvlist = []
|
||||
for meaning, devname in MEANINGS.items():
|
||||
dev = session.devices.get(devname)
|
||||
if dev:
|
||||
try:
|
||||
dev.read(0)
|
||||
except Exception as e:
|
||||
self.log.info('error %r when reading %s', e, dev)
|
||||
if meaning in SKIP_ENV:
|
||||
continue
|
||||
if dev:
|
||||
@@ -450,6 +463,7 @@ class FrappyManager(Readable):
|
||||
elif devname not in previous:
|
||||
envlist.append(devname)
|
||||
expt.setEnvironment(envlist)
|
||||
self.read(0)
|
||||
|
||||
|
||||
def cleanup_defunct():
|
||||
@@ -499,13 +513,15 @@ class FrappyNode(SecNodeDevice):
|
||||
def createDevices(self):
|
||||
super().createDevices()
|
||||
self.log.info('--- create devices ---')
|
||||
secnode = self._attached_secnode
|
||||
for devname, (_, devcfg) in self.setup_info.items():
|
||||
meanings = get_meanings(devcfg['secop_module'], devcfg['secop_properties'])
|
||||
params_cfg = devcfg['params_cfg']
|
||||
meanings = get_meanings(
|
||||
devcfg['secop_module'],
|
||||
devcfg['secop_properties'].get('meaning'),
|
||||
'target' in params_cfg)
|
||||
for meaning in meanings:
|
||||
alias = MEANINGS.get(meaning)
|
||||
if alias and alias not in session.devices:
|
||||
self.log.info('create alias %r for %s', alias, meaning)
|
||||
# alias is not yet created
|
||||
devcfg = ('nicos.core.device.DeviceAlias', {'descripton': meaning})
|
||||
session.configured_devices[alias] = devcfg
|
||||
@@ -514,7 +530,6 @@ class FrappyNode(SecNodeDevice):
|
||||
elif not alias:
|
||||
self.log.info('do not know meaning %s', meaning)
|
||||
if self.param_category:
|
||||
params_cfg = devcfg['params_cfg']
|
||||
dev = session.devices[devname]
|
||||
for pname, pargs in params_cfg.items():
|
||||
pinfo = dev.parameters[pname]
|
||||
@@ -582,3 +597,27 @@ class NullDevice(Moveable):
|
||||
|
||||
def doStart(self, target):
|
||||
self.log.warning('disabled, cannot not move')
|
||||
|
||||
|
||||
@usercommand
|
||||
@helparglist('')
|
||||
def frappy_list(service=None):
|
||||
"""list available configuration files"""
|
||||
fc = FrappyControl('this')
|
||||
content = []
|
||||
|
||||
def prt(line):
|
||||
content.append(line)
|
||||
|
||||
if service is None:
|
||||
prt('Available configuration files')
|
||||
prt('')
|
||||
prt('Hint: if no config file can be found which matches your needs exactly')
|
||||
prt('make a copy of an existing one, and change the description accordingly')
|
||||
prt('')
|
||||
prt('Usage (default argument "main"):')
|
||||
prt('')
|
||||
printTable(['command'], [['frappy_list(%r)' % s] for s in fc.services], prt)
|
||||
|
||||
fc.listcfg(service or 'main', prt)
|
||||
session.log.info('\n%s', '\n'.join(content))
|
||||
|
||||
+14
-17
@@ -82,6 +82,8 @@ except ImportError:
|
||||
|
||||
class NicosSecopClient(SecopClient):
|
||||
SPECIAL_NAMES = {'target', 'status', 'stop', 'pollinterval', 'reset'}
|
||||
MANGLED = re.compile(r'(.*)(_*)$')
|
||||
forbidden_names = set()
|
||||
|
||||
def internalize_name(self, name):
|
||||
"""name mangling
|
||||
@@ -93,22 +95,18 @@ class NicosSecopClient(SecopClient):
|
||||
"""
|
||||
if name in self.SPECIAL_NAMES:
|
||||
return name
|
||||
name = super().internalize_name(name).lower()
|
||||
prefix, sep, postfix = name.partition('_')
|
||||
if prefix not in dir(SecopMoveable):
|
||||
return name
|
||||
# mangle names matching NICOS device attributes
|
||||
# info -> info_, info_ -> info_1, info_1 -> info_2, ...
|
||||
if not sep:
|
||||
if not self.forbidden_names:
|
||||
self.forbidden_names.update(k.lower() for k in dir(SecopMoveable))
|
||||
name = super().internalize_name(name)
|
||||
match = self.MANGLED.match(name)
|
||||
if match:
|
||||
basename, postfix = match.groups()
|
||||
else:
|
||||
basename, postfix = name, ''
|
||||
if basename.lower() in self.forbidden_names:
|
||||
# mangle names matching NICOS device attributes
|
||||
# info -> info_, info_ -> info__, ...
|
||||
return name + '_'
|
||||
if not postfix:
|
||||
return name + '_1'
|
||||
try:
|
||||
num = int(postfix)
|
||||
if str(num) == postfix:
|
||||
return '%s_%d' % (prefix, num + 1)
|
||||
except ValueError:
|
||||
pass
|
||||
return name
|
||||
|
||||
|
||||
@@ -330,8 +328,7 @@ class SecNodeDevice(Readable):
|
||||
def get_setup_info(self):
|
||||
if self._mode == SIMULATION:
|
||||
db = session._getSyncDb()
|
||||
# we might do here in addition:
|
||||
session.simulationSync(db)
|
||||
# db = session.simulation_db
|
||||
return db.get('%s/setup_info' % self.name.lower())
|
||||
return self.setup_info
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ description = 'sample environment basics'
|
||||
group = 'optional'
|
||||
display_order = 50.5
|
||||
|
||||
# modules = ['frappy_sinq.commandsnew']
|
||||
modules = ['frappy_sinq.new']
|
||||
|
||||
devices = dict(
|
||||
frappy = device(
|
||||
|
||||
Reference in New Issue
Block a user