move files in from repo git.psi.ch/sinqdev/nicos-sinq
This commit is contained in:
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
log/*
|
||||||
|
html/*
|
||||||
|
*.pyc
|
||||||
|
pid/*
|
||||||
|
__pycache__
|
||||||
|
|
||||||
|
# ide
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Mac
|
||||||
|
.DS_Store
|
||||||
|
._*
|
||||||
|
|
||||||
|
# pytest
|
||||||
|
.cache
|
||||||
|
.coverage
|
||||||
|
|
||||||
|
# pyinstaller
|
||||||
|
dist/
|
207
commands.py
Normal file
207
commands.py
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from os.path import expanduser, basename, join
|
||||||
|
from glob import glob
|
||||||
|
from configparser import ConfigParser
|
||||||
|
|
||||||
|
from nicos import session, config
|
||||||
|
from nicos.utils import printTable
|
||||||
|
from nicos.commands import helparglist, usercommand
|
||||||
|
from nicos.commands.basic import AddSetup, CreateAllDevices, CreateDevice
|
||||||
|
from nicos.devices.secop import get_attaching_devices
|
||||||
|
|
||||||
|
home = expanduser('~')
|
||||||
|
if home not in sys.path:
|
||||||
|
# the first Frappy installations have /home/nicos in the PYTHONPATH in nicos.conf
|
||||||
|
# for newer Frappy installations this should be home (= /home/<instrument>)
|
||||||
|
# the following line fixes this in case nicos.conf is not yet updated
|
||||||
|
sys.path.append(home)
|
||||||
|
from nicos_sinq.frappy.devices import FrappyNode
|
||||||
|
from servicemanager import FrappyManager
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_defunct():
|
||||||
|
for devname, setupname in list(session.dynamic_devices.items()):
|
||||||
|
dev = session.devices.get(devname)
|
||||||
|
if dev and dev._defunct:
|
||||||
|
devnames = [d.name for d, _ in get_attaching_devices(dev)]
|
||||||
|
if devnames:
|
||||||
|
session.log.warning('can not remove device %r due to dependencies on %s'
|
||||||
|
% (devname, ', '.join(devnames)))
|
||||||
|
else:
|
||||||
|
session.destroyDevice(devname)
|
||||||
|
session.dynamic_devices.pop(devname, None)
|
||||||
|
|
||||||
|
|
||||||
|
SERVICES = FrappyManager.services
|
||||||
|
|
||||||
|
|
||||||
|
def all_info(all_cfg):
|
||||||
|
info = []
|
||||||
|
for srv, cfginfo in all_cfg.items():
|
||||||
|
if cfginfo is not None:
|
||||||
|
info.append('frappy_%s(%r)' % (srv, cfginfo))
|
||||||
|
return 'currently configured: %s' % ', '.join(info)
|
||||||
|
|
||||||
|
|
||||||
|
def applyAliasConfig():
|
||||||
|
"""Apply the desired aliases from session.alias_config."""
|
||||||
|
# reimplemented from Session.applyAliasConfig
|
||||||
|
# apply also when target dev name does not change, as the target device might have
|
||||||
|
# be exchanged in the mean time
|
||||||
|
for aliasname, targets in session.alias_config.items():
|
||||||
|
if aliasname not in session.devices:
|
||||||
|
# complain about this; setups should make sure that the device
|
||||||
|
# exists when configuring it
|
||||||
|
session.log.warning("alias device '%s' does not exist, cannot set"
|
||||||
|
' its target', aliasname)
|
||||||
|
continue
|
||||||
|
aliasdev = session.getDevice(aliasname)
|
||||||
|
for target, _ in sorted(targets, key=lambda t: -t[1]):
|
||||||
|
if target in session.devices:
|
||||||
|
try:
|
||||||
|
aliasdev.alias = target
|
||||||
|
except Exception:
|
||||||
|
session.log.exception("could not set '%s' alias", aliasdev)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
session.log.warning('none of the desired targets for alias %r '
|
||||||
|
'actually exist', aliasname)
|
||||||
|
|
||||||
|
|
||||||
|
def frappy_start(service, cfg=None):
|
||||||
|
if service not in SERVICES:
|
||||||
|
raise ValueError('unknown service %s' % service)
|
||||||
|
if cfg == '':
|
||||||
|
seaconn = session.devices.get('seaconn')
|
||||||
|
if seaconn and seaconn._attached_secnode:
|
||||||
|
seaconn.communicate('frappy_remove %s' % service)
|
||||||
|
startnode = None
|
||||||
|
all_cfg = {}
|
||||||
|
for srv in SERVICES:
|
||||||
|
nodename = 'se_' + srv
|
||||||
|
secnode = session.devices.get(nodename)
|
||||||
|
|
||||||
|
cfginfo = None if secnode is None else secnode()
|
||||||
|
if srv == service:
|
||||||
|
if cfg is not None:
|
||||||
|
if cfg and not secnode:
|
||||||
|
AddSetup('frappy_' + service)
|
||||||
|
secnode = session.devices[nodename]
|
||||||
|
if cfginfo:
|
||||||
|
secnode('')
|
||||||
|
startnode = secnode
|
||||||
|
cfginfo = cfg
|
||||||
|
elif cfg and cfginfo:
|
||||||
|
# remark: cfg might be a comma separated list of configurations
|
||||||
|
ourcfg = set(cfg.split(','))
|
||||||
|
other = set(cfginfo.split(','))
|
||||||
|
both = other & ourcfg
|
||||||
|
if both:
|
||||||
|
# remove ourcfg from other
|
||||||
|
cfginfo = ','.join(other - ourcfg)
|
||||||
|
# stop other server with the same cfg
|
||||||
|
# or restart with remaining cfgs
|
||||||
|
secnode(cfginfo)
|
||||||
|
all_cfg[srv] = cfginfo
|
||||||
|
if startnode and cfg:
|
||||||
|
startnode(cfg)
|
||||||
|
CreateDevice(str(startnode))
|
||||||
|
if cfg is not None:
|
||||||
|
cleanup_defunct()
|
||||||
|
CreateAllDevices()
|
||||||
|
applyAliasConfig()
|
||||||
|
if startnode and cfg == '':
|
||||||
|
startnode.disable()
|
||||||
|
return all_cfg
|
||||||
|
|
||||||
|
|
||||||
|
@usercommand
|
||||||
|
@helparglist('cfg')
|
||||||
|
def frappy_main(cfg=None):
|
||||||
|
"""(re)start frappy_main server with given cfg and load setup if needed
|
||||||
|
|
||||||
|
- without argument: list running frappy servers
|
||||||
|
- cfg = "": stop frappy_main server
|
||||||
|
"""
|
||||||
|
all_cfg = frappy_start('main', cfg)
|
||||||
|
if cfg:
|
||||||
|
stickcfg = cfg + 'stick'
|
||||||
|
if stickcfg in FrappyNode.available_cfg('stick'):
|
||||||
|
# if a default stick is available, start this also
|
||||||
|
all_cfg = frappy_start('stick', stickcfg)
|
||||||
|
session.log.info(all_info(all_cfg))
|
||||||
|
|
||||||
|
|
||||||
|
@usercommand
|
||||||
|
@helparglist('cfg')
|
||||||
|
def frappy_stick(cfg=None):
|
||||||
|
"""(re)start frappy_stick server with given cfg and load setup if needed
|
||||||
|
|
||||||
|
- without argument: list running frappy servers
|
||||||
|
- cfg = "": stop frappy_stick server
|
||||||
|
"""
|
||||||
|
session.log.info(all_info(frappy_start('stick', cfg)))
|
||||||
|
|
||||||
|
|
||||||
|
@usercommand
|
||||||
|
@helparglist('cfg,...')
|
||||||
|
def frappy_addons(cfg=None):
|
||||||
|
"""(re)start frappy_addons server with given cfg and load setup if needed
|
||||||
|
|
||||||
|
- without argument: list running frappy servers
|
||||||
|
- cfg = "": stop frappy_addons server
|
||||||
|
"""
|
||||||
|
session.log.info(all_info(frappy_start('addons', cfg)))
|
||||||
|
|
||||||
|
|
||||||
|
@usercommand
|
||||||
|
@helparglist('')
|
||||||
|
def frappy_list(service=None):
|
||||||
|
"""list available configuration files"""
|
||||||
|
table = []
|
||||||
|
bases = list(dict.fromkeys(expanduser(p) for p in FrappyNode.config_dirs(config.instrument, service or 'main')))
|
||||||
|
if service is None:
|
||||||
|
session.log.info('Available configuration files')
|
||||||
|
session.log.info(' ')
|
||||||
|
session.log.info('Hint: if no config file can be found which matches your needs exactly')
|
||||||
|
session.log.info('make a copy of an existing one, and change the description accordingly')
|
||||||
|
session.log.info(' ')
|
||||||
|
session.log.info('Usage (default argument "main"):')
|
||||||
|
session.log.info(' ')
|
||||||
|
printTable(['command'], [['frappy_list(%r)' % s] for s in SERVICES], session.log.info)
|
||||||
|
session.log.info(' ')
|
||||||
|
for cfgdir in bases:
|
||||||
|
for cfgfile in glob(join(cfgdir, '*.cfg')):
|
||||||
|
parser = ConfigParser()
|
||||||
|
parser.read(cfgfile)
|
||||||
|
desc = ''
|
||||||
|
for s in parser.sections():
|
||||||
|
if s == 'NODE' or s.startswith('node '):
|
||||||
|
desc = parser[s].get('description', '').split('\n')[0]
|
||||||
|
break
|
||||||
|
table.append([basename(cfgfile)[:-4], desc])
|
||||||
|
printTable(['cfg file', 'description'], table, session.log.info)
|
||||||
|
|
||||||
|
|
150
devices.py
Normal file
150
devices.py
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# *****************************************************************************
|
||||||
|
# NICOS, the Networked Instrument Control System of the MLZ
|
||||||
|
# Copyright (c) 2009-2018 by the NICOS contributors (see AUTHORS)
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
"""managing SECoP server and connections
|
||||||
|
|
||||||
|
SEC Node with added functionality for starting and stoping frappy servers
|
||||||
|
connected to a SEC node
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from os.path import expanduser
|
||||||
|
|
||||||
|
from nicos import config, session
|
||||||
|
from nicos.core import Override, Param, Moveable, status
|
||||||
|
from nicos.devices.secop import SecNodeDevice
|
||||||
|
from nicos.utils.comparestrings import compare
|
||||||
|
from servicemanager import FrappyManager
|
||||||
|
|
||||||
|
|
||||||
|
def suggest(poi, allowed_keys):
|
||||||
|
comp = {}
|
||||||
|
for key in allowed_keys:
|
||||||
|
comp[key] = compare(poi, key)
|
||||||
|
comp = sorted(comp.items(), key=lambda t: t[1], reverse=True)
|
||||||
|
return [m[0] for m in comp[:3] if m[1] > 2]
|
||||||
|
|
||||||
|
|
||||||
|
class FrappyNode(SecNodeDevice, Moveable):
|
||||||
|
"""SEC node device
|
||||||
|
|
||||||
|
with ability to start / restart / stop the frappy server
|
||||||
|
"""
|
||||||
|
|
||||||
|
parameter_overrides = {
|
||||||
|
'target': Override(description='configuration for the frappy server or host:port',
|
||||||
|
type=str, default=''),
|
||||||
|
}
|
||||||
|
parameters = {
|
||||||
|
'service': Param('frappy service name (main, stick or addons)', type=str, default=''),
|
||||||
|
}
|
||||||
|
|
||||||
|
_service_manager = FrappyManager()
|
||||||
|
_cfgvalue = None
|
||||||
|
|
||||||
|
def doStart(self, value):
|
||||||
|
if value == 'None':
|
||||||
|
value = None
|
||||||
|
self.restart(value)
|
||||||
|
|
||||||
|
def doStop(self):
|
||||||
|
"""never busy"""
|
||||||
|
|
||||||
|
def doRead(self, maxage=0):
|
||||||
|
if self._cfgvalue is None and self._cache:
|
||||||
|
self._cfgvalue = self._cache.get(self, 'value')
|
||||||
|
return self._cfgvalue
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def config_dirs(cls, ins, service):
|
||||||
|
sm = cls._service_manager
|
||||||
|
sm.get_info()
|
||||||
|
return sm.config_dirs(ins, service)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def available_cfg(cls, service):
|
||||||
|
ins = config.instrument
|
||||||
|
available_cfg = set()
|
||||||
|
for d in cls.config_dirs(ins, service):
|
||||||
|
try:
|
||||||
|
available_cfg |= set(c[:-4] for c in os.listdir(expanduser(d)) if c.endswith('.cfg'))
|
||||||
|
except FileNotFoundError: # ignore missing directories
|
||||||
|
pass
|
||||||
|
return available_cfg
|
||||||
|
|
||||||
|
def disable(self):
|
||||||
|
seaconn = session.devices.get('seaconn')
|
||||||
|
if seaconn and seaconn._attached_secnode:
|
||||||
|
seaconn.communicate('frappy_remove %s' % self.service)
|
||||||
|
self._set_status(*self._status)
|
||||||
|
|
||||||
|
def _set_status(self, code, text):
|
||||||
|
if self.uri == '':
|
||||||
|
code, text = status.DISABLED, 'disabled'
|
||||||
|
SecNodeDevice._set_status(self, code, text)
|
||||||
|
|
||||||
|
def restart(self, cfg=None):
|
||||||
|
"""restart frappy server
|
||||||
|
|
||||||
|
if value is not given: restart with the same config
|
||||||
|
else restart with given config
|
||||||
|
"""
|
||||||
|
was_cfg = self._cfgvalue and ':' not in self._cfgvalue
|
||||||
|
if cfg is None: # do restart anyway
|
||||||
|
cfg = self._cfgvalue
|
||||||
|
elif cfg == self._cfgvalue: # probably called from doStart
|
||||||
|
if cfg == '':
|
||||||
|
self.disable()
|
||||||
|
# do not restart in this case
|
||||||
|
return
|
||||||
|
is_cfg = cfg and ':' not in cfg
|
||||||
|
ins = config.instrument
|
||||||
|
info = self._service_manager.get_ins_info(ins)
|
||||||
|
if is_cfg:
|
||||||
|
available_cfg = self.available_cfg(self.service)
|
||||||
|
failed = False
|
||||||
|
for cfgitem in cfg.split(','):
|
||||||
|
if cfgitem not in available_cfg:
|
||||||
|
failed = True
|
||||||
|
suggestions = suggest(cfgitem, available_cfg)
|
||||||
|
if suggestions:
|
||||||
|
session.log.error('%s unknown, did you mean: %s' % (cfgitem, ', '.join(suggestions)))
|
||||||
|
if failed:
|
||||||
|
raise ValueError('use "frappy_list()" to get a list of valid frappy configurations')
|
||||||
|
uri = 'localhost:%d' % info[self.service]
|
||||||
|
else:
|
||||||
|
uri = cfg
|
||||||
|
if uri != self.uri:
|
||||||
|
self.uri = '' # disconnect
|
||||||
|
if was_cfg:
|
||||||
|
self._service_manager.do_stop(ins, self.service)
|
||||||
|
if uri:
|
||||||
|
if is_cfg:
|
||||||
|
self._service_manager.do_restart(ins, self.service, cfg, self.log)
|
||||||
|
self.uri = uri # connect
|
||||||
|
else:
|
||||||
|
self.disable()
|
||||||
|
self._cfgvalue = cfg
|
||||||
|
if self._cache:
|
||||||
|
self._cache.put(self, 'value', cfg)
|
||||||
|
self._setROParam('target', cfg)
|
26
setups/frappy.py
Normal file
26
setups/frappy.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
description = 'frappy'
|
||||||
|
group = 'optional'
|
||||||
|
|
||||||
|
modules = ['nicos_sinq.frappy.commands']
|
||||||
|
|
||||||
|
devices = {
|
||||||
|
'temperature': device('nicos.devices.generic.DeviceAlias'),
|
||||||
|
'magfield': device('nicos.devices.generic.DeviceAlias'),
|
||||||
|
}
|
||||||
|
alias_config = {
|
||||||
|
'temperature': {'se_ts': 110, 'se_tt': 100},
|
||||||
|
'magfield': {'se_mf': 100},
|
||||||
|
}
|
||||||
|
|
||||||
|
startupcode = '''
|
||||||
|
printinfo("=======================================================================================")
|
||||||
|
printinfo("Welcome to the NICOS frappy secnode setup!")
|
||||||
|
printinfo(" ")
|
||||||
|
printinfo("Usage:")
|
||||||
|
printinfo(" frappy_main('<main cfg>') # change main SE configuration (e.g. cryostat)")
|
||||||
|
printinfo(" frappy_stick('<stick cfg>') # change sample-stick configuration")
|
||||||
|
printinfo(" frappy_stick('') # remove stick")
|
||||||
|
printinfo(" frappy_main('') # remove main SE apparatus")
|
||||||
|
printinfo(" frappy_main() # show the current SE configuration")
|
||||||
|
printinfo("=======================================================================================")
|
||||||
|
'''
|
11
setups/frappy_addons.py
Normal file
11
setups/frappy_addons.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from os import environ
|
||||||
|
description = 'frappy addons'
|
||||||
|
group = 'optional'
|
||||||
|
|
||||||
|
devices = {
|
||||||
|
'se_addons':
|
||||||
|
device('nicos_sinq.frappy.devices.FrappyNode',
|
||||||
|
description='SEC node', unit='',
|
||||||
|
prefix='se_', auto_create=True, service='addons',
|
||||||
|
),
|
||||||
|
}
|
11
setups/frappy_main.py
Normal file
11
setups/frappy_main.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from os import environ
|
||||||
|
description = 'frappy main setup'
|
||||||
|
group = 'optional'
|
||||||
|
|
||||||
|
devices = {
|
||||||
|
'se_main':
|
||||||
|
device('nicos_sinq.frappy.devices.FrappyNode',
|
||||||
|
description='main SEC node', unit='',
|
||||||
|
prefix='se_', auto_create=True, service='main',
|
||||||
|
),
|
||||||
|
}
|
11
setups/frappy_stick.py
Normal file
11
setups/frappy_stick.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from os import environ
|
||||||
|
description = 'frappy stick setup'
|
||||||
|
group = 'optional'
|
||||||
|
|
||||||
|
devices = {
|
||||||
|
'se_stick':
|
||||||
|
device('nicos_sinq.frappy.devices.FrappyNode',
|
||||||
|
description='stick SEC node', unit='',
|
||||||
|
prefix='se_', auto_create=True, service='stick',
|
||||||
|
),
|
||||||
|
}
|
Reference in New Issue
Block a user