diff --git a/pcaspy-template-generator/project-name/py_project_name/csdev.py b/pcaspy-template-generator/project-name/py_project_name/csdev.py new file mode 100644 index 00000000..a10c5d0a --- /dev/null +++ b/pcaspy-template-generator/project-name/py_project_name/csdev.py @@ -0,0 +1,29 @@ +"""PVs definition for the IOC.""" + +from siriuspy import csdev as _csdev + + +class Const(_csdev.Const): + """Const class.""" + _register = _csdev.Const.register + + DisconnConn = _register("DisconnConn", _csdev.ETypes.DISCONN_CONN) + + +pvs_database = { + + 'Version-Cte': {'type': 'str', 'value': 'UNDEF'}, + 'TimestampBoot-Cte': { + 'type': 'float', 'value': 0, + 'prec': 7, 'unit': 'timestamp'}, + 'TimestampUpdate-Mon': { + 'type': 'float', 'value': 0, + 'prec': 7, 'unit': 'timestamp'}, + 'Log-Mon': {'type': 'str', 'value': 'Starting...'}, + 'ConnStatus-Mon': { + 'type': 'enum', 'value': Const.DisconnConn.Disconnected, + 'enums': _csdev.ETypes.DISCONN_CONN, + 'unit': 'DisconnConn', + }, + +} diff --git a/pcaspy-template-generator/project-name/py_project_name/main.py b/pcaspy-template-generator/project-name/py_project_name/main.py index 7bf98712..c0ae7c28 100644 --- a/pcaspy-template-generator/project-name/py_project_name/main.py +++ b/pcaspy-template-generator/project-name/py_project_name/main.py @@ -1,39 +1,53 @@ """Main Module of the IOC Logic.""" -import pvs as _pvs +import logging as _log import time as _time -__version__ = _pvs.__version__ +from siriuspy import epics as _epics +from siriuspy.callbacks import Callback as _Callback +from siriuspy.devices import DeviceSet as _DeviceSet +from . import csdev as _csdev -class App: - """Main Class of the IOC Logic.""" - pvs_database = _pvs.pvs_database +class App(_DeviceSet, _Callback): + """Main Class of the IOC Logic.""" - def get_database(self): - """Get the database.""" - db = dict() - for pre, pvs in self.pvs_database.items(): - for pvname, info in pvs.items(): - db[pre + pvname] = info - return db + SCAN_FREQUENCY = 1 # [Hz] def __init__(self, driver=None): """Initialize the instance.""" - self._driver = driver + _Callback.__init__(self) - @property - def driver(self): - """Set the driver of the App.""" - return self._driver + self.driver = driver + devs = tuple() + _DeviceSet.__init__(self, devices=devs) - @driver.setter - def driver(self, driver): - self._driver = driver + self._pvs_database = _csdev.pvs_database + + # status scanning + self.quit = False + self.scanning = False + self.thread_check_conns = _epics.CAThread( + target=self._run_updates, daemon=True) + self.thread_check_conns.start() + + def init_database(self): + """Set initial PV values.""" + pvn2vals = { + 'TimestampUpdate-Mon': _time.time(), + 'Log-Mon': 'Started.', + } + for pvn, val in pvn2vals.items(): + self.run_callbacks(pvn, val) + + @property + def pvs_database(self): + """Return pvs_database.""" + return self._pvs_database def process(self, interval): - """Trigger connection to external PVs in other classes.""" + """Sleep.""" _time.sleep(interval) def read(self, reason): @@ -41,10 +55,37 @@ def read(self, reason): # implementation here # The default behavior is to return None and let the driver read # from the database. - return None + value = None + return value def write(self, reason, value): """Write PV in the model.""" # implementation here # this should be used in case PV state change. return True # return True for successful write and False otherwise. + + def _run_updates(self): + # scan + tplanned = 1.0 / App.SCAN_FREQUENCY + while not self.quit: + if not self.scanning: + _time.sleep(tplanned) + continue + + _t0 = _time.time() + + # update sections status + self.run_callbacks('TimestampUpdate-Mon', _time.time()) + self.run_callbacks('ConnStatus-Mon', self.connected) + + # time mgmnt + ttook = _time.time() - _t0 + tsleep = tplanned - ttook + if tsleep > 0: + _time.sleep(tsleep) + else: + logstr = ( + 'Connections check took more than planned... ' + '{0:.3f}/{1:.3f} s'.format(ttook, tplanned) + ) + _log.warning(logstr) diff --git a/pcaspy-template-generator/project-name/py_project_name/pvs.py b/pcaspy-template-generator/project-name/py_project_name/pvs.py deleted file mode 100644 index 358d3265..00000000 --- a/pcaspy-template-generator/project-name/py_project_name/pvs.py +++ /dev/null @@ -1,14 +0,0 @@ -"""PVs definition for the IOC.""" - -with open('VERSION', 'r') as _f: - __version__ = _f.read().strip() - -pvs_database = {} - -pvs_database['TEST-'] = { - - 'Version-Cte': {'type': 'string', 'value': __version__}, - - 'PV1': {'type': 'float', 'value': 1.200, 'prec': 3, 'unit': 'm'}, - 'PV2': {'type': 'string', 'value': 'test'}, -} diff --git a/pcaspy-template-generator/project-name/py_project_name/py_project_name.py b/pcaspy-template-generator/project-name/py_project_name/py_project_name.py index 79642b70..402322bb 100755 --- a/pcaspy-template-generator/project-name/py_project_name/py_project_name.py +++ b/pcaspy-template-generator/project-name/py_project_name/py_project_name.py @@ -1,28 +1,53 @@ #!/usr/bin/env python-sirius +""".""" + +import logging as _log +import os as _os +import signal as _signal +import sys as _sys +import time as _time import pcaspy as _pcaspy import pcaspy.tools as _pcaspy_tools -import signal as _signal -import main as _main +from siriuspy import util as _util +from siriuspy.envars import VACA_PREFIX as _VACA_PREFIX +from .main import App as _App INTERVAL = 0.1 -stop_event = False -PREFIX = '' +STOP_EVENT = False + +IOC_PREFIX = 'AS-Glob:AP-DEVICE:' +IOC_NAME = 'AS-AP-DEVICE' def _stop_now(signum, frame): - print(' - SIGNAL received.') - global stop_event - stop_event = True + _ = frame + sname = _signal.Signals(signum).name + tstamp = _util.get_timestamp() + strf = f'{sname} received at {tstamp}' + _log.warning(strf) + _sys.stdout.flush() + _sys.stderr.flush() + global STOP_EVENT + STOP_EVENT = True + + +def _attribute_access_security_group(server, dbase): + for k, val in dbase.items(): + if k.endswith(('-RB', '-Sts', '-Cte', '-Mon')): + val.update({'asg': 'rbpv'}) + path_ = _os.path.abspath(_os.path.dirname(__file__)) + server.initAccessSecurityFile(path_ + '/access_rules.as') class _PCASDriver(_pcaspy.Driver): - def __init__(self, app=None): + def __init__(self, app): super().__init__() - self.app = app or _main.App() + self.app = app self.app.driver = self + self.app.add_callback(self.update_pv) def read(self, reason): value = self.app.read(reason) @@ -32,11 +57,18 @@ def read(self, reason): return value def write(self, reason, value): - app_ret = self.app.write(reason, value) - if app_ret: - self.setParam(reason, value) - self.updatePVs() - return app_ret + ret_val = self.app.write(reason, value) + if reason.endswith('-Cmd'): + value = self.getParam(reason) + 1 + if ret_val: + return super().write(reason, value) + return False + + def update_pv(self, pvname, value, **kwargs): + """Update PV.""" + _ = kwargs + self.setParam(pvname, value) + self.updatePV(pvname) def run(): @@ -45,32 +77,50 @@ def run(): _signal.signal(_signal.SIGINT, _stop_now) _signal.signal(_signal.SIGTERM, _stop_now) - # create the application model - app = _main.App() - - db = app.get_database() + # configure log file + _util.configure_log_file() + + # define IOC, init pvs database and create app object + _version = _util.get_last_commit_hash() + _ioc_prefix = _VACA_PREFIX + ('-' if _VACA_PREFIX else '') + _ioc_prefix += IOC_PREFIX + app = _App() + dbase = app.pvs_database + dbase['Version-Cte']['value'] = _version + dbase['TimestampBoot-Cte']['value'] = _time.time() + + # check if another IOC is running + pvname = _ioc_prefix + next(iter(dbase)) + if _util.check_pv_online(pvname, use_prefix=False): + raise ValueError('Another instance of this IOC is already running!') + + # check if another IOC is running + _util.print_ioc_banner( + ioc_name=IOC_NAME, + db=dbase, + description=IOC_NAME + ' Soft IOC', + version=_version, + prefix=_ioc_prefix) # create a new simple pcaspy server and driver to respond client's requests server = _pcaspy.SimpleServer() - server.createPV(PREFIX, db) - - # create the driver - pcas_driver = _PCASDriver(app) + _attribute_access_security_group(server, dbase) + server.createPV(_ioc_prefix, dbase) + driver = _PCASDriver(app) + app.init_database() # initiate a new thread responsible for listening for client connections server_thread = _pcaspy_tools.ServerThread(server) server_thread.start() # main loop - # while not stop_event.is_set(): - while not stop_event: - pcas_driver.app.process(INTERVAL) + driver.app.scanning = True + while not STOP_EVENT: + driver.app.process(INTERVAL) - print('exiting...') - # send stop signal to server thread + driver.app.scanning = False + driver.app.quit = True + + # sends stop signal to server thread server_thread.stop() server_thread.join() - - -if __name__ == '__main__': - run()