From a9c28dd9a38803baa63a1e813c8f2777926022a8 Mon Sep 17 00:00:00 2001 From: Grzegorz Kowalski Date: Mon, 21 Oct 2019 11:42:39 +0200 Subject: [PATCH 001/279] do not cast macro id to int --- src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroexecutor.py | 1 - .../qtgui/extra_macroexecutor/sequenceeditor/sequenceeditor.py | 1 - 2 files changed, 2 deletions(-) diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroexecutor.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroexecutor.py index be2f222a07..6cd671a865 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroexecutor.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroexecutor.py @@ -936,7 +936,6 @@ def onMacroStatusUpdated(self, data): "range"], data["step"], data["id"] if id is None: return - id = int(id) if id != self.macroId(): return macroName = macro.name diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/sequenceeditor.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/sequenceeditor.py index 97becda18d..be7fc7b350 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/sequenceeditor.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/sequenceeditor.py @@ -728,7 +728,6 @@ def onMacroStatusUpdated(self, data): "range"], data["step"], data["id"] if id is None: return - id = int(id) if not id in self.macroIds(): return macroName = macro.name From 7a9775eb2b41d153266221955ca5878fb51d7919 Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Wed, 17 Jun 2020 12:26:03 +0200 Subject: [PATCH 002/279] Add custom user to scan --- src/sardana/macroserver/scan/gscan.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/sardana/macroserver/scan/gscan.py b/src/sardana/macroserver/scan/gscan.py index 6330825a2a..c61dc17019 100644 --- a/src/sardana/macroserver/scan/gscan.py +++ b/src/sardana/macroserver/scan/gscan.py @@ -640,11 +640,16 @@ def _setupEnvironment(self, additional_env): serialno = 1 self.macro.setEnv("ScanID", serialno) + try: + user = self.macro.getEnv("ScanUser") + except UnknownEnv: + user = USER_NAME + env = ScanDataEnvironment( {'serialno': serialno, # TODO: this should be got from # self.measurement_group.getChannelsInfo() - 'user': USER_NAME, + 'user': user, 'title': self.macro.getCommand()}) # Initialize the data_desc list (and add the point number column) From 29f42d9fd61923589008b60629499d3cda51d10a Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Fri, 7 Aug 2020 14:19:29 +0100 Subject: [PATCH 003/279] Separate concerns in qt scan manager objects --- .../qt/qtgui/extra_sardana/showscanonline.py | 4 +- .../qt/qtgui/macrolistener/macrolistener.py | 42 ++++++++++++------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py b/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py index 102eab1aae..0a99f33673 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py @@ -29,6 +29,7 @@ import click +from taurus.external.qt import Qt from taurus.qt.qtgui.taurusgui import TaurusGui from sardana.taurus.qt.qtgui.macrolistener import (DynamicPlotManager, assertPlotAvailability) @@ -37,7 +38,8 @@ class ShowScanOnline(DynamicPlotManager): def __init__(self, parent): - DynamicPlotManager.__init__(self, parent) + DynamicPlotManager.__init__(self, parent=parent) + Qt.qApp.SDM.connectWriter("shortMessage", self, 'newShortMessage') def onExpConfChanged(self, expconf): DynamicPlotManager.onExpConfChanged(self, expconf) diff --git a/src/sardana/taurus/qt/qtgui/macrolistener/macrolistener.py b/src/sardana/taurus/qt/qtgui/macrolistener/macrolistener.py index f06fbbe6a3..f23d54d7b6 100644 --- a/src/sardana/taurus/qt/qtgui/macrolistener/macrolistener.py +++ b/src/sardana/taurus/qt/qtgui/macrolistener/macrolistener.py @@ -177,16 +177,15 @@ def _end_update(self): self._timer = None -class DynamicPlotManager(Qt.QObject, TaurusBaseComponent): - '''This is a manager of plots related to the execution of macros. +class PlotManager(Qt.QObject, TaurusBaseComponent): + ''' + This is a manager of plots related to the execution of macros. It dynamically creates/removes plots according to the configuration made by an ExperimentConfiguration widget. Currently it supports only 1D scan trends (2D scans are only half-baked) - To use it simply instantiate it and pass it a door name as a model. You may - want to call :meth:`onExpConfChanged` to update the configuration being - used. + To use it simply instantiate it and pass it a door name as a model. ''' plots_available = pyqtgraph is not None @@ -196,16 +195,13 @@ class DynamicPlotManager(Qt.QObject, TaurusBaseComponent): Single = 'single' # each curve has its own plot XAxis = 'x-axis' # group curves with same X-Axis - def __init__(self, parent=None): + def __init__(self, plot=None, parent=None): Qt.QObject.__init__(self, parent) TaurusBaseComponent.__init__(self, self.__class__.__name__) self._group_mode = self.XAxis - Qt.qApp.SDM.connectWriter("shortMessage", self, 'newShortMessage') - self._plot = MultiPlotWidget() - self.createPanel( - self._plot, 'Scan plot', registerconfig=False, permanent=False) + self.plot = plot or MultiPlotWidget() def setGroupMode(self, group): assert group in (self.Single, self.XAxis) @@ -329,7 +325,7 @@ def prepare(self, data_desc): raise NotImplementedError nb_points = data.get('total_scan_intervals', 2**16 - 1) + 1 - self._plot.prepare(plots, nb_points=nb_points) + self.plot.prepare(plots, nb_points=nb_points) # build status message serialno = 'Scan #{}'.format(data.get('serialno', '?')) @@ -350,18 +346,35 @@ def prepare(self, data_desc): def newPoint(self, point): data = point['data'] - self._plot.onNewPoint(data) + self.plot.onNewPoint(data) point_nb = 'Point #{}'.format(data['point_nb']) msg = self.message_template.format(progress=point_nb) self.newShortMessage.emit(msg) def end(self, end_data): data = end_data['data'] - self._plot.onEnd(data) + self.plot.onEnd(data) progress = 'Ended {}'.format(data['endtime']) msg = self.message_template.format(progress=progress) self.newShortMessage.emit(msg) + +class DynamicPlotManager(PlotManager): + '''This is a manager of plots related to the execution of macros. + It dynamically creates/removes plots according to the configuration made by + an ExperimentConfiguration widget. + + Currently it supports only 1D scan trends (2D scans are only half-baked) + + To use it simply instantiate it and pass it a door name as a model. + ''' + + def __init__(self, *args, **kwargs): + PlotManager.__init__(self, *args, **kwargs) + self.__panels = {} + self.createPanel( + self.plot, 'Scan plot', registerconfig=False, permanent=False) + def createPanel(self, widget, name, **kwargs): '''Creates a "panel" from a widget. In this basic implementation this means that the widgets is shown as a non-modal top window @@ -414,12 +427,13 @@ class MacroBroker(DynamicPlotManager): def __init__(self, parent): '''Passing the parent object (the main window) is mandatory''' - DynamicPlotManager.__init__(self, parent) + DynamicPlotManager.__init__(self, parent=parent) self._createPermanentPanels() # connect the broker to shared data Qt.qApp.SDM.connectReader("doorName", self.setModel) + Qt.qApp.SDM.connectWriter("shortMessage", self, 'newShortMessage') def setModel(self, doorname): ''' Reimplemented from :class:`DynamicPlotManager`.''' From 7cc9ffda0077dd5d38de9ce8212af0c5079bdc6c Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Fri, 7 Aug 2020 15:33:00 +0100 Subject: [PATCH 004/279] Add ScanPlotWidget and ScanPlotWindow --- .../qt/qtgui/extra_sardana/showscanonline.py | 43 +++++++++++++++---- .../qt/qtgui/macrolistener/macrolistener.py | 6 ++- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py b/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py index 0a99f33673..326318810b 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py @@ -23,16 +23,43 @@ ## ############################################################################## -"""This module contains a taurus ShowScanOnline widget.""" +""" +This module contains a taurus ShowScanWidget, ShowScanWindow and ShowScanOnline +widgets. +""" -__all__ = ["ShowScanOnline"] +__all__ = ["ScanPlotWidget", "ScanPlotWindow", "ShowScanOnline"] import click from taurus.external.qt import Qt from taurus.qt.qtgui.taurusgui import TaurusGui -from sardana.taurus.qt.qtgui.macrolistener import (DynamicPlotManager, - assertPlotAvailability) +from sardana.taurus.qt.qtgui.macrolistener import ( + MultiPlotWidget, PlotManager, DynamicPlotManager, assertPlotAvailability +) + + +class ScanPlotWidget(MultiPlotWidget): + + def __init__(self, parent=None): + super().__init__(parent) + self.manager = PlotManager(self) + self.setModel = self.manager.setModel + self.setGroupMode = self.manager.setGroupMode + + +class ScanPlotWindow(Qt.QMainWindow): + + def __init__(self, parent=None): + super().__init__() + plot_widget = ScanPlotWidget(parent=self) + self.setCentralWidget(plot_widget) + self.plotWidget = self.centralWidget + self.setModel = plot_widget.setModel + self.setGroupMode = plot_widget.setGroupMode + sbar = self.statusBar() + sbar.showMessage("Ready!") + plot_widget.manager.newShortMessage.connect(sbar.showMessage) class ShowScanOnline(DynamicPlotManager): @@ -108,12 +135,10 @@ def main(group, taurus_log_level, door): assertPlotAvailability() - gui = TaurusGuiLite() - - widget = ShowScanOnline(gui) - widget.setModel(door) + widget = ScanPlotWindow() widget.setGroupMode(group) - gui.show() + widget.setModel(door) + widget.show() return app.exec_() diff --git a/src/sardana/taurus/qt/qtgui/macrolistener/macrolistener.py b/src/sardana/taurus/qt/qtgui/macrolistener/macrolistener.py index f23d54d7b6..1a2e7a07c2 100644 --- a/src/sardana/taurus/qt/qtgui/macrolistener/macrolistener.py +++ b/src/sardana/taurus/qt/qtgui/macrolistener/macrolistener.py @@ -56,7 +56,10 @@ from sardana.taurus.core.tango.sardana import PlotType -__all__ = ['MacroBroker', 'DynamicPlotManager', 'assertPlotAvailability'] +__all__ = [ + 'MultiPlotWidget', 'MacroBroker', 'PlotManager', 'DynamicPlotManager', + 'assertPlotAvailability' +] __docformat__ = 'restructuredtext' @@ -91,6 +94,7 @@ class MultiPlotWidget(Qt.QWidget): def __init__(self, parent=None): super().__init__(parent) layout = Qt.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) self.win = pyqtgraph.GraphicsLayoutWidget() layout.addWidget(self.win) self._plots = {} From 868e10a902e16d93567c802a8600d4f198cef5fa Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Fri, 7 Aug 2020 17:52:09 +0100 Subject: [PATCH 005/279] Add ScanInfoForm and ScanPointForm --- .../qt/qtgui/extra_sardana/showscanonline.py | 186 +++++++++++++++++- .../qt/qtgui/extra_sardana/ui/ScanInfoForm.ui | 141 +++++++++++++ .../qtgui/extra_sardana/ui/ScanPointForm.ui | 52 +++++ .../qt/qtgui/extra_sardana/ui/ScanWindow.ui | 150 ++++++++++++++ 4 files changed, 525 insertions(+), 4 deletions(-) create mode 100644 src/sardana/taurus/qt/qtgui/extra_sardana/ui/ScanInfoForm.ui create mode 100644 src/sardana/taurus/qt/qtgui/extra_sardana/ui/ScanPointForm.ui create mode 100644 src/sardana/taurus/qt/qtgui/extra_sardana/ui/ScanWindow.ui diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py b/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py index 326318810b..44d7fbeaa7 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py @@ -28,17 +28,172 @@ widgets. """ -__all__ = ["ScanPlotWidget", "ScanPlotWindow", "ShowScanOnline"] +__all__ = [ + "ScanInfoForm", "ScanPointForm", "ScanPlotWidget", + "ScanPlotWindow", "ScanWindow", "ShowScanOnline" +] import click +import pkg_resources -from taurus.external.qt import Qt +from taurus.external.qt import Qt, uic +from taurus.qt.qtgui.base import TaurusBaseWidget from taurus.qt.qtgui.taurusgui import TaurusGui from sardana.taurus.qt.qtgui.macrolistener import ( MultiPlotWidget, PlotManager, DynamicPlotManager, assertPlotAvailability ) +def set_text(label, field=None, data=None, default='---'): + if field is None and data is None: + value = default + elif field is None: + value = data + elif data is None: + value = field + else: + value = data.get(field, default) + if isinstance(value, (tuple, list)): + value = ', '.join(value) + elif isinstance(value, float): + value = '{:8.4f}'.format(value) + else: + value = str(value) + if len(value) > 60: + value = '...{}'.format(value[-57:]) + label.setText(value) + + +def resize_form(form, new_size): + layout = form.layout() + curr_size = layout.rowCount() + nb = new_size - curr_size + while nb > 0: + layout.addRow(Qt.QLabel(), Qt.QLabel()) + nb -= 1 + while nb < 0: + layout.removeRow(layout.rowCount() - 1) + nb += 1 + + +def fill_form(form, fields, offset=0): + resize_form(form, len(fields) + offset) + layout = form.layout() + result = [] + for row, field in enumerate(fields): + label, value = field + w_item = layout.itemAt(row + offset, Qt.QFormLayout.LabelRole) + w_label = w_item.widget() + set_text(w_label, label) + w_item = layout.itemAt(row + offset, Qt.QFormLayout.FieldRole) + w_field = w_item.widget() + set_text(w_field, value) + result.append((w_label, w_field)) + return result + + +def load_scan_info_form(widget): + ui_name = pkg_resources.resource_filename(__package__ + '.ui', + 'ScanInfoForm.ui') + uic.loadUi(ui_name, baseinstance=widget) + return widget + + +class ScanInfoForm(Qt.QWidget, TaurusBaseWidget): + + def __init__(self, parent=None): + super().__init__(parent) + load_scan_info_form(self) + + def setModel(self, doorname): + super().setModel(doorname) + if not doorname: + return + door = self.getModelObj() + door.recordDataUpdated.connect(self.onRecordDataUpdated) + + def onRecordDataUpdated(self, record_data): + data = record_data[1] + handler = self.event_handler.get(data.get("type")) + handler and handler(self, data['data']) + + def onStart(self, meta): + set_text(self.title_value, 'title', meta) + set_text(self.scan_nb_value, 'serialno', meta) + set_text(self.start_value, 'starttime', meta) + set_text(self.end_value, 'endtime', meta) + set_text(self.status_value, 'Running') + + directory = meta.get('scandir', '') + self.directory_groupbox.setEnabled(True if directory else False) + self.directory_groupbox.setTitle('Directory: {}'.format(directory)) + files = meta.get('scanfile', ()) + if isinstance(files, str): + files = files, + elif files is None: + files = () + files = [('File:', filename) for filename in files] + fill_form(self.directory_groupbox, files) + + def onEnd(self, meta): + set_text(self.end_value, 'endtime', meta) + set_text(self.status_value, 'Finished') + + event_handler = { + "data_desc": onStart, + "record_end": onEnd + } + + +def load_scan_point_form(widget): + ui_name = pkg_resources.resource_filename(__package__ + '.ui', + 'ScanPointForm.ui') + uic.loadUi(ui_name, baseinstance=widget) + return widget + + +class ScanPointForm(Qt.QWidget, TaurusBaseWidget): + + def __init__(self, parent=None): + super().__init__(parent) + load_scan_point_form(self) + self._in_scan = False + + def setModel(self, doorname): + super().setModel(doorname) + if not doorname: + return + door = self.getModelObj() + door.recordDataUpdated.connect(self.onRecordDataUpdated) + + def onRecordDataUpdated(self, record_data): + data = record_data[1] + handler = self.event_handler.get(data.get("type")) + handler and handler(self, data['data']) + + def onStart(self, meta): + set_text(self.scan_nb_value, 'serialno', meta) + cols = meta['column_desc'] + col_labels = [(c['label']+':', '') for c in cols] + fields = fill_form(self, col_labels, 1) + self.fields = {col['name']: field for col, field in zip(cols, fields)} + self._in_scan = True + + def onPoint(self, point): + if self._in_scan: + for name, value in point.items(): + set_text(self.fields[name][1], value) + + def onEnd(self, meta): + self._in_scan = False + + event_handler = { + "data_desc": onStart, + "record_data": onPoint, + "record_end": onEnd + } + + class ScanPlotWidget(MultiPlotWidget): def __init__(self, parent=None): @@ -62,6 +217,29 @@ def __init__(self, parent=None): plot_widget.manager.newShortMessage.connect(sbar.showMessage) +def load_scan_window(widget): + ui_name = pkg_resources.resource_filename(__package__ + '.ui', + 'ScanWindow.ui') + uic.loadUi(ui_name, baseinstance=widget) + return widget + + +class ScanWindow(Qt.QMainWindow): + + def __init__(self, parent=None): + super().__init__() + load_scan_window(self) + sbar = self.statusBar() + sbar.showMessage("Ready!") + self.plot_widget.manager.newShortMessage.connect(sbar.showMessage) + + def setModel(self, model): + self.plot_widget.setModel(model) + self.info_form.setModel(model) + self.point_form.setModel(model) + + + class ShowScanOnline(DynamicPlotManager): def __init__(self, parent): @@ -135,8 +313,8 @@ def main(group, taurus_log_level, door): assertPlotAvailability() - widget = ScanPlotWindow() - widget.setGroupMode(group) + widget = ScanWindow() + widget.plot_widget.setGroupMode(group) widget.setModel(door) widget.show() return app.exec_() diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/ui/ScanInfoForm.ui b/src/sardana/taurus/qt/qtgui/extra_sardana/ui/ScanInfoForm.ui new file mode 100644 index 0000000000..3784123163 --- /dev/null +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/ui/ScanInfoForm.ui @@ -0,0 +1,141 @@ + + + Tiago Coutinho + scan_info_form + + + + 0 + 0 + 300 + 169 + + + + Form + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 3 + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Scan #: + + + + + + + --- + + + + + + + Title: + + + + + + + --- + + + + + + + Start: + + + + + + + --- + + + + + + + End: + + + + + + + --- + + + + + + + Status: + + + + + + + --- + + + + + + + Directory:--- + + + + 6 + + + 3 + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + + + + + diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/ui/ScanPointForm.ui b/src/sardana/taurus/qt/qtgui/extra_sardana/ui/ScanPointForm.ui new file mode 100644 index 0000000000..45056f10f3 --- /dev/null +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/ui/ScanPointForm.ui @@ -0,0 +1,52 @@ + + + Form + + + + 0 + 0 + 400 + 291 + + + + Form + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 3 + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Scan #: + + + + + + + + + + + diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/ui/ScanWindow.ui b/src/sardana/taurus/qt/qtgui/extra_sardana/ui/ScanWindow.ui new file mode 100644 index 0000000000..9c011166d6 --- /dev/null +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/ui/ScanWindow.ui @@ -0,0 +1,150 @@ + + + MainWindow + + + + 0 + 0 + 891 + 600 + + + + + + 3 + + + 0 + + + 6 + + + 6 + + + 6 + + + + + + + + + + Scan point + + + 2 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 250 + 0 + + + + QFrame::NoFrame + + + 0 + + + true + + + + + 0 + 0 + 250 + 240 + + + + + + + + + + + Scan information + + + 2 + + + + + 250 + 0 + + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 232 + 276 + + + + + + + + + + + + ScanPointForm + QWidget +
sardana.taurus.qt.qtgui.extra_sardana.showscanonline
+ 1 +
+ + ScanInfoForm + QWidget +
sardana.taurus.qt.qtgui.extra_sardana.showscanonline
+ 1 +
+ + ScanPlotWidget + QWidget +
sardana.taurus.qt.qtgui.extra_sardana.showscanonline
+ 1 +
+
+ + +
From 5e85fdca5687d4ede499e0dad7a768eb2af333c0 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 7 Sep 2020 12:49:30 +0200 Subject: [PATCH 006/279] Remove mym2 macro (not working, demo macro) --- src/sardana/macroserver/macros/demo.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/sardana/macroserver/macros/demo.py b/src/sardana/macroserver/macros/demo.py index afc591891f..9aaecf2a48 100644 --- a/src/sardana/macroserver/macros/demo.py +++ b/src/sardana/macroserver/macros/demo.py @@ -226,15 +226,6 @@ def sar_demo(self): self.print("DONE!") -@macro([["motor", Type.Moveable, None, '']]) -def mym2(self, pm): - self.output(pm.getMotorNames()) - elements = list(map(self.getMoveable, pm.elements)) - self.output(elements) - self.output(type(pm)) - self.output(type(elements[0])) - - @macro() def clear_sar_demo_hkl(self): """Undoes changes done with sar_demo""" From 37b8b7314069262c0bd6fa2b54ef89ff2a604508 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Sat, 19 Sep 2020 00:15:15 +0200 Subject: [PATCH 007/279] Reintroduce deply to github pages This was accidentally removed when working on deployment to pypi. --- .travis.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.travis.yml b/.travis.yml index 00a476ccca..7288db4a42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,3 +53,15 @@ script: # build docs - if [ $TEST == "doc" ]; then docker exec -t sardana-test /bin/bash -c "cd /sardana ; sphinx-build -W doc/source/ build/sphinx/html" ; fi - if [ $TEST == "doc" ]; then docker exec -t sardana-test /bin/bash -c "touch /sardana/build/sphinx/html/.nojekyll" ; fi + +deploy: + - provider: pages + local_dir: build/sphinx/html + repo: sardana-org/sardana-doc + skip_cleanup: true + github_token: $GITHUB_TOKEN # Set in the settings page of your repository, as a secure variable + keep_history: true + fqdn: sardana-controls.org # Set custom domain + on: + branch: develop + condition: "$TEST == doc" From 3e80da704fa918463617730221a99e79d0344865 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Sat, 26 Sep 2020 22:18:05 +0200 Subject: [PATCH 008/279] Update qtspock.rst --- doc/source/users/taurus/qtspock.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/users/taurus/qtspock.rst b/doc/source/users/taurus/qtspock.rst index c5579cd2bc..d4690b8302 100644 --- a/doc/source/users/taurus/qtspock.rst +++ b/doc/source/users/taurus/qtspock.rst @@ -20,7 +20,7 @@ as a standalone application .. code-block:: console - python3 -m sardana.taurua.qt.qtgui.extran_sardana.qtspock + python3 -m sardana.taurus.qt.qtgui.extra_sardana.qtspock or embedded in the :ref:`taurusgui_ui`: (when :ref:`panelcreation` use: `sardana.taurus.qt.qtgui.extra_sardana.qtspock` module and From 03825e6f812a3f1b75d527c83d0dfeefd57a9dc0 Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Mon, 28 Sep 2020 16:37:13 +0200 Subject: [PATCH 009/279] Fix macro function default settings Make sure we return the exact same default settings as the Macro class (ex: tools like the sequencer GUI expect hints to be a dict) --- src/sardana/macroserver/macro.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/sardana/macroserver/macro.py b/src/sardana/macroserver/macro.py index f062901a95..5013e5d42b 100644 --- a/src/sardana/macroserver/macro.py +++ b/src/sardana/macroserver/macro.py @@ -391,13 +391,24 @@ def my_macro1(self): def where_moveable(self, moveable): self.output("Moveable %s is at %s", moveable.getName(), moveable.getPosition())""" + param_def = [] + result_def = [] + env = () + hints = {} + interactive = False + def __init__(self, param_def=None, result_def=None, env=None, hints=None, interactive=None): - self.param_def = param_def - self.result_def = result_def - self.env = env - self.hints = hints - self.interactive = interactive + if param_def is not None: + self.param_def = param_def + if result_def is not None: + self.result_def = result_def + if env is not None: + self.env = env + if hints is not None: + self.hints = hints + if interactive is not None: + self.interactive = interactive def __call__(self, fn): fn.macro_data = {} From 0925e4209d0cc22a7cd3d22f4b9f915fcb984fab Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Tue, 29 Sep 2020 16:11:43 +0200 Subject: [PATCH 010/279] Fix double unit shown in motor widget --- src/sardana/taurus/qt/qtgui/extra_pool/poolmotor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sardana/taurus/qt/qtgui/extra_pool/poolmotor.py b/src/sardana/taurus/qt/qtgui/extra_pool/poolmotor.py index a31d5db83c..b2ec6f8b48 100644 --- a/src/sardana/taurus/qt/qtgui/extra_pool/poolmotor.py +++ b/src/sardana/taurus/qt/qtgui/extra_pool/poolmotor.py @@ -595,6 +595,7 @@ def __init__(self, parent=None, designMode=False): self.layout().addLayout(limits_layout, 0, 0) self.lbl_read = TaurusLabel() + self.lbl_read.setFgRole('rvalue.magnitude') self.lbl_read.setBgRole('quality') self.lbl_read.setSizePolicy(Qt.QSizePolicy( Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Fixed)) From df2bf51c4f3cea51e242efa61a102157d3ec94b7 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 7 Oct 2020 15:02:21 +0200 Subject: [PATCH 011/279] Delete .gitlab-ci-alba.yml Remove no longer needed file that is specific to ALBA packaging --- .gitlab-ci-alba.yml | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .gitlab-ci-alba.yml diff --git a/.gitlab-ci-alba.yml b/.gitlab-ci-alba.yml deleted file mode 100644 index 90f768f8bc..0000000000 --- a/.gitlab-ci-alba.yml +++ /dev/null @@ -1,8 +0,0 @@ -# This file is for configuring the CI/CD for creating (unofficial) -# debian packages for sardana at ALBA -# It has no efect unless you configure your gitlab instance to use it. -# TODO: generalise this so that it does not depend on ALBA's infrastructure - -include: -- https://git.cells.es/ctpkg/ci/ctpipeline/raw/master/ctjobdefs-ci.yml -- https://git.cells.es/ctpkg/ci/ctpipeline/raw/master/ctpipeline.yml From 7561d63639cd2088e5e455a579c0d028edde3c70 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 14 Oct 2020 09:24:47 +0200 Subject: [PATCH 012/279] Bump version 3.0.3 to 3.0.4-alpha --- .bumpversion.cfg | 2 +- src/sardana/release.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 11c0d4b9f9..d96215968e 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -3,7 +3,7 @@ commit = True message = Bump version {current_version} to {new_version} tag = False tag_name = {new_version} -current_version = 3.0.3 +current_version = 3.0.4-alpha parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? serialize = {major}.{minor}.{patch}-{release} diff --git a/src/sardana/release.py b/src/sardana/release.py index c4d742b604..d013fa377e 100644 --- a/src/sardana/release.py +++ b/src/sardana/release.py @@ -47,7 +47,7 @@ # we use semantic versioning (http://semver.org/) and we update it using the # bumpversion script (https://github.com/peritus/bumpversion) -version = '3.0.3' +version = '3.0.4-alpha' description = "instrument control and data acquisition system" From 7d5bf998a3ba9367d6f0db5188019423c2784b9a Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 14 Oct 2020 11:40:43 +0200 Subject: [PATCH 013/279] Allow to programmatically disable *deterministic scan* optimization Deterministic scan optimization of measurement is not compatible with attaching measurements on the same measurement group as hooks. Add deterministic_scan property to allow disabling this optimization. Fixes #1426 --- src/sardana/macroserver/scan/gscan.py | 35 +++++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/sardana/macroserver/scan/gscan.py b/src/sardana/macroserver/scan/gscan.py index eacd779395..466050eda1 100644 --- a/src/sardana/macroserver/scan/gscan.py +++ b/src/sardana/macroserver/scan/gscan.py @@ -1058,24 +1058,43 @@ def do_restore(self): class SScan(GScan): """Step scan""" + def __init__(self, macro, generator=None, moveables=[], env={}, + constraints=[], extrainfodesc=[]): + GScan.__init__(self, macro, generator=generator, moveables=moveables, + env=env, constraints=constraints, + extrainfodesc=extrainfodesc) + self._deterministic_scan = None + + @property + def deterministic_scan(self): + if self._deterministic_scan is None: + macro = self.macro + if hasattr(macro, "nb_points") and hasattr(macro, "integ_time"): + self._deterministic_scan = True + else: + self._deterministic_scan = False + return self._deterministic_scan + + @deterministic_scan.setter + def deterministic_scan(self, value): + self._deterministic_scan = value + def scan_loop(self): lstep = None macro = self.macro scream = False - self._deterministic_scan = False if hasattr(macro, "nb_points"): nb_points = float(macro.nb_points) - if hasattr(macro, "integ_time"): - integ_time = macro.integ_time - self.measurement_group.putIntegrationTime(integ_time) - self.measurement_group.setNbStarts(nb_points) - self.measurement_group.prepare() - self._deterministic_scan = True scream = True else: yield 0.0 + if self.deterministic_scan: + self.measurement_group.putIntegrationTime(macro.integ_time) + self.measurement_group.setNbStarts(macro.nb_points) + self.measurement_group.prepare() + self._sum_motion_time = 0 self._sum_acq_time = 0 @@ -1157,7 +1176,7 @@ def stepUp(self, n, step, lstep): # Acquire data self.debug("[START] acquisition") try: - if self._deterministic_scan: + if self.deterministic_scan: state, data_line = mg.count_raw() else: state, data_line = mg.count(integ_time) From 2de301b9a2ea04043c6920579254fb5a53b3adbf Mon Sep 17 00:00:00 2001 From: Jordi Andreu Date: Thu, 15 Oct 2020 17:02:41 +0200 Subject: [PATCH 014/279] Fix delay description in position domain Add the missing delay parameter in position domain in the CTScan. --- src/sardana/macroserver/scan/gscan.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sardana/macroserver/scan/gscan.py b/src/sardana/macroserver/scan/gscan.py index 6330825a2a..8b54144e7b 100644 --- a/src/sardana/macroserver/scan/gscan.py +++ b/src/sardana/macroserver/scan/gscan.py @@ -2382,8 +2382,10 @@ def _go_through_waypoints(self): initial_position = start total_time = abs(total_position) / path.max_vel delay_time = path.max_vel_time + delay_position = start - path.initial_user_pos synch = [ - {SynchParam.Delay: {SynchDomain.Time: delay_time}, + {SynchParam.Delay: {SynchDomain.Time: delay_time, + SynchDomain.Position: delay_position}, SynchParam.Initial: {SynchDomain.Position: initial_position}, SynchParam.Active: {SynchDomain.Position: active_position, SynchDomain.Time: active_time}, From 0b2ac1550329cba94e0500e7d2d8d040dd9ec8b6 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 6 Nov 2020 13:28:29 +0100 Subject: [PATCH 015/279] Implement per measurement preparation for trigger/gate Trigger/gate elements could be prepared for multiple starts in a deterministic scan and benefit from it. - Add PrepareOne() to TriggerGate controller. - Call preparation methods in PoolAcquision.preapre() - Implement empty `PrepareOne` in DummyTriggerGateController Implements #1432 --- src/sardana/pool/controller.py | 12 ++++++++++++ src/sardana/pool/poolacquisition.py | 12 ++++++++++++ .../poolcontrollers/DummyTriggerGateController.py | 3 +++ 3 files changed, 27 insertions(+) diff --git a/src/sardana/pool/controller.py b/src/sardana/pool/controller.py index fc24d05030..4895651f8f 100644 --- a/src/sardana/pool/controller.py +++ b/src/sardana/pool/controller.py @@ -912,6 +912,18 @@ class TriggerGateController(Controller, Synchronizer, Stopable, Startable): def __init__(self, inst, props, *args, **kwargs): Controller.__init__(self, inst, props, *args, **kwargs) + # TODO: Implement a Preparable interface and move this method + # and the Loadable.PrepareOne() there. + def PrepareOne(self, nb_starts): + """**Controller API**. Override if necessary. + Called to prepare the trigger/gate axis with the measurement + parameters. + Default implementation does nothing. + + :param int nb_starts: number of starts + """ + pass + class ZeroDController(Controller, Readable, Stopable): """Base class for a 0D controller. Inherit from this class to diff --git a/src/sardana/pool/poolacquisition.py b/src/sardana/pool/poolacquisition.py index 224fb6ce1f..9e66fdcb79 100644 --- a/src/sardana/pool/poolacquisition.py +++ b/src/sardana/pool/poolacquisition.py @@ -499,6 +499,9 @@ def prepare(self, config, acq_mode, value, synch_description=None, config.changed = False + # Call synchronizer controllers prepare method + self._prepare_synch_ctrls(ctrls_synch, nb_starts) + # Call hardware and software start controllers prepare method ctrls = ctrls_hw + ctrls_sw_start self._prepare_ctrls(ctrls, value, repetitions, latency, @@ -510,6 +513,7 @@ def prepare(self, config, acq_mode, value, synch_description=None, self._prepare_ctrls(ctrls_sw, value, repetitions, latency, nb_starts) + @staticmethod def _prepare_ctrls(ctrls, value, repetitions, latency, nb_starts): for ctrl in ctrls: @@ -518,6 +522,14 @@ def _prepare_ctrls(ctrls, value, repetitions, latency, nb_starts): pool_ctrl.ctrl.PrepareOne(axis, value, repetitions, latency, nb_starts) + @staticmethod + def _prepare_synch_ctrls(ctrls, nb_starts): + for ctrl in ctrls: + for chn in ctrl.get_channels(): + axis = chn.axis + pool_ctrl = ctrl.element + pool_ctrl.ctrl.PrepareOne(axis, nb_starts) + def is_running(self): """Checks if acquisition is running. diff --git a/src/sardana/pool/poolcontrollers/DummyTriggerGateController.py b/src/sardana/pool/poolcontrollers/DummyTriggerGateController.py index f2795057b6..bd746ed8ac 100644 --- a/src/sardana/pool/poolcontrollers/DummyTriggerGateController.py +++ b/src/sardana/pool/poolcontrollers/DummyTriggerGateController.py @@ -72,6 +72,9 @@ def StateOne(self, axis): print(e) return sta, status + def PrepareOne(self, axis, nb_starts): + self._log.debug('PrepareOne(%d): entering...' % axis) + def PreStartAll(self): pass From 8759ea1401c7e4984df988461d8a61d2b12d710f Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 6 Nov 2020 14:02:55 +0100 Subject: [PATCH 016/279] Document "How To Trigger/Gate" PrepareOne() --- doc/source/devel/api/api_controller.rst | 1 + doc/source/devel/api/sardana/pool/controller.rst | 12 ++++++++++++ .../howto_triggergatecontroller.rst | 12 ++++++++++++ doc/source/devel/howto_macros/scan_framework.rst | 1 + 4 files changed, 26 insertions(+) diff --git a/doc/source/devel/api/api_controller.rst b/doc/source/devel/api/api_controller.rst index f77e55bff9..be81875ddf 100644 --- a/doc/source/devel/api/api_controller.rst +++ b/doc/source/devel/api/api_controller.rst @@ -13,6 +13,7 @@ Controller API reference * :class:`ZeroDController` - 0D controller API * :class:`PseudoMotorController` - PseudoMotor controller API * :class:`PseudoCounterController` - PseudoCounter controller API + * :class:`TriggerGateController` - Trigger/Gate controller API * :class:`IORegisterController` - IORegister controller API .. _sardana-controller-data-type: diff --git a/doc/source/devel/api/sardana/pool/controller.rst b/doc/source/devel/api/sardana/pool/controller.rst index 4d0f9c79df..9377423f70 100644 --- a/doc/source/devel/api/sardana/pool/controller.rst +++ b/doc/source/devel/api/sardana/pool/controller.rst @@ -44,6 +44,7 @@ * :class:`OneDController` * :class:`TwoDController` * :class:`PseudoCounterController` + * :class:`TriggerGateController` * :class:`IORegisterController` @@ -216,6 +217,17 @@ Pseudo Counter Controller API :undoc-members: +Trigger/Gate Controller API +--------------------------- + +.. inheritance-diagram:: TriggerGateController + :parts: 1 + +.. autoclass:: TriggerGateController + :show-inheritance: + :members: + :undoc-members: + IO Register Controller API ---------------------------- diff --git a/doc/source/devel/howto_controllers/howto_triggergatecontroller.rst b/doc/source/devel/howto_controllers/howto_triggergatecontroller.rst index c1516526f8..0b89a2e1c1 100644 --- a/doc/source/devel/howto_controllers/howto_triggergatecontroller.rst +++ b/doc/source/devel/howto_controllers/howto_triggergatecontroller.rst @@ -89,6 +89,18 @@ The state should be a member of :obj:`~sardana.sardanadefs.State` (For backward compatibility reasons, it is also supported to return one of :class:`PyTango.DevState`). The status could be any string. +.. _sardana-TriggerGateController-howto-prepare: + +Prepare for measurement +~~~~~~~~~~~~~~~~~~~~~~~ + +To prepare a trigger for a measurement you can use the +:meth:`~sardana.pool.controller.TriggerGateController.PrepareOne` method which +receives as an argument the number of starts of the whole measurement. +This information may be used to prepare the hardware for generating +multiple events (triggers or gates) in a complex measurement +e.g. :ref:`sardana-macros-scanframework-determscan`. + .. _sardana-TriggerGateController-howto-load: Load synchronization description diff --git a/doc/source/devel/howto_macros/scan_framework.rst b/doc/source/devel/howto_macros/scan_framework.rst index 23a2a5db7d..43567fac30 100644 --- a/doc/source/devel/howto_macros/scan_framework.rst +++ b/doc/source/devel/howto_macros/scan_framework.rst @@ -195,6 +195,7 @@ the most basic features of a continuous scan:: :: (with more elaborated waypoint generator), see the code of :class:`~sardana.macroserver.macros.scan.meshc` +.. _sardana-macros-scanframework-determscan: Deterministic scans ------------------- From 74a92286355b825f7146f7982e930aa4963f1089 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 10 Nov 2020 00:02:45 +0100 Subject: [PATCH 017/279] WIP: try job on docker container --- .github/workflows/tests.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/tests.yaml diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000000..30ffdfb230 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,21 @@ +name: CI tests + +on: [pull_request] + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install on container & test + uses: docker://reszelaz/sardana + with: + volumes: + - .:/sardana + run: | + sleep 10 + cd /sardana && python3 setup.py install + supervisorctl start Pool + supervisorctl start MacroServer + xvfb-run -s '-screen 0 1920x1080x24' /bin/bash -c "pytest /usr/local/lib/python3.5/dist-packages/sardana-*.egg/sardana \ No newline at end of file From 45dc9d224461a93784c6f61514e884f43663a1d4 Mon Sep 17 00:00:00 2001 From: Jan Kotanski Date: Wed, 11 Nov 2020 13:00:56 +0100 Subject: [PATCH 018/279] add fix for a parameter of addData and starttime value --- src/sardana/macroserver/scan/test/helper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sardana/macroserver/scan/test/helper.py b/src/sardana/macroserver/scan/test/helper.py index d3bdefb8f7..b4fa2007bc 100644 --- a/src/sardana/macroserver/scan/test/helper.py +++ b/src/sardana/macroserver/scan/test/helper.py @@ -12,7 +12,7 @@ import time -from datetime import date +import datetime import threading import numpy import os @@ -47,7 +47,7 @@ def run(self): if skip: continue time.sleep(t) - _dict = dict(data=v, index=idx, label=self.name) + _dict = dict(value=v, index=idx, label=self.name) self.scan_data.addData(_dict) def get_obj(self): @@ -69,7 +69,7 @@ def createScanDataEnvironment(columns, scanDir='/tmp/', env['ScanFile'] = scanFile env['total_scan_intervals'] = -1.0 - today = date.today() + today = datetime.datetime.fromtimestamp(time.time()) env['datetime'] = today env['starttime'] = today env['endtime'] = today From a6fbcbefdf8990a9522f112fca39fd610cba3908 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 6 Nov 2020 14:51:25 +0100 Subject: [PATCH 019/279] Execute per measurement preparation in mesh Per measurement preparation in deterministic scans is done based on the integ_time and nb_points attribute presence in the macro obj. Add nb_points attribute to the mesh macro. --- src/sardana/macroserver/macros/scan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sardana/macroserver/macros/scan.py b/src/sardana/macroserver/macros/scan.py index 57e9aa22fa..0f48477711 100644 --- a/src/sardana/macroserver/macros/scan.py +++ b/src/sardana/macroserver/macros/scan.py @@ -709,6 +709,7 @@ def prepare(self, m1, m1_start_pos, m1_final_pos, m1_nr_interv, self.starts = numpy.array([m1_start_pos, m2_start_pos], dtype='d') self.finals = numpy.array([m1_final_pos, m2_final_pos], dtype='d') self.nr_intervs = numpy.array([m1_nr_interv, m2_nr_interv], dtype='i') + self.nb_points = (m1_nr_interv + 1) * (m2_nr_interv + 1) self.integ_time = integ_time self.bidirectional_mode = bidirectional From e289505d2392133b28ae6c8a528c265db3cca556 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 11 Nov 2020 22:42:12 +0100 Subject: [PATCH 020/279] Add tests to reveal lack of RefOne calls While acquiring with hardware synchronization RefOne must be called continuously to empty the hardware buffers. Add tests which reveal lack of such calls. --- src/sardana/pool/test/test_acquisition.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/sardana/pool/test/test_acquisition.py b/src/sardana/pool/test/test_acquisition.py index cf0c1139e8..d7d1833640 100644 --- a/src/sardana/pool/test/test_acquisition.py +++ b/src/sardana/pool/test/test_acquisition.py @@ -27,7 +27,7 @@ import numpy -from unittest import TestCase +from unittest import TestCase, mock from taurus.test import insertTest from sardana.sardanautils import is_number, is_pure_str @@ -592,6 +592,14 @@ def _prepare(self, integ_time, repetitions, latency_time, nb_starts): axis = self.channel.axis self.channel_ctrl.set_axis_par(axis, "value_ref_enabled", True) + def acquire(self, integ_time, repetitions, latency_time): + ctrl = self.channel_ctrl.ctrl + with mock.patch.object(ctrl, "RefOne", + wraps=ctrl.RefOne) as mock_RefOne: + BaseAcquisitionHardwareTestCase.acquire(self, integ_time, + repetitions, latency_time) + assert mock_RefOne.call_count > 1 + @insertTest(helper_name='acquire', integ_time=0.01, repetitions=10, latency_time=0.02) @@ -652,6 +660,14 @@ def _prepare(self, integ_time, repetitions, latency_time, nb_starts): axis = self.channel.axis self.channel_ctrl.set_axis_par(axis, "value_ref_enabled", True) + def acquire(self, integ_time, repetitions, latency_time): + ctrl = self.channel_ctrl.ctrl + with mock.patch.object(ctrl, "RefOne", + wraps=ctrl.RefOne) as mock_RefOne: + BaseAcquisitionHardwareTestCase.acquire(self, integ_time, + repetitions, latency_time) + assert mock_RefOne.call_count > 1 + @insertTest(helper_name='acquire', integ_time=0.01, repetitions=10, latency_time=0.02) From 5209b2dea11f63c5cacdc49f0f0a633722ce6a00 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 11 Nov 2020 22:47:45 +0100 Subject: [PATCH 021/279] Add regression tests for some ReadOne calls While acquiring with hardware synchronization ReadOne must be called continuously to empty the hardware buffers. Add tests to avoid regressions. --- src/sardana/pool/test/test_acquisition.py | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/sardana/pool/test/test_acquisition.py b/src/sardana/pool/test/test_acquisition.py index d7d1833640..7b02dd8370 100644 --- a/src/sardana/pool/test/test_acquisition.py +++ b/src/sardana/pool/test/test_acquisition.py @@ -567,6 +567,14 @@ def setUp(self): self.data_listener = AttributeListener(dtype=object, attr_name="valuebuffer") + def acquire(self, integ_time, repetitions, latency_time): + ctrl = self.channel_ctrl.ctrl + with mock.patch.object(ctrl, "ReadOne", + wraps=ctrl.ReadOne) as mock_ReadOne: + BaseAcquisitionHardwareTestCase.acquire(self, integ_time, + repetitions, latency_time) + assert mock_ReadOne.call_count > 1 + @insertTest(helper_name='acquire', integ_time=0.01, repetitions=10, latency_time=0.02) @@ -618,6 +626,14 @@ def setUp(self): self.data_listener = AttributeListener(dtype=object, attr_name="valuebuffer") + def acquire(self, integ_time, repetitions, latency_time): + ctrl = self.channel_ctrl.ctrl + with mock.patch.object(ctrl, "ReadOne", + wraps=ctrl.ReadOne) as mock_ReadOne: + BaseAcquisitionHardwareTestCase.acquire(self, integ_time, + repetitions, latency_time) + assert mock_ReadOne.call_count > 1 + @insertTest(helper_name='acquire', integ_time=0.01, repetitions=10, latency_time=0.02) @@ -636,6 +652,14 @@ def setUp(self): self.data_listener = AttributeListener(dtype=object, attr_name="valuebuffer") + def acquire(self, integ_time, repetitions, latency_time): + ctrl = self.channel_ctrl.ctrl + with mock.patch.object(ctrl, "ReadOne", + wraps=ctrl.ReadOne) as mock_ReadOne: + BaseAcquisitionHardwareTestCase.acquire(self, integ_time, + repetitions, latency_time) + assert mock_ReadOne.call_count > 1 + @insertTest(helper_name='acquire', integ_time=0.01, repetitions=10, latency_time=0.02) From 46a67d7fbdee160e47f10f3fcb4a91f5a562a1e5 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 11 Nov 2020 23:42:30 +0100 Subject: [PATCH 022/279] Refactor to avoid code duplicate --- src/sardana/pool/poolacquisition.py | 53 +++++++++++++++-------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/sardana/pool/poolacquisition.py b/src/sardana/pool/poolacquisition.py index 224fb6ce1f..c9230b5153 100644 --- a/src/sardana/pool/poolacquisition.py +++ b/src/sardana/pool/poolacquisition.py @@ -812,6 +812,30 @@ def _raw_read_ctrl_value_ref(self, ret, pool_ctrl): finally: self._value_info.finish_one() + def _process_value_buffer(self, acquirable, value, final=False): + final_str = "final " if final else "" + if is_value_error(value): + self.error("Loop %sread value error for %s" % (final_str, + acquirable.name)) + msg = "Details: " + "".join( + traceback.format_exception(*value.exc_info)) + self.debug(msg) + acquirable.put_value(value, propagate=2) + else: + acquirable.extend_value_buffer(value, propagate=2) + + def _process_value_ref_buffer(self, acquirable, value_ref, final=False): + final_str = "final " if final else "" + if is_value_error(value_ref): + self.error("Loop read ref %svalue error for %s" % + (final_str, acquirable.name)) + msg = "Details: " + "".join( + traceback.format_exception(*value_ref.exc_info)) + self.debug(msg) + acquirable.put_value_ref(value_ref, propagate=2) + else: + acquirable.extend_value_ref_buffer(value_ref, propagate=2) + def in_acquisition(self, states): """Determines if we are in acquisition or if the acquisition has ended based on the current unit trigger modes and states returned by the @@ -1056,15 +1080,7 @@ def action_loop(self): if not i % nb_states_per_value: self.read_value(ret=values) for acquirable, value in list(values.items()): - if is_value_error(value): - self.error("Loop read value error for %s" % - acquirable.name) - msg = "Details: " + "".join( - traceback.format_exception(*value.exc_info)) - self.debug(msg) - acquirable.put_value(value) - else: - acquirable.extend_value_buffer(value) + self._process_value_buffer(acquirable, value) time.sleep(nap) i += 1 @@ -1076,24 +1092,11 @@ def action_loop(self): for acquirable, state_info in list(states.items()): if acquirable in values: value = values[acquirable] - if is_value_error(value): - self.error("Loop final read value error for: %s" % - acquirable.name) - msg = "Details: " + "".join( - traceback.format_exception(*value.exc_info)) - self.debug(msg) - acquirable.put_value(value) - else: - acquirable.extend_value_buffer(value, propagate=2) + self._process_value_buffer(acquirable, value, final=True) if acquirable in value_refs: value_ref = value_refs[acquirable] - if is_value_error(value_ref): - self.error("Loop final read value ref error for: %s" % - acquirable.name) - msg = "Details: " + "".join( - traceback.format_exception(*value_ref.exc_info)) - self.debug(msg) - acquirable.extend_value_ref_buffer(value_ref, propagate=2) + self._process_value_ref_buffer(acquirable, value_ref, + final=True) state_info = acquirable._from_ctrl_state_info(state_info) set_state_info = functools.partial(acquirable.set_state_info, state_info, From 93f8e300aa5d8e30874b17b0971ee822f996e03b Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 11 Nov 2020 23:44:04 +0100 Subject: [PATCH 023/279] Call RefOne() continuously while acquiring with HW synch RefOne() must be called while acquiring with hardware synchronization in order to empty the buffers. Do it. --- src/sardana/pool/poolacquisition.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sardana/pool/poolacquisition.py b/src/sardana/pool/poolacquisition.py index c9230b5153..26e8439399 100644 --- a/src/sardana/pool/poolacquisition.py +++ b/src/sardana/pool/poolacquisition.py @@ -1081,6 +1081,9 @@ def action_loop(self): self.read_value(ret=values) for acquirable, value in list(values.items()): self._process_value_buffer(acquirable, value) + self.read_value_ref(ret=value_refs) + for acquirable, value_ref in list(value_refs.items()): + self._process_value_ref_buffer(acquirable, value_ref) time.sleep(nap) i += 1 From f60eec121874e110c360a333e08823c24c25a6ef Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 12 Nov 2020 22:27:52 +0100 Subject: [PATCH 024/279] Update CHANGELOG --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29c271e292..820f855bbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). This file follows the formats and conventions from [keepachangelog.com] +## [Unreleased] + +### Added + +* ... + +### Fixed + +* Recorders tests helpers (#1439) + ## [3.0.3] 2020-09-18 ### Added From cc4b08ada036dab072726ed30f7aea5a952d0667 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Fri, 13 Nov 2020 15:29:01 +0100 Subject: [PATCH 025/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 820f855bbd..c308960388 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This file follows the formats and conventions from [keepachangelog.com] ### Fixed +* Execute per measurement preparation in `mesh` scan macro (#1437) * Recorders tests helpers (#1439) ## [3.0.3] 2020-09-18 From 719722d84759bd8dcf26fd5560d72c382d922029 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 23 Nov 2020 15:59:31 +0100 Subject: [PATCH 026/279] Leave Tango device class attributes definition untouched When adding attributes the controller code can modify them e.g. format, type, maxdimsize. Make a copy of the standard class attributes proceeding from the class definition to avoid problems with modifying them. Fixes #1440. --- src/sardana/tango/pool/PoolDevice.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sardana/tango/pool/PoolDevice.py b/src/sardana/tango/pool/PoolDevice.py index ca4ce9b944..13060ffd4f 100644 --- a/src/sardana/tango/pool/PoolDevice.py +++ b/src/sardana/tango/pool/PoolDevice.py @@ -33,6 +33,7 @@ __docformat__ = 'restructuredtext' import time +from copy import deepcopy from PyTango import Util, DevVoid, DevLong64, DevBoolean, DevString,\ DevDouble, DevEncoded, DevVarStringArray, DispLevel, DevState, SCALAR, \ @@ -690,7 +691,9 @@ def _get_dynamic_attributes(self): attr_name_lower = attr_name.lower() if attr_name_lower in std_attrs_lower: data_info = DataInfo.toDataInfo(attr_name, attr_info) - tg_info = dev_class.standard_attr_list[attr_name] + # copy in order to leave the class attributes untouched + # the downstream code can append MaxDimSize to the attr. info + tg_info = deepcopy(dev_class.standard_attr_list[attr_name]) std_attrs[attr_name] = attr_name, tg_info, data_info else: data_info = DataInfo.toDataInfo(attr_name, attr_info) From 8c088b83af785c899f9e3d3b857d50f909ab76e4 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Wed, 2 Dec 2020 22:56:38 +0100 Subject: [PATCH 027/279] Update tests.yaml Add missing quotes --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 30ffdfb230..1f51af4bd0 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -18,4 +18,4 @@ jobs: cd /sardana && python3 setup.py install supervisorctl start Pool supervisorctl start MacroServer - xvfb-run -s '-screen 0 1920x1080x24' /bin/bash -c "pytest /usr/local/lib/python3.5/dist-packages/sardana-*.egg/sardana \ No newline at end of file + xvfb-run -s '-screen 0 1920x1080x24' /bin/bash -c "pytest /usr/local/lib/python3.5/dist-packages/sardana-*.egg/sardana" From f0b936db83cd13982abacf2ea6afffd88f520140 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Wed, 2 Dec 2020 22:57:38 +0100 Subject: [PATCH 028/279] Delete .travis.yml --- .travis.yml | 67 ----------------------------------------------------- 1 file changed, 67 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7288db4a42..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,67 +0,0 @@ -group: deprecated-2017Q4 - -language: python - -sudo: required - -services: - - docker - -python: - - "3.5" - -env: - global: - # Doctr deploy key for sardana-org/sardana-doc - - secure: "p/0UgVZzPKJQqcvQ/97qMgo9kPCE0cZ6vI+308YEJ2o9xj4a3FsfHCZ/vWtjdsrp1sQbtKVDesx+xmK4CLDzQeC2+Xskv8OZDjaG2jYkHcVosZEM3EGW8rLVKzoDWLr6cTy2wexLgjHPCsmrjukPs49/i5p+WU0no64YoLlZdp9TT+gvWSQJLIk6R4eqt4FHMszPybLv0pvb1SEiCzimlX1WM1pBrE0LHgchd2ZBYSUWTTwe+Koi4HCS4Bads8j20K2e3fFKcmR2u9DfmU+7Mf5HRJsj1LYJgBUF76lUG2/fZfpoDe8sWi+eUewTa3zNM4bhRLpV+pmG0ypplM4pIcdvwiHV03nGSGu6XK6OGQ/Mgsw0fmud4JR4f5g9DgEfERlyJKI4A9mPZQ327OmEwOOl33x2AFJAL05Qvm0yXCkf1dwgYXnZl44SQbAczY1NHFL90t6xbHtmTitJrE2Xb+4BLzMe3OOZj6j/0QeiXA4z1FnZr1s8UoAsm68iW194IuFg1RRG9FTISFWaBew5wzwvAJak0DxkpG0k43VkHiVC7sPHqr5CxXMXO/MuaptK2ti6iLK9xBAEUpO9HluOkeJq5WDIIxBiBS9tPi0i3vIpq87RjHkdw5n7pdIqnuJ1nXUjpWsuUyV3fLkY12fFxSbZgqmNhIE5/o9c5VP/69Y=" - - matrix: - - TEST="flake8" - - TEST="testsuite" DOCKER_IMG=reszelaz/sardana-test - - TEST="doc" DOCKER_IMG=reszelaz/sardana-test - - -before_install: - # install flake8 to perform python code style check in the script part - # install it using pip in order to get the newest version - - if [ $TEST == "flake8" ]; then sudo apt-get update -qq ; fi - - if [ $TEST == "flake8" ]; then sudo apt-get install -qq python3-pip; fi - - if [ $TEST == "flake8" ]; then sudo pip3 install flake8; fi - -install: - # run reszelaz/sardana-test docker container (Debian8 with sardana-deps) - - if [ $TEST != "flake8" ]; then docker pull $DOCKER_IMG; fi - - if [ $TEST != "flake8" ]; then docker run -d --name=sardana-test -h sardana-test --volume=`pwd`:/sardana $DOCKER_IMG; fi - - # wait approx. 10 s (supervisor starts mysql and Tango DB) - - if [ $TEST == "testsuite" ]; then sleep 10; fi - - # install sardana in order to create the launcher scripts for servers - - if [ $TEST == "testsuite" ]; then docker exec sardana-test bash -c "cd /sardana && python3 setup.py install"; fi - - # start Pool and MacroServer necessary for macro tests - - if [ $TEST == "testsuite" ]; then docker exec sardana-test supervisorctl start Pool; fi - - if [ $TEST == "testsuite" ]; then docker exec sardana-test supervisorctl start MacroServer; fi - -script: - # make the script fail if a line fails - - set -e - # run flake8 check on all python files in the project - - if [ $TEST == "flake8" ]; then ci/flake8_diff.sh; fi - # run the full testsuite - - if [ $TEST == "testsuite" ]; then docker exec sardana-test xvfb-run -s '-screen 0 1920x1080x24' /bin/bash -c "pytest /usr/local/lib/python3.5/dist-packages/sardana-*.egg/sardana"; fi - # build docs - - if [ $TEST == "doc" ]; then docker exec -t sardana-test /bin/bash -c "cd /sardana ; sphinx-build -W doc/source/ build/sphinx/html" ; fi - - if [ $TEST == "doc" ]; then docker exec -t sardana-test /bin/bash -c "touch /sardana/build/sphinx/html/.nojekyll" ; fi - -deploy: - - provider: pages - local_dir: build/sphinx/html - repo: sardana-org/sardana-doc - skip_cleanup: true - github_token: $GITHUB_TOKEN # Set in the settings page of your repository, as a secure variable - keep_history: true - fqdn: sardana-controls.org # Set custom domain - on: - branch: develop - condition: "$TEST == doc" From 4935392c4b12bbc6f6f5ec9d601053ca343f9bf3 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 3 Dec 2020 16:11:06 +0100 Subject: [PATCH 029/279] Try docker-run-action Try https://github.com/marketplace/actions/docker-run-action action. --- .github/workflows/tests.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 1f51af4bd0..e6b093c441 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -9,13 +9,13 @@ jobs: - name: Checkout uses: actions/checkout@v2 - name: Install on container & test - uses: docker://reszelaz/sardana + uses: addnab/docker-run-action@v1 with: - volumes: - - .:/sardana - run: | - sleep 10 - cd /sardana && python3 setup.py install - supervisorctl start Pool - supervisorctl start MacroServer - xvfb-run -s '-screen 0 1920x1080x24' /bin/bash -c "pytest /usr/local/lib/python3.5/dist-packages/sardana-*.egg/sardana" + image: reszelaz/sardana-test + options: -v ${{github.workspace}}:/sardana + run: | + sleep 10 + cd /sardana && python3 setup.py install + supervisorctl start Pool + supervisorctl start MacroServer + xvfb-run -s '-screen 0 1920x1080x24' /bin/bash -c "pytest /usr/local/lib/python3.5/dist-packages/sardana-*.egg/sardana" From b7f06050867d65ddc4644e3b315688199162d5a0 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 3 Dec 2020 16:24:24 +0100 Subject: [PATCH 030/279] Add option "-h" --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index e6b093c441..07e885aeaf 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -12,7 +12,7 @@ jobs: uses: addnab/docker-run-action@v1 with: image: reszelaz/sardana-test - options: -v ${{github.workspace}}:/sardana + options: -h sardana-test -v ${{github.workspace}}:/sardana run: | sleep 10 cd /sardana && python3 setup.py install From 0efd82783ca67a6dc1396c16429dea54f14a7b2a Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 3 Dec 2020 21:40:27 +0100 Subject: [PATCH 031/279] Try paresy fork of docker-run-action --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 07e885aeaf..9b29b91918 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -9,7 +9,7 @@ jobs: - name: Checkout uses: actions/checkout@v2 - name: Install on container & test - uses: addnab/docker-run-action@v1 + uses: paresy/docker-run-action@v1 with: image: reszelaz/sardana-test options: -h sardana-test -v ${{github.workspace}}:/sardana From 80d33866f784e47edb933e6b3a44f2a77f641af1 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 3 Dec 2020 21:53:40 +0100 Subject: [PATCH 032/279] Try approach of container https://www.petefreitag.com/item/903.cfm --- .github/workflows/tests.yaml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 9b29b91918..184ee4aeb0 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -5,17 +5,17 @@ on: [pull_request] jobs: tests: runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Install on container & test - uses: paresy/docker-run-action@v1 - with: - image: reszelaz/sardana-test - options: -h sardana-test -v ${{github.workspace}}:/sardana - run: | - sleep 10 - cd /sardana && python3 setup.py install - supervisorctl start Pool - supervisorctl start MacroServer - xvfb-run -s '-screen 0 1920x1080x24' /bin/bash -c "pytest /usr/local/lib/python3.5/dist-packages/sardana-*.egg/sardana" + container: + image: reszelaz/sardana-test + steps: + - uses: actions/checkout@v2 + - name: Install sardana + run: python3 setup.py install + - name: Start servers + run: | + sleep 10 + supervisorctl start Pool + supervisorctl start MacroServer + - name: Run tests + run: xvfb-run -s '-screen 0 1920x1080x24' /bin/bash -c "pytest /usr/local/lib/python3.5/dist-packages/sardana-*.egg/sardana" + From a6d85e80b120ba4ad8218a02243fc0328a4cef65 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 3 Dec 2020 22:27:12 +0100 Subject: [PATCH 033/279] Add -h option --- .github/workflows/tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 184ee4aeb0..c59f1a0ea8 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -7,6 +7,7 @@ jobs: runs-on: ubuntu-latest container: image: reszelaz/sardana-test + options: -h sardana-test steps: - uses: actions/checkout@v2 - name: Install sardana From 86f48731c7e8cd259dd5f85d541589ed1969dade Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 3 Dec 2020 22:38:02 +0100 Subject: [PATCH 034/279] Start supervisord --- .github/workflows/tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c59f1a0ea8..44c2aba30e 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -14,6 +14,7 @@ jobs: run: python3 setup.py install - name: Start servers run: | + /usr/bin/supervisord sleep 10 supervisorctl start Pool supervisorctl start MacroServer From 50a0162dc5675bb9d23a49a8f85172536eceec3a Mon Sep 17 00:00:00 2001 From: reszelaz Date: Fri, 4 Dec 2020 15:57:15 +0100 Subject: [PATCH 035/279] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c308960388..a17fef07aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ This file follows the formats and conventions from [keepachangelog.com] ### Fixed * Execute per measurement preparation in `mesh` scan macro (#1437) +* Continously read value references in hardware synchronized acquisition + instead of reading only at the end (#1442, #1448) * Recorders tests helpers (#1439) ## [3.0.3] 2020-09-18 From 8a4bbefb5bc18676d087b135c8c155f2ffd43d93 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Fri, 4 Dec 2020 16:23:25 +0100 Subject: [PATCH 036/279] Disable flake8 job in CI This job execution reveals problem with the flake8 test: https://travis-ci.org/github/sardana-org/sardana/jobs/745428261 This issue appeared after recent new release of flake8 and is due to the way we install flake8 on travis. Even it it should be relatively easy to fix, I think we should not invest time to travis since we will move to some other CI solution (see #1433) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7288db4a42..79d185ed08 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ env: - secure: "p/0UgVZzPKJQqcvQ/97qMgo9kPCE0cZ6vI+308YEJ2o9xj4a3FsfHCZ/vWtjdsrp1sQbtKVDesx+xmK4CLDzQeC2+Xskv8OZDjaG2jYkHcVosZEM3EGW8rLVKzoDWLr6cTy2wexLgjHPCsmrjukPs49/i5p+WU0no64YoLlZdp9TT+gvWSQJLIk6R4eqt4FHMszPybLv0pvb1SEiCzimlX1WM1pBrE0LHgchd2ZBYSUWTTwe+Koi4HCS4Bads8j20K2e3fFKcmR2u9DfmU+7Mf5HRJsj1LYJgBUF76lUG2/fZfpoDe8sWi+eUewTa3zNM4bhRLpV+pmG0ypplM4pIcdvwiHV03nGSGu6XK6OGQ/Mgsw0fmud4JR4f5g9DgEfERlyJKI4A9mPZQ327OmEwOOl33x2AFJAL05Qvm0yXCkf1dwgYXnZl44SQbAczY1NHFL90t6xbHtmTitJrE2Xb+4BLzMe3OOZj6j/0QeiXA4z1FnZr1s8UoAsm68iW194IuFg1RRG9FTISFWaBew5wzwvAJak0DxkpG0k43VkHiVC7sPHqr5CxXMXO/MuaptK2ti6iLK9xBAEUpO9HluOkeJq5WDIIxBiBS9tPi0i3vIpq87RjHkdw5n7pdIqnuJ1nXUjpWsuUyV3fLkY12fFxSbZgqmNhIE5/o9c5VP/69Y=" matrix: - - TEST="flake8" + # - TEST="flake8" - TEST="testsuite" DOCKER_IMG=reszelaz/sardana-test - TEST="doc" DOCKER_IMG=reszelaz/sardana-test From 0ff086cacb657a0349ea6fcf276e3a5d2bcf393a Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 4 Dec 2020 18:30:08 +0100 Subject: [PATCH 037/279] Document Tango connection recycling and disposal. More details in #1453. --- .../devel/howto_macros/macros_general.rst | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/doc/source/devel/howto_macros/macros_general.rst b/doc/source/devel/howto_macros/macros_general.rst index f8e1cb08c7..3fde9e05f4 100644 --- a/doc/source/devel/howto_macros/macros_general.rst +++ b/doc/source/devel/howto_macros/macros_general.rst @@ -965,6 +965,36 @@ simplified usage you should use Taurus. If you strive for very optimal access to Tango and don't need these benefits then most probably PyTango will work better for you. +.. hint:: + If you go for PyTango and wonder if creating a new `tango.DeviceProxy` + in frequent macro executions is inefficient from the I/O point of view you + should not worry about it cause Tango (more precisely CORBA) is taking + care about recycling the connection during a period of 120 s (default). + + If you still would like to optimize your code in order to avoid creation + of a new `tango.DeviceProxy` you may consider using the + `functools.lru_cache` as a singleton cache mechanism:: + + import functools + import tango + from sardana.macroserver.macro import macro + + Device = functools.lru_cache(maxsize=1024)(tango.DeviceProxy) + + @macro() + def switch_states(self): + """Switch TangoTest device state""" + proxy = Device('sys/tg_test/1') + proxy.SwitchStates() + + Here you don't need to worry about the opened connection to the + Tango device server in case you don't execute the macro for a while. + Again, Tango (more precisely CORBA) will take care about it. + See more details about the CORBA scavanger thread in: + `Tango client threading `_ + and `CORBA idle connection shutdown `_. + + .. _sardana-macro-using-external-libraries: Using external python libraries From 56fafea787363b4daa63e26fbac27f6c2917cb35 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Fri, 4 Dec 2020 18:31:47 +0100 Subject: [PATCH 038/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a17fef07aa..2fd27331df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Continously read value references in hardware synchronized acquisition instead of reading only at the end (#1442, #1448) * Recorders tests helpers (#1439) +* Disable flake8 job in travis CI (#1455) ## [3.0.3] 2020-09-18 From 7747c94a2cd9b4402c809584e89cc7ae95e5bc9d Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 8 Dec 2020 23:43:11 +0100 Subject: [PATCH 039/279] Add test to demonstrate h5 file lock problem Sardana H5 recorder opens and closes file at every scan. This prevents other applications e.g. PyMCA, silx to open them for reading in between the scans and keep them opened while next scan is being launched. This is described in #1124. Add test to demonstrate this issue. --- .../recorders/test/test_h5storage.py | 62 ++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/src/sardana/macroserver/recorders/test/test_h5storage.py b/src/sardana/macroserver/recorders/test/test_h5storage.py index 413f4e4797..085f50aa33 100644 --- a/src/sardana/macroserver/recorders/test/test_h5storage.py +++ b/src/sardana/macroserver/recorders/test/test_h5storage.py @@ -27,6 +27,7 @@ import os import tempfile +import multiprocessing from datetime import datetime import h5py @@ -36,8 +37,17 @@ from sardana.macroserver.scan import ColumnDesc from sardana.macroserver.recorders.h5storage import NXscanH5_FileRecorder + COL1_NAME = "col1" +ENV = { + "serialno": 0, + "starttime": None, + "title": "test", + "user": "user", + "datadesc": None, + "endtime": None +} class RecordList(dict): @@ -65,14 +75,7 @@ def setUp(self): except OSError: pass - self.env = { - "serialno": 0, - "starttime": None, - "title": "test", - "user": "user", - "datadesc": None, - "endtime": None - } + self.env = ENV self.record_list = RecordList(self.env) def test_dtype_float64(self): @@ -193,3 +196,46 @@ def tearDown(self): os.remove(self.path) except OSError: pass + + +def test_swmr(tmpdir): + path = str(tmpdir / "file.h5") + + def scan(path, serialno=0): + env = ENV.copy() + env["serialno"] = serialno + record_list = RecordList(env) + nb_records = 2 + # create description of channel data + data_desc = [ + ColumnDesc(name=COL1_NAME, + label=COL1_NAME, + dtype="float64", + shape=()) + ] + env["datadesc"] = data_desc + # simulate sardana scan + recorder = NXscanH5_FileRecorder(filename=path) + env["starttime"] = datetime.now() + recorder._startRecordList(record_list) + for i in range(nb_records): + record = Record({COL1_NAME: 0.1}, i) + recorder._writeRecord(record) + env["endtime"] = datetime.now() + recorder._endRecordList(record_list) + + def read_file(path, q): + with h5py.File(path, mode="r"): + q.put("opened") + q.get() + + q = multiprocessing.Queue() + reader = multiprocessing.Process(target=read_file, args=(path, q,)) + + scan(path, serialno=0) + reader.start() + info = q.get() + assert info == "opened" + scan(path, serialno=1) + q.put("end") + reader.join() From 83db290f9048aa4d2c9215fa7daed0ecee41e3e6 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 8 Dec 2020 23:50:32 +0100 Subject: [PATCH 040/279] Add singleton handler of H5 files to keep them opened Sardana H5 recorder opens and closes file at every scan. This prevents other applications e.g. PyMCA, silx to open them for reading in between the scans and keep them opened while next scan is being launched. This is described in #1124. - Add singleton handler of H5 files to keep them opened - In NXscanH5_FileRecorder open the file only if it is not already opened - Mode "populate instrument info" and "create NXData" to startRecordList instead of endRecordList (necessary by SWMR) - Adapt test to avoid failure --- .../macroserver/recorders/h5storage.py | 25 ++++--- src/sardana/macroserver/recorders/h5util.py | 72 +++++++++++++++++++ .../recorders/test/test_h5storage.py | 36 ++++++++-- 3 files changed, 117 insertions(+), 16 deletions(-) create mode 100644 src/sardana/macroserver/recorders/h5util.py diff --git a/src/sardana/macroserver/recorders/h5storage.py b/src/sardana/macroserver/recorders/h5storage.py index 2b34d5abe7..565f5e9f86 100644 --- a/src/sardana/macroserver/recorders/h5storage.py +++ b/src/sardana/macroserver/recorders/h5storage.py @@ -42,6 +42,9 @@ from sardana.sardanautils import is_pure_str from sardana.taurus.core.tango.sardana import PlotType from sardana.macroserver.scan.recorder import BaseFileRecorder, SaveModes +from sardana.macroserver.recorders.h5util import _h5_file_handler, \ + _open_h5_file + VDS_available = True try: @@ -97,6 +100,7 @@ def __init__(self, filename=None, macro=None, overwrite=False, **pars): + '($|(?=[/#?])))?(?P%(path)s)$') self.pattern = pattern % dict(scheme=scheme, authority=authority, path=path) + self._close = False def getFormat(self): return 'HDF5::NXscan' @@ -123,10 +127,14 @@ def _openFile(self, fname): """Open the file with given filename (create if it does not exist) Populate the root of the file with some metadata from the NXroot definition""" - if os.path.exists(fname): - fd = h5py.File(fname, mode='r+') - else: - fd = h5py.File(fname, mode='w-') + try: + fd = _h5_file_handler[fname] + except KeyError: + fd = _open_h5_file(fname) + self._close = True + try: + fd.attrs['NX_class'] + except KeyError: fd.attrs['NX_class'] = 'NXroot' fd.attrs['file_name'] = fname fd.attrs['file_time'] = datetime.now().isoformat() @@ -254,7 +262,8 @@ def _startRecordList(self, recordlist): pass self._createPreScanSnapshot(env) - + self._populateInstrumentInfo() + self._createNXData() self.fd.flush() def _compression(self, shape, compfilter='gzip'): @@ -349,9 +358,6 @@ def _endRecordList(self, recordlist): if self.filename is None: return - self._populateInstrumentInfo() - self._createNXData() - env = self.currentlist.getEnviron() nxentry = self.fd[self.entryname] @@ -416,7 +422,8 @@ def _endRecordList(self, recordlist): self.fd.flush() self.debug('Finishing recording %d on file %s:', env['serialno'], self.filename) - self.fd.close() + if self._close: + self.fd.close() self.currentlist = None def writeRecordList(self, recordlist): diff --git a/src/sardana/macroserver/recorders/h5util.py b/src/sardana/macroserver/recorders/h5util.py new file mode 100644 index 0000000000..3b9ad16282 --- /dev/null +++ b/src/sardana/macroserver/recorders/h5util.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python + +############################################################################## +## +# This file is part of Sardana +## +# http://www.sardana-controls.org/ +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Sardana is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Sardana 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Sardana. If not, see . +## +############################################################################## + +""" +This module provides a recorder for NXscans implemented with h5py (no nxs) +""" + +import os +import h5py + + +def _open_h5_file(fname, libver='earliest'): + mode = 'w-' + if os.path.exists(fname): + mode = 'r+' + fd = h5py.File(fname, mode=mode, libver=libver) + return fd + + +class _H5FileHandler: + + def __init__(self): + self._files = {} + + def __getitem__(self, fname): + return self._files[fname] + + @property + def files(self): + return self._files.keys() + + def open_file(self, fname, swmr_mode=False): + if swmr_mode: + libver = 'latest' + else: + libver ='earliest' + fd = _open_h5_file(fname, libver=libver) + if swmr_mode: + fd.swmr_mode = True + self._files[fname] = fd + return fd + + def close_file(self, fname): + try: + fd = self._files.pop(fname) + except KeyError: + raise ValueError('{} is not opened'.format(fname)) + fd.close() + +_h5_file_handler = _H5FileHandler() diff --git a/src/sardana/macroserver/recorders/test/test_h5storage.py b/src/sardana/macroserver/recorders/test/test_h5storage.py index 085f50aa33..9b05dd2666 100644 --- a/src/sardana/macroserver/recorders/test/test_h5storage.py +++ b/src/sardana/macroserver/recorders/test/test_h5storage.py @@ -27,6 +27,7 @@ import os import tempfile +import contextlib import multiprocessing from datetime import datetime @@ -36,6 +37,26 @@ from sardana.macroserver.scan import ColumnDesc from sardana.macroserver.recorders.h5storage import NXscanH5_FileRecorder +from sardana.macroserver.recorders.h5util import _h5_file_handler + + +@contextlib.contextmanager +def h5_write_session(fname, swmr_mode=False): + """Context manager for HDF5 file write session. + + Maintains HDF5 file opened for the context lifetime. + It optionally can open the file as SWRM writer. + + :param fname: Path of the file to be opened + :type fname: str + :param swmr_mode: Use SWMR write mode + :type swmr_mode: bool + """ + fd = _h5_file_handler.open_file(fname, swmr_mode) + try: + yield fd + finally: + _h5_file_handler.close_file(fname) COL1_NAME = "col1" @@ -232,10 +253,11 @@ def read_file(path, q): q = multiprocessing.Queue() reader = multiprocessing.Process(target=read_file, args=(path, q,)) - scan(path, serialno=0) - reader.start() - info = q.get() - assert info == "opened" - scan(path, serialno=1) - q.put("end") - reader.join() + with h5_write_session(path): + scan(path, serialno=0) + reader.start() + info = q.get() + assert info == "opened" + scan(path, serialno=1) + q.put("end") + reader.join() From e6a9a4aefad67617a973578a21082d2214904f26 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 9 Dec 2020 12:47:57 +0100 Subject: [PATCH 041/279] Add _getEnv (non mAPI) method to Macro class There are use cases when we need to retrieve the environment variable even if the macro had been stopped/aborterd e.g. h5_write_session context manager. Add _getEnv non-mAPI method to Macro class. --- src/sardana/macroserver/macro.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/sardana/macroserver/macro.py b/src/sardana/macroserver/macro.py index 728ef8a85a..e455721012 100644 --- a/src/sardana/macroserver/macro.py +++ b/src/sardana/macroserver/macro.py @@ -1766,6 +1766,13 @@ def getInstrument(self, name): # Handle macro environment #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + def _getEnv(self, key=None, macro_name=None, door_name=None): + door_name = door_name or self.getDoorName() + macro_name = macro_name or self._name + + return self.macro_server.get_env(key=key, macro_name=macro_name, + door_name=door_name) + @mAPI def getEnv(self, key=None, macro_name=None, door_name=None): """**Macro API**. Gets the local environment matching the given @@ -1794,11 +1801,7 @@ def getEnv(self, key=None, macro_name=None, door_name=None): :return: a :obj:`dict` containing the environment :rtype: :obj:`dict`""" - door_name = door_name or self.getDoorName() - macro_name = macro_name or self._name - - return self.macro_server.get_env(key=key, macro_name=macro_name, - door_name=door_name) + return self._getEnv(key=key, macro_name=macro_name, door_name=door_name) @mAPI def getGlobalEnv(self): From a14ac935e500014c516c4c3679420883fdd17e04 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 9 Dec 2020 12:49:42 +0100 Subject: [PATCH 042/279] Add h5storage macros h5storage macros are used to control the h5 write sessions. --- src/sardana/macroserver/macros/h5storage.py | 131 ++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 src/sardana/macroserver/macros/h5storage.py diff --git a/src/sardana/macroserver/macros/h5storage.py b/src/sardana/macroserver/macros/h5storage.py new file mode 100644 index 0000000000..7debd26948 --- /dev/null +++ b/src/sardana/macroserver/macros/h5storage.py @@ -0,0 +1,131 @@ +############################################################################## +## +# This file is part of Sardana +## +# http://www.sardana-controls.org/ +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Sardana is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Sardana 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Sardana. If not, see . +## +############################################################################## + +"""HDF5 storage macros""" +import os +import contextlib + +from sardana.sardanautils import is_non_str_seq +from sardana.macroserver.macro import macro, Type, Optional +from sardana.macroserver.msexception import UnknownEnv +from sardana.macroserver.recorders.h5storage import NXscanH5_FileRecorder +from sardana.macroserver.recorders.h5util import _h5_file_handler + + +def _get_h5_scan_files(macro): + scan_dir = macro._getEnv("ScanDir") + scan_files = macro._getEnv("ScanFile") + if not is_non_str_seq(scan_files): + scan_files = [scan_files] + h5_scan_files = [] + for scan_file in scan_files: + file_name, file_ext = os.path.splitext(scan_file) + if file_ext not in NXscanH5_FileRecorder.formats.values(): + continue + h5_scan_files.append(os.path.join(scan_dir, scan_file)) + return h5_scan_files + + +def _h5_start_session(macro, swmr_mode): + if swmr_mode is None: + try: + swmr_mode = macro._getEnv("ScanH5SWMR") + except UnknownEnv: + swmr_mode = False + for file_path in _get_h5_scan_files(macro): + _h5_file_handler.open_file(file_path, swmr_mode) + + +@macro([["swmr_mode", Type.Boolean, Optional, "Enable SWMR mode"]]) +def h5_start_session(self, swmr_mode): + """Start write session for HDF5 scan file(s) + + Open HDF5 scan files in write mode and keep them for the needs of + recorders until the session is closed by ``h5_end_swmr``. + + Optionally, enable SWMR mode (either with ``swmr_mode`` parameter or + with ``ScanH5SWMR`` environment variable. + + Resolve configured H5 scan file names by inspecting ScanDir and ScanFile + environment variables. + """ + _h5_start_session(self, swmr_mode) + + +def _h5_end_session(macro): + for file_path in _get_h5_scan_files(macro): + _h5_file_handler.close_file(file_path) + +@macro() +def h5_end_session(self): + """End SWMR write session for HDF5 scan file(s) + + Close previously opened HDF5 scan files with the use ``h5_start_swmr``. + + Resolve configured H5 scan file names by inspecting ScanDir and ScanFile + environment variables. + """ + _h5_end_session(self) + + +@macro() +def h5_ls_session(self): + """List scan files opened for SWMR write session with ``h5_start_swmr`` + """ + for file_path in _h5_file_handler.files: + self.print(file_path) + + +@contextlib.contextmanager +def h5_write_session(macro, swmr_mode=False): + """Context manager for HDF5 file write session. + + Maintains HDF5 file opened for the context lifetime. + Optionally, open the file as SWMR writer. + + Resolve configured H5 scan file names by inspecting ScanDir and ScanFile + environment variables. + + Example of macro executing multiple scans within the same write session:: + + @macro() + def experiment(self): + with h5_write_session(macro=self, swmr_mode=True): + for i in range(10) + self.execMacro("ascan", "mot01", 0, 10, 10, 0.1) + + :param macro: macro object + :type macro: `~sardana.macroserver.macro.Macro` + :param swmr_mode: Use SWMR write mode + :type swmr_mode: bool + """ + _h5_start_session(macro, swmr_mode) + try: + yield None + finally: + _h5_end_session(macro) + + + + + From 894129454bbd5ac42902f6f37d1f3c3659693733 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 9 Dec 2020 12:50:54 +0100 Subject: [PATCH 043/279] Document h5 write sessions --- .../devel/api/sardana/macroserver/macros.rst | 1 + .../sardana/macroserver/macros/h5storage.rst | 6 ++ doc/source/devel/howto_recorders.rst | 22 ------- .../users/environment_variable_catalog.rst | 11 ++++ doc/source/users/scan.rst | 61 +++++++++++++++++++ 5 files changed, 79 insertions(+), 22 deletions(-) create mode 100644 doc/source/devel/api/sardana/macroserver/macros/h5storage.rst diff --git a/doc/source/devel/api/sardana/macroserver/macros.rst b/doc/source/devel/api/sardana/macroserver/macros.rst index 5701f1f48f..546be9945f 100644 --- a/doc/source/devel/api/sardana/macroserver/macros.rst +++ b/doc/source/devel/api/sardana/macroserver/macros.rst @@ -16,6 +16,7 @@ env expconf expert + h5storage hkl ioregister lists diff --git a/doc/source/devel/api/sardana/macroserver/macros/h5storage.rst b/doc/source/devel/api/sardana/macroserver/macros/h5storage.rst new file mode 100644 index 0000000000..6e415dd394 --- /dev/null +++ b/doc/source/devel/api/sardana/macroserver/macros/h5storage.rst @@ -0,0 +1,6 @@ +:mod:`~sardana.macroserver.macros.h5storage` +============================================ + +.. automodule:: sardana.macroserver.macros.h5storage + :imported-members: + :members: \ No newline at end of file diff --git a/doc/source/devel/howto_recorders.rst b/doc/source/devel/howto_recorders.rst index 0b7c2d3748..053c1fd587 100644 --- a/doc/source/devel/howto_recorders.rst +++ b/doc/source/devel/howto_recorders.rst @@ -22,28 +22,6 @@ element which is also identified by its name. Recorders are developed as Python classes, and recorder libraries are just Python modules aggregating these classes. -Type of recorders ------------------ - -Sardana defines some standard recorders e.g. the Spock output recorder or the -SPEC file recorder. From the other hand users may define their custom recorders. -Sardana provides the following standard recorders (grouped by types): - -* file [*] - * FIO_FileRecorder - * NXscan_FileRecorder - * SPEC_FileRecorder - -* shared memory [*] - * SPSRecorder - * ShmRecorder - -* output - * JsonRecorder [*] - * OutputRecorder - -[*] Scan Framework provides mechanisms to enable and select this recorders using -the environment variables. Writing a custom recorder ------------------------- diff --git a/doc/source/users/environment_variable_catalog.rst b/doc/source/users/environment_variable_catalog.rst index e66214594d..bb27fff031 100644 --- a/doc/source/users/environment_variable_catalog.rst +++ b/doc/source/users/environment_variable_catalog.rst @@ -300,6 +300,17 @@ be used). Recorder class is implicitly selected based on the file extension. For example "myexperiment.spec" will by default store data in SPEC compatible format. +.. _scanh5swmr: + +ScanH5SWMR +~~~~~~~~~~ + +*Not mandatory, set by user* + +Enable/disable HDF5 SWMR mode when using HDF5 *write sessions* with +:ref:`sardana-users-scan-data-storage-nxscanh5_filerecorder`. + + .. _scanrecorder: ScanRecorder diff --git a/doc/source/users/scan.rst b/doc/source/users/scan.rst index 29c7236c12..0241ea1fe2 100644 --- a/doc/source/users/scan.rst +++ b/doc/source/users/scan.rst @@ -187,6 +187,67 @@ Scans are highly configurable using the environment variables Variables, check the :ref:`Environment Variable Catalog ` +.. _sardana-users-scan-data-storage: + +Data storage +------------ + +Data being produced by scans can be optionally handled by *recorders* and +sent to a variety of destinations. Typical use case is to store the scan data +in a file. + +Built-in recorders +^^^^^^^^^^^^^^^^^^ + +Sardana defines some standard recorders e.g. the Spock output recorder or the +SPEC file recorder. From the other hand users may define their custom recorders. +Sardana provides the following standard recorders (grouped by types): + +* file [*] + * FIO_FileRecorder + * NXscanH5_FileRecorder + * SPEC_FileRecorder + +* shared memory [*] + * SPSRecorder + * ShmRecorder + +* output + * JsonRecorder [*] + * OutputRecorder + +[*] Scan Framework provides mechanisms to enable and select this recorders +using the environment variables. + +.. _sardana-users-scan-data-storage-nxscanh5_filerecorder: + +NXscanH5_FileRecorder +""""""""""""""""""""" + +NXscanH5_FileRecorder is a scan recorder which writes the scan data according +to the NXscan `NeXus `_ application definition +in the `HDF5 `_ file format. + +Sardana scan recorders are instantiated per scan execution and therefore this +recorder opens and closes the HDF5 file for writing when the scan starts +and ends respectively. This may cause file locking issues with reading +applications opened in between the scans. To overcome this issue +the *write session* concept, with optional support of SWMR mode, +was introduced for this particular recorder. + +The write sessions use case scenarios: + +* Manual session control with macros + To start and end the session you can use + `~sardana.macroserver.macros.h5storage.h5_start_session` and + `~sardana.macroserver.macros.h5storage.h5_end_session` macros. + You can list the active sessions with + `~sardana.macroserver.macros.h5storage.h5_ls_session` macro. +* Programmatic session control with context manager (for macro developers) + You can use the `~sardana.macroserver.macros.h5storage.h5_write_session` + context manager to ensure that the write session is only active over a + specific part of your macro code. + .. _sardana-users-scan-snapshot: Scan snapshots From a466bde09bd220e1a0d5f9f53fdf062b3be83d56 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 10 Dec 2020 12:21:20 +0100 Subject: [PATCH 044/279] Add H5 file handler cleanup atexit. --- src/sardana/macroserver/recorders/h5util.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/sardana/macroserver/recorders/h5util.py b/src/sardana/macroserver/recorders/h5util.py index 3b9ad16282..ae28da1adb 100644 --- a/src/sardana/macroserver/recorders/h5util.py +++ b/src/sardana/macroserver/recorders/h5util.py @@ -29,6 +29,7 @@ import os import h5py +import atexit def _open_h5_file(fname, libver='earliest'): @@ -43,6 +44,7 @@ class _H5FileHandler: def __init__(self): self._files = {} + atexit.register(self.clean_up) def __getitem__(self, fname): return self._files[fname] @@ -69,4 +71,9 @@ def close_file(self, fname): raise ValueError('{} is not opened'.format(fname)) fd.close() + def clean_up(self): + for file in self.files: + self.close_file(file) + + _h5_file_handler = _H5FileHandler() From 653e1a9fc37b310bc63e6f80fcd477c5099f56db Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 10 Dec 2020 12:40:16 +0100 Subject: [PATCH 045/279] Use Event instead of Queue in h5storage SWMR test --- .../recorders/test/test_h5storage.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/sardana/macroserver/recorders/test/test_h5storage.py b/src/sardana/macroserver/recorders/test/test_h5storage.py index 9b05dd2666..6babb8d401 100644 --- a/src/sardana/macroserver/recorders/test/test_h5storage.py +++ b/src/sardana/macroserver/recorders/test/test_h5storage.py @@ -220,7 +220,6 @@ def tearDown(self): def test_swmr(tmpdir): - path = str(tmpdir / "file.h5") def scan(path, serialno=0): env = ENV.copy() @@ -245,19 +244,19 @@ def scan(path, serialno=0): env["endtime"] = datetime.now() recorder._endRecordList(record_list) - def read_file(path, q): + def read_file(path, event): with h5py.File(path, mode="r"): - q.put("opened") - q.get() - - q = multiprocessing.Queue() - reader = multiprocessing.Process(target=read_file, args=(path, q,)) + event.set() + event.wait() + path = str(tmpdir / "file.h5") + event = multiprocessing.Event() + reader = multiprocessing.Process(target=read_file, args=(path, event)) with h5_write_session(path): scan(path, serialno=0) reader.start() - info = q.get() - assert info == "opened" + event.wait() + event.clear() scan(path, serialno=1) - q.put("end") + event.set() reader.join() From 3402dd4ef30880f8dd28eb35ff47a9280de3854c Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 10 Dec 2020 15:35:36 +0100 Subject: [PATCH 046/279] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fd27331df..6e75c61883 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ This file follows the formats and conventions from [keepachangelog.com] * Execute per measurement preparation in `mesh` scan macro (#1437) * Continously read value references in hardware synchronized acquisition instead of reading only at the end (#1442, #1448) +* Avoid problems when defining different, e.g. shape, standard attributes, + e.g. pseudo counter's value, in controllers (#1440, #1446) * Recorders tests helpers (#1439) * Disable flake8 job in travis CI (#1455) From 9b98a7b887c1a8c0df496a29fd1348677c7527e2 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 10 Dec 2020 16:15:13 +0100 Subject: [PATCH 047/279] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e75c61883..74dcb816f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ This file follows the formats and conventions from [keepachangelog.com] ### Added -* ... +* Initial delay in position domain to the synchronization description + in *ct* like continuous scans (#1428) ### Fixed From 1d20c6943b0354b773312d7aca7250422871564b Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 10 Dec 2020 16:26:28 +0100 Subject: [PATCH 048/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74dcb816f1..d6dab22ed0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Initial delay in position domain to the synchronization description in *ct* like continuous scans (#1428) +* Avoid double printing of user units in PMTV: read widget and units widget (#1424) ### Fixed From 4462c393903dd5b291b6f7a88135334704a1c9bf Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 10 Dec 2020 22:59:33 +0100 Subject: [PATCH 049/279] Document ScanUser environment variable. --- doc/source/users/environment_variable_catalog.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/doc/source/users/environment_variable_catalog.rst b/doc/source/users/environment_variable_catalog.rst index e7f72174fb..c22db3681b 100644 --- a/doc/source/users/environment_variable_catalog.rst +++ b/doc/source/users/environment_variable_catalog.rst @@ -308,7 +308,7 @@ ScanRecorder Its value may be either of type string or of list of strings. If ScanRecorder variable is defined, it explicitly indicates which recorder -class should be used and for which file defined by ScanFile (based on the +class should be used and for which file defined by ScanFile (based on the order). Example 1: @@ -334,6 +334,17 @@ Example 2: .. seealso:: More about the extension to recorder map in :ref:`sardana-writing-recorders`. +.. _scanuser: + +ScanUser +~~~~~~~~ +*Not mandatory, set by user* + +Its value is of type string. Its value is delivered to the recorders which +may use it, for example, as a user contact information. If not set, the OS +user executing the Sardana server (which executes the scan) will be passed to +the recorders instead. + .. _sharedmemory: SharedMemory From dbe63dc7275e75442f863bb5391252ba2e13ea7e Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 10 Dec 2020 23:43:54 +0100 Subject: [PATCH 050/279] Document showscan online information panels. --- .../_static/showscan-online-infopanels.png | Bin 0 -> 53153 bytes doc/source/users/taurus/showscan.rst | 9 +++++++++ 2 files changed, 9 insertions(+) create mode 100644 doc/source/_static/showscan-online-infopanels.png diff --git a/doc/source/_static/showscan-online-infopanels.png b/doc/source/_static/showscan-online-infopanels.png new file mode 100644 index 0000000000000000000000000000000000000000..8fb460471c05c3b40c2524d7d4c7fbeac33a7d82 GIT binary patch literal 53153 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
GC+#8Vyye5>Ey^m>%= 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

*!=-1dKUjUD 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 A3jhEB literal 0 HcmV?d00001 diff --git a/doc/source/users/taurus/showscan.rst b/doc/source/users/taurus/showscan.rst index df040ed9e4..e2f208ae70 100644 --- a/doc/source/users/taurus/showscan.rst +++ b/doc/source/users/taurus/showscan.rst @@ -48,6 +48,15 @@ plot per curve or group curves by the selected x axis Showscan online plotting three physical counters against the motor's position on separate plots. +Finally, the *scan point* and the *scan information* panels are available +and offer online updates on the channel values of the current scan point +and some general scan information e.g. scan file, start and end time, etc. +respectively. + +.. figure:: /_static/showscan-online-infopanels.png + + Showscan online plotting with separate plots and information panels. + ---------------- Showscan offline ---------------- From 21f28bebd05fe1922f24f07362b5e771519edc4c Mon Sep 17 00:00:00 2001 From: reszelaz Date: Fri, 11 Dec 2020 09:58:35 +0100 Subject: [PATCH 051/279] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6dab22ed0..7966ad9c7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ This file follows the formats and conventions from [keepachangelog.com] ### Added +* *scan information* and *scan point* forms to the *showscan online* widget (#1386) +* `ScanPlotWidget`, `ScanPlotWindow`, `ScanInfoForm`, `ScanPointForm` and `ScanWindow` + widget classes for easier composition of custom GUIs involving online scan plotting (#1386) * Initial delay in position domain to the synchronization description in *ct* like continuous scans (#1428) * Avoid double printing of user units in PMTV: read widget and units widget (#1424) From 831f26db98b279a03b966ad954b4d5e8af1c2b8c Mon Sep 17 00:00:00 2001 From: reszelaz Date: Fri, 11 Dec 2020 11:13:51 +0100 Subject: [PATCH 052/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7966ad9c7e..040098cf0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This file follows the formats and conventions from [keepachangelog.com] * *scan information* and *scan point* forms to the *showscan online* widget (#1386) * `ScanPlotWidget`, `ScanPlotWindow`, `ScanInfoForm`, `ScanPointForm` and `ScanWindow` widget classes for easier composition of custom GUIs involving online scan plotting (#1386) +* Add `ScanUser` environment variable (#1355) * Initial delay in position domain to the synchronization description in *ct* like continuous scans (#1428) * Avoid double printing of user units in PMTV: read widget and units widget (#1424) From f072f75249fc22cda273c05483936e3c30e4f03e Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 11 Dec 2020 13:55:54 +0100 Subject: [PATCH 053/279] Document deterministic scan property of SScan --- doc/source/devel/api/sardana/macroserver/scan.rst | 4 ++-- src/sardana/macroserver/scan/gscan.py | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/doc/source/devel/api/sardana/macroserver/scan.rst b/doc/source/devel/api/sardana/macroserver/scan.rst index 2a969b062e..0663819c11 100644 --- a/doc/source/devel/api/sardana/macroserver/scan.rst +++ b/doc/source/devel/api/sardana/macroserver/scan.rst @@ -26,8 +26,8 @@ GScan :show-inheritance: :members: -GScan ------ +Scan +---- .. inheritance-diagram:: SScan :parts: 1 diff --git a/src/sardana/macroserver/scan/gscan.py b/src/sardana/macroserver/scan/gscan.py index 466050eda1..e4641c1f2e 100644 --- a/src/sardana/macroserver/scan/gscan.py +++ b/src/sardana/macroserver/scan/gscan.py @@ -1067,6 +1067,15 @@ def __init__(self, macro, generator=None, moveables=[], env={}, @property def deterministic_scan(self): + """Check if the scan is a deterministic scan. + + Scan is considered as deterministic scan if + the `~sardana.macroserver.macro.Macro` specialization owning + the scan object contains ``nb_points`` and ``integ_time`` attributes. + + Scan flow depends on this property (some optimizations are applied). + These can be disabled by setting this property to `False`. + """ if self._deterministic_scan is None: macro = self.macro if hasattr(macro, "nb_points") and hasattr(macro, "integ_time"): From f1715588ff83dfe284909438974bea3b61192073 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Fri, 11 Dec 2020 15:43:19 +0100 Subject: [PATCH 054/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 040098cf0e..58808c94f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This file follows the formats and conventions from [keepachangelog.com] * `ScanPlotWidget`, `ScanPlotWindow`, `ScanInfoForm`, `ScanPointForm` and `ScanWindow` widget classes for easier composition of custom GUIs involving online scan plotting (#1386) * Add `ScanUser` environment variable (#1355) +* Allow to programmatically disable *deterministic scan* optimization (#1426, #1427) * Initial delay in position domain to the synchronization description in *ct* like continuous scans (#1428) * Avoid double printing of user units in PMTV: read widget and units widget (#1424) From 1f733ce471267a52c706a35228631f9cd1450c4f Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 11 Dec 2020 23:33:43 +0100 Subject: [PATCH 055/279] Do not use int ids for MacroNode MacroServer assigns uuid as macro ids when these were not passed in the XML requesting the macro execution. The same applies to the spock and macrobutton. Make the sequencer and the macroexecutor follow the same behavior instead of generating consecutive int number ids. --- .../taurus/core/tango/sardana/macro.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/sardana/taurus/core/tango/sardana/macro.py b/src/sardana/taurus/core/tango/sardana/macro.py index d77feb4f91..4c497d121d 100644 --- a/src/sardana/taurus/core/tango/sardana/macro.py +++ b/src/sardana/taurus/core/tango/sardana/macro.py @@ -32,6 +32,7 @@ import os import copy +import uuid import types import tempfile @@ -833,7 +834,6 @@ def fromList(self, params): class MacroNode(BranchNode): """Class to represent macro element.""" - count = 0 def __init__(self, parent=None, name=None, params_def=None, macro_info=None): @@ -867,7 +867,7 @@ def id(self): """ Getter of macro's id property - :return: (int) + :return: (str) .. seealso: :meth:`MacroNode.setId`, assignId """ @@ -878,7 +878,7 @@ def setId(self, id): """ Setter of macro's id property - :param id: (int) new macro's id + :param id: (str) new macro's id See Also: id, assignId """ @@ -890,16 +890,13 @@ def assignId(self): If macro didn't have an assigned id it assigns it and return macro's id. - :return: (int) + :return: (str) See Also: id, setId """ - id = self.id() - if id is not None: - return id - MacroNode.count += 1 - self.setId(MacroNode.count) - return MacroNode.count + id_ = str(uuid.uuid1()) + self.setId(id_) + return id_ def name(self): return self._name @@ -1160,7 +1157,7 @@ def toXml(self, withId=True): if withId: id_ = self.id() if id_ is not None: - macroElement.set("id", str(self.id())) + macroElement.set("id", self.id()) for hookPlace in self.hookPlaces(): hookElement = etree.SubElement(macroElement, "hookPlace") hookElement.text = hookPlace From ca6a912f1320705077e3523578c6192168ea78e0 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Sat, 12 Dec 2020 09:04:13 +0100 Subject: [PATCH 056/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58808c94f8..33b25e7fd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ This file follows the formats and conventions from [keepachangelog.com] instead of reading only at the end (#1442, #1448) * Avoid problems when defining different, e.g. shape, standard attributes, e.g. pseudo counter's value, in controllers (#1440, #1446) +* Problems with macro id's when `sequencer` executes from _plain text_ files (#1215, #1216) * Recorders tests helpers (#1439) * Disable flake8 job in travis CI (#1455) From 73f645786ff8b751707b34c0bcd5e5e7dda05dd1 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Tue, 15 Dec 2020 23:30:38 +0100 Subject: [PATCH 057/279] Update CHANGELOG.md --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33b25e7fd6..dc1a00da5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ This file follows the formats and conventions from [keepachangelog.com] * Initial delay in position domain to the synchronization description in *ct* like continuous scans (#1428) * Avoid double printing of user units in PMTV: read widget and units widget (#1424) +* Documentation example on how to more efficiently access Tango with PyTango + in macros/controllers (#1456) ### Fixed @@ -23,7 +25,8 @@ This file follows the formats and conventions from [keepachangelog.com] instead of reading only at the end (#1442, #1448) * Avoid problems when defining different, e.g. shape, standard attributes, e.g. pseudo counter's value, in controllers (#1440, #1446) -* Problems with macro id's when `sequencer` executes from _plain text_ files (#1215, #1216) +* Problems with macro id's when `sequencer` executes from _plain text_ files (#1215, #1216) +* `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) * Recorders tests helpers (#1439) * Disable flake8 job in travis CI (#1455) From 62823733cfa886aec6dd3baf17506efe786c1862 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 17 Dec 2020 18:18:49 +0100 Subject: [PATCH 058/279] Fix createMacro and prepareMacro docstring Fix copy&paste mistake in docstring. Fixes #1444 --- src/sardana/macroserver/macro.py | 80 ++++++++++++++++---------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/sardana/macroserver/macro.py b/src/sardana/macroserver/macro.py index e637b32601..55c1ef822c 100644 --- a/src/sardana/macroserver/macro.py +++ b/src/sardana/macroserver/macro.py @@ -1132,35 +1132,35 @@ def createMacro(self, *pars): Several different parameter formats are supported:: # several parameters: - self.execMacro('ascan', 'th', '0', '100', '10', '1.0') - self.execMacro('mv', [[motor.getName(), '0']]) - self.execMacro('mv', motor.getName(), '0') # backwards compatibility - see note - self.execMacro('ascan', 'th', 0, 100, 10, 1.0) - self.execMacro('mv', [[motor.getName(), 0]]) - self.execMacro('mv', motor.getName(), 0) # backwards compatibility - see note + self.createMacro('ascan', 'th', '0', '100', '10', '1.0') + self.createMacro('mv', [[motor.getName(), '0']]) + self.createMacro('mv', motor.getName(), '0') # backwards compatibility - see note + self.createMacro('ascan', 'th', 0, 100, 10, 1.0) + self.createMacro('mv', [[motor.getName(), 0]]) + self.createMacro('mv', motor.getName(), 0) # backwards compatibility - see note th = self.getObj('th') - self.execMacro('ascan', th, 0, 100, 10, 1.0) - self.execMacro('mv', [[th, 0]]) - self.execMacro('mv', th, 0) # backwards compatibility - see note + self.createMacro('ascan', th, 0, 100, 10, 1.0) + self.createMacro('mv', [[th, 0]]) + self.createMacro('mv', th, 0) # backwards compatibility - see note # a sequence of parameters: - self.execMacro(['ascan', 'th', '0', '100', '10', '1.0') - self.execMacro(['mv', [[motor.getName(), '0']]]) - self.execMacro(['mv', motor.getName(), '0']) # backwards compatibility - see note - self.execMacro(('ascan', 'th', 0, 100, 10, 1.0)) - self.execMacro(['mv', [[motor.getName(), 0]]]) - self.execMacro(['mv', motor.getName(), 0]) # backwards compatibility - see note + self.createMacro(['ascan', 'th', '0', '100', '10', '1.0']) + self.createMacro(['mv', [[motor.getName(), '0']]]) + self.createMacro(['mv', motor.getName(), '0']) # backwards compatibility - see note + self.createMacro(('ascan', 'th', 0, 100, 10, 1.0)) + self.createMacro(['mv', [[motor.getName(), 0]]]) + self.createMacro(['mv', motor.getName(), 0]) # backwards compatibility - see note th = self.getObj('th') - self.execMacro(['ascan', th, 0, 100, 10, 1.0]) - self.execMacro(['mv', [[th, 0]]]) - self.execMacro(['mv', th, 0]) # backwards compatibility - see note + self.createMacro(['ascan', th, 0, 100, 10, 1.0]) + self.createMacro(['mv', [[th, 0]]]) + self.createMacro(['mv', th, 0]) # backwards compatibility - see note # a space separated string of parameters (this is not compatible # with multiple or nested repeat parameters, furthermore the repeat # parameter must be the last one): - self.execMacro('ascan th 0 100 10 1.0') - self.execMacro('mv %s 0' % motor.getName()) + self.createMacro('ascan th 0 100 10 1.0') + self.createMacro('mv %s 0' % motor.getName()) .. note:: From Sardana 2.0 the repeat parameter values must be passed as lists of items. An item of a repeat parameter containing more @@ -1203,34 +1203,34 @@ def prepareMacro(self, *args, **kwargs): Several different parameter formats are supported:: # several parameters: - self.execMacro('ascan', 'th', '0', '100', '10', '1.0') - self.execMacro('mv', [[motor.getName(), '0']]) - self.execMacro('mv', motor.getName(), '0') # backwards compatibility - see note - self.execMacro('ascan', 'th', 0, 100, 10, 1.0) - self.execMacro('mv', [[motor.getName(), 0]]) - self.execMacro('mv', motor.getName(), 0) # backwards compatibility - see note + self.prepareMacro('ascan', 'th', '0', '100', '10', '1.0') + self.prepareMacro('mv', [[motor.getName(), '0']]) + self.prepareMacro('mv', motor.getName(), '0') # backwards compatibility - see note + self.prepareMacro('ascan', 'th', 0, 100, 10, 1.0) + self.prepareMacro('mv', [[motor.getName(), 0]]) + self.prepareMacro('mv', motor.getName(), 0) # backwards compatibility - see note th = self.getObj('th') - self.execMacro('ascan', th, 0, 100, 10, 1.0) - self.execMacro('mv', [[th, 0]]) - self.execMacro('mv', th, 0) # backwards compatibility - see note + self.prepareMacro('ascan', th, 0, 100, 10, 1.0) + self.prepareMacro('mv', [[th, 0]]) + self.prepareMacro('mv', th, 0) # backwards compatibility - see note # a sequence of parameters: - self.execMacro(['ascan', 'th', '0', '100', '10', '1.0']) - self.execMacro(['mv', [[motor.getName(), '0']]]) - self.execMacro(['mv', motor.getName(), '0']) # backwards compatibility - see note - self.execMacro(('ascan', 'th', 0, 100, 10, 1.0)) - self.execMacro(['mv', [[motor.getName(), 0]]]) - self.execMacro(['mv', motor.getName(), 0]) # backwards compatibility - see note + self.prepareMacro(['ascan', 'th', '0', '100', '10', '1.0']) + self.prepareMacro(['mv', [[motor.getName(), '0']]]) + self.prepareMacro(['mv', motor.getName(), '0']) # backwards compatibility - see note + self.prepareMacro(('ascan', 'th', 0, 100, 10, 1.0)) + self.prepareMacro(['mv', [[motor.getName(), 0]]]) + self.prepareMacro(['mv', motor.getName(), 0]) # backwards compatibility - see note th = self.getObj('th') - self.execMacro(['ascan', th, 0, 100, 10, 1.0]) - self.execMacro(['mv', [[th, 0]]]) - self.execMacro(['mv', th, 0]) # backwards compatibility - see note + self.prepareMacro(['ascan', th, 0, 100, 10, 1.0]) + self.prepareMacro(['mv', [[th, 0]]]) + self.prepareMacro(['mv', th, 0]) # backwards compatibility - see note # a space separated string of parameters (this is not compatible # with multiple or nested repeat parameters, furthermore the repeat # parameter must be the last one): - self.execMacro('ascan th 0 100 10 1.0') - self.execMacro('mv %s 0' % motor.getName()) + self.prepareMacro('ascan th 0 100 10 1.0') + self.prepareMacro('mv %s 0' % motor.getName()) .. note:: From Sardana 2.0 the repeat parameter values must be passed as lists of items. An item of a repeat parameter containing more From 0dfd9dbd23dacaba27bcd69f48c0ad39c16654f5 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 17 Dec 2020 22:23:49 +0100 Subject: [PATCH 059/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc1a00da5c..b68e6351b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ This file follows the formats and conventions from [keepachangelog.com] * `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) * Recorders tests helpers (#1439) * Disable flake8 job in travis CI (#1455) +* `createMacro()` and `prepareMacro()` docstring (#1460, #1444) ## [3.0.3] 2020-09-18 From bbfcc1e9a6fbbd639a44db05d6f680850575c293 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 18 Dec 2020 11:15:50 +0100 Subject: [PATCH 060/279] Add InterruptException to sardana.macroserver.macro module As Stop and Abort exception interrupt exceptions may be necessary when programming macros in order to properly implement broad exception catching. --- src/sardana/macroserver/macro.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sardana/macroserver/macro.py b/src/sardana/macroserver/macro.py index e637b32601..21f50c2cf8 100644 --- a/src/sardana/macroserver/macro.py +++ b/src/sardana/macroserver/macro.py @@ -33,7 +33,8 @@ __all__ = ["OverloadPrint", "PauseEvent", "Hookable", "ExecMacroHook", "MacroFinder", "Macro", "macro", "iMacro", "imacro", "MacroFunc", "Type", "Table", "List", "ViewOption", - "LibraryError", "Optional"] + "LibraryError", "Optional", "StopException", "AbortException", + "InterruptException"] __docformat__ = 'restructuredtext' @@ -60,7 +61,7 @@ from sardana.macroserver.msparameter import Type, ParamType, Optional from sardana.macroserver.msexception import StopException, AbortException, \ ReleaseException, MacroWrongParameterType, UnknownEnv, UnknownMacro, \ - LibraryError + LibraryError, InterruptException from sardana.macroserver.msoptions import ViewOption from sardana.taurus.core.tango.sardana.pool import PoolElement From d46387c4f6462e69d2258c63990baa38fcfdb915 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 18 Dec 2020 11:16:14 +0100 Subject: [PATCH 061/279] Add interrupt exception to Macro API documentation --- doc/source/devel/api/api_macro.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/doc/source/devel/api/api_macro.rst b/doc/source/devel/api/api_macro.rst index 3ac6de1219..810ad1bff1 100644 --- a/doc/source/devel/api/api_macro.rst +++ b/doc/source/devel/api/api_macro.rst @@ -42,3 +42,23 @@ imacro decorator :members: :undoc-members: +StopException +------------- + +.. autoclass:: StopException + :members: + :undoc-members: + +AbortException +-------------- + +.. autoclass:: AbortException + :members: + :undoc-members: + +InterruptException +------------------ + +.. autoclass:: InterruptException + :members: + :undoc-members: From 484af30b4865ec4bf1014389c82b3dd0f4455b02 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 18 Dec 2020 11:16:54 +0100 Subject: [PATCH 062/279] Document exception handling in macros --- .../devel/howto_macros/macros_general.rst | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/doc/source/devel/howto_macros/macros_general.rst b/doc/source/devel/howto_macros/macros_general.rst index 3fde9e05f4..cf863ab4e6 100644 --- a/doc/source/devel/howto_macros/macros_general.rst +++ b/doc/source/devel/howto_macros/macros_general.rst @@ -866,6 +866,55 @@ of user's interruption you must override the withing the :meth:`~sardana.macroserver.macro.Macro.on_stop` or :meth:`~sardana.macroserver.macro.Macro.on_abort`. +.. _sardana-macro-exception-handling: + +Handling exceptions +------------------- + +Please refer to the +`Python Errors and Exceptions `_ +documentation on how to deal with exceptions in your macro code. + +.. important:: + :ref:`sardana-macro-handling-macro-stop-and-abort` is internally implemented + using Python exceptions. So, your ``except`` clause can not simply catch any + exception type without re-raising it - this would ignore the macro stop/abort + request done in the ``try ... except`` block. If you still would like to + use the broad catching, you need to catch and raise the stop/abort exception + first: + + .. code-block:: python + :emphasize-lines: 7 + + import time + + from sardana.macroserver.macro import macro, StopException + + @macro() + def exception_macro(self): + self.output("Starting stoppable process") + try: + for i in range(10): + self.output("In iteration: {}".format(i)) + time.sleep(1) + except StopException: + raise + except Exception: + self.warning("Exception, but we continue") + self.output("After 'try ... except' block") + + If you do not program lines 12-13 and you stop your macro within + the ``try ... except`` block then the macro will continue and print the + output from line 16. + + You may choose to catch and re-raise: + `~sardana.macroserver.macro.StopException`, + `~sardana.macroserver.macro.AbortException` or + `~sardana.macroserver.macro.InterruptException`. The last one will + take care of stopping and aborting at the same time. + + + .. _sardana-macro-adding-hooks-support: Adding hooks support From 0ee9d1560b89a600512037cf68c2ae74646a3ea7 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 18 Dec 2020 11:17:14 +0100 Subject: [PATCH 063/279] Add table of contents to how-to macros --- doc/source/devel/howto_macros/macros_general.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/source/devel/howto_macros/macros_general.rst b/doc/source/devel/howto_macros/macros_general.rst index cf863ab4e6..07cda863ed 100644 --- a/doc/source/devel/howto_macros/macros_general.rst +++ b/doc/source/devel/howto_macros/macros_general.rst @@ -13,6 +13,10 @@ Writing macros This chapter provides the necessary information to write macros in sardana. The complete macro :term:`API` can be found :ref:`here `. +.. contents:: Table of contents + :depth: 3 + :backlinks: entry + What is a macro --------------- From caeddcbe2cfe90efa4b8a19bcf6be17b085b3157 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Fri, 18 Dec 2020 13:33:53 +0100 Subject: [PATCH 064/279] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b68e6351b4..95229e6b8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ This file follows the formats and conventions from [keepachangelog.com] * Initial delay in position domain to the synchronization description in *ct* like continuous scans (#1428) * Avoid double printing of user units in PMTV: read widget and units widget (#1424) +* Document how to properly deal with exceptions in macros in order to not interfer + with macro stopping/aborting (#1461) * Documentation example on how to more efficiently access Tango with PyTango in macros/controllers (#1456) From 607f70177fe351d2ac9e934583333ed805594d15 Mon Sep 17 00:00:00 2001 From: Stanislaw Cabala Date: Mon, 21 Dec 2020 12:02:38 +0100 Subject: [PATCH 065/279] Allow running Spock without Qt bindings --- src/sardana/spock/inputhandler.py | 110 +-------------- src/sardana/spock/ipython_01_00/genutils.py | 23 +++- src/sardana/spock/qtinputhandler.py | 142 ++++++++++++++++++++ src/sardana/spock/spockms.py | 9 +- 4 files changed, 165 insertions(+), 119 deletions(-) create mode 100644 src/sardana/spock/qtinputhandler.py diff --git a/src/sardana/spock/inputhandler.py b/src/sardana/spock/inputhandler.py index 7b9b6f93c3..73add6ef0c 100644 --- a/src/sardana/spock/inputhandler.py +++ b/src/sardana/spock/inputhandler.py @@ -25,18 +25,10 @@ """Spock submodule. It contains an input handler""" -__all__ = ['SpockInputHandler', 'InputHandler'] +__all__ = ['SpockInputHandler'] __docformat__ = 'restructuredtext' -import sys -from multiprocessing import Process, Pipe - -from taurus.core import TaurusManager -from taurus.core.util.singleton import Singleton -from taurus.external.qt import Qt, compat -from taurus.qt.qtgui.dialog import TaurusMessageBox, TaurusInputDialog - from sardana.taurus.core.tango.sardana.macroserver import BaseInputHandler from sardana.spock import genutils @@ -67,103 +59,3 @@ def input(self, input_data=None): def input_timeout(self, input_data): print("SpockInputHandler input timeout") - - -class MessageHandler(Qt.QObject): - - messageArrived = Qt.pyqtSignal(compat.PY_OBJECT) - - def __init__(self, conn, parent=None): - Qt.QObject.__init__(self, parent) - self._conn = conn - self._dialog = None - self.messageArrived.connect(self.on_message) - - def handle_message(self, input_data): - self.messageArrived.emit(input_data) - - def on_message(self, input_data): - msg_type = input_data['type'] - if msg_type == 'input': - if 'macro_name' in input_data and 'title' not in input_data: - input_data['title'] = input_data['macro_name'] - self._dialog = dialog = TaurusInputDialog(input_data=input_data) - dialog.activateWindow() - dialog.exec_() - ok = dialog.result() - value = dialog.value() - ret = dict(input=None, cancel=False) - if ok: - ret['input'] = value - else: - ret['cancel'] = True - self._conn.send(ret) - elif msg_type == 'timeout': - dialog = self._dialog - if dialog: - dialog.close() - - -class InputHandler(Singleton, BaseInputHandler): - - def __init__(self): - # don't call super __init__ on purpose - pass - - def init(self, *args, **kwargs): - self._conn, child_conn = Pipe() - self._proc = proc = Process(target=self.safe_run, - name="SpockInputHandler", args=(child_conn,)) - proc.daemon = True - proc.start() - - def input(self, input_data=None): - # parent process - data_type = input_data.get('data_type', 'String') - if isinstance(data_type, str): - ms = genutils.get_macro_server() - interfaces = ms.getInterfaces() - if data_type in interfaces: - input_data['data_type'] = [ - elem.name for elem in list(interfaces[data_type].values())] - self._conn.send(input_data) - ret = self._conn.recv() - return ret - - def input_timeout(self, input_data): - # parent process - self._conn.send(input_data) - - def safe_run(self, conn): - # child process - try: - return self.run(conn) - except Exception as e: - msgbox = TaurusMessageBox(*sys.exc_info()) - conn.send((e, False)) - msgbox.exec_() - - def run(self, conn): - # child process - self._conn = conn - app = Qt.QApplication.instance() - if app is None: - app = Qt.QApplication(['spock']) - app.setQuitOnLastWindowClosed(False) - self._msg_handler = MessageHandler(conn) - TaurusManager().addJob(self.run_forever, None) - app.exec_() - conn.close() - print("Quit input handler") - - def run_forever(self): - # child process - message, conn = True, self._conn - while message: - message = conn.recv() - if not message: - continue - self._msg_handler.handle_message(message) - app = Qt.QApplication.instance() - if app: - app.quit() diff --git a/src/sardana/spock/ipython_01_00/genutils.py b/src/sardana/spock/ipython_01_00/genutils.py index 1c26d28af8..df85a3882d 100644 --- a/src/sardana/spock/ipython_01_00/genutils.py +++ b/src/sardana/spock/ipython_01_00/genutils.py @@ -82,7 +82,10 @@ from taurus.core.util.codecs import CodecFactory # make sure Qt is properly initialized -from taurus.external.qt import Qt +try: + from taurus.external.qt import Qt +except ImportError: + pass from sardana.spock import exception from sardana.spock import colors @@ -110,7 +113,11 @@ def get_gui_mode(): - return 'qt' + try: + import taurus.external.qt.Qt + return 'qt' + except ImportError: + return None def get_pylab_mode(): @@ -1159,7 +1166,8 @@ def out_prompt_tokens(self): term_app = config.TerminalIPythonApp term_app.display_banner = True term_app.gui = gui_mode - term_app.pylab = 'qt' + if gui_mode == 'qt': + term_app.pylab = 'qt' term_app.pylab_import_all = False #term_app.nosep = False #term_app.classic = True @@ -1280,8 +1288,13 @@ def mainloop(app=None, user_ns=None): def prepare_input_handler(): # initialize input handler as soon as possible - import sardana.spock.inputhandler - _ = sardana.spock.inputhandler.InputHandler() + + try: + import sardana.spock.qtinputhandler + _ = sardana.spock.inputhandler.InputHandler() + except ImportError: + import sardana.spock.inputhandler + _ = sardana.spock.inputhandler.SpockInputHandler() def prepare_cmdline(argv=None): diff --git a/src/sardana/spock/qtinputhandler.py b/src/sardana/spock/qtinputhandler.py new file mode 100644 index 0000000000..c1dea5d8eb --- /dev/null +++ b/src/sardana/spock/qtinputhandler.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python + +############################################################################## +## +# This file is part of Sardana +## +# http://www.sardana-controls.org/ +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Sardana is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Sardana 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Sardana. If not, see . +## +############################################################################## + +"""Spock submodule. It contains an input handler""" + +__all__ = ['InputHandler'] + +__docformat__ = 'restructuredtext' + +import sys +from multiprocessing import Process, Pipe + +from taurus.core import TaurusManager +from taurus.core.util.singleton import Singleton +from taurus.external.qt import Qt, compat +from taurus.qt.qtgui.dialog import TaurusMessageBox, TaurusInputDialog + +from sardana.taurus.core.tango.sardana.macroserver import BaseInputHandler + +from sardana.spock import genutils + + +class MessageHandler(Qt.QObject): + + messageArrived = Qt.pyqtSignal(compat.PY_OBJECT) + + def __init__(self, conn, parent=None): + Qt.QObject.__init__(self, parent) + self._conn = conn + self._dialog = None + self.messageArrived.connect(self.on_message) + + def handle_message(self, input_data): + self.messageArrived.emit(input_data) + + def on_message(self, input_data): + msg_type = input_data['type'] + if msg_type == 'input': + if 'macro_name' in input_data and 'title' not in input_data: + input_data['title'] = input_data['macro_name'] + self._dialog = dialog = TaurusInputDialog(input_data=input_data) + dialog.activateWindow() + dialog.exec_() + ok = dialog.result() + value = dialog.value() + ret = dict(input=None, cancel=False) + if ok: + ret['input'] = value + else: + ret['cancel'] = True + self._conn.send(ret) + elif msg_type == 'timeout': + dialog = self._dialog + if dialog: + dialog.close() + + +class InputHandler(Singleton, BaseInputHandler): + + def __init__(self): + # don't call super __init__ on purpose + pass + + def init(self, *args, **kwargs): + self._conn, child_conn = Pipe() + self._proc = proc = Process(target=self.safe_run, + name="SpockInputHandler", args=(child_conn,)) + proc.daemon = True + proc.start() + + def input(self, input_data=None): + # parent process + data_type = input_data.get('data_type', 'String') + if isinstance(data_type, str): + ms = genutils.get_macro_server() + interfaces = ms.getInterfaces() + if data_type in interfaces: + input_data['data_type'] = [ + elem.name for elem in list(interfaces[data_type].values())] + self._conn.send(input_data) + ret = self._conn.recv() + return ret + + def input_timeout(self, input_data): + # parent process + self._conn.send(input_data) + + def safe_run(self, conn): + # child process + try: + return self.run(conn) + except Exception as e: + msgbox = TaurusMessageBox(*sys.exc_info()) + conn.send((e, False)) + msgbox.exec_() + + def run(self, conn): + # child process + self._conn = conn + app = Qt.QApplication.instance() + if app is None: + app = Qt.QApplication(['spock']) + app.setQuitOnLastWindowClosed(False) + self._msg_handler = MessageHandler(conn) + TaurusManager().addJob(self.run_forever, None) + app.exec_() + conn.close() + print("Quit input handler") + + def run_forever(self): + # child process + message, conn = True, self._conn + while message: + message = conn.recv() + if not message: + continue + self._msg_handler.handle_message(message) + app = Qt.QApplication.instance() + if app: + app.quit() diff --git a/src/sardana/spock/spockms.py b/src/sardana/spock/spockms.py index adbcf9853a..b76e5a1a88 100644 --- a/src/sardana/spock/spockms.py +++ b/src/sardana/spock/spockms.py @@ -38,19 +38,21 @@ from sardana.sardanautils import is_pure_str, is_non_str_seq from sardana.spock import genutils from sardana.util.parser import ParamParser -from sardana.spock.inputhandler import SpockInputHandler, InputHandler from sardana import sardanacustomsettings CHANGE_EVTS = TaurusEventType.Change, TaurusEventType.Periodic if genutils.get_gui_mode() == 'qt': + from taurus.external.qt import Qt from sardana.taurus.qt.qtcore.tango.sardana.macroserver import QDoor, QMacroServer + from sardana.spock.qtinputhandler import InputHandler BaseDoor = QDoor BaseMacroServer = QMacroServer BaseGUIViewer = object else: from sardana.taurus.core.tango.sardana.macroserver import BaseDoor, BaseMacroServer + from sardana.spock.inputhandler import SpockInputHandler BaseGUIViewer = object @@ -290,7 +292,7 @@ def __init__(self, name, **kw): self.call__init__(BaseDoor, name, **kw) def create_input_handler(self): - return SpockInputHandler(self) + return SpockInputHandler() def get_color_mode(self): return genutils.get_color_mode() @@ -554,9 +556,6 @@ def _processRecordData(self, data): return BaseDoor._processRecordData(self, data) -from taurus.external.qt import Qt - - class QSpockDoor(SpockBaseDoor): def __init__(self, name, **kw): From 9f589af737380a2950d112af2660a9f998f7fba5 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 21 Dec 2020 22:57:09 +0100 Subject: [PATCH 066/279] Add emulated HW RoI to dummy 2D controller --- .../poolcontrollers/DummyTwoDController.py | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/sardana/pool/poolcontrollers/DummyTwoDController.py b/src/sardana/pool/poolcontrollers/DummyTwoDController.py index fdd543b933..22b699d103 100644 --- a/src/sardana/pool/poolcontrollers/DummyTwoDController.py +++ b/src/sardana/pool/poolcontrollers/DummyTwoDController.py @@ -107,6 +107,7 @@ def __init__(self, idx): self.saving_enabled = False self.value_ref_pattern = "h5file:///tmp/dummy2d_default_{index}.h5" self.value_ref_enabled = False + self.roi = [0, 0, 0, 0] class BaseValue(object): @@ -164,7 +165,16 @@ class BasicDummyTwoDController(TwoDController): FSet: 'setAmplitude', Description: ("Amplitude. Maybe a number or a tango attribute " "(must start with tango://)"), - DefaultValue: '1.0'} + DefaultValue: '1.0' + }, + 'RoI': { + Type: (int,), + FGet: 'getRoI', + FSet: 'setRoI', + Description: ("Region of Interest of image " + "(begin_x, end_x, begin_y, end_y)"), + DefaultValue: [0, 0, 0, 0] + } } def __init__(self, inst, props, *args, **kwargs): @@ -284,6 +294,9 @@ def _updateChannelValue(self, axis, elapsed_time): y_size = self.BufferSize[1] amplitude = axis * self.integ_time * channel.amplitude.get() img = generate_img(x_size, y_size, amplitude) + roi = channel.roi + if roi != [0, 0, 0, 0]: + img = img[roi[0]:roi[1], roi[2]:roi[3]] if self._synchronization == AcqSynch.SoftwareTrigger: channel.value = img channel.acq_idx += 1 @@ -368,6 +381,44 @@ def setAmplitude(self, axis, value): klass = TangoValue channel.amplitude = klass(value) + def getRoI(self, axis): + idx = axis - 1 + channel = self.channels[idx] + return channel.roi + + def setRoI(self, axis, value): + idx = axis - 1 + channel = self.channels[idx] + try: + value = value.tolist() + except AttributeError: + pass + if len(value) != 4: + raise ValueError("RoI is not a list of four elements") + if any(not isinstance(v, int) for v in value): + raise ValueError("RoI is not a list of integers") + if value[1] <= value[0]: + raise ValueError("RoI[1] is lower or equal than RoI[0]") + if value[3] <= value[2]: + raise ValueError("RoI[3] is lower or equal than RoI[2]") + x_dim = self.BufferSize[0] + if value[0] > (x_dim - 1): + raise ValueError( + "RoI[0] exceeds detector X dimension - 1 ({})".format( + x_dim - 1)) + if value[1] > x_dim: + raise ValueError( + "RoI[1] exceeds detector X dimension ({})".format(x_dim)) + y_dim = self.BufferSize[1] + if value[2] > (y_dim - 1): + raise ValueError( + "RoI[2] exceeds detector Y dimension - 1 ({})".format( + y_dim - 1)) + if value[3] > y_dim: + raise ValueError( + "RoI[3] exceeds detector Y dimension ({})".format(y_dim)) + channel.roi = value + def GetCtrlPar(self, par): if par == "synchronization": return self._synchronization @@ -488,6 +539,9 @@ def _updateChannelValue(self, axis, elapsed_time): y_size = self.BufferSize[1] amplitude = axis * self.integ_time * channel.amplitude.get() img = generate_img(x_size, y_size, amplitude) + roi = channel.roi + if roi != [0, 0, 0, 0]: + img = img[roi[0]:roi[1], roi[2]:roi[3]] if self._synchronization == AcqSynch.SoftwareTrigger: channel.value = img if channel.value_ref_enabled: From 684d4d569e85dd5c995582c0a622a3a369dc4987 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 21 Dec 2020 23:32:28 +0100 Subject: [PATCH 067/279] Add emulated HW RoI to dummy 1D controller --- .../poolcontrollers/DummyOneDController.py | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/sardana/pool/poolcontrollers/DummyOneDController.py b/src/sardana/pool/poolcontrollers/DummyOneDController.py index 88f4e38ac5..d962408792 100644 --- a/src/sardana/pool/poolcontrollers/DummyOneDController.py +++ b/src/sardana/pool/poolcontrollers/DummyOneDController.py @@ -44,6 +44,7 @@ def __init__(self, idx): self.active = True self.amplitude = BaseValue('1.0') self._counter = 0 + self.roi = [0, 0] class BaseValue(object): @@ -89,7 +90,15 @@ class DummyOneDController(OneDController): FGet: 'getAmplitude', FSet: 'setAmplitude', Description: 'Amplitude. Maybe a number or a tango attribute(must start with tango://)', - DefaultValue: '1.0'}, + DefaultValue: '1.0' + }, + 'RoI': { + Type: (int,), + FGet: 'getRoI', + FSet: 'setRoI', + Description: "Region of Interest of spectrum (begin, end)", + DefaultValue: [0, 0] + } } def __init__(self, inst, props, *args, **kwargs): @@ -170,7 +179,11 @@ def _updateChannelValue(self, axis, elapsed_time): t = self.integ_time x = numpy.linspace(-10, 10, self.BufferSize[0]) amplitude = axis * t * channel.amplitude.get() - channel.value = gauss(x, 0, amplitude, 4) + spectrum = gauss(x, 0, amplitude, 4) + roi = channel.roi + if roi != [0, 0]: + spectrum = spectrum[roi[0]:roi[1]] + channel.value = spectrum elif self._synchronization in (AcqSynch.HardwareTrigger, AcqSynch.HardwareGate): if self.integ_time is not None: @@ -277,3 +290,30 @@ def setAmplitude(self, axis, value): if value.startswith("tango://"): klass = TangoValue channel.amplitude = klass(value) + + def getRoI(self, axis): + idx = axis - 1 + channel = self.channels[idx] + return channel.roi + + def setRoI(self, axis, value): + idx = axis - 1 + channel = self.channels[idx] + try: + value = value.tolist() + except AttributeError: + pass + if len(value) != 2: + raise ValueError("RoI is not a list of two elements") + if any(not isinstance(v, int) for v in value): + raise ValueError("RoI is not a list of integers") + if value[1] <= value[0]: + raise ValueError("RoI[1] is lower or equal than RoI[0]") + dim = self.BufferSize[0] + if value[0] > (dim - 1): + raise ValueError( + "RoI[0] exceeds detector dimension - 1 ({})".format(dim - 1)) + if value[1] > dim: + raise ValueError( + "RoI[1] exceeds detector dimension ({})".format(dim)) + channel.roi = value From 36ede5b93c4a239f75c22511aee01ceab557f3d6 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 21 Dec 2020 23:48:02 +0100 Subject: [PATCH 068/279] Allow to reset RoI of 1D and 2D --- src/sardana/pool/poolcontrollers/DummyOneDController.py | 2 +- src/sardana/pool/poolcontrollers/DummyTwoDController.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/sardana/pool/poolcontrollers/DummyOneDController.py b/src/sardana/pool/poolcontrollers/DummyOneDController.py index d962408792..106c3754c6 100644 --- a/src/sardana/pool/poolcontrollers/DummyOneDController.py +++ b/src/sardana/pool/poolcontrollers/DummyOneDController.py @@ -307,7 +307,7 @@ def setRoI(self, axis, value): raise ValueError("RoI is not a list of two elements") if any(not isinstance(v, int) for v in value): raise ValueError("RoI is not a list of integers") - if value[1] <= value[0]: + if value != [0, 0] and value[1] <= value[0]: raise ValueError("RoI[1] is lower or equal than RoI[0]") dim = self.BufferSize[0] if value[0] > (dim - 1): diff --git a/src/sardana/pool/poolcontrollers/DummyTwoDController.py b/src/sardana/pool/poolcontrollers/DummyTwoDController.py index 22b699d103..9b34c1dc71 100644 --- a/src/sardana/pool/poolcontrollers/DummyTwoDController.py +++ b/src/sardana/pool/poolcontrollers/DummyTwoDController.py @@ -397,10 +397,11 @@ def setRoI(self, axis, value): raise ValueError("RoI is not a list of four elements") if any(not isinstance(v, int) for v in value): raise ValueError("RoI is not a list of integers") - if value[1] <= value[0]: - raise ValueError("RoI[1] is lower or equal than RoI[0]") - if value[3] <= value[2]: - raise ValueError("RoI[3] is lower or equal than RoI[2]") + if value != [0, 0, 0, 0]: + if value[1] <= value[0]: + raise ValueError("RoI[1] is lower or equal than RoI[0]") + if value[3] <= value[2]: + raise ValueError("RoI[3] is lower or equal than RoI[2]") x_dim = self.BufferSize[0] if value[0] > (x_dim - 1): raise ValueError( From 844de3469425021222e68e63470143571f77b6b6 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 23 Dec 2020 00:18:17 +0100 Subject: [PATCH 069/279] Add shape concept to experimental channels Shape is a result of channel's configuration e.g. RoI, binning. The idea is to remove the shape from the measurement group configuration. Add: - shape property on the kernel level - implement backwards compatibility for 1D and 2D controllers not implementing shape - add Shape read-only Tango attribute --- src/sardana/pool/controller.py | 12 +++++++ src/sardana/pool/poolbasechannel.py | 48 +++++++++++++++++++++++++++- src/sardana/tango/pool/PoolDevice.py | 18 +++++++++-- 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/sardana/pool/controller.py b/src/sardana/pool/controller.py index 4895651f8f..e8d3282d42 100644 --- a/src/sardana/pool/controller.py +++ b/src/sardana/pool/controller.py @@ -878,6 +878,8 @@ class CounterTimerController(Controller, Readable, Startable, Stopable, # TODO: in case of Tango ValueBuffer type is overridden by DevEncoded 'ValueBuffer': {'type': str, 'description': 'Value buffer', }, + 'Shape': {'type': (int,), + 'description': 'Shape of the value, it is an empty array'} } standard_axis_attributes.update(Controller.standard_axis_attributes) @@ -940,6 +942,8 @@ class ZeroDController(Controller, Readable, Stopable): # TODO: in case of Tango ValueBuffer type is overridden by DevEncoded 'ValueBuffer': {'type': str, 'description': 'Value buffer', }, + 'Shape': {'type': (int,), + 'description': 'Shape of the value, it is an empty array'} } standard_axis_attributes.update(Controller.standard_axis_attributes) @@ -972,6 +976,9 @@ class OneDController(Controller, Readable, Startable, Stopable, Loadable): # TODO: in case of Tango ValueBuffer type is overridden by DevEncoded 'ValueBuffer': {'type': str, 'description': 'Value buffer', }, + 'Shape': {'type': (int,), + 'description': 'Shape of the value, it is an array with ' + '1 element - X dimension'} } standard_axis_attributes.update(Controller.standard_axis_attributes) @@ -1017,6 +1024,9 @@ class TwoDController(Controller, Readable, Startable, Stopable, Loadable): # TODO: in case of Tango ValueBuffer type is overridden by DevEncoded 'ValueBuffer': {'type': str, 'description': 'Value buffer', }, + 'Shape': {'type': (int,), + 'description': 'Shape of the value, it is an array with ' + '2 elements: X and Y dimensions'} } standard_axis_attributes.update(Controller.standard_axis_attributes) @@ -1263,6 +1273,8 @@ class PseudoCounterController(Controller): # TODO: in case of Tango ValueBuffer type is overridden by DevEncoded 'ValueBuffer': {'type': str, 'description': 'Data', }, + 'Shape': {'type': (int,), + 'description': 'Shape of the value, it is an empty array'} } #: A :obj:`str` representing the controller gender diff --git a/src/sardana/pool/poolbasechannel.py b/src/sardana/pool/poolbasechannel.py index 29f739bad9..a644cbc410 100644 --- a/src/sardana/pool/poolbasechannel.py +++ b/src/sardana/pool/poolbasechannel.py @@ -30,7 +30,7 @@ __docformat__ = 'restructuredtext' -from sardana.sardanadefs import AttrQuality +from sardana.sardanadefs import AttrQuality, ElementType from sardana.sardanaattribute import SardanaAttribute from sardana.sardanabuffer import SardanaBuffer from sardana.pool.poolelement import PoolElement @@ -120,6 +120,7 @@ def __init__(self, **kwargs): acq_name = "%s.Acquisition" % self._name self.set_action_cache(self.AcquisitionClass(self, name=acq_name)) self._integration_time = 0 + self._shape = None def has_pseudo_elements(self): """Informs whether this channel forms part of any pseudo element @@ -645,6 +646,51 @@ def set_integration_time(self, integration_time, propagate=1): integration_time = property(get_integration_time, set_integration_time, doc="channel integration time") + # ------------------------------------------------------------------------- + # shape + # ------------------------------------------------------------------------- + + def get_shape(self, cache=True, propagate=1): + if not cache or self._shape is None: + shape = self.read_shape() + self._set_shape(shape, propagate=propagate) + return self._shape + + def _set_shape(self, shape, propagate=1): + self._shape = shape + if not propagate: + return + self.fire_event( + EventType("shape", priority=propagate), shape) + + def read_shape(self): + try: + shape = self.controller.get_axis_par(self.axis, "shape") + except: + shape = None + if shape is None: + # backwards compatibility for controllers not implementing + # shape axis par + if self.get_type() in (ElementType.OneDExpChannel, + ElementType.TwoDExpChannel): + self.warning( + "not implementing shape axis parameter in 1D and 2D " + "controllers is deprecated since Jan21") + value = self.value.value + import numpy + try: + shape = numpy.shape(value) + except Exception as e: + raise RuntimeError( + "can not provide backwards compatibility, you must" + "implement shape axis parameter") from e + # scalar channel + else: + shape = [] + return shape + + shape = property(get_shape, doc="channel value shape") + def _prepare(self): # TODO: think of implementing the preparation in the software # acquisition action, similarly as it is done for the global diff --git a/src/sardana/tango/pool/PoolDevice.py b/src/sardana/tango/pool/PoolDevice.py index 13060ffd4f..246ea19141 100644 --- a/src/sardana/tango/pool/PoolDevice.py +++ b/src/sardana/tango/pool/PoolDevice.py @@ -902,7 +902,7 @@ def _encode_value_ref_chunk(self, value_ref_chunk): def initialize_dynamic_attributes(self): attrs = PoolElementDevice.initialize_dynamic_attributes(self) - non_detect_evts = "integrationtime", + non_detect_evts = "integrationtime", "shape" for attr_name in non_detect_evts: if attr_name in attrs: @@ -941,6 +941,13 @@ def write_IntegrationTime(self, attr): :type attr: :class:`~PyTango.Attribute`""" self.element.integration_time = attr.get_write_value() + def read_Shape(self, attr): + """Reads the shape. + + :param attr: tango attribute + :type attr: :class:`~PyTango.Attribute`""" + attr.set_value(self.element.shape) + class PoolExpChannelDeviceClass(PoolElementDeviceClass): @@ -955,7 +962,14 @@ class PoolExpChannelDeviceClass(PoolElementDeviceClass): attr_list.update(PoolElementDeviceClass.attr_list) standard_attr_list = { - 'ValueBuffer': [[DevEncoded, SCALAR, READ]] + 'ValueBuffer': [[DevEncoded, SCALAR, READ]], + 'Shape': [[DevLong64, SPECTRUM, READ, 2], + {'label': "Shape (X,Y)", + 'description': "Shape of the value. It is an array with \n" + "at most 2 elements: X and Y dimensions. \n" + "0-element array - scalar\n" + "1-element array (X) - spectrum\n" + "2-element array (X, Y) - image"}], } standard_attr_list.update(PoolElementDeviceClass.standard_attr_list) From e01ce34c940c147c231e108c7cd6aafce8bb4b08 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 23 Dec 2020 00:18:50 +0100 Subject: [PATCH 070/279] Implement shape axis par for 1D and 2D dummies --- src/sardana/pool/poolcontrollers/DummyOneDController.py | 9 +++++++++ src/sardana/pool/poolcontrollers/DummyTwoDController.py | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/sardana/pool/poolcontrollers/DummyOneDController.py b/src/sardana/pool/poolcontrollers/DummyOneDController.py index 106c3754c6..0009cc67c6 100644 --- a/src/sardana/pool/poolcontrollers/DummyOneDController.py +++ b/src/sardana/pool/poolcontrollers/DummyOneDController.py @@ -317,3 +317,12 @@ def setRoI(self, axis, value): raise ValueError( "RoI[1] exceeds detector dimension ({})".format(dim)) channel.roi = value + + def GetAxisPar(self, axis, par): + idx = axis - 1 + channel = self.channels[idx] + if par == "shape": + roi = channel.roi + if roi == [0, 0]: + return self.BufferSize + return [roi[1] - roi[0]] diff --git a/src/sardana/pool/poolcontrollers/DummyTwoDController.py b/src/sardana/pool/poolcontrollers/DummyTwoDController.py index 9b34c1dc71..f48f63cb4b 100644 --- a/src/sardana/pool/poolcontrollers/DummyTwoDController.py +++ b/src/sardana/pool/poolcontrollers/DummyTwoDController.py @@ -430,6 +430,15 @@ def SetCtrlPar(self, par, value): if par == "synchronization": self._synchronization = value + def GetAxisPar(self, axis, par): + idx = axis - 1 + channel = self.channels[idx] + if par == "shape": + roi = channel.roi + if roi == [0, 0, 0, 0]: + return self.BufferSize + return [roi[1] - roi[0], roi[3] - roi[2]] + def getSynchronizer(self): if self._synchronizer is None: return "None" From 90e6af8d868d122ccffb713f44cf5f6294d1ae11 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 23 Dec 2020 00:19:48 +0100 Subject: [PATCH 071/279] Read shape attribute in scans Instead of using measurement group configuration read shape of experimental channels at the beginning of each scan. --- src/sardana/macroserver/scan/gscan.py | 50 +++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/src/sardana/macroserver/scan/gscan.py b/src/sardana/macroserver/scan/gscan.py index f0662ad2d1..74d5cb38cb 100644 --- a/src/sardana/macroserver/scan/gscan.py +++ b/src/sardana/macroserver/scan/gscan.py @@ -83,6 +83,32 @@ class ScanException(MacroServerException): pass +def _get_shape(channel): + # internal Sardana channel + if isinstance(channel, PyTango.DeviceProxy): + try: + shape = channel.shape + except: + return None + if shape is None: # in PyTango empty spectrum is None + return [] + return shape + # external channel (Tango attribute) + elif isinstance(channel, PyTango.AttributeProxy): + try: + attr_conf = channel.get_config() + except: + return None + if attr_conf.data_format == PyTango.AttrDataFormat.SCALAR: + return [] + try: + value = channel.read().value + except: + return [n for n in (attr_conf.max_dim_x, + attr_conf.max_dim_y) if n > 0] + return list(np.shape(value)) + + class ExtraData(object): def __init__(self, **kwargs): @@ -675,16 +701,24 @@ def _setupEnvironment(self, additional_env): counters = [] for ci in channels_info: full_name = ci.full_name + shape = None + instrument = '' try: # Use DeviceProxy instead of taurus to avoid crashes in Py3 # See: tango-controls/pytango#292 # channel = taurus.Device(full_name) channel = PyTango.DeviceProxy(full_name) - instrument = channel.instrument - except Exception: - # full_name of external channels is the name of the attribute - # external channels are not assigned to instruments - instrument = '' + except: + try: + channel = PyTango.AttributeProxy(full_name) + except: + channel = None + if channel: + shape = _get_shape(channel) + try: + instrument = channel.instrument + except: + instrument = '' try: instrumentFullName = self.macro.findObjs( instrument, type_class=Type.Instrument)[0].getFullName() @@ -692,6 +726,10 @@ def _setupEnvironment(self, additional_env): raise except Exception: instrumentFullName = '' + if shape is None: + self.warning("unknown shape of {}, assuming scalar".format( + ci.name)) + shape = [] # substitute the axis placeholder by the corresponding moveable. plotAxes = [] i = 0 @@ -713,7 +751,7 @@ def _setupEnvironment(self, additional_env): column = ColumnDesc(name=ci.full_name, label=ci.label, dtype=ci.data_type, - shape=ci.shape, + shape=shape, instrument=instrumentFullName, source=ci.source, output=ci.output, From ee812c3e9953da59a3b54768d96546906d85202a Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 23 Dec 2020 00:22:22 +0100 Subject: [PATCH 072/279] flake8 --- src/sardana/macroserver/scan/gscan.py | 12 ++++++------ src/sardana/pool/poolbasechannel.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sardana/macroserver/scan/gscan.py b/src/sardana/macroserver/scan/gscan.py index 74d5cb38cb..afcad7e31d 100644 --- a/src/sardana/macroserver/scan/gscan.py +++ b/src/sardana/macroserver/scan/gscan.py @@ -88,7 +88,7 @@ def _get_shape(channel): if isinstance(channel, PyTango.DeviceProxy): try: shape = channel.shape - except: + except Exception: return None if shape is None: # in PyTango empty spectrum is None return [] @@ -97,13 +97,13 @@ def _get_shape(channel): elif isinstance(channel, PyTango.AttributeProxy): try: attr_conf = channel.get_config() - except: + except Exception: return None if attr_conf.data_format == PyTango.AttrDataFormat.SCALAR: return [] try: value = channel.read().value - except: + except Exception: return [n for n in (attr_conf.max_dim_x, attr_conf.max_dim_y) if n > 0] return list(np.shape(value)) @@ -708,16 +708,16 @@ def _setupEnvironment(self, additional_env): # See: tango-controls/pytango#292 # channel = taurus.Device(full_name) channel = PyTango.DeviceProxy(full_name) - except: + except Exception: try: channel = PyTango.AttributeProxy(full_name) - except: + except Exception: channel = None if channel: shape = _get_shape(channel) try: instrument = channel.instrument - except: + except Exception: instrument = '' try: instrumentFullName = self.macro.findObjs( diff --git a/src/sardana/pool/poolbasechannel.py b/src/sardana/pool/poolbasechannel.py index a644cbc410..631d5955e6 100644 --- a/src/sardana/pool/poolbasechannel.py +++ b/src/sardana/pool/poolbasechannel.py @@ -666,7 +666,7 @@ def _set_shape(self, shape, propagate=1): def read_shape(self): try: shape = self.controller.get_axis_par(self.axis, "shape") - except: + except Exception: shape = None if shape is None: # backwards compatibility for controllers not implementing From 52a259aed1f5af446d63a3ee7c2ddaa321ab42db Mon Sep 17 00:00:00 2001 From: Abdullah Amjad <13bscsaamjad@seecs.edu.pk> Date: Mon, 11 Jan 2021 16:11:06 +0100 Subject: [PATCH 073/279] fixed signature of PrepareOne method on TriggerGateController (#1468) Co-authored-by: Abdullah --- src/sardana/pool/controller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sardana/pool/controller.py b/src/sardana/pool/controller.py index 4895651f8f..3574399f03 100644 --- a/src/sardana/pool/controller.py +++ b/src/sardana/pool/controller.py @@ -914,12 +914,13 @@ def __init__(self, inst, props, *args, **kwargs): # TODO: Implement a Preparable interface and move this method # and the Loadable.PrepareOne() there. - def PrepareOne(self, nb_starts): + def PrepareOne(self, axis, nb_starts): """**Controller API**. Override if necessary. Called to prepare the trigger/gate axis with the measurement parameters. Default implementation does nothing. + :param int axis: axis :param int nb_starts: number of starts """ pass From 6419df1cbbc777b8229db132c5fadf119529f6c5 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Mon, 11 Jan 2021 16:18:14 +0100 Subject: [PATCH 074/279] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95229e6b8a..7351706761 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ This file follows the formats and conventions from [keepachangelog.com] * *scan information* and *scan point* forms to the *showscan online* widget (#1386) * `ScanPlotWidget`, `ScanPlotWindow`, `ScanInfoForm`, `ScanPointForm` and `ScanWindow` widget classes for easier composition of custom GUIs involving online scan plotting (#1386) +* Include trigger/gate elements in the per-measurement preparation (#1432, #1443, #1468) + * Add `PrepareOne()` to TriggerGate controller. + * Call TriggerGate controller preparation methods in the _acquision action_ * Add `ScanUser` environment variable (#1355) * Allow to programmatically disable *deterministic scan* optimization (#1426, #1427) * Initial delay in position domain to the synchronization description From 498724f14b97d5e95935a031e6980d988f83bc17 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 14 Jan 2021 09:59:50 +0100 Subject: [PATCH 075/279] Fix bug in string formatting Fix ValueError (unsupported format character ''') by properly formatting string. --- src/sardana/pool/poolpseudomotor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sardana/pool/poolpseudomotor.py b/src/sardana/pool/poolpseudomotor.py index 7819b4cdea..e63fb214ab 100644 --- a/src/sardana/pool/poolpseudomotor.py +++ b/src/sardana/pool/poolpseudomotor.py @@ -135,7 +135,7 @@ def get_physical_write_positions(self): # because of a cold start pos_attr.update(propagate=0) if pos_attr.in_error(): - raise PoolException("Cannot get '%' position" % pos_attr.obj.name, + raise PoolException("Cannot get '%s' position" % pos_attr.obj.name, exc_info=pos_attr.exc_info) value = pos_attr.value ret.append(value) @@ -149,7 +149,7 @@ def get_physical_positions(self): if not pos_attr.has_value(): pos_attr.update(propagate=0) if pos_attr.in_error(): - raise PoolException("Cannot get '%' position" % pos_attr.obj.name, + raise PoolException("Cannot get '%s' position" % pos_attr.obj.name, exc_info=pos_attr.exc_info) ret.append(pos_attr.value) return ret From 2567e33faccb7edb90ea510f9dd60ec17a23be4c Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 14 Jan 2021 12:56:39 +0100 Subject: [PATCH 076/279] Complement and fix docs on setting ORBendPoint * document the possibility to fix IP address * fix examples to use "giop:tcp:" prefix --- doc/source/users/configuration/server.rst | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/doc/source/users/configuration/server.rst b/doc/source/users/configuration/server.rst index f7cf79fe0e..6859fd8f6f 100644 --- a/doc/source/users/configuration/server.rst +++ b/doc/source/users/configuration/server.rst @@ -7,25 +7,30 @@ Sardana system can :ref:`run as one or many Tango device servers`. Tango device servers listens on a TCP port for the CORBA requests. Usually it is fine to use the randomly assigned port (default behavior) but sometimes -it may be necessary to use a fixed port number. For example, when the server -needs to be accessed from another isolated network and we want to open -connections only for the given ports. +it may be necessary to use a fixed port number or even IP address. +For example, when the server needs to be accessed from another isolated +network and we want to open connections only for the given ports or IPs. -There are three possibilities to assign the port explicitly (the order -indicates the precedence): +There are three possibilities to assign the IP and/or port in format of the +ORBendPoint explicitly (the order indicates the precedence): + +.. note:: + The ORBendPoint is in the following format: ``giop:tcp::`` + and both IP and port are optional, so you could only fix the IP, + only fix the port, fix both of them or none of them. - using OS environment variable ``ORBendPoint`` e.g. .. code-block:: bash - $ export ORBendPoint=28366 - $ Pool demo1 -ORBendPoint 28366 + $ export ORBendPoint=giop:tcp:192.168.0.100:28366 + $ Pool demo1 - using Tango device server command line argument ``-ORBendPoint`` .. code-block:: bash - $ Pool demo1 -ORBendPoint 28366 + $ Pool demo1 -ORBendPoint giop:tcp:192.168.0.100:28366 - using Tango DB free property with object name: ``ORBendPoint`` and property name: ``/``) @@ -34,7 +39,7 @@ indicates the precedence): import tango db = tango.Database() - db.put_property("ORBendPoint", {"Pool/demo1": 28366}) + db.put_property("ORBendPoint", {"Pool/demo1": "giop:tcp:192.168.0.100:28366"}) .. note:: From f2f73ac20b1f397450109a582e264577cfabd011 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 14 Jan 2021 14:50:59 +0100 Subject: [PATCH 077/279] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7351706761..f09a6ab13b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Avoid double printing of user units in PMTV: read widget and units widget (#1424) * Document how to properly deal with exceptions in macros in order to not interfer with macro stopping/aborting (#1461) +* Documentation on how to start Tango servers on fixed IP - ORBendPoint (#1470) * Documentation example on how to more efficiently access Tango with PyTango in macros/controllers (#1456) @@ -35,6 +36,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Recorders tests helpers (#1439) * Disable flake8 job in travis CI (#1455) * `createMacro()` and `prepareMacro()` docstring (#1460, #1444) +* String formatting when rising exceptions in pseudomotors (#1469) ## [3.0.3] 2020-09-18 From ded02b6c3b368f0506f4357e422f91ec1e27abc4 Mon Sep 17 00:00:00 2001 From: Stanislaw Cabala Date: Fri, 15 Jan 2021 08:12:54 +0100 Subject: [PATCH 078/279] Explicitly require Qt bindings when Qt input handler for Spock is requested --- src/sardana/spock/ipython_01_00/genutils.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/sardana/spock/ipython_01_00/genutils.py b/src/sardana/spock/ipython_01_00/genutils.py index df85a3882d..8009c8e128 100644 --- a/src/sardana/spock/ipython_01_00/genutils.py +++ b/src/sardana/spock/ipython_01_00/genutils.py @@ -1289,12 +1289,15 @@ def mainloop(app=None, user_ns=None): def prepare_input_handler(): # initialize input handler as soon as possible - try: - import sardana.spock.qtinputhandler - _ = sardana.spock.inputhandler.InputHandler() - except ImportError: - import sardana.spock.inputhandler - _ = sardana.spock.inputhandler.SpockInputHandler() + from sardana import sardanacustomsettings + + if sardanacustomsettings.SPOCK_INPUT_HANDLER == "Qt": + + try: + import sardana.spock.qtinputhandler + _ = sardana.spock.qtinputhandler.InputHandler() + except ImportError: + raise Exception("Cannot use Spock Qt input handler!") def prepare_cmdline(argv=None): From 1c69766fd850747ed94824224290e265e5d8f7f9 Mon Sep 17 00:00:00 2001 From: Stanislaw Cabala Date: Fri, 15 Jan 2021 08:27:59 +0100 Subject: [PATCH 079/279] Move imports --- src/sardana/spock/spockms.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sardana/spock/spockms.py b/src/sardana/spock/spockms.py index b76e5a1a88..95b67b149b 100644 --- a/src/sardana/spock/spockms.py +++ b/src/sardana/spock/spockms.py @@ -46,13 +46,11 @@ if genutils.get_gui_mode() == 'qt': from taurus.external.qt import Qt from sardana.taurus.qt.qtcore.tango.sardana.macroserver import QDoor, QMacroServer - from sardana.spock.qtinputhandler import InputHandler BaseDoor = QDoor BaseMacroServer = QMacroServer BaseGUIViewer = object else: from sardana.taurus.core.tango.sardana.macroserver import BaseDoor, BaseMacroServer - from sardana.spock.inputhandler import SpockInputHandler BaseGUIViewer = object @@ -292,6 +290,8 @@ def __init__(self, name, **kw): self.call__init__(BaseDoor, name, **kw) def create_input_handler(self): + from sardana.spock.inputhandler import SpockInputHandler + return SpockInputHandler() def get_color_mode(self): @@ -574,6 +574,9 @@ def recordDataReceived(self, s, t, v): return res def create_input_handler(self): + from sardana.spock.inputhandler import SpockInputHandler + from sardana.spock.qtinputhandler import InputHandler + inputhandler = getattr(sardanacustomsettings, 'SPOCK_INPUT_HANDLER', "CLI") From 5a3dd44d88c75bc5f999c1657fa598a11786f77e Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 15 Jan 2021 10:59:32 +0100 Subject: [PATCH 080/279] Make sardanacustomsettings access more robust --- src/sardana/spock/ipython_01_00/genutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sardana/spock/ipython_01_00/genutils.py b/src/sardana/spock/ipython_01_00/genutils.py index 8009c8e128..c92ca164df 100644 --- a/src/sardana/spock/ipython_01_00/genutils.py +++ b/src/sardana/spock/ipython_01_00/genutils.py @@ -1291,7 +1291,7 @@ def prepare_input_handler(): from sardana import sardanacustomsettings - if sardanacustomsettings.SPOCK_INPUT_HANDLER == "Qt": + if getattr(sardanacustomsettings, "SPOCK_INPUT_HANDLER", "CLI") == "Qt": try: import sardana.spock.qtinputhandler From 8b864a146d89a0581c641935da863f302dbd718f Mon Sep 17 00:00:00 2001 From: Stanislaw Cabala Date: Fri, 15 Jan 2021 12:35:34 +0100 Subject: [PATCH 081/279] Remove (hopefully) redundant import --- src/sardana/spock/spockms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sardana/spock/spockms.py b/src/sardana/spock/spockms.py index 95b67b149b..88c0ef120a 100644 --- a/src/sardana/spock/spockms.py +++ b/src/sardana/spock/spockms.py @@ -44,7 +44,6 @@ if genutils.get_gui_mode() == 'qt': - from taurus.external.qt import Qt from sardana.taurus.qt.qtcore.tango.sardana.macroserver import QDoor, QMacroServer BaseDoor = QDoor BaseMacroServer = QMacroServer From 530a0730f12cafac00d37fec7c7b1bc6c19383ba Mon Sep 17 00:00:00 2001 From: stanislaw55 <32959446+stanislaw55@users.noreply.github.com> Date: Fri, 15 Jan 2021 12:36:43 +0100 Subject: [PATCH 082/279] Use more robust access to custom settings Co-authored-by: reszelaz --- src/sardana/spock/ipython_01_00/genutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sardana/spock/ipython_01_00/genutils.py b/src/sardana/spock/ipython_01_00/genutils.py index 8009c8e128..c92ca164df 100644 --- a/src/sardana/spock/ipython_01_00/genutils.py +++ b/src/sardana/spock/ipython_01_00/genutils.py @@ -1291,7 +1291,7 @@ def prepare_input_handler(): from sardana import sardanacustomsettings - if sardanacustomsettings.SPOCK_INPUT_HANDLER == "Qt": + if getattr(sardanacustomsettings, "SPOCK_INPUT_HANDLER", "CLI") == "Qt": try: import sardana.spock.qtinputhandler From 567db38568c6661ada6a6f7953a133f443e14a38 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 15 Jan 2021 12:51:48 +0100 Subject: [PATCH 083/279] Make write of integration time more robust Write of the integration time may fail e.g. problems with reading latency_time of the controller and we fill the cache anyway. This causes problems with not initialized synchronization description. Fill integration time cache only if the write worked. --- src/sardana/taurus/core/tango/sardana/pool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sardana/taurus/core/tango/sardana/pool.py b/src/sardana/taurus/core/tango/sardana/pool.py index 91c2bfa124..8f476ad29e 100644 --- a/src/sardana/taurus/core/tango/sardana/pool.py +++ b/src/sardana/taurus/core/tango/sardana/pool.py @@ -2460,8 +2460,8 @@ def setIntegrationTime(self, ctime): def putIntegrationTime(self, ctime): if self._last_integ_time == ctime: return - self._last_integ_time = ctime self.getIntegrationTimeObj().write(ctime) + self._last_integ_time = ctime def getAcquisitionModeObj(self): return self._getAttrEG('AcquisitionMode') From 7cc9229347e69ad798c4c91f65e1bc0105851629 Mon Sep 17 00:00:00 2001 From: Stanislaw Cabala Date: Fri, 15 Jan 2021 14:35:23 +0100 Subject: [PATCH 084/279] Add checks for magics using Qt --- src/sardana/spock/magic.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/sardana/spock/magic.py b/src/sardana/spock/magic.py index 0f4b1c6126..eff733ee5c 100644 --- a/src/sardana/spock/magic.py +++ b/src/sardana/spock/magic.py @@ -40,6 +40,15 @@ def expconf(self, parameter_s=''): """Launches a GUI for configuring the environment variables for the experiments (scans)""" + + try: + from taurus.external.qt import qt + except ImportError: + print("Qt binding is not available. ExpConf cannot work without it." + "(hint: maybe you want to use experiment configuration macros? " + "https://sardana-controls.org/users/standard_macro_catalog.html#experiment-configuration-macros)") + return + try: from sardana.taurus.qt.qtgui.extra_sardana import ExpDescriptionEditor except: @@ -81,6 +90,13 @@ def showscan(self, parameter_s=''): Where *online* means plot the scan as it runs and *offline* means - extract the scan data from the file - works only with HDF5 files. """ + + try: + from taurus.external.qt import qt + except ImportError: + print("Qt binding is not available. Showscan cannot work without it.") + return + params = parameter_s.split() door = get_door() scan_nb = None @@ -121,6 +137,12 @@ def showscan(self, parameter_s=''): def spsplot(self, parameter_s=''): + try: + from taurus.external.qt import qt + except ImportError: + print("Qt binding is not available. SPSplot cannot work without it.") + return + get_door().plot() From c646206b3f532e717be073c4c01e33a6e6358d75 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Fri, 15 Jan 2021 14:37:13 +0100 Subject: [PATCH 085/279] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f09a6ab13b..ddd6e3810b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,8 @@ This file follows the formats and conventions from [keepachangelog.com] * `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) * Recorders tests helpers (#1439) * Disable flake8 job in travis CI (#1455) -* `createMacro()` and `prepareMacro()` docstring (#1460, #1444) +* `createMacro()` and `prepareMacro()` docstring (#1460, #1444) +* Make write of MeasurementGroup (Taurus extension) integration time more robust (#1473) * String formatting when rising exceptions in pseudomotors (#1469) ## [3.0.3] 2020-09-18 From 1bca450b6f305415a488e8998d60c2f9fcc0baac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Piekarski?= Date: Fri, 15 Jan 2021 16:29:58 +0100 Subject: [PATCH 086/279] set stop flags in multiple elements movement --- src/sardana/pool/poolbasegroup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sardana/pool/poolbasegroup.py b/src/sardana/pool/poolbasegroup.py index c7f456bc14..1779628f43 100644 --- a/src/sardana/pool/poolbasegroup.py +++ b/src/sardana/pool/poolbasegroup.py @@ -358,6 +358,8 @@ def stop(self): self.debug("Stopping %s %s", ctrl.name, [e.name for e in elements]) try: + for el in elements: + el.stop() error_elements = ctrl.stop_elements(elements=elements) if len(error_elements) > 0: element_names = [elem.name for elem in error_elements] From fafce7ce6484e0c328d64674349ec33d2efd953a Mon Sep 17 00:00:00 2001 From: Stanislaw Cabala Date: Fri, 15 Jan 2021 16:37:17 +0100 Subject: [PATCH 087/279] Fix broken import --- src/sardana/spock/magic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sardana/spock/magic.py b/src/sardana/spock/magic.py index eff733ee5c..b6e2ea1f30 100644 --- a/src/sardana/spock/magic.py +++ b/src/sardana/spock/magic.py @@ -42,7 +42,7 @@ def expconf(self, parameter_s=''): for the experiments (scans)""" try: - from taurus.external.qt import qt + from taurus.external.qt import Qt except ImportError: print("Qt binding is not available. ExpConf cannot work without it." "(hint: maybe you want to use experiment configuration macros? " @@ -92,7 +92,7 @@ def showscan(self, parameter_s=''): """ try: - from taurus.external.qt import qt + from taurus.external.qt import Qt except ImportError: print("Qt binding is not available. Showscan cannot work without it.") return @@ -138,7 +138,7 @@ def showscan(self, parameter_s=''): def spsplot(self, parameter_s=''): try: - from taurus.external.qt import qt + from taurus.external.qt import Qt except ImportError: print("Qt binding is not available. SPSplot cannot work without it.") return From 717287222dd1cc9df35521c1c25152a3d949cc5d Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 19 Jan 2021 16:14:38 +0100 Subject: [PATCH 088/279] Add Qt check for macro plotting --- src/sardana/spock/spockms.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sardana/spock/spockms.py b/src/sardana/spock/spockms.py index 88c0ef120a..78c511c8c5 100644 --- a/src/sardana/spock/spockms.py +++ b/src/sardana/spock/spockms.py @@ -514,6 +514,11 @@ def processRecordData(self, data): and data['type'] == 'function'): func_name = data['func_name'] if func_name.startswith("pyplot."): + try: + from taurus.external.qt import Qt + except ImportError: + print("Qt binding is not available. Macro plotting cannot work without it.") + return func_name = self.MathFrontend + "." + func_name args = data['args'] kwargs = data['kwargs'] From 413fbd611ed32986ccf052fbbca0778ed40e0b65 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Wed, 20 Jan 2021 09:30:07 +0100 Subject: [PATCH 089/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddd6e3810b..f29e1099aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ This file follows the formats and conventions from [keepachangelog.com] e.g. pseudo counter's value, in controllers (#1440, #1446) * Problems with macro id's when `sequencer` executes from _plain text_ files (#1215, #1216) * `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) +* Allow running Spock without Qt bindings (#1462, #1463) * Recorders tests helpers (#1439) * Disable flake8 job in travis CI (#1455) * `createMacro()` and `prepareMacro()` docstring (#1460, #1444) From 21599de89fcb394e9c8fc7ec753613cf65254f5c Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Wed, 20 Jan 2021 12:51:59 +0100 Subject: [PATCH 090/279] Add macro name and line to macro status 'start' event --- src/sardana/macroserver/macro.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/sardana/macroserver/macro.py b/src/sardana/macroserver/macro.py index abff4a2d0a..afb91c0d3a 100644 --- a/src/sardana/macroserver/macro.py +++ b/src/sardana/macroserver/macro.py @@ -538,6 +538,8 @@ def __init__(self, *args, **kwargs): self._id = kwargs.get('id') self._desc = "Macro '%s'" % self._macro_line self._macro_status = {'id': self._id, + 'name': self._name, + 'macro_line': self._macro_line, 'range': (0.0, 100.0), 'state': 'start', 'step': 0.0} @@ -2334,6 +2336,12 @@ def exec_(self): # make sure a 0.0 progress is sent yield macro_status + # Avoid repeating same information on subsequent events. If, in the + # future, clients that connect in the middle of macro execution need + # this information, just simply remove the lines below + del macro_status['name'] + del macro_status['macro_line'] + # allow any macro to be paused at the beginning of its execution self.pausePoint() From f1a044e54ea655a8fb559aaceb8304085460c3e1 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 20 Jan 2021 22:59:40 +0100 Subject: [PATCH 091/279] Fix createMacro test Test is running twice the same macro object (created only once). This looks like a copy&paste error. Remove the second run. --- src/sardana/macroserver/test/res/macros/testmacros.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sardana/macroserver/test/res/macros/testmacros.py b/src/sardana/macroserver/test/res/macros/testmacros.py index 638315865b..450db21879 100644 --- a/src/sardana/macroserver/test/res/macros/testmacros.py +++ b/src/sardana/macroserver/test/res/macros/testmacros.py @@ -88,7 +88,6 @@ def run(self, *args): params = (99, 1., 2.) expected_params = (99, [1., 2.]) - self.runMacro(macro) macro, pars = self.createMacro('pt6_base', *params) self.runMacro(macro) result = macro.data From a24de020f0ca8fc686e4d21fab6679509120d7ea Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Thu, 21 Jan 2021 12:02:59 +0100 Subject: [PATCH 092/279] Fix #1477: Launch showscan executable directly --- src/sardana/spock/magic.py | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/sardana/spock/magic.py b/src/sardana/spock/magic.py index b6e2ea1f30..f5578052e2 100644 --- a/src/sardana/spock/magic.py +++ b/src/sardana/spock/magic.py @@ -102,33 +102,8 @@ def showscan(self, parameter_s=''): scan_nb = None if len(params) > 0: if params[0].lower() == 'online': - try: - from sardana.taurus.qt.qtgui.extra_sardana import \ - ShowScanOnline - - except Exception as e: - print("Error importing ShowScanOnline") - print(e) - return - - doorname = get_door().fullname - # =============================================================== - # ugly hack to avoid ipython/qt thread problems #e.g. see - # https://sourceforge.net/p/sardana/tickets/10/ - # this hack does not allow inter-process communication and - # leaves the widget open after closing spock - # - # @todo: investigate cause of segfaults when using launching qt - # widgets from ipython - # - - # https://sourceforge.net/p/sardana/tickets/10/ import subprocess - import sys - fname = sys.modules[ShowScanOnline.__module__].__file__ - python_executable = which_python_executable() - args = [python_executable, fname, doorname, - '--taurus-log-level=error'] + args = ['showscan', '--taurus-log-level=error', get_door().fullname] subprocess.Popen(args) return else: From 1ae8bd08c684d867d3ad48b070947a562c36adb2 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Thu, 21 Jan 2021 17:40:47 +0100 Subject: [PATCH 093/279] add hook-places to mv macro --- src/sardana/macroserver/macros/standard.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index 7be0178ebd..3fe23003c0 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -459,9 +459,10 @@ def run(self, motor_list): self.execMacro('wm', motor_list, **Table.PrettyOpts) -class mv(Macro): +class mv(Macro, Hookable): """Move motor(s) to the specified position(s)""" + hints = {'allowsHooks': ('pre-move', 'post-move')} param_def = [ ['motor_pos_list', [['motor', Type.Moveable, None, 'Motor to move'], @@ -470,6 +471,9 @@ class mv(Macro): ] def run(self, motor_pos_list): + for preAcqHook in self.getHooks('pre-move'): + preAcqHook() + motors, positions = [], [] for m, p in motor_pos_list: motors.append(m) @@ -484,6 +488,9 @@ def run(self, motor_pos_list): msg.append(motor.information()) self.info("\n".join(msg)) + for postAcqHook in self.getHooks('post-move'): + postAcqHook() + class mstate(Macro): """Prints the state of a motor""" From 97a7f98b02ecd31d5bce92df8c77d1ef1090c68b Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Thu, 21 Jan 2021 17:49:03 +0100 Subject: [PATCH 094/279] add PRE_POST_MOVE_HOOK_IN_MV parameter to sardanacustomsetting module --- src/sardana/sardanacustomsettings.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/sardana/sardanacustomsettings.py b/src/sardana/sardanacustomsettings.py index 97c4859628..adb89e9c77 100644 --- a/src/sardana/sardanacustomsettings.py +++ b/src/sardana/sardanacustomsettings.py @@ -100,3 +100,12 @@ #: - 0 - history will not be filled #: - - max number of macros stored in the history MACROEXECUTOR_MAX_HISTORY = 100 + +#: pre-move and post-move hooks applied in simple mv-based macros +#: Available options: +#: +#: - False (or no setting) - macros which are hooked to the pre-move and post-move +#: hook places are not called in simple mv-based macros but only in scan-based macros +#: - True - macros which are hooked to the pre-move and post-move +#: hook places are called before and/or after any move a motor +PRE_POST_MOVE_HOOK_IN_MV = 100 From 927185220d264011312ee3a5bfd99d98ad88d3c7 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Thu, 21 Jan 2021 17:54:46 +0100 Subject: [PATCH 095/279] set default value of PRE_POST_MOVE_HOOK_IN_MV to False --- src/sardana/sardanacustomsettings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sardana/sardanacustomsettings.py b/src/sardana/sardanacustomsettings.py index adb89e9c77..2b817209a1 100644 --- a/src/sardana/sardanacustomsettings.py +++ b/src/sardana/sardanacustomsettings.py @@ -108,4 +108,4 @@ #: hook places are not called in simple mv-based macros but only in scan-based macros #: - True - macros which are hooked to the pre-move and post-move #: hook places are called before and/or after any move a motor -PRE_POST_MOVE_HOOK_IN_MV = 100 +PRE_POST_MOVE_HOOK_IN_MV = False From 2d33a5fda155438908832321a88a1042361f3852 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Thu, 21 Jan 2021 17:55:40 +0100 Subject: [PATCH 096/279] add condition of sardanacustomsettings to call pre/post-move hooks in mv-macro --- src/sardana/macroserver/macros/standard.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index 3fe23003c0..450d709a72 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -45,6 +45,7 @@ from sardana.macroserver.msexception import StopException, UnknownEnv from sardana.macroserver.scan.scandata import Record from sardana.macroserver.macro import Optional +from sardana import sardanacustomsettings ########################################################################## # @@ -470,9 +471,15 @@ class mv(Macro, Hookable): None, 'List of motor/position pairs'], ] + def prepare(self): + selsardanacustomsettings + def run(self, motor_pos_list): - for preAcqHook in self.getHooks('pre-move'): - preAcqHook() + enable_hooks = getattr(sardanacustomsettings, 'PRE_POST_MOVE_HOOK_IN_MV') + + if enable_hooks: + for preAcqHook in self.getHooks('pre-move'): + preAcqHook() motors, positions = [], [] for m, p in motor_pos_list: @@ -488,8 +495,9 @@ def run(self, motor_pos_list): msg.append(motor.information()) self.info("\n".join(msg)) - for postAcqHook in self.getHooks('post-move'): - postAcqHook() + if enable_hooks: + for postAcqHook in self.getHooks('post-move'): + postAcqHook() class mstate(Macro): From 262353d5ba2c4e0d817528f55d9e45760a67c7ee Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Thu, 21 Jan 2021 17:59:29 +0100 Subject: [PATCH 097/279] fix error in mv macro --- src/sardana/macroserver/macros/standard.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index 450d709a72..1bb220aaaf 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -471,9 +471,6 @@ class mv(Macro, Hookable): None, 'List of motor/position pairs'], ] - def prepare(self): - selsardanacustomsettings - def run(self, motor_pos_list): enable_hooks = getattr(sardanacustomsettings, 'PRE_POST_MOVE_HOOK_IN_MV') From 5c180304ec071fa65153f57f35aa258ffcf81c0b Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Thu, 21 Jan 2021 18:05:09 +0100 Subject: [PATCH 098/279] fix flake8 --- src/sardana/macroserver/macros/standard.py | 3 ++- src/sardana/sardanacustomsettings.py | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index 1bb220aaaf..e41de2d09b 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -472,7 +472,8 @@ class mv(Macro, Hookable): ] def run(self, motor_pos_list): - enable_hooks = getattr(sardanacustomsettings, 'PRE_POST_MOVE_HOOK_IN_MV') + enable_hooks = getattr(sardanacustomsettings, + 'PRE_POST_MOVE_HOOK_IN_MV') if enable_hooks: for preAcqHook in self.getHooks('pre-move'): diff --git a/src/sardana/sardanacustomsettings.py b/src/sardana/sardanacustomsettings.py index 2b817209a1..9a2f7554bc 100644 --- a/src/sardana/sardanacustomsettings.py +++ b/src/sardana/sardanacustomsettings.py @@ -92,7 +92,7 @@ #: - "dumb" - worst performance but directly available with Python 3. MS_ENV_SHELVE_BACKEND = None -#: macroexecutor maximum number of macros stored in the history. +#: macroexecutor maximum number of macros stored in the history. #: Available options: #: #: - None (or no setting) - unlimited history (may slow down the GUI operation @@ -101,11 +101,12 @@ #: - - max number of macros stored in the history MACROEXECUTOR_MAX_HISTORY = 100 -#: pre-move and post-move hooks applied in simple mv-based macros +#: pre-move and post-move hooks applied in simple mv-based macros #: Available options: #: -#: - False (or no setting) - macros which are hooked to the pre-move and post-move -#: hook places are not called in simple mv-based macros but only in scan-based macros +#: - False (or no setting) - macros which are hooked to the pre-move and +#: post-move hook places are not called in simple mv-based macros but +#: only in scan-based macros #: - True - macros which are hooked to the pre-move and post-move #: hook places are called before and/or after any move a motor PRE_POST_MOVE_HOOK_IN_MV = False From be8e45431cd7dd37bdf749773e1632136f0ca453 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Fri, 22 Jan 2021 12:25:06 +0100 Subject: [PATCH 099/279] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f29e1099aa..03f9c93b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ This file follows the formats and conventions from [keepachangelog.com] ### Added -* *scan information* and *scan point* forms to the *showscan online* widget (#1386) +* *scan information* and *scan point* forms to the *showscan online* widget (#1386, #1477, #1479) * `ScanPlotWidget`, `ScanPlotWindow`, `ScanInfoForm`, `ScanPointForm` and `ScanWindow` widget classes for easier composition of custom GUIs involving online scan plotting (#1386) * Include trigger/gate elements in the per-measurement preparation (#1432, #1443, #1468) From d3f30d8ea9fdb5797ebd07a12dab7042805d1ea5 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 22 Jan 2021 13:02:17 +0100 Subject: [PATCH 100/279] Disable testsuite on travis-ci --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 79d185ed08..c3456c4a4e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ env: matrix: # - TEST="flake8" - - TEST="testsuite" DOCKER_IMG=reszelaz/sardana-test + # - TEST="testsuite" DOCKER_IMG=reszelaz/sardana-test - TEST="doc" DOCKER_IMG=reszelaz/sardana-test From e0e80eb7272f8196a0be9287ce15ad9c1a6b5ae3 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 25 Jan 2021 12:26:07 +0100 Subject: [PATCH 101/279] Fix firing PsuedoCounter shape events Consider event values which are not SardanaAttribute instances as it is in the case of shape axis parameter. --- src/sardana/tango/pool/PseudoCounter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sardana/tango/pool/PseudoCounter.py b/src/sardana/tango/pool/PseudoCounter.py index f1c422295a..9293d9cf6b 100644 --- a/src/sardana/tango/pool/PseudoCounter.py +++ b/src/sardana/tango/pool/PseudoCounter.py @@ -152,6 +152,8 @@ def _on_pseudo_counter_changed(self, event_source, event_type, else: value = event_value.value timestamp = event_value.timestamp + else: + value = event_value self.set_attribute(attr, value=value, w_value=w_value, timestamp=timestamp, quality=quality, From 9a2ffd4d70ab3235db5735ed3633b6f25acea7c2 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 25 Jan 2021 12:27:14 +0100 Subject: [PATCH 102/279] Do not read shape from the cache Shape may change externally e.g. by changing RoI or binning. Always read it from the controller. --- src/sardana/tango/pool/PoolDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sardana/tango/pool/PoolDevice.py b/src/sardana/tango/pool/PoolDevice.py index 246ea19141..5ecd5ee59a 100644 --- a/src/sardana/tango/pool/PoolDevice.py +++ b/src/sardana/tango/pool/PoolDevice.py @@ -946,7 +946,7 @@ def read_Shape(self, attr): :param attr: tango attribute :type attr: :class:`~PyTango.Attribute`""" - attr.set_value(self.element.shape) + attr.set_value(self.element.get_shape(cache=False)) class PoolExpChannelDeviceClass(PoolElementDeviceClass): From f573fccc50ff465817a63e2ff4da33c5f4d3af1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Piekarski?= Date: Mon, 25 Jan 2021 12:38:48 +0100 Subject: [PATCH 103/279] move loop before try except clause --- src/sardana/pool/poolbasegroup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sardana/pool/poolbasegroup.py b/src/sardana/pool/poolbasegroup.py index 1779628f43..1ccf152921 100644 --- a/src/sardana/pool/poolbasegroup.py +++ b/src/sardana/pool/poolbasegroup.py @@ -357,9 +357,9 @@ def stop(self): for ctrl, elements in list(self.get_physical_elements().items()): self.debug("Stopping %s %s", ctrl.name, [e.name for e in elements]) + for el in elements: + el.stop() try: - for el in elements: - el.stop() error_elements = ctrl.stop_elements(elements=elements) if len(error_elements) > 0: element_names = [elem.name for elem in error_elements] From 4820387094bc63105eed11e66a79ec27b4f6ca7f Mon Sep 17 00:00:00 2001 From: Tim Schoof Date: Mon, 25 Jan 2021 14:24:09 +0100 Subject: [PATCH 104/279] Fix example in docstring of QtSpockWidget --- src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py b/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py index 54d8535170..a20f3d2800 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py @@ -146,8 +146,9 @@ class QtSpockWidget(RichJupyterWidget, TaurusBaseWidget): from taurus.external.qt import Qt from sardana.taurus.qt.qtgui.extra_sardana.qtspock import QtSpockWidget app = Qt.QApplication([]) - widget = QtSpockWidget() + widget = QtSpockWidget(use_model_from_profile=True) widget.show() + widget.start_kernel() app.aboutToQuit.connect(widget.shutdown_kernel) app.exec_() """ From e6d704366869eea4c0673e2d6619442e08f2ac42 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 26 Jan 2021 15:18:27 +0100 Subject: [PATCH 105/279] Avoid hangs of test in case it fails on 2nd scan --- src/sardana/macroserver/recorders/test/test_h5storage.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/sardana/macroserver/recorders/test/test_h5storage.py b/src/sardana/macroserver/recorders/test/test_h5storage.py index 6babb8d401..21b6769927 100644 --- a/src/sardana/macroserver/recorders/test/test_h5storage.py +++ b/src/sardana/macroserver/recorders/test/test_h5storage.py @@ -257,6 +257,8 @@ def read_file(path, event): reader.start() event.wait() event.clear() - scan(path, serialno=1) - event.set() - reader.join() + try: + scan(path, serialno=1) + finally: + event.set() + reader.join() From fc14cd8fe8ede413e94c6140629a6b9fa460aa1c Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 26 Jan 2021 14:52:27 +0100 Subject: [PATCH 106/279] Add unittest for addCustomData --- .../macroserver/recorders/test/test_h5storage.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/sardana/macroserver/recorders/test/test_h5storage.py b/src/sardana/macroserver/recorders/test/test_h5storage.py index 413f4e4797..657dff0dfa 100644 --- a/src/sardana/macroserver/recorders/test/test_h5storage.py +++ b/src/sardana/macroserver/recorders/test/test_h5storage.py @@ -193,3 +193,17 @@ def tearDown(self): os.remove(self.path) except OSError: pass + + +@pytest.fixture +def recorder(tmpdir): + path = str(tmpdir / "file.h5") + return NXscanH5_FileRecorder(filename=path) + + +@pytest.mark.parametrize("custom_data", ["str_custom_data", 8, True]) +def test_addCustomData(recorder, custom_data): + name = "custom_data_name" + recorder.addCustomData(custom_data, name) + with h5py.File(recorder.filename) as fd: + assert fd["entry"]["custom_data"][name].value == custom_data From db6a12f8cddcbb6013497ee8c910582ffd5be1e9 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 26 Jan 2021 14:59:40 +0100 Subject: [PATCH 107/279] Fix addCustomData with strings --- src/sardana/macroserver/recorders/h5storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sardana/macroserver/recorders/h5storage.py b/src/sardana/macroserver/recorders/h5storage.py index 2b34d5abe7..55959a22e0 100644 --- a/src/sardana/macroserver/recorders/h5storage.py +++ b/src/sardana/macroserver/recorders/h5storage.py @@ -578,7 +578,7 @@ def _addCustomData(self, value, name, nxpath=None, dtype=None, **kwargs): if numpy.isscalar(value): dtype = numpy.dtype(type(value)).name if numpy.issubdtype(dtype, str): - dtype = 'char' + dtype = NXscanH5_FileRecorder.str_dt if dtype == 'bool': value, dtype = int(value), 'int8' else: From 92390f3ccf8910878fc734671b1428b21c7a7cef Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 26 Jan 2021 15:35:09 +0100 Subject: [PATCH 108/279] Add missing pytest import --- src/sardana/macroserver/recorders/test/test_h5storage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sardana/macroserver/recorders/test/test_h5storage.py b/src/sardana/macroserver/recorders/test/test_h5storage.py index 657dff0dfa..d6f32cfcaf 100644 --- a/src/sardana/macroserver/recorders/test/test_h5storage.py +++ b/src/sardana/macroserver/recorders/test/test_h5storage.py @@ -31,6 +31,7 @@ import h5py import numpy +import pytest from unittest import TestCase from sardana.macroserver.scan import ColumnDesc From 01c95cd0b4dd1955f086752f080f0f62664d4bcc Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 26 Jan 2021 16:43:10 +0100 Subject: [PATCH 109/279] Fix PreScanSnapshot of strings --- src/sardana/macroserver/recorders/h5storage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sardana/macroserver/recorders/h5storage.py b/src/sardana/macroserver/recorders/h5storage.py index 2b34d5abe7..1f2b8597b7 100644 --- a/src/sardana/macroserver/recorders/h5storage.py +++ b/src/sardana/macroserver/recorders/h5storage.py @@ -296,6 +296,7 @@ def _createPreScanSnapshot(self, env): self.debug('Pre-scan snapshot of %s will be stored as type %s', dd.name, dtype) elif dd.dtype == 'str': + dtype = NXscanH5_FileRecorder.str_dt dd.dtype = NXscanH5_FileRecorder.str_dt if dtype in self.supported_dtypes: From 1a05485b92ee5269fb1a0c10759b7554c317b3e7 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Wed, 27 Jan 2021 10:51:22 +0100 Subject: [PATCH 110/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03f9c93b57..ec90095c6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ This file follows the formats and conventions from [keepachangelog.com] instead of reading only at the end (#1442, #1448) * Avoid problems when defining different, e.g. shape, standard attributes, e.g. pseudo counter's value, in controllers (#1440, #1446) +* PreScanSnapshot of strings (#1486) * Problems with macro id's when `sequencer` executes from _plain text_ files (#1215, #1216) * `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) * Allow running Spock without Qt bindings (#1462, #1463) From 578e870a0948e9ad96222462c010d2a789546a03 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Wed, 27 Jan 2021 15:19:37 +0100 Subject: [PATCH 111/279] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec90095c6a..193675afff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,8 @@ This file follows the formats and conventions from [keepachangelog.com] instead of reading only at the end (#1442, #1448) * Avoid problems when defining different, e.g. shape, standard attributes, e.g. pseudo counter's value, in controllers (#1440, #1446) -* PreScanSnapshot of strings (#1486) +* Storing string values in PreScanSnapshot in NXscanH5_FileRecorder (#1486) +* Storing string values as custom data in NXscanH5_FileRecorder (#1485) * Problems with macro id's when `sequencer` executes from _plain text_ files (#1215, #1216) * `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) * Allow running Spock without Qt bindings (#1462, #1463) From c47263ed2d47e27c76243a3483010f1c0dacc7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Piekarski?= Date: Wed, 27 Jan 2021 16:41:44 +0100 Subject: [PATCH 112/279] Set flag stopped instead using stop command --- src/sardana/pool/poolbasegroup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sardana/pool/poolbasegroup.py b/src/sardana/pool/poolbasegroup.py index 1ccf152921..3eb182aac9 100644 --- a/src/sardana/pool/poolbasegroup.py +++ b/src/sardana/pool/poolbasegroup.py @@ -358,7 +358,7 @@ def stop(self): self.debug("Stopping %s %s", ctrl.name, [e.name for e in elements]) for el in elements: - el.stop() + el._stopped = True try: error_elements = ctrl.stop_elements(elements=elements) if len(error_elements) > 0: From 037d75df2b1e406e92bb844e9ce43328b6d8afaa Mon Sep 17 00:00:00 2001 From: schooft <42000386+schooft@users.noreply.github.com> Date: Wed, 27 Jan 2021 16:54:34 +0100 Subject: [PATCH 113/279] Add string to QApplication argument list This is a workaround for a bug in some PyQt versions, see issue #1209. Co-authored-by: reszelaz --- src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py b/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py index a20f3d2800..a516bfcbae 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py @@ -145,7 +145,7 @@ class QtSpockWidget(RichJupyterWidget, TaurusBaseWidget): from taurus.external.qt import Qt from sardana.taurus.qt.qtgui.extra_sardana.qtspock import QtSpockWidget - app = Qt.QApplication([]) + app = Qt.QApplication(["qtspock"]) widget = QtSpockWidget(use_model_from_profile=True) widget.show() widget.start_kernel() From 23b0b786a5f799b27e47fe1ba1c2d31ff119de48 Mon Sep 17 00:00:00 2001 From: Tim Schoof Date: Wed, 27 Jan 2021 17:30:41 +0100 Subject: [PATCH 114/279] Show info in widget if no kernel was started yet Because the widget is reset when a new kernel is started, the message is visible only if no kernel has been started yet. --- src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py b/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py index a516bfcbae..e9a84ba2ed 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py @@ -181,6 +181,8 @@ def __init__( self._door_name = None self._door_alias = None + self.append_stream("Waiting for kernel to start") + self.kernel_manager = SpockKernelManager(kernel_name=kernel) self.kernel_manager.kernel_about_to_launch.connect( self._handle_kernel_lauched) From 4a5efaa489664200ebfe96872a8679bd16fd2311 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 28 Jan 2021 12:54:01 +0100 Subject: [PATCH 115/279] Add script to upgrade mntgrp from Sardana 2 to Sardana 3 --- scripts/upgrade/upgrade_mntgrp.py | 106 ++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 scripts/upgrade/upgrade_mntgrp.py diff --git a/scripts/upgrade/upgrade_mntgrp.py b/scripts/upgrade/upgrade_mntgrp.py new file mode 100644 index 0000000000..1154520e03 --- /dev/null +++ b/scripts/upgrade/upgrade_mntgrp.py @@ -0,0 +1,106 @@ +""" This serves to upgrade MeasurementGroups from Sardana 2 to Sardana 3: + +To get usage help: python3 upgrade_mntgrp.py --help +""" + +import re +import sys +try: + import argparse +except ImportError: + from taurus.external import argparse +try: + from itertools import zip_longest +except ImportError: + from itertools import izip_longest as zip_longest + +import tango +import taurus +from sardana.taurus.core.tango.sardana import registerExtensions + + +def grouper(iterable, n, fillvalue=None): + """Collect data into fixed-length chunks or blocks""" + # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx" + args = [iter(iterable)] * n + return zip_longest(*args, fillvalue=fillvalue) + + +def replace_tango_db(tango_db_pqdn, tango_db_fqdn, s): + # first step: pqdn -> fqdn + new_s = re.sub(tango_db_pqdn, tango_db_fqdn, s) + # second step: add missing scheme + new_s = re.sub("(? Date: Thu, 28 Jan 2021 13:33:44 +0100 Subject: [PATCH 116/279] Document upgrade_mntgrp.py script in the migration guide --- doc/source/devel/guide_migration/2to3.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/source/devel/guide_migration/2to3.rst b/doc/source/devel/guide_migration/2to3.rst index 8e4f3939c3..2788143042 100644 --- a/doc/source/devel/guide_migration/2to3.rst +++ b/doc/source/devel/guide_migration/2to3.rst @@ -45,6 +45,10 @@ Sardana v3 was reduced by long time ago deprecated features. You can find a list of them together with the suggested substitutes in this `table `_. +For migrating the measurement group configurations (non-URI model names) you +can use the `upgrade_mntgrp.py `_ +script. + From 30db3e3f172527e95c97dcb46004d8af4c348a20 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 28 Jan 2021 13:54:03 +0100 Subject: [PATCH 117/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 193675afff..f81c78e0f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Problems with macro id's when `sequencer` executes from _plain text_ files (#1215, #1216) * `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) * Allow running Spock without Qt bindings (#1462, #1463) +* Docstring of QtSpockWidget (#1484) * Recorders tests helpers (#1439) * Disable flake8 job in travis CI (#1455) * `createMacro()` and `prepareMacro()` docstring (#1460, #1444) From e19c58f4d4b72a6d6976e9bfee2e036aacc1f7b6 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 29 Jan 2021 16:44:50 +0100 Subject: [PATCH 118/279] Remove shape from the expconf It was decided in #1296 that shape will be changed from the measurement group configuration to the experimental channel read only attribute. Hence remove it from the expconf widget. --- .../users/taurus/experimentconfiguration.rst | 1 - src/sardana/pool/poolmeasurementgroup.py | 2 +- .../taurus/core/tango/sardana/sardana.py | 2 +- .../qtgui/extra_sardana/measurementgroup.py | 39 +++---------------- 4 files changed, 7 insertions(+), 37 deletions(-) diff --git a/doc/source/users/taurus/experimentconfiguration.rst b/doc/source/users/taurus/experimentconfiguration.rst index 25fe0a7308..0c82b245a7 100644 --- a/doc/source/users/taurus/experimentconfiguration.rst +++ b/doc/source/users/taurus/experimentconfiguration.rst @@ -75,7 +75,6 @@ a given channel or its controller: process. * output - whether the channel acquisition results should be printed, for example, by the output recorder during the scan. Can be either True or False. -* shape - shape of the data * data type - type of the data * plot type - select the online scan plot type for the channel. Can have one of the following values: diff --git a/src/sardana/pool/poolmeasurementgroup.py b/src/sardana/pool/poolmeasurementgroup.py index 6776a6f045..eb05b13034 100644 --- a/src/sardana/pool/poolmeasurementgroup.py +++ b/src/sardana/pool/poolmeasurementgroup.py @@ -953,7 +953,7 @@ def _fill_channel_data(self, channel, channel_data): channel_data['normalization'] = channel_data.get('normalization', Normalization.No) # TODO: think of filling other keys: data_type, data_units, nexus_path - # shape here instead of feeling them on the Taurus extension level + # here instead of feeling them on the Taurus extension level if ctype != ElementType.External: ctrl_name = channel.controller.full_name diff --git a/src/sardana/taurus/core/tango/sardana/sardana.py b/src/sardana/taurus/core/tango/sardana/sardana.py index 1b0fc8c9b2..f14f94f80c 100644 --- a/src/sardana/taurus/core/tango/sardana/sardana.py +++ b/src/sardana/taurus/core/tango/sardana/sardana.py @@ -56,7 +56,7 @@ "PlotAxes", "Timer", "Monitor", "Synchronization", "ValueRefPattern", "ValueRefEnabled", "Conditioning", "Normalization", "NXPath", - "Shape", "DataType", "Unknown", "Synchronizer")) + "DataType", "Unknown", "Synchronizer")) PlotType = Enumeration("PlotType", ("No", "Spectrum", "Image")) diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/measurementgroup.py b/src/sardana/taurus/qt/qtgui/extra_sardana/measurementgroup.py index e7c16f7e01..3d6171800e 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/measurementgroup.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/measurementgroup.py @@ -136,7 +136,7 @@ def createChannelDict(channel, index=None, **kwargs): # If the channel is a Tango one, try to guess data_type, shape and # data_units attrproxy = attrconf = value = None - dtype = shape = None + dtype = None try: attrproxy = PyTango.AttributeProxy(source) attrconf = attrproxy.get_config() @@ -147,15 +147,9 @@ def createChannelDict(channel, index=None, **kwargs): print(str(e)) if value is not None: - shape = list(numpy.shape(value)) dtype = getattr(value, 'dtype', numpy.dtype(type(value))).name ret['data_units'] = attrconf.unit elif attrconf is not None: - if attrconf.data_format == PyTango.AttrDataFormat.SCALAR: - shape = [] - else: - shape = [n for n in (attrconf.max_dim_x, - attrconf.max_dim_y) if n > 0] dtype = FROM_TANGO_TO_STR_TYPE[attrconf.data_type] ret['data_units'] = attrconf.unit @@ -166,8 +160,6 @@ def createChannelDict(channel, index=None, **kwargs): # elif dtype == 'bool': # dtype='int8' ret['data_type'] = dtype - if shape is not None: - ret['shape'] = shape # now overwrite using the arguments ret.update(kwargs) @@ -179,13 +171,7 @@ def createChannelDict(channel, index=None, **kwargs): # Choose a default plot_type for the channel if 'plot_type' not in ret: - default_plot_type = {0: PlotType.Spectrum, - 2: PlotType.Image, None: PlotType.No} - try: - rank = len(ret['shape']) - except KeyError: - rank = None # if shape is not known, use the default plot_type - ret['plot_type'] = default_plot_type.get(rank, PlotType.No) + ret['plot_type'] = PlotType.No # And a default value for plot_axes if 'plot_axes' not in ret: @@ -248,8 +234,6 @@ def getElementTypeToolTip(t): return "Channel active or not" elif t == ChannelView.Output: return "Channel output active or not" - elif t == ChannelView.Shape: - return "Shape of the data (using numpy convention). For example, a scalar will have shape=(), a spectrum of 10 elements will have shape=(10,) and an image of 20x30 will be shape=(20,30)" elif t == ChannelView.DataType: return "Type of data for storing (valid types are: char, float32, float64, [u]int{8|16|32|64})", elif t == ChannelView.PlotType: @@ -305,7 +289,6 @@ class MntGrpChannelItem(BaseMntGrpChannelItem): itemdata_keys_map = {ChannelView.Channel: 'label', ChannelView.Enabled: 'enabled', ChannelView.Output: 'output', - ChannelView.Shape: 'shape', ChannelView.DataType: 'data_type', ChannelView.PlotType: 'plot_type', ChannelView.PlotAxes: 'plot_axes', @@ -334,8 +317,6 @@ def data(self, index): ret = Normalization[ret] elif taurus_role == ChannelView.PlotAxes: ret = "|".join(ret) - elif taurus_role == ChannelView.Shape: - ret = str(ret) return ret def setData(self, index, qvalue): @@ -363,16 +344,6 @@ def setData(self, index, qvalue): data = Normalization[qvalue] elif taurus_role == ChannelView.PlotAxes: data = [a for a in qvalue.split('|')] - elif taurus_role == ChannelView.Shape: - s = qvalue - try: - data = eval(s, {}, {}) - if not isinstance(data, (tuple, list)): - raise ValueError - except: - from taurus.core.util.log import Logger - Logger(self.__class__.__name__).error('Invalid shape %s', s) - data = () else: raise NotImplementedError('Unknown role') ch_data[key] = data @@ -394,13 +365,13 @@ class MntGrpUnitItem(TaurusBaseTreeItem): class BaseMntGrpChannelModel(TaurusBaseModel): - ColumnNames = ("Channel", "enabled", "output", "Shape", "Data Type", + ColumnNames = ("Channel", "enabled", "output", "Data Type", "Plot Type", "Plot Axes", "Timer", "Monitor", "Synchronizer", "Synchronization", "Ref Enabled", "Ref Pattern", "Conditioning", "Normalization", "NeXus Path") ColumnRoles = ((ChannelView.Channel, ChannelView.Channel), - ChannelView.Enabled, ChannelView.Output, ChannelView.Shape, + ChannelView.Enabled, ChannelView.Output, ChannelView.DataType, ChannelView.PlotType, ChannelView.PlotAxes, ChannelView.Timer, ChannelView.Monitor, ChannelView.Synchronizer, @@ -978,7 +949,7 @@ class MntGrpChannelEditor(TaurusBaseTableWidget): DftPerspective = "Channel" _simpleViewColumns = (ChannelView.Channel, ChannelView.Output, - ChannelView.Shape, ChannelView.PlotType, ChannelView.PlotAxes) + ChannelView.PlotType, ChannelView.PlotAxes) _simpleView = False def __init__(self, parent=None, designMode=False, with_filter_widget=True, perspective=None): From ff0197a29fb4ca635bfdbf7fda1037eaa1bb1bef Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Fri, 29 Jan 2021 17:51:25 +0100 Subject: [PATCH 119/279] add self.motors to mv and umv macros --- src/sardana/macroserver/macros/standard.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index e41de2d09b..f5934963b4 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -479,17 +479,17 @@ def run(self, motor_pos_list): for preAcqHook in self.getHooks('pre-move'): preAcqHook() - motors, positions = [], [] + self.motors, positions = [], [] for m, p in motor_pos_list: - motors.append(m) + self.motors.append(m) positions.append(p) self.debug("Starting %s movement to %s", m.getName(), p) - motion = self.getMotion(motors) + motion = self.getMotion(self.motors) state, pos = motion.move(positions) if state != DevState.ON: self.warning("Motion ended in %s", state.name) msg = [] - for motor in motors: + for motor in self.motors: msg.append(motor.information()) self.info("\n".join(msg)) @@ -521,6 +521,7 @@ def prepare(self, motor_pos_list, **opts): pos, posObj = motor.getPosition(force=True), motor.getPositionObj() self.all_pos.append([pos]) posObj.subscribeEvent(self.positionChanged, motor) + self.motors = self.all_names def run(self, motor_pos_list): self.print_pos = True From 3b3ac751a70f98caa51708905b199cc6c6ac67dc Mon Sep 17 00:00:00 2001 From: Benjamin Bertrand Date: Sun, 31 Jan 2021 10:46:27 +0100 Subject: [PATCH 120/279] Add LICENSE to python package --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 6f5eef71c1..8ca9272ff2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -12,3 +12,4 @@ exclude .ropeproject exclude build_* include CHANGELOG.md +include LICENSE From b5c9b537591879175977a4f54aa3ff9f3ff4be10 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Mon, 1 Feb 2021 09:58:13 +0100 Subject: [PATCH 121/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f81c78e0f4..3b6eaccc33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Documentation on how to start Tango servers on fixed IP - ORBendPoint (#1470) * Documentation example on how to more efficiently access Tango with PyTango in macros/controllers (#1456) +* LICENSE file to python source distribution (#1490) ### Fixed From 0ab58a7e0ec385d1f10b8d85437c0c7fe4fce3cd Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 1 Feb 2021 12:52:06 +0100 Subject: [PATCH 122/279] Remove TODO --- .../howto_controllers/howto_countertimercontroller.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst index 6d4f751265..f056e0dbfb 100644 --- a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst @@ -6,12 +6,6 @@ How to write a counter/timer controller ======================================= -.. important:: - Counter/timer controller :term:`API` was extended in SEP18_ but this is - still not documented in this chapter. Please check the said SEP for more - information about the additional :term:`API` or eventual changes. - - The basics ---------- From ac6f0a3213ffc51029c31f0c733e62c8db301ddf Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 1 Feb 2021 13:13:19 +0100 Subject: [PATCH 123/279] Use equality instead of identity checks for numbers and strings Starting with Python 3.8 these are reported as warnings: The compiler now produces a SyntaxWarning when identity checks (is and is not) are used with certain types of literals (e.g. strings, numbers). These can often work by accident in CPython, but are not guaranteed by the language spec. The warning advises users to use equality tests (== and !=) instead. --- src/sardana/macroserver/macros/lists.py | 2 +- src/sardana/tango/core/util.py | 2 +- src/sardana/taurus/core/tango/sardana/macroserver.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sardana/macroserver/macros/lists.py b/src/sardana/macroserver/macros/lists.py index e137453bff..33ae623299 100644 --- a/src/sardana/macroserver/macros/lists.py +++ b/src/sardana/macroserver/macros/lists.py @@ -143,7 +143,7 @@ def obj2Row(self, o, cols=None): def run(self, filter): objs = self.objs(filter) nb = len(objs) - if nb is 0: + if nb == 0: if self.subtype is Macro.All: if isinstance(self.type, str): t = self.type.lower() diff --git a/src/sardana/tango/core/util.py b/src/sardana/tango/core/util.py index 7f84681d03..293aea9a8e 100644 --- a/src/sardana/tango/core/util.py +++ b/src/sardana/tango/core/util.py @@ -1206,7 +1206,7 @@ def prepare_logging(options, args, tango_args, start_time=None, def prepare_rconsole(options, args, tango_args): port = options.rconsole_port - if port is None or port is 0: + if port is None or port == 0: return taurus.debug("Setting up rconsole on port %d...", port) try: diff --git a/src/sardana/taurus/core/tango/sardana/macroserver.py b/src/sardana/taurus/core/tango/sardana/macroserver.py index 8849c9ef81..4518b88d60 100644 --- a/src/sardana/taurus/core/tango/sardana/macroserver.py +++ b/src/sardana/taurus/core/tango/sardana/macroserver.py @@ -665,7 +665,7 @@ 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: + 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) From 36975bcab51dc4b875a40e8dbf3d7a8948f97635 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 1 Feb 2021 13:27:06 +0100 Subject: [PATCH 124/279] Add TOC to how to counter/timer controller --- .../howto_controllers/howto_countertimercontroller.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst index f056e0dbfb..88f36f5083 100644 --- a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst @@ -6,6 +6,13 @@ How to write a counter/timer controller ======================================= +This chapter provides the necessary information to write a counter/timer +controller in Sardana. + +.. contents:: Table of contents + :depth: 3 + :backlinks: entry + The basics ---------- From 4092d3b5ddc6d7a6509b1e468579c2163ce70ebb Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 1 Feb 2021 14:27:57 +0100 Subject: [PATCH 125/279] Move timer/monitor roles to advanced topics --- .../howto_controllers/howto_countertimercontroller.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst index 88f36f5083..b82f108ff5 100644 --- a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst @@ -217,10 +217,14 @@ Here is an example of the possible implementation of def AbortOne(self, axis): self.springfield.AbortChannel(axis) + +Advanced topics +--------------- + .. _sardana-countertimercontroller-howto-timermonitor: Timer and monitor roles ------------------------ +~~~~~~~~~~~~~~~~~~~~~~~ Usually counters can work in either of two modes: timer or monitor. In both of them, one counter in a group is assigned a special role to control when @@ -237,9 +241,6 @@ Controller may announce its default timer axis with the .. _sardana-countertimercontroller-howto-advanced: -Advanced topics ---------------- - .. _sardana-countertimercontroller-howto-timestamp-value: Timestamp a counter value From 64292674584beb3d97b0be1b518ff0dfd641536d Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 1 Feb 2021 14:28:38 +0100 Subject: [PATCH 126/279] Document hardware and software start synchronization --- .../howto_countertimercontroller.rst | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst index b82f108ff5..f7c88d918a 100644 --- a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst @@ -410,8 +410,8 @@ We can modify our counter controller to take profit of this hardware feature: def StartAll(self): self.springfield.startCounters(self._counters_info) -Hardware synchronization -~~~~~~~~~~~~~~~~~~~~~~~~ +External (hardware) synchronization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The synchronization achieved in :ref:`sardana-countertimercontroller-howto-mutliple-acquisition` may not be enough when it comes to acquiring with multiple controllers at the @@ -448,8 +448,10 @@ Here is an example of the possible implementation of SynchMap = { AcqSynch.SoftwareTrigger : 1, AcqSynch.SoftwareGate : 2, - AcqSynch.HardwareTrigger: 3, - AcqSynch.HardwareGate: 4 + AcqSynch.SoftwareStart : 3, + AcqSynch.HardwareTrigger: 4, + AcqSynch.HardwareGate: 5, + AcqSynch.HardwareStart: 6 } def SetCtrlPar(self, name, value): @@ -503,8 +505,11 @@ can be: :attr:`~sardana.pool.pooldefs.AcqSynch.SoftwareGate` synchronization - a sequence of counter values: either :class:`float` or :obj:`~sardana.sardanavalue.SardanaValue` - in case of the :attr:`~sardana.pool.pooldefs.AcqSynch.HardwareTrigger` or - :attr:`~sardana.pool.pooldefs.AcqSynch.HardwareGate` synchronization + in case of the :attr:`~sardana.pool.pooldefs.AcqSynch.HardwareTrigger`, + :attr:`~sardana.pool.pooldefs.AcqSynch.HardwareGate`, + :attr:`~sardana.pool.pooldefs.AcqSynch.HardwareStart` or + :attr:`~sardana.pool.pooldefs.AcqSynch.SoftwareStart` + synchronization Sardana assumes that the counter values are returned in the order of acquisition and that there are no gaps in between them. From 05549efd2f7d9aef99d77f38deafd9c8209ec653 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 1 Feb 2021 16:06:15 +0100 Subject: [PATCH 127/279] Document latency time --- .../howto_countertimercontroller.rst | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst index f7c88d918a..0bd9f92e0c 100644 --- a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst @@ -483,6 +483,60 @@ Here is an example of the possible implementation of self.springfield.SetRepetitions(repetitions) return value +In order to make the acquisition flow smoothly the synchronizer and +the counter/timer controllers needs to agree on the synchronization pace. +The counter/timer controller manifest what is the maximum allowed pace for him +by means of the ``latency_time`` controller parameter (in seconds). This parameter +corresponds to the minimum time necessary by the hardware controller to re-arm +for the next acquisition. + +Here is an example of the possible implementation of +:meth:`~sardana.pool.controller.Controller.GetCtrlPar`: + +.. code-block:: python + :emphasize-lines: 3 + + class SpringfieldCounterTimerController(CounterTimerController): + + def GetCtrlPar(self, name): + if name == "latency_time": + return self.springfield.GetLatencyTime() + +.. warning:: + By default, the `~sardana.pool.controller.CounterTimerController` + base classes returns zero latency time controller parameter. + If in your controller you override + the :meth:`~sardana.pool.controller.Controller.GetCtrlPar` method + remember to always call the super class method as fallback: + + .. code-block:: python + :emphasize-lines: 5 + + def GetCtrlPar(self, name): + if name == "some_par": + return "some_val" + else: + return super().GetCtrlPar(name) + + +In the case of the :attr:`~sardana.pool.pooldefs.AcqSynch.HardwareStart` or +:attr:`~sardana.pool.pooldefs.AcqSynch.SoftwareStart` synchronizations +the counter/timer hardware *auto* triggers itself during the measurement process. +In order to fully configure the hardware and set the re-trigger pace you can +use the ``latency`` argument (in seconds) +of the :meth:`~sardana.pool.controller.Loadable.LoadOne` method: + +.. code-block:: python + :emphasize-lines: 3 + + class SpringfieldCounterTimerController(CounterTimerController): + + def LoadOne(self, axis, value, repetitions, latency): + self.springfield.LoadChannel(axis, value) + self.springfield.SetRepetitions(repetitions) + self.springfield.SetLatency(latency) + return value + Get counter values """""""""""""""""" From ec258579ab643fe88f8f4df6ecb7600c22bbd4ca Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 1 Feb 2021 16:06:37 +0100 Subject: [PATCH 128/279] Document per measurement preparation --- .../howto_countertimercontroller.rst | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst index 0bd9f92e0c..1fa444fc7d 100644 --- a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst @@ -568,7 +568,31 @@ can be: Sardana assumes that the counter values are returned in the order of acquisition and that there are no gaps in between them. -.. todo:: document how to skip the readouts while acquiring +Per measurement preparation +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Since SEP18_ counter/timer controllers may take a profit from the per measurement +preparation and reserve resources for a sequence of +:attr:`~sardana.pool.pooldefs.AcqSynch.SoftwareTrigger` +or :attr:`~sardana.pool.pooldefs.AcqSynch.SoftwareGate` acquisitions +already in the :meth:`~sardana.pool.controller.Loadable.PrepareOne` method. +This method is called only once at the beginning of the measurement e.g. +:ref:`Deterministic step scans ` +or :ref:`sardana-users-scan-continuous`. +It enables an opportunity for significant dead time optimization thanks to the +single per measurement configuration instead of the multiple per acquisition +preparation using the :meth:`~sardana.pool.controller.Loadable.LoadOne`. + +Here is an example of the possible implementation of +:meth:`~sardana.pool.controller.Loadable.PrepareOne`: + +.. code-block:: python + :emphasize-lines: 3 + + class SpringfieldCounterTimerController(CounterTimerController): + + def PrepareOne(self, value, repetitions, latency, nb_starts): + return self.springfield.SetNbStarts() .. _ALBA: http://www.cells.es/ From 53b8c8dc428c26f6087a00667899b6d4eae7ebcb Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 1 Feb 2021 16:07:02 +0100 Subject: [PATCH 129/279] Add more detail to LoadOne documentation --- .../devel/howto_controllers/howto_countertimercontroller.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst index 1fa444fc7d..0ed780c81a 100644 --- a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst @@ -467,7 +467,7 @@ Multiple acquisitions It is a very common scenario to execute multiple hardware synchronized acquisitions in a row. One example of this type of measurements are the :ref:`sardana-users-scan-continuous`. The controller receives the number of -acquisitions via the third argument of the +acquisitions via the ``repetitions`` argument of the :meth:`~sardana.pool.controller.Loadable.LoadOne` method. Here is an example of the possible implementation of From 8ee19847c68a583d6918c4c525041942f1e6cfdb Mon Sep 17 00:00:00 2001 From: reszelaz Date: Mon, 1 Feb 2021 17:06:26 +0100 Subject: [PATCH 130/279] Correct typo --- .../devel/howto_controllers/howto_countertimercontroller.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst index 0ed780c81a..2f620616d4 100644 --- a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst @@ -504,7 +504,7 @@ Here is an example of the possible implementation of .. warning:: By default, the `~sardana.pool.controller.CounterTimerController` - base classes returns zero latency time controller parameter. + base classes return zero latency time controller parameter. If in your controller you override the :meth:`~sardana.pool.controller.Controller.GetCtrlPar` method remember to always call the super class method as fallback: From ef4a8e112ec02e0614dd7bf813c983f669c6bf4c Mon Sep 17 00:00:00 2001 From: reszelaz Date: Mon, 1 Feb 2021 17:15:07 +0100 Subject: [PATCH 131/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b6eaccc33..88b5808054 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Initial delay in position domain to the synchronization description in *ct* like continuous scans (#1428) * Avoid double printing of user units in PMTV: read widget and units widget (#1424) +* Missing documentation of SEP18 concepts to how-to counter/timer controller (#995, #1492) * Document how to properly deal with exceptions in macros in order to not interfer with macro stopping/aborting (#1461) * Documentation on how to start Tango servers on fixed IP - ORBendPoint (#1470) From d3f76e65d6e4470865a2fbf86329497aa56fbb1b Mon Sep 17 00:00:00 2001 From: reszelaz Date: Mon, 1 Feb 2021 17:16:18 +0100 Subject: [PATCH 132/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88b5808054..1315493d49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Problems with macro id's when `sequencer` executes from _plain text_ files (#1215, #1216) * `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) * Allow running Spock without Qt bindings (#1462, #1463) +* Use equality instead of identity checks for numbers and strings (#1491) * Docstring of QtSpockWidget (#1484) * Recorders tests helpers (#1439) * Disable flake8 job in travis CI (#1455) From eaa77c46befed4bfdf00efb04b32ffb43dfea81d Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Mon, 1 Feb 2021 21:46:12 +0100 Subject: [PATCH 133/279] Update sardanacustomsettings.py change default value of PRE_POST_MOVE_HOOK_IN_MV from False to True --- src/sardana/sardanacustomsettings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sardana/sardanacustomsettings.py b/src/sardana/sardanacustomsettings.py index 9a2f7554bc..feb5d7a7cc 100644 --- a/src/sardana/sardanacustomsettings.py +++ b/src/sardana/sardanacustomsettings.py @@ -109,4 +109,4 @@ #: only in scan-based macros #: - True - macros which are hooked to the pre-move and post-move #: hook places are called before and/or after any move a motor -PRE_POST_MOVE_HOOK_IN_MV = False +PRE_POST_MOVE_HOOK_IN_MV = True From d08c9747b9ec068c79b319a292ff8e7dbada0760 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Mon, 1 Feb 2021 22:28:26 +0100 Subject: [PATCH 134/279] Update sardanacustomsettings.py udpate comment of PRE_POST_MOVE_HOOK_IN_MV for True as default value --- src/sardana/sardanacustomsettings.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sardana/sardanacustomsettings.py b/src/sardana/sardanacustomsettings.py index feb5d7a7cc..feda0f2e03 100644 --- a/src/sardana/sardanacustomsettings.py +++ b/src/sardana/sardanacustomsettings.py @@ -104,9 +104,9 @@ #: pre-move and post-move hooks applied in simple mv-based macros #: Available options: #: -#: - False (or no setting) - macros which are hooked to the pre-move and -#: post-move hook places are not called in simple mv-based macros but -#: only in scan-based macros -#: - True - macros which are hooked to the pre-move and post-move -#: hook places are called before and/or after any move a motor +#: - True (or no setting) - macros which are hooked to the pre-move and +#: post-move hook places are called before and/or after any move of a motor +#: - False - macros which are hooked to the pre-move and post-move hook +#: places are not called in simple mv-based macros but only in scan-based +#: macros PRE_POST_MOVE_HOOK_IN_MV = True From 99db4283e0c0ffc6decbe4c9265b80987f8294ac Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Mon, 1 Feb 2021 22:30:23 +0100 Subject: [PATCH 135/279] update mv macro add default value True to PRE_POST_MOVE_HOOK_IN_MV sardanacustomsettings value --- src/sardana/macroserver/macros/standard.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index f5934963b4..daabe536ca 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -473,7 +473,8 @@ class mv(Macro, Hookable): def run(self, motor_pos_list): enable_hooks = getattr(sardanacustomsettings, - 'PRE_POST_MOVE_HOOK_IN_MV') + 'PRE_POST_MOVE_HOOK_IN_MV', + True) if enable_hooks: for preAcqHook in self.getHooks('pre-move'): From 0dbf9528579e731762050afc8fc8dd14274241ef Mon Sep 17 00:00:00 2001 From: reszelaz Date: Tue, 2 Feb 2021 09:12:46 +0100 Subject: [PATCH 136/279] Rename variables --- src/sardana/macroserver/macros/standard.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index daabe536ca..46ffc32f9b 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -477,8 +477,8 @@ def run(self, motor_pos_list): True) if enable_hooks: - for preAcqHook in self.getHooks('pre-move'): - preAcqHook() + for preMoveHook in self.getHooks('pre-move'): + preMoveHook() self.motors, positions = [], [] for m, p in motor_pos_list: @@ -495,8 +495,8 @@ def run(self, motor_pos_list): self.info("\n".join(msg)) if enable_hooks: - for postAcqHook in self.getHooks('post-move'): - postAcqHook() + for postMoveHook in self.getHooks('post-move'): + postMoveHook() class mstate(Macro): From 773a02dbe1b06805e2d654f8484fff9c336b1361 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 2 Feb 2021 22:54:24 +0100 Subject: [PATCH 137/279] Add Referable interface to API docs --- doc/source/devel/api/sardana/pool/controller.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/source/devel/api/sardana/pool/controller.rst b/doc/source/devel/api/sardana/pool/controller.rst index 9377423f70..cef9637a52 100644 --- a/doc/source/devel/api/sardana/pool/controller.rst +++ b/doc/source/devel/api/sardana/pool/controller.rst @@ -25,6 +25,7 @@ :columns: 3 * :class:`Readable` + * :class:`Referable` * :class:`Startable` * :class:`Stopable` * :class:`Loadable` @@ -59,6 +60,17 @@ Readable interface :members: :undoc-members: +Referable interface +------------------- + +.. inheritance-diagram:: Referable + :parts: 1 + +.. autoclass:: Referable + :show-inheritance: + :members: + :undoc-members: + Startable interface ------------------- From 287ce150f3cc6e92e7ee5a2a33942dbf5cacea45 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 2 Feb 2021 22:55:13 +0100 Subject: [PATCH 138/279] Add links in how-to counter/timer controller guide --- .../howto_countertimercontroller.rst | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst index 2f620616d4..ccd319b2af 100644 --- a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst @@ -1,6 +1,6 @@ .. currentmodule:: sardana.pool.controller -.. _sardana-countertimercontroller-howto-basics: +.. _sardana-countertimercontroller: ======================================= How to write a counter/timer controller @@ -13,6 +13,8 @@ controller in Sardana. :depth: 3 :backlinks: entry +.. _sardana-countertimercontroller-howto-basics: + The basics ---------- @@ -217,6 +219,7 @@ Here is an example of the possible implementation of def AbortOne(self, axis): self.springfield.AbortChannel(axis) +.. _sardana-countertimercontroller-howto-advanced: Advanced topics --------------- @@ -239,8 +242,6 @@ parameter ``acquisition_mode``. Controller may announce its default timer axis with the :obj:`~sardana.pool.controller.Loadable.default_timer` class attribute. -.. _sardana-countertimercontroller-howto-advanced: - .. _sardana-countertimercontroller-howto-timestamp-value: Timestamp a counter value @@ -410,6 +411,8 @@ We can modify our counter controller to take profit of this hardware feature: def StartAll(self): self.springfield.startCounters(self._counters_info) +.. _sardana-countertimercontroller-howto-external-synchronization: + External (hardware) synchronization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -537,6 +540,8 @@ of the :meth:`~sardana.pool.controller.Loadable.LoadOne` method: self.springfield.SetLatency(latency) return value +.. _sardana-countertimercontroller-howto-external-synchronization-get-values: + Get counter values """""""""""""""""" @@ -568,6 +573,8 @@ can be: Sardana assumes that the counter values are returned in the order of acquisition and that there are no gaps in between them. +.. _sardana-countertimercontroller-per-measurement-preparation: + Per measurement preparation ~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 48305151ebfe3dea57bd2e24d7158bf608302af8 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 2 Feb 2021 22:55:40 +0100 Subject: [PATCH 139/279] Document how-to 2D controller guide --- .../howto_controllers/howto_2dcontroller.rst | 188 +++++++++++++++++- 1 file changed, 183 insertions(+), 5 deletions(-) diff --git a/doc/source/devel/howto_controllers/howto_2dcontroller.rst b/doc/source/devel/howto_controllers/howto_2dcontroller.rst index bc73fceb67..daec570c5c 100644 --- a/doc/source/devel/howto_controllers/howto_2dcontroller.rst +++ b/doc/source/devel/howto_controllers/howto_2dcontroller.rst @@ -1,16 +1,192 @@ .. currentmodule:: sardana.pool.controller -.. _sardana-2dcontroller-howto-basics: +.. _sardana-2dcontroller-howto: ============================ How to write a 2D controller ============================ -The basics ----------- +This chapter provides the necessary information to write a two dimensional (2D) +experimental channel controller in Sardana. + +.. contents:: Table of contents + :depth: 3 + :backlinks: entry + +.. _sardana-2dcontroller-general-guide: + +General guide +------------- + +:ref:`2D experimental channels ` +together with :ref:`1D experimental channels ` +and :ref:`counter/timers ` +belong to the same family of *timerable* experimental channels. + +To write a 2D controller class you can follow +the :ref:`sardana-countertimercontroller` guide keeping in mind +differences explained in continuation. + +.. _sardana-2dcontroller-differences-countertimer: + +Differences with counter/timer controller +----------------------------------------- + +Class definition +~~~~~~~~~~~~~~~~ + +:ref:`The basics of the counter/timer controller ` +chapter explains how to define the counter/timer controller class. +Here you need to simply inherit from the +`~sardana.pool.controller.TwoDController` class: + +.. code-block:: python + :emphasize-lines: 3 + + from sardana.pool.controller import TwoDController + + class SpringfieldTwoDController(TwoDController): + + def __init__(self, inst, props, *args, **kwargs): + super().__init__(inst, props, *args, **kwargs) + +.. _sardana-2dcontroller-getvalue: + +Get 2D value +~~~~~~~~~~~~ + +:ref:`Get counter value ` chapter +explains how to read a counter/timer value +using the :meth:`~sardana.pool.controller.Readable.ReadOne` method. +Here you need to implement the same method but its return value +must be a two-dimensional `numpy.array` (or eventually +a `~sardana.sardanavalue.SardanaValue` object) containing an image instead +of a scalar value. + +.. _sardana-2dcontroller-getvalues: + +Get 2D values +~~~~~~~~~~~~~ + +:ref:`Get counter values ` +chapter explains how to read counter/timer values +using the :meth:`~sardana.pool.controller.Readable.ReadOne` method while +acquiring with external (hardware) synchronization. +Here you need to implement the same method but its return value +must be a sequence with two-dimensional `numpy.array` objects (or eventually +with :obj:`~sardana.sardanavalue.SardanaValue` objects) containing the images +instead of a scalar values. + +Advanced topics +--------------- + +.. _sardana-2dcontroller-valuereferencing: + +Working with value referencing +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +2D experimental channels may produce big arrays of data at high +frame rate. Reading this data and storing it using sardana +is not always optimal. `SEP2`_ introduced data saving duality, optionally, +leaving the data storage at the responsibility of the detector +(or an intermediate software layer e.g. `LImA`_). In this case sardana +just deals with the reference to the data. + +In order to announce the referencing capability the 2D controller must +additionally inherit from the `~sardana.pool.controller.Referable` class: + +.. code-block:: python + :emphasize-lines: 3 + + from sardana.pool.controller import TwoDController, Referable + + class SpringfieldTwoDController(TwoDController, Referable): + + def __init__(self, inst, props, *args, **kwargs): + super().__init__(inst, props, *args, **kwargs) + +.. _sardana-2dcontroller-getvaluereference: + +Get 2D value reference +"""""""""""""""""""""" + +To get the 2D value reference, sardana calls the +:meth:`~sardana.pool.controller.Referable.RefOne` method. This method +receives an axis as parameter and should return a URI (`str`) +pointing to the value. + +Here is an example of the possible implementation of +:meth:`~sardana.pool.controller.Referable.RefOne`: + +.. code-block:: python + :emphasize-lines: 3 + + class SpringfieldTwoDController(TwoDController): + + def RefOne(self, axis): + value_ref = self.springfield.getValueRef(axis) + return value_ref + +.. _sardana-2dcontroller-getvaluesreferences: + +Get 2D values references +"""""""""""""""""""""""" + +:ref:`Get counter values ` +chapter explains how to read counter/timer values +using the :meth:`~sardana.pool.controller.Readable.ReadOne` method while +acquiring with external (hardware) synchronization. +Here you need to implement the :meth:`~sardana.pool.controller.Referable.RefOne` +method and its return value must be a sequence with URIs (`str`) +pointing to the values. + +.. _sardana-2dcontroller-configvaluereference: + +Configure 2D value reference +"""""""""""""""""""""""""""" + +Two axis parameters: ``value_ref_pattern`` (`str`) +and ``value_ref_enabled`` (`bool`) are foreseen for configuring where to store +the values and whether to use the value referencing. Here you need to implement +the :meth:`~sardana.pool.controller.Controller.SetAxiPar` method. + +Here is an example of the possible implementation of +:meth:`~sardana.pool.controller.Controller.SetAxisPar`: + +.. code-block:: python + + class SpringfieldTwoDController(TwoDController): + + def SetAxisPar(self, axis, par, value): + if par == "value_ref_pattern": + self.springfield.setValueRefPattern(axis, value) + elif par == "value_ref_enabled": + self.springfield.setValueRefEnabled(axis, value) + +.. hint:: + Use `Python Format String Syntax `_ + e.g. ``file:///tmp/sample1_{index:02d}`` to configure a dynamic value + referencing using the acquisition index or any other parameter + (acquisition index can be reset in the + :ref:`per measurement preparation `. + phase) + +When value referencing is used +"""""""""""""""""""""""""""""" + +Sardana will :ref:`sardana-2dcontroller-getvaluereference` when: + + - channel has referencing capability and it is enabled + +Sardana will :ref:`sardana-2dcontroller-getvalue` when any of these +conditions applies: + + - channel does not have referencing capability + - channel has referencing capability but it is disabled + - there is a pseudo counter based on this channel + +Hence, in some configurations, both methods may be used simultaneously. -.. todo:: document 2D controller howto - .. _ALBA: http://www.cells.es/ .. _ANKA: http://http://ankaweb.fzk.de/ .. _ELETTRA: http://http://www.elettra.trieste.it/ @@ -35,3 +211,5 @@ The basics .. _numpy: http://numpy.scipy.org/ .. _SPEC: http://www.certif.com/ .. _EPICS: http://www.aps.anl.gov/epics/ +.. _SEP2: http://www.sardana-controls.org/sep/?SEP2.md +.. _LImA: https://lima1.readthedocs.io/en/latest/ From 07204ff1d8b50900af4e57e91f82881db47a55e1 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 2 Feb 2021 22:55:49 +0100 Subject: [PATCH 140/279] Document how-to 1D controller guide --- .../howto_controllers/howto_1dcontroller.rst | 94 ++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/doc/source/devel/howto_controllers/howto_1dcontroller.rst b/doc/source/devel/howto_controllers/howto_1dcontroller.rst index 634654e76f..3ece6b1888 100644 --- a/doc/source/devel/howto_controllers/howto_1dcontroller.rst +++ b/doc/source/devel/howto_controllers/howto_1dcontroller.rst @@ -9,8 +9,96 @@ How to write a 1D controller The basics ---------- -.. todo:: document 1D controller howto - +This chapter provides the necessary information to write a one dimensional (1D) +experimental channel controller in Sardana. + +.. contents:: Table of contents + :depth: 3 + :backlinks: entry + +.. _sardana-1dcontroller-general-guide: + +General guide +------------- + +:ref:`1D experimental channels ` +together with :ref:`2D experimental channels ` +and :ref:`counter/timers ` +belong to the same family of *timerable* experimental channels. + +To write a 1D controller class you can follow +the :ref:`sardana-countertimercontroller` guide keeping in mind +differences explained in continuation. + +.. _sardana-1dcontroller-differences-countertimer: + +Differences with counter/timer controller +----------------------------------------- + +Class definition +~~~~~~~~~~~~~~~~ + +:ref:`The basics of the counter/timer controller ` +chapter explains how to define the counter/timer controller class. +Here you need to simply inherit from the +`~sardana.pool.controller.OneDController` class: + +.. code-block:: python + :emphasize-lines: 3 + + from sardana.pool.controller import OneDController + + class SpringfieldOneDController(OneDController): + + def __init__(self, inst, props, *args, **kwargs): + super().__init__(inst, props, *args, **kwargs) + +.. _sardana-1dcontroller-getvalue: + +Get 1D value +~~~~~~~~~~~~ + +:ref:`Get counter value ` chapter +explains how to read a counter/timer value +using the :meth:`~sardana.pool.controller.Readable.ReadOne` method. +Here you need to implement the same method but its return value +must be a one-dimensional `numpy.array` (or eventually +a `~sardana.sardanavalue.SardanaValue` object) containing the spectrum instead +of a scalar value. + +.. _sardana-1dcontroller-getvalues: + +Get 1D values +~~~~~~~~~~~~~ + +:ref:`Get counter values ` +chapter explains how to read counter/timer values +using the :meth:`~sardana.pool.controller.Readable.ReadOne` method while +acquiring with external (hardware) synchronization. +Here you need to implement the same method but its return value +must be a sequence with one-dimensional `numpy.array` objects (or eventually +with :obj:`~sardana.sardanavalue.SardanaValue` objects) containing spectrums +instead of scalar values. + +Advanced topics +--------------- + +.. _sardana-1dcontroller-valuereferencing: + +Working with value referencing +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1D experimental channels may produce significant arrays of data at high +frame rate. Reading this data and storing it using sardana +is not always optimal. `SEP2`_ introduced data saving duality, optionally, +leaving the data storage at the responsibility of the detector +(or an intermediate software layer e.g. `LImA`_). In this case sardana +just deals with the reference to the data. + +Please refer to :ref:`sardana-2dcontroller-valuereferencing` chapter from +:ref:`sardana-2dcontroller-howto` in order to implement this feature for 1D +controller. + .. _ALBA: http://www.cells.es/ .. _ANKA: http://http://ankaweb.fzk.de/ .. _ELETTRA: http://http://www.elettra.trieste.it/ @@ -35,3 +123,5 @@ The basics .. _numpy: http://numpy.scipy.org/ .. _SPEC: http://www.certif.com/ .. _EPICS: http://www.aps.anl.gov/epics/ +.. _SEP2: http://www.sardana-controls.org/sep/?SEP2.md +.. _LImA: https://lima1.readthedocs.io/en/latest/ From 26b3e1db98ce0280e612b5908478147b7050dd5c Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 3 Feb 2021 22:55:06 +0100 Subject: [PATCH 141/279] Rename ROI to RoI --- doc/source/glossary.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/glossary.rst b/doc/source/glossary.rst index 3e34bf868b..186604e130 100644 --- a/doc/source/glossary.rst +++ b/doc/source/glossary.rst @@ -430,8 +430,8 @@ Glossary dial See :term:`dial position` - ROI - *Region of interest* are samples within a data set identified for a + RoI + *Region of Interest* are samples within a data set identified for a particular purpose. .. _plug-in: http://en.wikipedia.org/wiki/Plug-in_(computing) From ff0f2eca4f73672db7ccc8fb9f9fcde6cf42fe99 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 3 Feb 2021 22:55:44 +0100 Subject: [PATCH 142/279] Add how-to get shape of 2D --- .../howto_controllers/howto_2dcontroller.rst | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/doc/source/devel/howto_controllers/howto_2dcontroller.rst b/doc/source/devel/howto_controllers/howto_2dcontroller.rst index daec570c5c..7e52c94283 100644 --- a/doc/source/devel/howto_controllers/howto_2dcontroller.rst +++ b/doc/source/devel/howto_controllers/howto_2dcontroller.rst @@ -27,6 +27,31 @@ To write a 2D controller class you can follow the :ref:`sardana-countertimercontroller` guide keeping in mind differences explained in continuation. +.. _sardana-2dcontroller-general-guide-shape: + +Get 2D shape +~~~~~~~~~~~~ + +2D controller must provide a shape of the image which will be produced by +acquisition. The shape can be either static e.g. defined by the detector's +sensor size or dynamic e.g. depending on the detector's (or an intermediate +control software layer e.g. `LImA`_) configuration like :term:`RoI` or binning. + +In any case you must provide the shape in the format of a two-element sequence +with horizonatal and vertical dimensions using +the :meth:`~sardana.pool.controller.Controller.GetAxisPar` method. + +Here is an example of the possible implementation of +:meth:`~sardana.pool.controller.Controller.GetAxisPar`: + +.. code-block:: python + + class SpringfieldTwoDController(TwoDController): + + def GetAxisPar(self, axis, par): + if par == "shape": + return self.springfield.getShape(axis) + .. _sardana-2dcontroller-differences-countertimer: Differences with counter/timer controller From a4b43c754467ab632234419b57ab736d6c3c408e Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 3 Feb 2021 22:56:25 +0100 Subject: [PATCH 143/279] Add how-to get shape of 1D --- .../howto_controllers/howto_1dcontroller.rst | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/doc/source/devel/howto_controllers/howto_1dcontroller.rst b/doc/source/devel/howto_controllers/howto_1dcontroller.rst index 3ece6b1888..bbb4319a7e 100644 --- a/doc/source/devel/howto_controllers/howto_1dcontroller.rst +++ b/doc/source/devel/howto_controllers/howto_1dcontroller.rst @@ -30,6 +30,31 @@ To write a 1D controller class you can follow the :ref:`sardana-countertimercontroller` guide keeping in mind differences explained in continuation. +.. _sardana-1dcontroller-general-guide-shape: + +Get 1D shape +~~~~~~~~~~~~ + +1D controller must provide a shape of the spectrum which will be produced by +acquisition. The shape can be either static e.g. defined by the detector's +sensor size or dynamic e.g. depending on the detector's (or an intermediate +control software layer e.g. `LImA`_) configuration like :term:`RoI` or binning. + +In any case you must provide the shape in the format of a one-element sequence +with the length of the spectrum using +the :meth:`~sardana.pool.controller.Controller.GetAxisPar` method. + +Here is an example of the possible implementation of +:meth:`~sardana.pool.controller.Controller.GetAxisPar`: + +.. code-block:: python + + class SpringfieldOneDController(TwoDController): + + def GetAxisPar(self, axis, par): + if par == "shape": + return self.springfield.getShape(axis) + .. _sardana-1dcontroller-differences-countertimer: Differences with counter/timer controller From bbfaf7feec9bb49ed9655305de1d6ddd4c12fddf Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 3 Feb 2021 22:57:15 +0100 Subject: [PATCH 144/279] Rename shape to format in pseudo counter how-to --- .../howto_controllers/howto_pseudocountercontroller.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst b/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst index cf08f43da5..232bd0add9 100644 --- a/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst @@ -80,12 +80,12 @@ Pseudo counters instantiated from your controller will have a default interface, which among others, comprises the *value* attribute. This attribute is feed with the result of the :meth:`~sardana.pool.controller.PseudoCounterController.calc` method and by -default it expects values of ``float`` type and scalar shape. You can easily +default it expects values of ``float`` type and scalar format. You can easily :ref:`change the default interface `. -This way you could program a pseudo counter to obtain an image :term:`ROI` +This way you could program a pseudo counter to obtain an image :term:`RoI` of a :ref:`2D experimental channel `. -Here is an example of how to change *value* attribute's shape to an image +Here is an example of how to change *value* attribute's format to an image and specify its maximum dimension of 1024 x 1024 pixels: .. code-block:: python From 43e5cb52aac4d27216fb15d4ebe6c4d7d641f704 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 3 Feb 2021 22:57:35 +0100 Subject: [PATCH 145/279] Add how-to get shape for pseudo counter --- .../howto_pseudocountercontroller.rst | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst b/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst index 232bd0add9..cb690600a1 100644 --- a/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst @@ -97,6 +97,26 @@ and specify its maximum dimension of 1024 x 1024 pixels: axis_attrs['Value'][MaxDimSize] = (1024, 1024) return axis_attrs +Get pseudo counter shape +------------------------ + +If you change the pseudo counter format to spectrum or image then you +controller must provide the shape of the calculation result in either +of the formats: + +- one-element sequence with the length of the spectrum +- two-element sequence with the horizonatal and vertical dimensions of the image + +using the :meth:`~sardana.pool.controller.Controller.GetAxisPar` method. + +Here is an example of the possible implementation of +:meth:`~sardana.pool.controller.Controller.GetAxisPar`: + +.. code-block:: python + + def GetAxisPar(self, axis, par): + if par == "shape": + return [1024, 1024] Including external variables in the calculation ----------------------------------------------- From 169d47a5bd52f7107675d900fafef64b1ffacf8a Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 3 Feb 2021 22:57:47 +0100 Subject: [PATCH 146/279] Fix indentation --- .../devel/howto_controllers/howto_pseudocountercontroller.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst b/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst index cb690600a1..4a8c6b1b3d 100644 --- a/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst @@ -90,7 +90,7 @@ and specify its maximum dimension of 1024 x 1024 pixels: .. code-block:: python - def GetAxisAttributes(self, axis): + def GetAxisAttributes(self, axis): axis_attrs = PseudoCounterController.GetAxisAttributes(self, axis) axis_attrs = dict(axis_attrs) axis_attrs['Value'][Type] = ((float, ), ) From 31f475c0587502e8447a768c955f97a0f8ed3571 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 3 Feb 2021 22:58:35 +0100 Subject: [PATCH 147/279] Fix wrong reference o SetAxisPar --- doc/source/devel/howto_controllers/howto_2dcontroller.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/devel/howto_controllers/howto_2dcontroller.rst b/doc/source/devel/howto_controllers/howto_2dcontroller.rst index 7e52c94283..daf6d97cf1 100644 --- a/doc/source/devel/howto_controllers/howto_2dcontroller.rst +++ b/doc/source/devel/howto_controllers/howto_2dcontroller.rst @@ -173,7 +173,7 @@ Configure 2D value reference Two axis parameters: ``value_ref_pattern`` (`str`) and ``value_ref_enabled`` (`bool`) are foreseen for configuring where to store the values and whether to use the value referencing. Here you need to implement -the :meth:`~sardana.pool.controller.Controller.SetAxiPar` method. +the :meth:`~sardana.pool.controller.Controller.SetAxisPar` method. Here is an example of the possible implementation of :meth:`~sardana.pool.controller.Controller.SetAxisPar`: From f40114d41348d01796e87a906caa0163d396dd39 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 3 Feb 2021 22:58:59 +0100 Subject: [PATCH 148/279] Minor correction --- doc/source/devel/howto_controllers/howto_1dcontroller.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/devel/howto_controllers/howto_1dcontroller.rst b/doc/source/devel/howto_controllers/howto_1dcontroller.rst index bbb4319a7e..429ac416d6 100644 --- a/doc/source/devel/howto_controllers/howto_1dcontroller.rst +++ b/doc/source/devel/howto_controllers/howto_1dcontroller.rst @@ -121,7 +121,7 @@ leaving the data storage at the responsibility of the detector just deals with the reference to the data. Please refer to :ref:`sardana-2dcontroller-valuereferencing` chapter from -:ref:`sardana-2dcontroller-howto` in order to implement this feature for 1D +:ref:`sardana-2dcontroller-howto` guide in order to implement this feature for 1D controller. .. _ALBA: http://www.cells.es/ From 1157690d89d6701684d4269b796444e4a1f1d03f Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 4 Feb 2021 11:48:30 +0100 Subject: [PATCH 149/279] Fix typo --- doc/source/devel/howto_controllers/howto_2dcontroller.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/devel/howto_controllers/howto_2dcontroller.rst b/doc/source/devel/howto_controllers/howto_2dcontroller.rst index daf6d97cf1..e7510c19c2 100644 --- a/doc/source/devel/howto_controllers/howto_2dcontroller.rst +++ b/doc/source/devel/howto_controllers/howto_2dcontroller.rst @@ -38,7 +38,7 @@ sensor size or dynamic e.g. depending on the detector's (or an intermediate control software layer e.g. `LImA`_) configuration like :term:`RoI` or binning. In any case you must provide the shape in the format of a two-element sequence -with horizonatal and vertical dimensions using +with horizontal and vertical dimensions using the :meth:`~sardana.pool.controller.Controller.GetAxisPar` method. Here is an example of the possible implementation of From 15633e092d47f927725ee509e52800658519f4f5 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 4 Feb 2021 11:49:23 +0100 Subject: [PATCH 150/279] Fix typo --- .../devel/howto_controllers/howto_pseudocountercontroller.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst b/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst index 4a8c6b1b3d..49bbe2cc27 100644 --- a/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst @@ -105,7 +105,7 @@ controller must provide the shape of the calculation result in either of the formats: - one-element sequence with the length of the spectrum -- two-element sequence with the horizonatal and vertical dimensions of the image +- two-element sequence with the horizontal and vertical dimensions of the image using the :meth:`~sardana.pool.controller.Controller.GetAxisPar` method. From 77b1177dbc3e64079a8f69255131fadc0b5867d0 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 8 Feb 2021 22:59:04 +0100 Subject: [PATCH 151/279] Improve backwards compat. to auto-discover shape In case the controller parameter "shape" is not implemented the backwards compatibility layer tries to read the value attribute and determine the shape from the value. Adds another kind of fallback and try to get it from the max_dim_size of the value attribute. --- src/sardana/pool/poolbasechannel.py | 34 +++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/sardana/pool/poolbasechannel.py b/src/sardana/pool/poolbasechannel.py index 631d5955e6..34624a5f2c 100644 --- a/src/sardana/pool/poolbasechannel.py +++ b/src/sardana/pool/poolbasechannel.py @@ -650,6 +650,23 @@ def set_integration_time(self, integration_time, propagate=1): # shape # ------------------------------------------------------------------------- + def _get_shape_from_max_dim_size(self): + # MaxDimSize could actually become a standard fallback + # in case the shape controller parameters is not implemented + # reconsider it whenever backwards compatibility with reading the value + # is about to be removed + try: + from sardana.pool.controller import MaxDimSize + controller = self.controller + axis_attr_info = controller.get_axis_attributes(self.axis) + value_info = axis_attr_info["Value"] + shape = value_info[MaxDimSize] + except Exception as e: + raise RuntimeError( + "can not provide backwards compatibility, you must" + "implement shape axis parameter") from e + return shape + def get_shape(self, cache=True, propagate=1): if not cache or self._shape is None: shape = self.read_shape() @@ -677,13 +694,16 @@ def read_shape(self): "not implementing shape axis parameter in 1D and 2D " "controllers is deprecated since Jan21") value = self.value.value - import numpy - try: - shape = numpy.shape(value) - except Exception as e: - raise RuntimeError( - "can not provide backwards compatibility, you must" - "implement shape axis parameter") from e + if value is None: + self.debug("could not get shape from value") + shape = self._get_shape_from_max_dim_size() + else: + import numpy + try: + shape = numpy.shape(value) + except Exception: + self.debug("could not get shape from value") + shape = self._get_shape_from_max_dim_size() # scalar channel else: shape = [] From 27124fe667def79d60d9a78c2cb01541ba0b1b70 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 9 Feb 2021 11:34:05 +0100 Subject: [PATCH 152/279] Add note on backwards compatibility with MaxDimSize --- .../devel/howto_controllers/howto_1dcontroller.rst | 8 ++++++-- .../devel/howto_controllers/howto_2dcontroller.rst | 8 ++++++-- .../howto_controllers/howto_pseudocountercontroller.rst | 9 ++++++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/doc/source/devel/howto_controllers/howto_1dcontroller.rst b/doc/source/devel/howto_controllers/howto_1dcontroller.rst index 429ac416d6..90f5a341d5 100644 --- a/doc/source/devel/howto_controllers/howto_1dcontroller.rst +++ b/doc/source/devel/howto_controllers/howto_1dcontroller.rst @@ -35,12 +35,12 @@ differences explained in continuation. Get 1D shape ~~~~~~~~~~~~ -1D controller must provide a shape of the spectrum which will be produced by +1D controller should provide a shape of the spectrum which will be produced by acquisition. The shape can be either static e.g. defined by the detector's sensor size or dynamic e.g. depending on the detector's (or an intermediate control software layer e.g. `LImA`_) configuration like :term:`RoI` or binning. -In any case you must provide the shape in the format of a one-element sequence +In any case you should provide the shape in the format of a one-element sequence with the length of the spectrum using the :meth:`~sardana.pool.controller.Controller.GetAxisPar` method. @@ -55,6 +55,10 @@ Here is an example of the possible implementation of if par == "shape": return self.springfield.getShape(axis) +For backwards compatibility, in case of not implementing the ``shape`` axis +parameter, shape will be determined from the ``MaxDimSize`` of the ``Value`` +attribute, currently (4096,). + .. _sardana-1dcontroller-differences-countertimer: Differences with counter/timer controller diff --git a/doc/source/devel/howto_controllers/howto_2dcontroller.rst b/doc/source/devel/howto_controllers/howto_2dcontroller.rst index e7510c19c2..fa6d42d80b 100644 --- a/doc/source/devel/howto_controllers/howto_2dcontroller.rst +++ b/doc/source/devel/howto_controllers/howto_2dcontroller.rst @@ -32,12 +32,12 @@ differences explained in continuation. Get 2D shape ~~~~~~~~~~~~ -2D controller must provide a shape of the image which will be produced by +2D controller should provide a shape of the image which will be produced by acquisition. The shape can be either static e.g. defined by the detector's sensor size or dynamic e.g. depending on the detector's (or an intermediate control software layer e.g. `LImA`_) configuration like :term:`RoI` or binning. -In any case you must provide the shape in the format of a two-element sequence +In any case you should provide the shape in the format of a two-element sequence with horizontal and vertical dimensions using the :meth:`~sardana.pool.controller.Controller.GetAxisPar` method. @@ -52,6 +52,10 @@ Here is an example of the possible implementation of if par == "shape": return self.springfield.getShape(axis) +For backwards compatibility, in case of not implementing the ``shape`` axis +parameter, shape will be determined frm the ``MaxDimSize`` of the ``Value`` +attribute, currently (4096, 4096). + .. _sardana-2dcontroller-differences-countertimer: Differences with counter/timer controller diff --git a/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst b/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst index 49bbe2cc27..1a55a4b862 100644 --- a/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_pseudocountercontroller.rst @@ -73,6 +73,8 @@ negative. The value close to the zero indicates the beam centered in the middle. Similarly behaves the horizontal pseudo counter. The total pseudo counter is the mean value of all the four sensors and indicates the beam intensity. +.. _sardana-pseudocountercontroller-howto-changing-interface: + Changing default interface -------------------------- @@ -101,7 +103,7 @@ Get pseudo counter shape ------------------------ If you change the pseudo counter format to spectrum or image then you -controller must provide the shape of the calculation result in either +controller should provide the shape of the calculation result in either of the formats: - one-element sequence with the length of the spectrum @@ -118,6 +120,11 @@ Here is an example of the possible implementation of if par == "shape": return [1024, 1024] + +For backwards compatibility, in case of not implementing the ``shape`` axis +parameter, shape will be determined from the ``MaxDimSize`` of the ``Value`` +attribute as defined in :ref:`sardana-pseudocountercontroller-howto-changing-interface`. + Including external variables in the calculation ----------------------------------------------- From fc64b51b49ecdac98e9739209b56b34c6a4db4f7 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Tue, 9 Feb 2021 16:19:38 +0100 Subject: [PATCH 153/279] Update CHANGELOG.md --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1315493d49..8b2789011b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ This file follows the formats and conventions from [keepachangelog.com] ### Added +* `shape` controller axis parameter (plugin), `shape` experimental channel + attribute (kernel) and `Shape` Tango attribute to the experimental channels + (#1296, #1466) * *scan information* and *scan point* forms to the *showscan online* widget (#1386, #1477, #1479) * `ScanPlotWidget`, `ScanPlotWindow`, `ScanInfoForm`, `ScanPointForm` and `ScanWindow` widget classes for easier composition of custom GUIs involving online scan plotting (#1386) @@ -18,6 +21,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Initial delay in position domain to the synchronization description in *ct* like continuous scans (#1428) * Avoid double printing of user units in PMTV: read widget and units widget (#1424) +* Documentation on how to write 1D and 2D controllers (#1494) * Missing documentation of SEP18 concepts to how-to counter/timer controller (#995, #1492) * Document how to properly deal with exceptions in macros in order to not interfer with macro stopping/aborting (#1461) @@ -46,6 +50,15 @@ This file follows the formats and conventions from [keepachangelog.com] * Make write of MeasurementGroup (Taurus extension) integration time more robust (#1473) * String formatting when rising exceptions in pseudomotors (#1469) +### Changed + +* Experimental channel shape is now considered as a result of the configuration + and not part of the measurement group configuration (#1296, #1466) + +### Removed + +* `shape` from the measurement group configuration and `expconf` (#1296, #1466) + ## [3.0.3] 2020-09-18 ### Added From 98d351148424a31a74baf777d2fd15ff32a7c7f7 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 10 Feb 2021 22:05:21 +0100 Subject: [PATCH 154/279] Fill parent_macro in case of executing XML hooks (sequencer) XML hooks, usually composed by the sequencer, as other hooks (programmatic or general), needs information about the parent macro. Fill it when preparing the macro object. See #1472 for more details. --- src/sardana/macroserver/msmacromanager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sardana/macroserver/msmacromanager.py b/src/sardana/macroserver/msmacromanager.py index 7b95b8fd7c..43cc00d04c 100644 --- a/src/sardana/macroserver/msmacromanager.py +++ b/src/sardana/macroserver/msmacromanager.py @@ -1613,8 +1613,9 @@ def __runStatelessXML(self, xml=None): def __runXMLMacro(self, xml): assert xml.tag == 'macro' + parent_macro = self.getRunningMacro() try: - macro_obj, _ = self._prepareXMLMacro(xml) + macro_obj, _ = self._prepareXMLMacro(xml, parent_macro) except AbortException as ae: raise ae except Exception as e: From f5edb4f47ca86e56fe80d9db946e2a3569f0248b Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 11 Feb 2021 11:25:08 +0100 Subject: [PATCH 155/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b2789011b..a64ae85da6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ This file follows the formats and conventions from [keepachangelog.com] e.g. pseudo counter's value, in controllers (#1440, #1446) * Storing string values in PreScanSnapshot in NXscanH5_FileRecorder (#1486) * Storing string values as custom data in NXscanH5_FileRecorder (#1485) +* Fill parent_macro in case of executing XML hooks e.g. in sequencer (#1497) * Problems with macro id's when `sequencer` executes from _plain text_ files (#1215, #1216) * `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) * Allow running Spock without Qt bindings (#1462, #1463) From 851307e4920d68ee2c5f90610be1b12dcad6bd3b Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 17 Feb 2021 12:30:43 +0100 Subject: [PATCH 156/279] Update link to Sardana plugins catalogue --- doc/source/users/adding_elements.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/source/users/adding_elements.rst b/doc/source/users/adding_elements.rst index 198d42732b..21353ee9f4 100644 --- a/doc/source/users/adding_elements.rst +++ b/doc/source/users/adding_elements.rst @@ -7,7 +7,8 @@ Adding real elements For the sake of demonstration, let's suppose you want to integrate a slit with IcePAP-based motors into Sardana. -Before doing anything else you should check the `controller plugin register `_. +Before doing anything else you should check the +`Sardana plugins catalogue `_. There's a high chance that somebody already wrote the plugin for your hardware. If not, you can :ref:`write the plugin yourself `. From 8f84ea4c8b03782a4b101639833c048ffb33f18c Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 19 Feb 2021 14:42:31 +0100 Subject: [PATCH 157/279] Ensure order of moveables is preserved in Motion object Move of motion object sends motors to wrong positions. This happens when using moveables from different Pools and is not 100 % reproducible. This is due to the fact that we rely on simple dictionaries (their order is not preserved in Python 3.5) when constructing the Motion object from the list of moveables requested by the user. Further move requests on that object assume that the positions correspond to the order of moveables requested at the Motion object creation time. Currently the order may be lost. Use OrderedDict instead to avoid such problems. --- src/sardana/taurus/core/tango/sardana/motion.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sardana/taurus/core/tango/sardana/motion.py b/src/sardana/taurus/core/tango/sardana/motion.py index ace214903e..cc28ae9492 100644 --- a/src/sardana/taurus/core/tango/sardana/motion.py +++ b/src/sardana/taurus/core/tango/sardana/motion.py @@ -30,6 +30,7 @@ __docformat__ = 'restructuredtext' import time +from collections import OrderedDict from taurus.core.util.containers import CaselessDict @@ -270,7 +271,7 @@ def init_by_names(self, names, moveable_srcs, allow_repeat, allow_unknown): allow_repeat=allow_repeat, allow_unknown=allow_unknown) # map - ms_moveables = {} + ms_moveables = OrderedDict() for moveable_source, ms_names in list(ms_elem_names.items()): moveable = moveable_source.getMoveable(ms_names) ms_moveables[moveable_source] = moveable @@ -330,7 +331,7 @@ def getElemNamesByMoveableSource(self, names, moveable_sources, belong to the that motion source. """ - ms_elems = {} + ms_elems = OrderedDict() for name in names: moveable = None From 6e09a749e271a50f590da208462f1da6db066780 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 19 Feb 2021 18:49:25 +0100 Subject: [PATCH 158/279] Fix getting macroserver from remote door _get_macroserver_for_door() is assuming that the macroserver belongs to the default Tango database. In case the door is a remote door (on different Tango database) the macroserver is never found. Fix it by interrogating the Tango database of the door and by creating a macroserver device with full name. --- src/sardana/taurus/core/tango/sardana/macroserver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sardana/taurus/core/tango/sardana/macroserver.py b/src/sardana/taurus/core/tango/sardana/macroserver.py index 4518b88d60..783d558b5c 100644 --- a/src/sardana/taurus/core/tango/sardana/macroserver.py +++ b/src/sardana/taurus/core/tango/sardana/macroserver.py @@ -424,11 +424,10 @@ def macro_server(self): def _get_macroserver_for_door(self): """Returns the MacroServer device object in the same DeviceServer as this door""" - db = self.factory().getDatabase() + db = self.getParentObj() 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] @@ -436,7 +435,8 @@ def _get_macroserver_for_door(self): if dev.lower() == door_name: for i, klass in enumerate(klasses): if klass == 'MacroServer': - return self.factory().getDevice(devs[i]) + full_name = db.getFullName() + "/" + devs[i] + return self.factory().getDevice(full_name) else: return None From 3e749f9a50ee21ca479e55828f370e385f42d702 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 22 Feb 2021 17:09:49 +0100 Subject: [PATCH 159/279] Assert motor sign is -1 or 1 Setting motor sign to 0 may lead to wrong position calculation. Assert motor sign is -1 or 1. Fix #1345. --- src/sardana/pool/poolmotor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sardana/pool/poolmotor.py b/src/sardana/pool/poolmotor.py index da281c21d3..bba40d11bc 100644 --- a/src/sardana/pool/poolmotor.py +++ b/src/sardana/pool/poolmotor.py @@ -417,6 +417,8 @@ def get_sign(self, cache=True): return self._sign def set_sign(self, sign, propagate=1): + assert sign in (-1, 1), \ + "sign must be either -1 or 1 (not {})".format(sign) old_sign = self._sign.value self._sign.set_value(sign, propagate=propagate) # invert lower with upper limit switches and send event in case of From 98bd4565b7e1422844e25823f21f033f0497db73 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 22 Feb 2021 22:10:00 +0100 Subject: [PATCH 160/279] Document sardanacustomsettings.LOG_BCK_COUNT --- src/sardana/sardanacustomsettings.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/sardana/sardanacustomsettings.py b/src/sardana/sardanacustomsettings.py index 97c4859628..b0e75f9d72 100644 --- a/src/sardana/sardanacustomsettings.py +++ b/src/sardana/sardanacustomsettings.py @@ -37,10 +37,13 @@ #: UnitTests Pool Device name: Pool Device to use in unit tests. UNITTEST_POOL_NAME = "pool/demo1/1" -#: Size and number of rotating backups of the log files. -#: The Pool and MacroServer Device servers will use these values for their -#: logs. +#: Size of rotating backups of the log files. +#: The Pool, MacroServer and Sardana device servers will use these values +#: for their logs. LOG_FILES_SIZE = 1e7 +#: Number of rotating backups of the log files. +#: The Pool, MacroServer and Sardana device servers will use these values +#: for their logs. LOG_BCK_COUNT = 5 #: Input handler for spock interactive macros. Accepted values are: From b07d73016472869dc4b021ab76dd6d6e21765e8f Mon Sep 17 00:00:00 2001 From: reszelaz Date: Wed, 24 Feb 2021 15:12:56 +0100 Subject: [PATCH 161/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a64ae85da6..c700b6e78f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Initial delay in position domain to the synchronization description in *ct* like continuous scans (#1428) * Avoid double printing of user units in PMTV: read widget and units widget (#1424) +* Assert motor sign is -1 or 1 (#1345, #1507) * Documentation on how to write 1D and 2D controllers (#1494) * Missing documentation of SEP18 concepts to how-to counter/timer controller (#995, #1492) * Document how to properly deal with exceptions in macros in order to not interfer From 77b7302cedb1417c5ea864d3190d4f65eea19c8e Mon Sep 17 00:00:00 2001 From: reszelaz Date: Wed, 24 Feb 2021 15:16:05 +0100 Subject: [PATCH 162/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c700b6e78f..cb59c04153 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Problems with macro id's when `sequencer` executes from _plain text_ files (#1215, #1216) * `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) * Allow running Spock without Qt bindings (#1462, #1463) +* Fix getting macroserver from remote door in Sardana-Taurus Door extension (#1506) * Use equality instead of identity checks for numbers and strings (#1491) * Docstring of QtSpockWidget (#1484) * Recorders tests helpers (#1439) From bc6f55179d4c45f854b8f702ebdca546baab62da Mon Sep 17 00:00:00 2001 From: reszelaz Date: Wed, 24 Feb 2021 15:27:00 +0100 Subject: [PATCH 163/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb59c04153..8bf8ada24b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Execute per measurement preparation in `mesh` scan macro (#1437) * Continously read value references in hardware synchronized acquisition instead of reading only at the end (#1442, #1448) +* Ensure order of moveables is preserved in Motion object (#1505) * Avoid problems when defining different, e.g. shape, standard attributes, e.g. pseudo counter's value, in controllers (#1440, #1446) * Storing string values in PreScanSnapshot in NXscanH5_FileRecorder (#1486) From 41f3896a3052a728122e370b97e432d1d4659e94 Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Wed, 24 Feb 2021 19:29:52 +0100 Subject: [PATCH 164/279] hdf5: Fix create virtual dataset source with default name --- src/sardana/macroserver/recorders/h5storage.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sardana/macroserver/recorders/h5storage.py b/src/sardana/macroserver/recorders/h5storage.py index 35fb062310..1913a8acf3 100644 --- a/src/sardana/macroserver/recorders/h5storage.py +++ b/src/sardana/macroserver/recorders/h5storage.py @@ -397,7 +397,9 @@ def _endRecordList(self, recordlist): continue uri_groups = group.groupdict() filename = uri_groups["filepath"] - dataset = uri_groups.get("dataset", "dataset") + dataset = uri_groups["dataset"] + if dataset is None: + dataset = "dataset" vsource = h5py.VirtualSource(filename, dataset, shape=(dim_1, dim_2)) layout[i] = vsource From a50a7c1e41557a33c52921beb97d032268cac060 Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Wed, 24 Feb 2021 19:30:49 +0100 Subject: [PATCH 165/279] hdf5: Some versions of h5py (ex: 3.1) get label as bytes instead of str --- src/sardana/macroserver/recorders/h5storage.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/sardana/macroserver/recorders/h5storage.py b/src/sardana/macroserver/recorders/h5storage.py index 1913a8acf3..5fde90302b 100644 --- a/src/sardana/macroserver/recorders/h5storage.py +++ b/src/sardana/macroserver/recorders/h5storage.py @@ -363,7 +363,9 @@ def _endRecordList(self, recordlist): if dd.value_ref_enabled: measurement = nxentry['measurement'] first_reference = measurement[label][0] - + # in some versions of h5py we get bytes + if isinstance(first_reference, bytes): + first_reference = first_reference.decode() group = re.match(self.pattern, first_reference) if group is None: msg = 'Unsupported reference %s' % first_reference @@ -390,6 +392,9 @@ def _endRecordList(self, recordlist): for i in range(nb_points): reference = measurement[label][i] + # in some versions of h5py we get bytes + if isinstance(reference, bytes): + reference = reference.decode() group = re.match(self.pattern, reference) if group is None: msg = 'Unsupported reference %s' % first_reference From b70c4b2d2af1db32e2d4fc7585d88a6be0ec41e3 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Tue, 2 Mar 2021 10:25:59 +0100 Subject: [PATCH 166/279] fill self.motors before pre-scan hooks --- src/sardana/macroserver/macros/standard.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index 46ffc32f9b..248320fdcd 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -472,6 +472,11 @@ class mv(Macro, Hookable): ] def run(self, motor_pos_list): + self.motors, positions = [], [] + for m, p in motor_pos_list: + self.motors.append(m) + positions.append(p) + enable_hooks = getattr(sardanacustomsettings, 'PRE_POST_MOVE_HOOK_IN_MV', True) @@ -480,11 +485,9 @@ def run(self, motor_pos_list): for preMoveHook in self.getHooks('pre-move'): preMoveHook() - self.motors, positions = [], [] - for m, p in motor_pos_list: - self.motors.append(m) - positions.append(p) + for m, p in zip(self.motors, positions): self.debug("Starting %s movement to %s", m.getName(), p) + motion = self.getMotion(self.motors) state, pos = motion.move(positions) if state != DevState.ON: From 3e87958d89e41823f7b78a55de7310fd71fb3bc4 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 8 Mar 2021 10:50:14 +0100 Subject: [PATCH 167/279] Read value in serial mode Value is read in a worker thread, however the read synchronizes with the worker thread anyway in order to return the read value. Read it in serial mode to avoid extra complexity of involving a thread. It is also done the same way for the motor's dial position readout. --- src/sardana/pool/poolbasechannel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sardana/pool/poolbasechannel.py b/src/sardana/pool/poolbasechannel.py index 34624a5f2c..0638805104 100644 --- a/src/sardana/pool/poolbasechannel.py +++ b/src/sardana/pool/poolbasechannel.py @@ -241,7 +241,7 @@ def read_value(self): value :rtype: :class:`~sardana.sardanavalue.SardanaValue`""" - return self.acquisition.read_value()[self] + return self.acquisition.read_value(serial=True)[self] def put_value(self, value, quality=AttrQuality.Valid, propagate=1): """Sets a value. From ff77fe8c618812d465543b7918ac2e37b0a47b46 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 8 Mar 2021 11:55:47 +0100 Subject: [PATCH 168/279] Open msenvironment shelve file always with 'c' flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Empty environment files used with dumb backend fails to open with 'w' flag. This does not happen for gdbm backend. Avoid this problem by always using 'c' flag, which according to docs [1]: "Open database for reading and writing, creating it if it doesn’t exist". Tested and it does not harm when the environment shelve files is not empty - its content is preserved. Fixes #1425. [1] https://docs.python.org/3/library/dbm.html#dbm.open --- src/sardana/macroserver/msenvmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sardana/macroserver/msenvmanager.py b/src/sardana/macroserver/msenvmanager.py index ac43dd5c20..64b5499214 100644 --- a/src/sardana/macroserver/msenvmanager.py +++ b/src/sardana/macroserver/msenvmanager.py @@ -145,7 +145,7 @@ def setEnvironmentDb(self, f_name): raise ose if os.path.exists(f_name) or os.path.exists(f_name + ".dat"): try: - self._env = shelve.open(f_name, flag='w', writeback=False) + self._env = shelve.open(f_name, flag='c', writeback=False) except Exception: self.error("Failed to access environment in %s", f_name) self.debug("Details:", exc_info=1) From a21b740b285501104533bc0a7516f6d388b2c676 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Mon, 8 Mar 2021 15:47:46 +0100 Subject: [PATCH 169/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bf8ada24b..3bb80aea8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ This file follows the formats and conventions from [keepachangelog.com] * `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) * Allow running Spock without Qt bindings (#1462, #1463) * Fix getting macroserver from remote door in Sardana-Taurus Door extension (#1506) +* MacroServer opening empty environment files used with dumb backend (#1425, #1514) * Use equality instead of identity checks for numbers and strings (#1491) * Docstring of QtSpockWidget (#1484) * Recorders tests helpers (#1439) From 98b12d0dcb61b2de034f7cb79cfaeb1114473696 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 11 Mar 2021 09:43:07 +0100 Subject: [PATCH 170/279] docs: fix formatting in User's Overview --- doc/source/users/overview.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/source/users/overview.rst b/doc/source/users/overview.rst index ac5e88907c..54df2bfe3b 100644 --- a/doc/source/users/overview.rst +++ b/doc/source/users/overview.rst @@ -7,7 +7,11 @@ Overview Sardana is the control program initially developed at ALBA_. Our mission statement: - `Produce a modular, high performance, robust, and generic user environment for control applications in large and small installations. Make Sardana the generic user environment distributed in the Tango project and the standard basis of collaborations in control.` + Produce a modular, high performance, robust, and generic user environment + for control applications in large and small installations. + Make Sardana the generic user environment distributed in the Tango project + and the standard basis of collaborations in control. + Up to now, control applications in large installations have been notoriously difficult to share. Inspired by the success of the Tango_ collaboration, ALBA_ From 03f4fc6a9aa83224640b356b90e33f0383ebe2dd Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 11 Mar 2021 13:24:26 +0100 Subject: [PATCH 171/279] Revert "Open msenvironment shelve file always with 'c' flag" --- src/sardana/macroserver/msenvmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sardana/macroserver/msenvmanager.py b/src/sardana/macroserver/msenvmanager.py index 64b5499214..ac43dd5c20 100644 --- a/src/sardana/macroserver/msenvmanager.py +++ b/src/sardana/macroserver/msenvmanager.py @@ -145,7 +145,7 @@ def setEnvironmentDb(self, f_name): raise ose if os.path.exists(f_name) or os.path.exists(f_name + ".dat"): try: - self._env = shelve.open(f_name, flag='c', writeback=False) + self._env = shelve.open(f_name, flag='w', writeback=False) except Exception: self.error("Failed to access environment in %s", f_name) self.debug("Details:", exc_info=1) From 4ef2756b90c0f656cd565a6d823d9338af7a95e3 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 11 Mar 2021 23:49:25 +0100 Subject: [PATCH 172/279] Fix msenvironment when empty and used with dumb backend Empty environments used with the dumb backend, when this is never correctly closed [*], fails to open with the 'w' flag. This is because the `.dir` file is never created and it is necessary to determine the backend by the `dbm.whichdb()`. Avoid this problem by always closing the dbm. Actually this is only necessary for the dumb backend but do it for gnu as well - it does not harm and makes the code cleaner. [*] The MacroServer process currently crashes at exit - see: sardana-org/sardana#719 Fixes #1425. --- src/sardana/macroserver/msenvmanager.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/sardana/macroserver/msenvmanager.py b/src/sardana/macroserver/msenvmanager.py index ac43dd5c20..46da8b5056 100644 --- a/src/sardana/macroserver/msenvmanager.py +++ b/src/sardana/macroserver/msenvmanager.py @@ -53,7 +53,7 @@ def _dbm_dumb(filename): return dbm.dumb.open(filename, "c") -def _dbm_shelve(filename, backend): +def _create_dbm(filename, backend): if backend is None: try: return _dbm_gnu(filename) @@ -143,22 +143,22 @@ def setEnvironmentDb(self, f_name): self.error("Creating environment: %s" % ose.strerror) self.debug("Details:", exc_info=1) raise ose - if os.path.exists(f_name) or os.path.exists(f_name + ".dat"): - try: - self._env = shelve.open(f_name, flag='w', writeback=False) - except Exception: - self.error("Failed to access environment in %s", f_name) - self.debug("Details:", exc_info=1) - raise - else: + if not os.path.exists(f_name) and not os.path.exists(f_name + ".dat"): backend = getattr(sardanacustomsettings, "MS_ENV_SHELVE_BACKEND", None) try: - self._env = shelve.Shelf(_dbm_shelve(f_name, backend)) + dbm = _create_dbm(f_name, backend) + dbm.close() except Exception: self.error("Failed to create environment in %s", f_name) self.debug("Details:", exc_info=1) raise + try: + self._env = shelve.open(f_name, flag='w', writeback=False) + except Exception: + self.error("Failed to access environment in %s", f_name) + self.debug("Details:", exc_info=1) + raise self.info("Environment is being stored in %s", f_name) From d4cf62b4feb2123a1fc1bd252bc880673c70e0cc Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Fri, 12 Mar 2021 11:16:34 +0100 Subject: [PATCH 173/279] allow hooks for umv and append to mv hooks --- src/sardana/macroserver/macros/standard.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index 248320fdcd..c24c5480e8 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -511,9 +511,10 @@ def run(self, motor): self.info("Motor %s" % str(motor.stateObj.read().rvalue)) -class umv(Macro): +class umv(Macro, Hookable): """Move motor(s) to the specified position(s) and update""" + hints = {'allowsHooks': ('pre-move', 'post-move')} param_def = mv.param_def def prepare(self, motor_pos_list, **opts): @@ -530,7 +531,9 @@ def prepare(self, motor_pos_list, **opts): def run(self, motor_pos_list): self.print_pos = True try: - self.execMacro('mv', motor_pos_list) + mv, _ = self.createMacro('mv', motor_pos_list) + mv.appendHook(self.hooks) + self.runMacro(mv) finally: self.finish() From f0f12d477011c757287a9358bda3ff92840310f6 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Fri, 12 Mar 2021 12:06:23 +0100 Subject: [PATCH 174/279] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bb80aea8c..ba5c4648b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,7 @@ This file follows the formats and conventions from [keepachangelog.com] * `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) * Allow running Spock without Qt bindings (#1462, #1463) * Fix getting macroserver from remote door in Sardana-Taurus Door extension (#1506) -* MacroServer opening empty environment files used with dumb backend (#1425, #1514) +* MacroServer opening empty environment files used with dumb backend (#1425, #1514, #1517, #1520) * Use equality instead of identity checks for numbers and strings (#1491) * Docstring of QtSpockWidget (#1484) * Recorders tests helpers (#1439) From 62d4e95a690a135cf37a99dbccacd8a51b677ab3 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 12 Mar 2021 14:58:54 +0100 Subject: [PATCH 175/279] fix(mntgrp): respect timer/monitor passed in user configuration timer/monitor configuration parameters are still kept in the measurement group configuration for the backwards compatibility. Respect the user configuration values (if present) otherwise fill it with the software master timer/monitor or a random timerable channel (last fallback). --- src/sardana/pool/poolmeasurementgroup.py | 70 ++++++++++++++++++------ 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/src/sardana/pool/poolmeasurementgroup.py b/src/sardana/pool/poolmeasurementgroup.py index eb05b13034..98675a96ee 100644 --- a/src/sardana/pool/poolmeasurementgroup.py +++ b/src/sardana/pool/poolmeasurementgroup.py @@ -525,6 +525,32 @@ def get_timerable_ctrls(self, acq_synch=None, enabled=None): return self._filter_ctrls(timerable_ctrls, enabled) + def get_timerable_channels(self, acq_synch=None, enabled=None): + """Return timerable channels. + + Allow to filter channels based on acquisition synchronization or + whether these are enabled/disabled. + + :param acq_synch: (optional) filter controller based on acquisition + synchronization + :type acq_synch: :class:`~sardana.pool.pooldefs.AcqSynch` + :param enabled: (optional) filter controllers whether these are + enabled/disabled: + + - :obj:`True` - enabled only + - :obj:`False` - disabled only + - :obj:`None` - all + + :type enabled: :obj:`bool` or :obj:`None` + :return: timerable channels that fulfils the filtering criteria + :rtype: list<:class:`~sardana.pool.poolmeasurementgroup.ChannelConfiguration`> # noqa + """ + channels = [] + ctrls = self.get_timerable_ctrls(acq_synch, enabled) + for ctrl in ctrls: + channels.extend(ctrl.get_channels(enabled)) + return channels + def get_zerod_ctrls(self, enabled=None): """Return 0D controllers. @@ -842,29 +868,41 @@ def set_configuration_from_user(self, cfg): # Fill user configuration with measurement group's timer & monitor # This is a backwards compatibility cause the measurement group's # timer & monitor are not used - if master_timer_sw is not None: + mnt_grp_timer = cfg.get('timer') + if mnt_grp_timer: + timerable_channels = self.get_timerable_channels(enabled=True) + if mnt_grp_timer in [ch.full_name for ch in timerable_channels]: + user_config['timer'] = mnt_grp_timer + else: + raise ValueError( + 'timer {} is not present/enabled'.format(mnt_grp_timer)) + elif master_timer_sw is not None: user_config['timer'] = master_timer_sw.full_name elif master_timer_sw_start is not None: user_config['timer'] = master_timer_sw_start.full_name - else: # Measurement Group with all channel synchronized by hardware - mnt_grp_timer = cfg.get('timer') - if mnt_grp_timer: - user_config['timer'] = mnt_grp_timer + else: + # Measurement Group with all channel synchronized by hardware + # for backwards compatibility use a random monitor + user_config['timer'] = user_config_ctrl['timer'] + + mnt_grp_monitor = cfg.get('monitor') + if mnt_grp_monitor: + timerable_channels = self.get_timerable_channels(enabled=True) + if mnt_grp_monitor in [ch.full_name for ch in timerable_channels]: + user_config['monitor'] = mnt_grp_monitor else: - # for backwards compatibility use a random monitor - user_config['timer'] = user_config_ctrl['timer'] - - if master_monitor_sw is not None: + raise ValueError( + 'monitor {} is not present/enabled'.format( + mnt_grp_monitor)) + elif master_monitor_sw is not None: user_config['monitor'] = master_monitor_sw.full_name elif master_monitor_sw_start is not None: user_config['monitor'] = master_monitor_sw_start.full_name - else: # Measurement Group with all channel synchronized by hardware - mnt_grp_monitor = cfg.get('monitor') - if mnt_grp_monitor: - user_config['monitor'] = mnt_grp_monitor - else: - # for backwards compatibility use a random monitor - user_config['monitor'] = user_config_ctrl['monitor'] + else: + # Measurement Group with all channel synchronized by hardware + # for backwards compatibility use a random monitor + user_config['monitor'] = user_config_ctrl['monitor'] + # Update internals values self._label = label From a805a8246db674f65dc1df12473ab22f0e81c4f4 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 15 Mar 2021 11:35:00 +0100 Subject: [PATCH 176/279] test: add Hookable tests Add test to reveal problem with setting hooks to an empty list. --- src/sardana/macroserver/test/test_macro.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/sardana/macroserver/test/test_macro.py diff --git a/src/sardana/macroserver/test/test_macro.py b/src/sardana/macroserver/test/test_macro.py new file mode 100644 index 0000000000..074b0ae1b7 --- /dev/null +++ b/src/sardana/macroserver/test/test_macro.py @@ -0,0 +1,8 @@ +from sardana.macroserver.macro import Hookable + + +def test_Hookable(): + hookable = Hookable() + assert len(hookable.hooks) == 0 + hookable.hooks = [] + assert len(hookable.hooks) == 0 \ No newline at end of file From d48109854fbd8eb97d04295e82178cae5db61f1b Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 15 Mar 2021 11:41:46 +0100 Subject: [PATCH 177/279] fix: Hookable setting hooks to empty list --- src/sardana/macroserver/macro.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sardana/macroserver/macro.py b/src/sardana/macroserver/macro.py index afb91c0d3a..e7d4b52450 100644 --- a/src/sardana/macroserver/macro.py +++ b/src/sardana/macroserver/macro.py @@ -283,6 +283,8 @@ def hooks(self, hooks): # delete _hookHintsDict to force its recreation on the next access if hasattr(self, '_hookHintsDict'): del self._hookHintsDict + if len(self._hooks) == 0: + return # create _hookHintsDict self._getHookHintsDict()['_ALL_'] = list(zip(*self._hooks))[0] nohints = self._hookHintsDict['_NOHINTS_'] From 36116d38d02b36e9b33336bc635f30e92526a096 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 15 Mar 2021 11:42:48 +0100 Subject: [PATCH 178/279] test: enhance Hookable test to get unexisting hook place --- src/sardana/macroserver/test/test_macro.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sardana/macroserver/test/test_macro.py b/src/sardana/macroserver/test/test_macro.py index 074b0ae1b7..0c9f483292 100644 --- a/src/sardana/macroserver/test/test_macro.py +++ b/src/sardana/macroserver/test/test_macro.py @@ -4,5 +4,8 @@ def test_Hookable(): hookable = Hookable() assert len(hookable.hooks) == 0 + hooks = hookable.getHooks("unexisting-hook-place") + assert len(hooks) == 0 hookable.hooks = [] - assert len(hookable.hooks) == 0 \ No newline at end of file + assert len(hookable.hooks) == 0 + hooks = hookable.getHooks("unexisting-hook-place") From dec4af7c1fc1d5e37a77f1279cc80068438deff8 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 15 Mar 2021 11:46:18 +0100 Subject: [PATCH 179/279] test: add missing header to test_macro.py --- src/sardana/macroserver/test/test_macro.py | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/sardana/macroserver/test/test_macro.py b/src/sardana/macroserver/test/test_macro.py index 0c9f483292..2b6ee0b54e 100644 --- a/src/sardana/macroserver/test/test_macro.py +++ b/src/sardana/macroserver/test/test_macro.py @@ -1,3 +1,28 @@ +#!/usr/bin/env python + +############################################################################## +## +# This file is part of Sardana +## +# http://www.sardana-controls.org/ +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Sardana is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Sardana 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Sardana. If not, see . +## +############################################################################## + from sardana.macroserver.macro import Hookable From a6b55a6e5f259fff9c251411c626d735a11a0af7 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 15 Mar 2021 11:46:34 +0100 Subject: [PATCH 180/279] test: add missing header to test_msparameter.py --- .../macroserver/test/test_msparameter.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/sardana/macroserver/test/test_msparameter.py b/src/sardana/macroserver/test/test_msparameter.py index 3151081d39..07f2ece2bf 100644 --- a/src/sardana/macroserver/test/test_msparameter.py +++ b/src/sardana/macroserver/test/test_msparameter.py @@ -1,3 +1,28 @@ +#!/usr/bin/env python + +############################################################################## +## +# This file is part of Sardana +## +# http://www.sardana-controls.org/ +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Sardana is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Sardana 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Sardana. If not, see . +## +############################################################################## + import unittest from taurus.test import insertTest From 497cf2ba041bd083b831f90e317b13a88e7efa33 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 15 Mar 2021 11:51:55 +0100 Subject: [PATCH 181/279] test: add missing assert to test_Hookable --- src/sardana/macroserver/test/test_macro.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sardana/macroserver/test/test_macro.py b/src/sardana/macroserver/test/test_macro.py index 2b6ee0b54e..7f21ce2c32 100644 --- a/src/sardana/macroserver/test/test_macro.py +++ b/src/sardana/macroserver/test/test_macro.py @@ -34,3 +34,4 @@ def test_Hookable(): hookable.hooks = [] assert len(hookable.hooks) == 0 hooks = hookable.getHooks("unexisting-hook-place") + assert len(hooks) == 0 From 7e828eaadb8b0304da07831a62d0d3d1ea528492 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Mon, 15 Mar 2021 13:00:50 +0100 Subject: [PATCH 182/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba5c4648b3..7111e25be3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Allow running Spock without Qt bindings (#1462, #1463) * Fix getting macroserver from remote door in Sardana-Taurus Door extension (#1506) * MacroServer opening empty environment files used with dumb backend (#1425, #1514, #1517, #1520) +* Setting `Hookable.hooks` to empty list (#1522) * Use equality instead of identity checks for numbers and strings (#1491) * Docstring of QtSpockWidget (#1484) * Recorders tests helpers (#1439) From 6913a158e22d4435cffde689f3a3ef871a6d3e85 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 15 Mar 2021 15:12:11 +0100 Subject: [PATCH 183/279] refactor: move _filter_ctrls outside of MeasurementConfiguration --- src/sardana/pool/poolmeasurementgroup.py | 27 ++++++++++++------------ 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/sardana/pool/poolmeasurementgroup.py b/src/sardana/pool/poolmeasurementgroup.py index 98675a96ee..a9490adb33 100644 --- a/src/sardana/pool/poolmeasurementgroup.py +++ b/src/sardana/pool/poolmeasurementgroup.py @@ -124,6 +124,17 @@ def _to_fqdn(name, logger=None): return full_name +def _filter_ctrls(ctrls, enabled=None): + if enabled is None: + return ctrls + + filtered_ctrls = [] + for ctrl in ctrls: + if ctrl.enabled == enabled: + filtered_ctrls.append(ctrl) + return filtered_ctrls + + class ConfigurationItem(object): """Container of configuration attributes related to a given element. @@ -482,16 +493,6 @@ def get_acq_synch_by_controller(self, controller): controller = controller.element return self._ctrl_acq_synch[controller] - def _filter_ctrls(self, ctrls, enabled): - if enabled is None: - return ctrls - - filtered_ctrls = [] - for ctrl in ctrls: - if ctrl.enabled == enabled: - filtered_ctrls.append(ctrl) - return filtered_ctrls - def get_timerable_ctrls(self, acq_synch=None, enabled=None): """Return timerable controllers. @@ -523,7 +524,7 @@ def get_timerable_ctrls(self, acq_synch=None, enabled=None): else: timerable_ctrls = list(self._timerable_ctrls[acq_synch]) - return self._filter_ctrls(timerable_ctrls, enabled) + return _filter_ctrls(timerable_ctrls, enabled) def get_timerable_channels(self, acq_synch=None, enabled=None): """Return timerable channels. @@ -567,7 +568,7 @@ def get_zerod_ctrls(self, enabled=None): :return: 0D controllers that fulfils the filtering criteria :rtype: list<:class:`~sardana.pool.poolmeasurementgroup.ControllerConfiguration`> # noqa """ - return self._filter_ctrls(self._zerod_ctrls, enabled) + return _filter_ctrls(self._zerod_ctrls, enabled) def get_synch_ctrls(self, enabled=None): """Return synchronizer (currently only trigger/gate) controllers. @@ -585,7 +586,7 @@ def get_synch_ctrls(self, enabled=None): :return: synchronizer controllers that fulfils the filtering criteria :rtype: list<:class:`~sardana.pool.poolmeasurementgroup.ControllerConfiguration`> # noqa """ - return self._filter_ctrls(self._synch_ctrls, enabled) + return _filter_ctrls(self._synch_ctrls, enabled) def get_master_timer_software(self): """Return master timer in software acquisition. From b77cb506b749676ea38198cc87b734bca476bca7 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 15 Mar 2021 16:36:52 +0100 Subject: [PATCH 184/279] refactor: get_timerable_ctrls and get_timerable_channels --- src/sardana/pool/poolmeasurementgroup.py | 43 ++++++++++++++---------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/sardana/pool/poolmeasurementgroup.py b/src/sardana/pool/poolmeasurementgroup.py index a9490adb33..bce628ae05 100644 --- a/src/sardana/pool/poolmeasurementgroup.py +++ b/src/sardana/pool/poolmeasurementgroup.py @@ -135,6 +135,29 @@ def _filter_ctrls(ctrls, enabled=None): return filtered_ctrls +def _get_timerable_ctrls(ctrls, acq_synch=None, enabled=None): + timerable_ctrls = [] + if acq_synch is None: + for ctrls in list(ctrls.values()): + timerable_ctrls += ctrls + elif isinstance(acq_synch, list): + acq_synch_list = acq_synch + for acq_synch in acq_synch_list: + timerable_ctrls += ctrls[acq_synch] + else: + timerable_ctrls = list(ctrls[acq_synch]) + + return _filter_ctrls(timerable_ctrls, enabled) + + +def _get_timerable_channels(ctrls, acq_synch=None, enabled=None): + timerable_ctrls = _get_timerable_ctrls(ctrls, acq_synch, enabled) + timerable_channels = [] + for ctrl in timerable_ctrls: + timerable_channels.extend(ctrl.get_channels(enabled)) + return timerable_channels + + class ConfigurationItem(object): """Container of configuration attributes related to a given element. @@ -513,18 +536,7 @@ def get_timerable_ctrls(self, acq_synch=None, enabled=None): :return: timerable controllers that fulfils the filtering criteria :rtype: list<:class:`~sardana.pool.poolmeasurementgroup.ControllerConfiguration`> # noqa """ - timerable_ctrls = [] - if acq_synch is None: - for ctrls in list(self._timerable_ctrls.values()): - timerable_ctrls += ctrls - elif isinstance(acq_synch, list): - acq_synch_list = acq_synch - for acq_synch in acq_synch_list: - timerable_ctrls += self._timerable_ctrls[acq_synch] - else: - timerable_ctrls = list(self._timerable_ctrls[acq_synch]) - - return _filter_ctrls(timerable_ctrls, enabled) + return _get_timerable_ctrls(self._timerable_ctrls, acq_synch, enabled) def get_timerable_channels(self, acq_synch=None, enabled=None): """Return timerable channels. @@ -546,11 +558,8 @@ def get_timerable_channels(self, acq_synch=None, enabled=None): :return: timerable channels that fulfils the filtering criteria :rtype: list<:class:`~sardana.pool.poolmeasurementgroup.ChannelConfiguration`> # noqa """ - channels = [] - ctrls = self.get_timerable_ctrls(acq_synch, enabled) - for ctrl in ctrls: - channels.extend(ctrl.get_channels(enabled)) - return channels + return _get_timerable_channels(self._timerable_ctrls, acq_synch, + enabled) def get_zerod_ctrls(self, enabled=None): """Return 0D controllers. From 3085f5e8a60dd9504eb08640e966c0bede3ec7ba Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 15 Mar 2021 16:39:05 +0100 Subject: [PATCH 185/279] refactor: use _get_timerable_channels to look for timer/monitor MeasurementConfiguration.get_timerable_channels still can not be used when we are setting new configuration. MeasurementConfiguration's internal objects are filled at the end of the new configuration setting process. --- src/sardana/pool/poolmeasurementgroup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sardana/pool/poolmeasurementgroup.py b/src/sardana/pool/poolmeasurementgroup.py index bce628ae05..aa52ee16fa 100644 --- a/src/sardana/pool/poolmeasurementgroup.py +++ b/src/sardana/pool/poolmeasurementgroup.py @@ -880,7 +880,8 @@ def set_configuration_from_user(self, cfg): # timer & monitor are not used mnt_grp_timer = cfg.get('timer') if mnt_grp_timer: - timerable_channels = self.get_timerable_channels(enabled=True) + timerable_channels = _get_timerable_channels(timerable_ctrls, + enabled=True) if mnt_grp_timer in [ch.full_name for ch in timerable_channels]: user_config['timer'] = mnt_grp_timer else: @@ -897,7 +898,8 @@ def set_configuration_from_user(self, cfg): mnt_grp_monitor = cfg.get('monitor') if mnt_grp_monitor: - timerable_channels = self.get_timerable_channels(enabled=True) + timerable_channels = _get_timerable_channels(timerable_ctrls, + enabled=True) if mnt_grp_monitor in [ch.full_name for ch in timerable_channels]: user_config['monitor'] = mnt_grp_monitor else: From 118bf65c3e66ce351b3f624be1f0de3cfc4624bc Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 15 Mar 2021 16:44:21 +0100 Subject: [PATCH 186/279] docs: make set_configuration_from_user docstring more explicit --- src/sardana/pool/poolmeasurementgroup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sardana/pool/poolmeasurementgroup.py b/src/sardana/pool/poolmeasurementgroup.py index aa52ee16fa..451fa98d61 100644 --- a/src/sardana/pool/poolmeasurementgroup.py +++ b/src/sardana/pool/poolmeasurementgroup.py @@ -637,8 +637,9 @@ def set_configuration_from_user(self, cfg): """Set measurement configuration from serializable data structure. Setting of the configuration includes the validation process. Setting - of invalid configuration raises an exception hence it is not necessary - that the client application does the validation. + of invalid configuration raises an exception and leaves the object + as it was before the setting process. Thanks to that it is not + necessary that the client application does the validation. The configuration parameters for given channels/controllers may differ depending on their types e.g. 0D channel does not support timer From 141a0714bde0348c53180787c6825ac8f1cd52f7 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 15 Mar 2021 21:59:43 +0100 Subject: [PATCH 187/279] refactor: rename timerable_channels to timerable_chs --- src/sardana/pool/poolmeasurementgroup.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sardana/pool/poolmeasurementgroup.py b/src/sardana/pool/poolmeasurementgroup.py index 451fa98d61..4b2575fec6 100644 --- a/src/sardana/pool/poolmeasurementgroup.py +++ b/src/sardana/pool/poolmeasurementgroup.py @@ -881,9 +881,9 @@ def set_configuration_from_user(self, cfg): # timer & monitor are not used mnt_grp_timer = cfg.get('timer') if mnt_grp_timer: - timerable_channels = _get_timerable_channels(timerable_ctrls, - enabled=True) - if mnt_grp_timer in [ch.full_name for ch in timerable_channels]: + timerable_chs = _get_timerable_channels(timerable_ctrls, + enabled=True) + if mnt_grp_timer in [ch.full_name for ch in timerable_chs]: user_config['timer'] = mnt_grp_timer else: raise ValueError( @@ -899,9 +899,9 @@ def set_configuration_from_user(self, cfg): mnt_grp_monitor = cfg.get('monitor') if mnt_grp_monitor: - timerable_channels = _get_timerable_channels(timerable_ctrls, - enabled=True) - if mnt_grp_monitor in [ch.full_name for ch in timerable_channels]: + timerable_chs = _get_timerable_channels(timerable_ctrls, + enabled=True) + if mnt_grp_monitor in [ch.full_name for ch in timerable_chs]: user_config['monitor'] = mnt_grp_monitor else: raise ValueError( From e4d71af41b0dece2a5d9cd4d76bc979bfc8729e9 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 15 Mar 2021 22:02:18 +0100 Subject: [PATCH 188/279] fix: consider case where no timerable channels are enabled --- src/sardana/pool/poolmeasurementgroup.py | 25 +++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/sardana/pool/poolmeasurementgroup.py b/src/sardana/pool/poolmeasurementgroup.py index 4b2575fec6..44dc34ac38 100644 --- a/src/sardana/pool/poolmeasurementgroup.py +++ b/src/sardana/pool/poolmeasurementgroup.py @@ -883,11 +883,13 @@ def set_configuration_from_user(self, cfg): if mnt_grp_timer: timerable_chs = _get_timerable_channels(timerable_ctrls, enabled=True) - if mnt_grp_timer in [ch.full_name for ch in timerable_chs]: - user_config['timer'] = mnt_grp_timer - else: - raise ValueError( - 'timer {} is not present/enabled'.format(mnt_grp_timer)) + if len(timerable_chs) > 0: + if mnt_grp_timer in [ch.full_name for ch in timerable_chs]: + user_config['timer'] = mnt_grp_timer + else: + raise ValueError( + 'timer {} is not present/enabled'.format(mnt_grp_timer) + ) elif master_timer_sw is not None: user_config['timer'] = master_timer_sw.full_name elif master_timer_sw_start is not None: @@ -901,12 +903,13 @@ def set_configuration_from_user(self, cfg): if mnt_grp_monitor: timerable_chs = _get_timerable_channels(timerable_ctrls, enabled=True) - if mnt_grp_monitor in [ch.full_name for ch in timerable_chs]: - user_config['monitor'] = mnt_grp_monitor - else: - raise ValueError( - 'monitor {} is not present/enabled'.format( - mnt_grp_monitor)) + if len(timerable_chs) > 0: + if mnt_grp_monitor in [ch.full_name for ch in timerable_chs]: + user_config['monitor'] = mnt_grp_monitor + else: + raise ValueError( + 'monitor {} is not present/enabled'.format( + mnt_grp_monitor)) elif master_monitor_sw is not None: user_config['monitor'] = master_monitor_sw.full_name elif master_monitor_sw_start is not None: From dd6db360f2f1af667e12f59f9ef82275429a9eb2 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 15 Mar 2021 22:59:13 +0100 Subject: [PATCH 189/279] refactor: split Hookable.hooks setter It may be desired to explicitly overwrite hooks without logging warning message e.g. to avoid duplication of general hooks. Split Hookable.hooks setter and define Hookable._setHooks() for this purposes. --- src/sardana/macroserver/macro.py | 12 +++++++----- src/sardana/macroserver/macros/standard.py | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/sardana/macroserver/macro.py b/src/sardana/macroserver/macro.py index e7d4b52450..d0a32f6d58 100644 --- a/src/sardana/macroserver/macro.py +++ b/src/sardana/macroserver/macro.py @@ -257,17 +257,19 @@ def hooks(self, hooks): contains the hooks that don't provide hints """ - if not isinstance(hooks, list): - self.error( - 'the hooks must be passed as a list>') - return - if len(self.hooks) > 0: msg = ("This macro defines its own hooks. Previously defined " "hooks, including the general ones, would be only called " "if these own hooks were added using the appendHook " "method or appended to the self.hooks.") self.warning(msg) + self._setHooks(hooks) + + def _setHooks(self, hooks): + if not isinstance(hooks, list): + self.error( + 'the hooks must be passed as a list>') + return # store self._hooks, making sure it is of type: # list> self._hooks = [] diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index c24c5480e8..b5dd1e189d 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -532,7 +532,7 @@ def run(self, motor_pos_list): self.print_pos = True try: mv, _ = self.createMacro('mv', motor_pos_list) - mv.appendHook(self.hooks) + mv._setHooks(self.hooks) self.runMacro(mv) finally: self.finish() From 3557cdd8a6f64e4debbe43d7b68390f4a213193c Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 15 Mar 2021 23:16:54 +0100 Subject: [PATCH 190/279] feat: add pre-move and post-move hook places to mvr and umvr --- src/sardana/macroserver/macros/standard.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index b5dd1e189d..447eaa4b43 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -564,9 +564,10 @@ def printAllPos(self): self.flushOutput() -class mvr(Macro): +class mvr(Macro, Hookable): """Move motor(s) relative to the current position(s)""" + hints = {'allowsHooks': ('pre-move', 'post-move')} param_def = [ ['motor_disp_list', [['motor', Type.Moveable, None, 'Motor to move'], @@ -584,12 +585,15 @@ def run(self, motor_disp_list): else: pos += disp motor_pos_list.append([motor, pos]) - self.execMacro('mv', motor_pos_list) + mv, _ = self.createMacro('mv', motor_pos_list) + mv._setHooks(self.hooks) + self.runMacro(mv) -class umvr(Macro): +class umvr(Macro, Hookable): """Move motor(s) relative to the current position(s) and update""" + hints = {'allowsHooks': ('pre-move', 'post-move')} param_def = mvr.param_def def run(self, motor_disp_list): @@ -602,7 +606,9 @@ def run(self, motor_disp_list): else: pos += disp motor_pos_list.append([motor, pos]) - self.execMacro('umv', motor_pos_list) + umv, _ = self.createMacro('umv', motor_pos_list) + umv._setHooks(self.hooks) + self.runMacro(umv) # TODO: implement tw macro with param repeats in order to be able to pass # multiple motors and multiple deltas. Also allow to pass the integration time From 43557968b3e4557402d44b61854cafab5807d634 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 15 Mar 2021 23:20:32 +0100 Subject: [PATCH 191/279] feat: add pre-move and post-move hook places to br and ubr --- src/sardana/macroserver/macros/hkl.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/sardana/macroserver/macros/hkl.py b/src/sardana/macroserver/macros/hkl.py index 1f08299ade..23f97362e1 100644 --- a/src/sardana/macroserver/macros/hkl.py +++ b/src/sardana/macroserver/macros/hkl.py @@ -197,13 +197,14 @@ def fl(self, ch, if mat: return regx.sub(repl, ch) -class br(Macro, _diffrac): +class br(Macro, _diffrac, Hookable): """Move the diffractometer to the reciprocal space coordinates given by H, K and L. If a fourth parameter is given, the combination of angles to be set is the correspondig to the given index. The index of the angles combinations are then changed.""" + hints = {'allowsHooks': ('pre-move', 'post-move')} param_def = [ ['H', Type.String, None, "H value"], ['K', Type.String, None, "K value"], @@ -259,7 +260,9 @@ def run(self, H, K, L, AnglesIndex, FlagNotBlocking, FlagPrinting): cmd = cmd + " " + str(angle) if FlagPrinting == 1: cmd = "u" + cmd - self.execMacro(cmd) + mv, _ = self.createMacro(cmd) + mv._setHooks(self.hooks) + self.runMacro(mv) else: for name, angle in zip(self.angle_names, angles_list): angle_dev = self.getObj(self.angle_device_names[name]) @@ -269,11 +272,11 @@ def run(self, H, K, L, AnglesIndex, FlagNotBlocking, FlagPrinting): hkl_values[l_idx], self.diffrac.WaveLength]) -class ubr(Macro, _diffrac): +class ubr(Macro, _diffrac, Hookable): """Move the diffractometer to the reciprocal space coordinates given by H, K and L und update. """ - + hints = {'allowsHooks': ('pre-move', 'post-move')} param_def = [ ["hh", Type.String, "Not set", "H position"], ["kk", Type.String, "Not set", "K position"], @@ -286,7 +289,9 @@ def prepare(self, hh, kk, ll, AnglesIndex): def run(self, hh, kk, ll, AnglesIndex): if ll != "Not set": - self.execMacro("br", hh, kk, ll, AnglesIndex, 0, 1) + br, _ = self.prepareMacro("br", hh, kk, ll, AnglesIndex, 0, 1) + br._setHooks(self.hooks) + self.runMacro(br) else: self.output("usage: ubr H K L [Trajectory]") From 66fc20e78aeb6b160e6488e0ee09e6656fdabacb Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 16 Mar 2021 12:27:20 +0100 Subject: [PATCH 192/279] docs: add sardana.taurus.core.tango.sardana.macro to API --- .../api/sardana/taurus/core/tango/sardana.rst | 1 + .../taurus/core/tango/sardana/macro.rst | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 doc/source/devel/api/sardana/taurus/core/tango/sardana/macro.rst diff --git a/doc/source/devel/api/sardana/taurus/core/tango/sardana.rst b/doc/source/devel/api/sardana/taurus/core/tango/sardana.rst index 9912a909b5..65e6b75b8e 100644 --- a/doc/source/devel/api/sardana/taurus/core/tango/sardana.rst +++ b/doc/source/devel/api/sardana/taurus/core/tango/sardana.rst @@ -22,6 +22,7 @@ pool macroserver + macro .. autofunction:: registerExtensions .. autofunction:: unregisterExtensions diff --git a/doc/source/devel/api/sardana/taurus/core/tango/sardana/macro.rst b/doc/source/devel/api/sardana/taurus/core/tango/sardana/macro.rst new file mode 100644 index 0000000000..9b14d3b524 --- /dev/null +++ b/doc/source/devel/api/sardana/taurus/core/tango/sardana/macro.rst @@ -0,0 +1,37 @@ +.. currentmodule:: sardana.taurus.core.tango.sardana.macro + + +:mod:`~sardana.taurus.core.tango.sardana.macro` +=============================================== + +.. automodule:: sardana.taurus.core.tango.sardana.macro + +.. rubric:: Classes + +.. hlist:: + :columns: 4 + + * :class:`Macro` + * :class:`MacroInfo` + +Macro +----- + +.. inheritance-diagram:: Macro + :parts: 1 + +.. autoclass:: Macro + :show-inheritance: + :members: + :undoc-members: + +MacroInfo +--------- + +.. inheritance-diagram:: MacroInfo + :parts: 1 + +.. autoclass:: MacroInfo + :show-inheritance: + :members: + :undoc-members: From 19cf2e57a2930b245954b17616b4fb5ad162d0ac Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 16 Mar 2021 12:48:49 +0100 Subject: [PATCH 193/279] docs: document allowed hooks in macro documentation (client) For example, print "allowed hooks" in macro description in Spock. --- .../taurus/core/tango/sardana/macro.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/sardana/taurus/core/tango/sardana/macro.py b/src/sardana/taurus/core/tango/sardana/macro.py index 4c497d121d..3db0a9ae07 100644 --- a/src/sardana/taurus/core/tango/sardana/macro.py +++ b/src/sardana/taurus/core/tango/sardana/macro.py @@ -89,6 +89,9 @@ def _buildDoc(self): if self.hasResult(): doc += '\n\nResult:\n\t' doc += '\n\t'.join(self.getResultDescr()) + if self.allowsHooks(): + doc += '\n\nAllows hooks:\n\t' + doc += '\n\t'.join(self.getAllowedHooks()) self.doc = doc def _hasParamComplex(self, parameters=None): @@ -299,6 +302,31 @@ def formatResult(self, result): else: return tuple(res) + def allowsHooks(self): + """Checks whether the macro allows hooks + + :return: True or False depending if the macro allows hooks + :rtype: bool + """ + try: + self.hints["allowsHooks"] + except KeyError: + return False + return True + + def getAllowedHooks(self): + """Gets allowed hooks + + :return: list with the allowed hooks or empty list if the macro + does not allow hooks + :rtype: list[str] + """ + try: + allowed_hooks = self.hints["allowsHooks"] + except KeyError: + return [] + return allowed_hooks + def __str__(self): return self.name From c40c6aa53e970ceb9a10b91d573bda993b4aa287 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 16 Mar 2021 13:28:48 +0100 Subject: [PATCH 194/279] fix: Macro.hasResult() and Macro.hasParams() Macro.hasResult() and Macro.hasParams() wrongly returns True if the result and params definition is an empty list. This leads to adding empty: "Parameters" and "Result" sections in the macro's description in Spock. Fix it and consider empty lists as no parameters and result. --- src/sardana/taurus/core/tango/sardana/macro.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sardana/taurus/core/tango/sardana/macro.py b/src/sardana/taurus/core/tango/sardana/macro.py index 4c497d121d..3948a8a46b 100644 --- a/src/sardana/taurus/core/tango/sardana/macro.py +++ b/src/sardana/taurus/core/tango/sardana/macro.py @@ -137,7 +137,7 @@ def hasParams(self): :return: (bool) True if the macro has parameters or False otherwise """ - return hasattr(self, 'parameters') + return hasattr(self, 'parameters') and len(self.parameters) > 0 def getParamList(self): """Returs the list of parameters @@ -223,7 +223,7 @@ def hasResult(self): :return: (bool) True if the macro has a result or False otherwise """ - return hasattr(self, 'result') + return hasattr(self, 'result') and len(self.result) > 0 def getResultList(self): """Returns the list of results From d1bfb86fe2fd71ea225485bf2b533d089268938c Mon Sep 17 00:00:00 2001 From: reszelaz Date: Wed, 17 Mar 2021 11:04:10 +0100 Subject: [PATCH 195/279] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7111e25be3..daf07d82f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,8 @@ This file follows the formats and conventions from [keepachangelog.com] * Fix getting macroserver from remote door in Sardana-Taurus Door extension (#1506) * MacroServer opening empty environment files used with dumb backend (#1425, #1514, #1517, #1520) * Setting `Hookable.hooks` to empty list (#1522) +* `Macro.hasResult()` and `Macro.hasParams()` what avoids adding empty _Parameters_ and _Result_ + sections in the macro description in Spock (#1524) * Use equality instead of identity checks for numbers and strings (#1491) * Docstring of QtSpockWidget (#1484) * Recorders tests helpers (#1439) From 327bbd29edf80609a845d7cbea7e0fe537ca969c Mon Sep 17 00:00:00 2001 From: reszelaz Date: Wed, 17 Mar 2021 11:06:24 +0100 Subject: [PATCH 196/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index daf07d82f7..11d03bb5b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Initial delay in position domain to the synchronization description in *ct* like continuous scans (#1428) * Avoid double printing of user units in PMTV: read widget and units widget (#1424) +* Allowed hooks to macro description in Spock (#1523) * Assert motor sign is -1 or 1 (#1345, #1507) * Documentation on how to write 1D and 2D controllers (#1494) * Missing documentation of SEP18 concepts to how-to counter/timer controller (#995, #1492) From 3483359fcb0afa8f7f01285f2df1eda263f9705f Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 17 Mar 2021 22:28:47 +0100 Subject: [PATCH 197/279] perf: remove redundant print of positions at the end of umv PoolMotion action at the very end emits first the position event and then the state event. If the mv macro, which is internally executed by the umv macro, finished, then we can assume that no more position events will arrive corresponding to the requested move. This is because the mv macro finishes only after the state transition which is based on the event and we are executing the events callback sequentially. The event emission order is preserved on the client side according to: https://www.tango-controls.org/community/forum/c/general/development/is-events-order-maintained Removing of this extra print also improves the behaviour when post-move hooks use macro logging as described in #1480. --- src/sardana/macroserver/macros/standard.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index 7be0178ebd..8fa37876cf 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -518,7 +518,6 @@ def run(self, motor_pos_list): def finish(self): self._clean() - self.printAllPos() def _clean(self): for motor, pos in self.getParameters()[0]: From 96bc07437e0bea149ff2ef5fe2824926114a8cf3 Mon Sep 17 00:00:00 2001 From: Jan Garrevoet Date: Thu, 18 Mar 2021 14:51:57 +0100 Subject: [PATCH 198/279] also apply position formatting to the limits --- src/sardana/macroserver/macros/standard.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index 7be0178ebd..17f421e9ee 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -369,8 +369,17 @@ def run(self, motor_list): except: val1 = str_fmt % motor.getPosition(force=True) - val2 = str_fmt % posObj.getMaxRange().magnitude - val3 = str_fmt % posObj.getMinRange().magnitude + try: + val2 = fmt % posObj.getMaxRange().magnitude + val2 = str_fmt % val2 + except Exception: + val2 = str_fmt % posObj.getMaxRange().magnitude + + try: + val3 = fmt % posObj.getMinRange().magnitude + val3 = str_fmt % val3 + except Exception: + val3 = str_fmt % posObj.getMinRange().magnitude if show_ctrlaxis: valctrl = str_fmt % (ctrl_name) From 9c23e45907d5822da49687912c797eebb63a8451 Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Thu, 10 Oct 2019 12:13:37 +0200 Subject: [PATCH 199/279] Fix Tango inheritance --- src/sardana/tango/core/SardanaDevice.py | 8 ++++---- src/sardana/tango/pool/Pool.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sardana/tango/core/SardanaDevice.py b/src/sardana/tango/core/SardanaDevice.py index 3e496be7a2..7d8d9ca70c 100644 --- a/src/sardana/tango/core/SardanaDevice.py +++ b/src/sardana/tango/core/SardanaDevice.py @@ -35,7 +35,7 @@ import threading import PyTango.constants -from PyTango import Device_4Impl, DeviceClass, Util, DevState, \ +from PyTango import LatestDeviceImpl, DeviceClass, Util, DevState, \ AttrQuality, TimeVal, ArgType, ApiUtil, DevFailed, WAttribute from taurus.core.util.threadpool import ThreadPool @@ -66,7 +66,7 @@ def get_thread_pool(): return __thread_pool -class SardanaDevice(Device_4Impl, Logger): +class SardanaDevice(LatestDeviceImpl, Logger): """SardanaDevice represents the base class for all Sardana :class:`PyTango.DeviceImpl` classes""" @@ -74,7 +74,7 @@ def __init__(self, dclass, name): """Constructor""" self.in_constructor = True try: - Device_4Impl.__init__(self, dclass, name) + LatestDeviceImpl.__init__(self, dclass, name) self.init(name) Logger.__init__(self, name) @@ -512,7 +512,7 @@ def _get_class_properties(self): return dict(ProjectTitle="Sardana", Description="Generic description", doc_url="http://sardana-controls.org/", __icon=self.get_name().lower() + ".png", - InheritedFrom=["Device_4Impl"]) + InheritedFrom=["Device_5Impl"]) def write_class_property(self): """Write class properties ``ProjectTitle``, ``Description``, diff --git a/src/sardana/tango/pool/Pool.py b/src/sardana/tango/pool/Pool.py index e073abf4f4..54c0e29934 100644 --- a/src/sardana/tango/pool/Pool.py +++ b/src/sardana/tango/pool/Pool.py @@ -48,12 +48,12 @@ import collections -class Pool(PyTango.Device_4Impl, Logger): +class Pool(PyTango.LatestDeviceImpl, Logger): ElementsCache = None def __init__(self, cl, name): - PyTango.Device_4Impl.__init__(self, cl, name) + PyTango.LatestDeviceImpl.__init__(self, cl, name) Logger.__init__(self, name) self.init(name) self.init_device() @@ -1576,7 +1576,7 @@ def __init__(self, name): def _get_class_properties(self): return dict(ProjectTitle="Sardana", Description="Device Pool management class", doc_url="http://sardana-controls.org/", - InheritedFrom="Device_4Impl") + InheritedFrom="Device_5Impl") def write_class_property(self): util = PyTango.Util.instance() From 34bd40be3d316c5d6f0a88ac88ef9001c36a0143 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 18 Mar 2021 21:35:26 +0100 Subject: [PATCH 200/279] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d03bb5b0..4dc90b78dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,8 @@ This file follows the formats and conventions from [keepachangelog.com] * Experimental channel shape is now considered as a result of the configuration and not part of the measurement group configuration (#1296, #1466) +* Use `LatestDeviceImpl` (currently `Device_5Impl`) for as a base class of the Sardana Tango + devices (#1214, #1301, #1531) ### Removed From 321b6a692bbf83649eea2044857b6aada05e1368 Mon Sep 17 00:00:00 2001 From: Jan Garrevoet Date: Fri, 19 Mar 2021 06:29:32 +0100 Subject: [PATCH 201/279] move value formating to helper function --- src/sardana/macroserver/macros/standard.py | 53 ++++++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index 17f421e9ee..0f9d3af912 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -328,6 +328,36 @@ class wm(Macro): None, 'List of motor to show'], ] + @staticmethod + def format_value(fmt, str_fmt, value): + """ + Formats given value following the fmt and/or str_fmt rules. + + Parameters + ---------- + fmt : str + The value format. + + str_fmt : str + The string format. + + value : float + The value to be formatted. + + Returns + ------- + str + The string formatted value. + """ + + if fmt is not None: + fmt_value = fmt % value + fmt_value = str_fmt % fmt_value + else: + fmt_value = str_fmt % value + + return fmt_value + def prepare(self, motor_list, **opts): self.table_opts = {} @@ -362,24 +392,17 @@ def run(self, motor_list): posObj = motor.getPositionObj() if pos_format > -1: fmt = '%c.%df' % ('%', int(pos_format)) + else: + fmt = None - try: - val1 = fmt % motor.getPosition(force=True) - val1 = str_fmt % val1 - except: - val1 = str_fmt % motor.getPosition(force=True) + val1 = motor.getPosition(force=True) + val1 = self.format_value(fmt, str_fmt, val1) - try: - val2 = fmt % posObj.getMaxRange().magnitude - val2 = str_fmt % val2 - except Exception: - val2 = str_fmt % posObj.getMaxRange().magnitude + val2 = motor.getPosition(force=True) + val2 = self.format_value(fmt, str_fmt, val2) - try: - val3 = fmt % posObj.getMinRange().magnitude - val3 = str_fmt % val3 - except Exception: - val3 = str_fmt % posObj.getMinRange().magnitude + val3 = motor.getPosition(force=True) + val3 = self.format_value(fmt, str_fmt, val3) if show_ctrlaxis: valctrl = str_fmt % (ctrl_name) From d86b76a35777b8da3cd3e167a485ac15a298f1e3 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 22 Mar 2021 11:28:49 +0100 Subject: [PATCH 202/279] fix: avoid registering atexit on module import Importing the h5util module registers an atexit callback. This is an unwanted secondary effect e.g. when building docs. Register and unregister cleanup when opening the fist file and closing the last file. --- src/sardana/macroserver/recorders/h5util.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sardana/macroserver/recorders/h5util.py b/src/sardana/macroserver/recorders/h5util.py index ae28da1adb..178dc3c82e 100644 --- a/src/sardana/macroserver/recorders/h5util.py +++ b/src/sardana/macroserver/recorders/h5util.py @@ -44,7 +44,6 @@ class _H5FileHandler: def __init__(self): self._files = {} - atexit.register(self.clean_up) def __getitem__(self, fname): return self._files[fname] @@ -61,6 +60,8 @@ def open_file(self, fname, swmr_mode=False): fd = _open_h5_file(fname, libver=libver) if swmr_mode: fd.swmr_mode = True + if not self._files: + atexit.register(self.clean_up) self._files[fname] = fd return fd @@ -69,6 +70,8 @@ def close_file(self, fname): fd = self._files.pop(fname) except KeyError: raise ValueError('{} is not opened'.format(fname)) + if not self._files: + atexit.unregister(self.clean_up) fd.close() def clean_up(self): From c5f6385ef1d629cf56d92dc981e4733b8e310fe3 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 22 Mar 2021 16:16:33 +0100 Subject: [PATCH 203/279] docs: enable sphinx napoleon extension Docstring using the Numpy style, e.g. as proposed in 321b6a6, cause warning "Unexpected section title". Enable sphinx napoleon extension to avoid this warning and preprocess other styles in addition to a pure rst. --- doc/source/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/conf.py b/doc/source/conf.py index 9392b1c373..39e8d2c97a 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -77,6 +77,7 @@ def type_getattr(self, name): 'sardanaextension', 'ipython_console_highlighting', 'spock_console_highlighting', + 'sphinxcontrib.napoleon' ] try: From 85f653eb0d5d64fa2dd87f4237ec819408a57449 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 22 Mar 2021 16:20:10 +0100 Subject: [PATCH 204/279] style: correct Macro.getEnv() dostring formatting Using napoleon extension triggered a new warning about indentation in the Macro.getEnv() method. Use correct indentation to avoid the warning. --- src/sardana/macroserver/macro.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sardana/macroserver/macro.py b/src/sardana/macroserver/macro.py index e7d4b52450..29efd3382e 100644 --- a/src/sardana/macroserver/macro.py +++ b/src/sardana/macroserver/macro.py @@ -1787,12 +1787,12 @@ def getEnv(self, key=None, macro_name=None, door_name=None): """**Macro API**. Gets the local 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. + - 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. :raises: UnknownEnv From 5dcec309cdd62c5548e9dd903f5478b220609035 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 22 Mar 2021 16:26:04 +0100 Subject: [PATCH 205/279] docs: use correct Google style docstring in GetMacroInfo MacroServer.GetMacroInfo() docstring is using an unknown docstring format but very similar to Google style. Furthermore, after enabling napoleon it triggered warnings. Use Google style for this method docstring. --- src/sardana/tango/macroserver/MacroServer.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/sardana/tango/macroserver/MacroServer.py b/src/sardana/tango/macroserver/MacroServer.py index b21c0f14c6..c3e1109fcb 100644 --- a/src/sardana/tango/macroserver/MacroServer.py +++ b/src/sardana/tango/macroserver/MacroServer.py @@ -247,15 +247,17 @@ def is_Elements_allowed(self, req_type): is_TypeList_allowed = is_Elements_allowed def GetMacroInfo(self, macro_names): - """GetMacroInfo(list macro_names): + """Get macro information - Returns a list of string containing macro information. - Each string is a JSON encoded. + Returns a list of strings containing macro information. + Each string is a JSON encoded. + + Args: + macro_names (list(str)): macro(s) name(s) + + Returns: + list(str): macro(s) information - Params: - - macro_name: a list of strings with the macro(s) name(s) - Returns: - - a list of string containing macro information. """ macro_server = self.macro_server codec = CodecFactory().getCodec('json') From cba176ced63cdbdf66aa9342ea75f121efd549a4 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 22 Mar 2021 16:40:52 +0100 Subject: [PATCH 206/279] fix: use max and min range instead of position in wm Avoid copy&paste error and use use max and min range instead of position in wm. --- src/sardana/macroserver/macros/standard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index 0f9d3af912..4f1969a206 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -398,10 +398,10 @@ def run(self, motor_list): val1 = motor.getPosition(force=True) val1 = self.format_value(fmt, str_fmt, val1) - val2 = motor.getPosition(force=True) + val2 = posObj.getMaxRange().magnitude val2 = self.format_value(fmt, str_fmt, val2) - val3 = motor.getPosition(force=True) + val3 = posObj.getMinRange().magnitude val3 = self.format_value(fmt, str_fmt, val3) if show_ctrlaxis: From a70cb00836d75c59b1c4d784a6429a23a2190a0e Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 23 Mar 2021 13:31:43 +0100 Subject: [PATCH 207/279] Improve swmr test test_swmr() is prone to a race condition because of reuse of threading event. Fix it by using two events instead of reusing. Also add a test of swmr without h5_write_session context (using HDF5_USE_FILE_LOCKING="FALSE") --- .../recorders/test/test_h5storage.py | 100 +++++++++++------- 1 file changed, 64 insertions(+), 36 deletions(-) diff --git a/src/sardana/macroserver/recorders/test/test_h5storage.py b/src/sardana/macroserver/recorders/test/test_h5storage.py index 0686d91366..ce904f97e4 100644 --- a/src/sardana/macroserver/recorders/test/test_h5storage.py +++ b/src/sardana/macroserver/recorders/test/test_h5storage.py @@ -34,7 +34,7 @@ import h5py import numpy import pytest -from unittest import TestCase +from unittest import TestCase, mock from sardana.macroserver.scan import ColumnDesc from sardana.macroserver.recorders.h5storage import NXscanH5_FileRecorder @@ -234,46 +234,74 @@ def test_addCustomData(recorder, custom_data): assert fd["entry"]["custom_data"][name].value == custom_data -def test_swmr(tmpdir): - - def scan(path, serialno=0): - env = ENV.copy() - env["serialno"] = serialno - record_list = RecordList(env) - nb_records = 2 - # create description of channel data - data_desc = [ - ColumnDesc(name=COL1_NAME, - label=COL1_NAME, - dtype="float64", - shape=()) - ] - env["datadesc"] = data_desc - # simulate sardana scan - recorder = NXscanH5_FileRecorder(filename=path) - env["starttime"] = datetime.now() - recorder._startRecordList(record_list) - for i in range(nb_records): - record = Record({COL1_NAME: 0.1}, i) - recorder._writeRecord(record) - env["endtime"] = datetime.now() - recorder._endRecordList(record_list) - - def read_file(path, event): +def _scan(path, serialno=0): + env = ENV.copy() + env["serialno"] = serialno + record_list = RecordList(env) + nb_records = 2 + # create description of channel data + data_desc = [ + ColumnDesc(name=COL1_NAME, + label=COL1_NAME, + dtype="float64", + shape=()) + ] + env["datadesc"] = data_desc + # simulate sardana scan + recorder = NXscanH5_FileRecorder(filename=path) + env["starttime"] = datetime.now() + recorder._startRecordList(record_list) + for i in range(nb_records): + record = Record({COL1_NAME: 0.1}, i) + recorder._writeRecord(record) + env["endtime"] = datetime.now() + recorder._endRecordList(record_list) + + +def test_swmr_with_h5_session(tmpdir): + + def read_file(path, ready, done): with h5py.File(path, mode="r"): - event.set() - event.wait() + ready.set() + done.wait() path = str(tmpdir / "file.h5") - event = multiprocessing.Event() - reader = multiprocessing.Process(target=read_file, args=(path, event)) + reader_is_ready = multiprocessing.Event() + writer_is_done = multiprocessing.Event() + reader = multiprocessing.Process( + target=read_file, args=(path, reader_is_ready, writer_is_done) + ) with h5_write_session(path): - scan(path, serialno=0) + _scan(path, serialno=0) reader.start() - event.wait() - event.clear() + reader_is_ready.wait() try: - scan(path, serialno=1) + _scan(path, serialno=1) finally: - event.set() + writer_is_done.set() reader.join() + + +def test_swmr_without_h5_session(tmpdir): + + @mock.patch.dict(os.environ, {"HDF5_USE_FILE_LOCKING": "FALSE"}) + def read_file(path, ready, done): + with h5py.File(path, mode="r"): + ready.set() + done.wait() + + path = str(tmpdir / "file.h5") + reader_is_ready = multiprocessing.Event() + writer_is_done = multiprocessing.Event() + reader = multiprocessing.Process( + target=read_file, args=(path, reader_is_ready, writer_is_done) + ) + + _scan(path, serialno=0) + reader.start() + reader_is_ready.wait() + try: + _scan(path, serialno=1) + finally: + writer_is_done.set() + reader.join() From 44a9914bec168c354fcae7d864cf5d48a40d17cc Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 23 Mar 2021 15:45:55 +0100 Subject: [PATCH 208/279] Mark test_swmr_without_h5_session as xfail for old hdf5 test_swmr_without_h5_session requires support for HDF5_USE_FILE_LOCKING environment variable. Mark the test as expected failure if hdf5 does not support it. --- src/sardana/macroserver/recorders/test/test_h5storage.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sardana/macroserver/recorders/test/test_h5storage.py b/src/sardana/macroserver/recorders/test/test_h5storage.py index ce904f97e4..60a03b297a 100644 --- a/src/sardana/macroserver/recorders/test/test_h5storage.py +++ b/src/sardana/macroserver/recorders/test/test_h5storage.py @@ -282,6 +282,10 @@ def read_file(path, ready, done): reader.join() +@pytest.mark.xfail( + condition=h5py.version.hdf5_version_tuple < (1, 10, 1), + reason="HDF5_USE_FILE_LOCKING not supported by hdf5<1.10.1" +) def test_swmr_without_h5_session(tmpdir): @mock.patch.dict(os.environ, {"HDF5_USE_FILE_LOCKING": "FALSE"}) From e19dae622e5e5383a13883fbb1be0298005a7cd8 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 24 Mar 2021 16:30:39 +0100 Subject: [PATCH 209/279] Handle SWMR error when opening a h5_session Provide a more user-friendly explanation when there is a problem handling a hdf5 file file in swmr mode --- src/sardana/macroserver/recorders/h5util.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/sardana/macroserver/recorders/h5util.py b/src/sardana/macroserver/recorders/h5util.py index 178dc3c82e..abc96eb927 100644 --- a/src/sardana/macroserver/recorders/h5util.py +++ b/src/sardana/macroserver/recorders/h5util.py @@ -54,12 +54,15 @@ def files(self): def open_file(self, fname, swmr_mode=False): if swmr_mode: - libver = 'latest' + try: + fd = _open_h5_file(fname, libver='latest') + fd.swmr_mode = True + except ValueError as e: + raise ValueError( + "Cannot open '{}' in swmr mode: {}".format(fname, e) + ) else: - libver ='earliest' - fd = _open_h5_file(fname, libver=libver) - if swmr_mode: - fd.swmr_mode = True + fd = _open_h5_file(fname) if not self._files: atexit.register(self.clean_up) self._files[fname] = fd From 921a8b4e5cbab12733f1fde703cc9c7d222f0b30 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 24 Mar 2021 17:11:16 +0100 Subject: [PATCH 210/279] Make h5_start_session output session details Print file name(s) , SWMR mode and hdf5 compatibility info --- src/sardana/macroserver/macros/h5storage.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sardana/macroserver/macros/h5storage.py b/src/sardana/macroserver/macros/h5storage.py index 7debd26948..cf80658b1c 100644 --- a/src/sardana/macroserver/macros/h5storage.py +++ b/src/sardana/macroserver/macros/h5storage.py @@ -53,7 +53,10 @@ def _h5_start_session(macro, swmr_mode): except UnknownEnv: swmr_mode = False for file_path in _get_h5_scan_files(macro): - _h5_file_handler.open_file(file_path, swmr_mode) + fd = _h5_file_handler.open_file(file_path, swmr_mode) + macro.print("H5 session open for '{}'".format(file_path)) + macro.print("\t SWMR mode: {}".format(swmr_mode)) + macro.print("\t HDF5 version compatibility: {}".format(fd.libver)) @macro([["swmr_mode", Type.Boolean, Optional, "Enable SWMR mode"]]) From cffe08673ba6e9549af0da69875bdfcc0883400e Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 24 Mar 2021 17:15:03 +0100 Subject: [PATCH 211/279] doc: Fix outdated docstrings Some docstrings referred to outdated version of the start session macro. Fix them --- src/sardana/macroserver/macros/h5storage.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sardana/macroserver/macros/h5storage.py b/src/sardana/macroserver/macros/h5storage.py index cf80658b1c..fa2c982862 100644 --- a/src/sardana/macroserver/macros/h5storage.py +++ b/src/sardana/macroserver/macros/h5storage.py @@ -81,9 +81,9 @@ def _h5_end_session(macro): @macro() def h5_end_session(self): - """End SWMR write session for HDF5 scan file(s) + """End write session for HDF5 scan file(s) - Close previously opened HDF5 scan files with the use ``h5_start_swmr``. + Close previously opened HDF5 scan files with the use ``h5_start_session``. Resolve configured H5 scan file names by inspecting ScanDir and ScanFile environment variables. @@ -93,7 +93,7 @@ def h5_end_session(self): @macro() def h5_ls_session(self): - """List scan files opened for SWMR write session with ``h5_start_swmr`` + """List scan files opened for write session with ``h5_start_session`` """ for file_path in _h5_file_handler.files: self.print(file_path) From a03f39eebc7fd3156b325ff45f83372a0f375431 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 24 Mar 2021 17:24:38 +0100 Subject: [PATCH 212/279] Reject opening more than one h5_session Do not allow to start a new session if one is already open --- src/sardana/macroserver/macros/h5storage.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sardana/macroserver/macros/h5storage.py b/src/sardana/macroserver/macros/h5storage.py index fa2c982862..0087f3d397 100644 --- a/src/sardana/macroserver/macros/h5storage.py +++ b/src/sardana/macroserver/macros/h5storage.py @@ -72,6 +72,11 @@ def h5_start_session(self, swmr_mode): Resolve configured H5 scan file names by inspecting ScanDir and ScanFile environment variables. """ + if _h5_file_handler.files: + self.error( + "A session is already started. End it before starting a new one" + ) + return _h5_start_session(self, swmr_mode) From 0df8d7b6c876b64e41a0141a4fb3288cf3531d9d Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 24 Mar 2021 21:44:09 +0100 Subject: [PATCH 213/279] Add optional path parameter to h5_end_session This allows to close sessions even if the Scandata/ScanFiles variables changed --- src/sardana/macroserver/macros/h5storage.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/sardana/macroserver/macros/h5storage.py b/src/sardana/macroserver/macros/h5storage.py index 0087f3d397..d3a07ff8d9 100644 --- a/src/sardana/macroserver/macros/h5storage.py +++ b/src/sardana/macroserver/macros/h5storage.py @@ -80,20 +80,26 @@ def h5_start_session(self, swmr_mode): _h5_start_session(self, swmr_mode) -def _h5_end_session(macro): - for file_path in _get_h5_scan_files(macro): +def _h5_end_session(macro, path=None): + if path is None: + paths = _get_h5_scan_files(macro) + else: + paths = [path] + for file_path in paths: _h5_file_handler.close_file(file_path) -@macro() -def h5_end_session(self): + +@macro([["path", Type.String, Optional, + "File name for which the session should be ended"]]) +def h5_end_session(self, path): """End write session for HDF5 scan file(s) Close previously opened HDF5 scan files with the use ``h5_start_session``. - Resolve configured H5 scan file names by inspecting ScanDir and ScanFile - environment variables. + If the optional argument "path" is not passed, resolve configured H5 scan + file names by inspecting ScanDir and ScanFile environment variables. """ - _h5_end_session(self) + _h5_end_session(self, path) @macro() From cdfa187556847c09ef2d06dc689f6f59f6698e6a Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Wed, 24 Mar 2021 22:36:38 +0100 Subject: [PATCH 214/279] correct assignment of motors in umv --- src/sardana/macroserver/macros/standard.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index 52f98bcad5..7de376e3c3 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -520,13 +520,14 @@ class umv(Macro, Hookable): def prepare(self, motor_pos_list, **opts): self.all_names = [] self.all_pos = [] + self.motors = [] self.print_pos = False for motor, pos in motor_pos_list: self.all_names.append([motor.getName()]) + self.motors.append(motor) pos, posObj = motor.getPosition(force=True), motor.getPositionObj() self.all_pos.append([pos]) posObj.subscribeEvent(self.positionChanged, motor) - self.motors = self.all_names def run(self, motor_pos_list): self.print_pos = True From 115dd37f49a1a9fa5f3bc0a7dbfe14f8a8bdcf48 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Wed, 24 Mar 2021 22:44:20 +0100 Subject: [PATCH 215/279] add self.motors to mvr and umvr --- src/sardana/macroserver/macros/standard.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index 7de376e3c3..cc1e4f7bc7 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -576,9 +576,10 @@ class mvr(Macro, Hookable): ] def run(self, motor_disp_list): - motor_pos_list = [] + self.motors, motor_pos_list = [], [] for motor, disp in motor_disp_list: pos = motor.getPosition(force=True) + self.motors.append(motor) if pos is None: self.error("Cannot get %s position" % motor.getName()) return @@ -597,9 +598,10 @@ class umvr(Macro, Hookable): param_def = mvr.param_def def run(self, motor_disp_list): - motor_pos_list = [] + self.motors, motor_pos_list = [], [] for motor, disp in motor_disp_list: pos = motor.getPosition(force=True) + self.motors.append(motor) if pos is None: self.error("Cannot get %s position" % motor.getName()) return From c70b671e0ec59798b61c0cb8ca591c3ba2f5a7d1 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 24 Mar 2021 22:49:34 +0100 Subject: [PATCH 216/279] Handle chained exceptions in _H5FileHandler.open_file --- src/sardana/macroserver/recorders/h5util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sardana/macroserver/recorders/h5util.py b/src/sardana/macroserver/recorders/h5util.py index abc96eb927..d502479f65 100644 --- a/src/sardana/macroserver/recorders/h5util.py +++ b/src/sardana/macroserver/recorders/h5util.py @@ -59,8 +59,8 @@ def open_file(self, fname, swmr_mode=False): fd.swmr_mode = True except ValueError as e: raise ValueError( - "Cannot open '{}' in swmr mode: {}".format(fname, e) - ) + "Cannot open '{}' in swmr mode".format(fname) + ) from e else: fd = _open_h5_file(fname) if not self._files: From 6e7900449757b362f8edf05297e2f8078a23df44 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 24 Mar 2021 22:56:59 +0100 Subject: [PATCH 217/279] Add h5_{start,end}_session_path macros - Add macros to start/end h5 sessions based on file paths instead of ScanDir and ScanPath - Support a path argument in the h5_write_session context - Do not block creation of new session even if some sessions is already started (just print hint on how to close) --- src/sardana/macroserver/macros/h5storage.py | 83 +++++++++++++++------ 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/src/sardana/macroserver/macros/h5storage.py b/src/sardana/macroserver/macros/h5storage.py index d3a07ff8d9..6dfdcde376 100644 --- a/src/sardana/macroserver/macros/h5storage.py +++ b/src/sardana/macroserver/macros/h5storage.py @@ -46,13 +46,17 @@ def _get_h5_scan_files(macro): return h5_scan_files -def _h5_start_session(macro, swmr_mode): +def _h5_start_session(macro, path=None, swmr_mode=None): + if path is None: + paths = _get_h5_scan_files(macro) + else: + paths = [path] if swmr_mode is None: try: swmr_mode = macro._getEnv("ScanH5SWMR") except UnknownEnv: swmr_mode = False - for file_path in _get_h5_scan_files(macro): + for file_path in paths: fd = _h5_file_handler.open_file(file_path, swmr_mode) macro.print("H5 session open for '{}'".format(file_path)) macro.print("\t SWMR mode: {}".format(swmr_mode)) @@ -64,20 +68,43 @@ def h5_start_session(self, swmr_mode): """Start write session for HDF5 scan file(s) Open HDF5 scan files in write mode and keep them for the needs of - recorders until the session is closed by ``h5_end_swmr``. + recorders until the session is closed by ``h5_end_session``. + + The session file path is obtained by inspecting ScanDir and ScanFile + environment variables. If you want to use a different file path, use + ``h5_start_session_path`` Optionally, enable SWMR mode (either with ``swmr_mode`` parameter or - with ``ScanH5SWMR`` environment variable. + with ``ScanH5SWMR`` environment variable) + """ + sessions = _h5_file_handler.files + if sessions: + self.print("Session(s) already started. Can be ended with: ") + for p in sessions: + self.print("\th5_end_session_path " + p ) + self.print("") + _h5_start_session(self, None, swmr_mode) - Resolve configured H5 scan file names by inspecting ScanDir and ScanFile - environment variables. + +@macro([["path", Type.String, None, + "File name for which the session should be started"], + ["swmr_mode", Type.Boolean, Optional, "Enable SWMR mode"]]) +def h5_start_session_path(self, path, swmr_mode): + """Start write session for HDF5 file path + + Open HDF5 files in write mode and keep them for the needs of + recorders until the session is closed by ``h5_end_session``. + + Optionally, enable SWMR mode (either with ``swmr_mode`` parameter or + with ``ScanH5SWMR`` environment variable) """ - if _h5_file_handler.files: - self.error( - "A session is already started. End it before starting a new one" - ) - return - _h5_start_session(self, swmr_mode) + sessions = _h5_file_handler.files + if sessions: + self.print("Session(s) already started. Can be ended with: ") + for p in sessions: + self.print("\th5_end_session_path " + p) + self.print("") + _h5_start_session(self, path, swmr_mode) def _h5_end_session(macro, path=None): @@ -89,15 +116,27 @@ def _h5_end_session(macro, path=None): _h5_file_handler.close_file(file_path) -@macro([["path", Type.String, Optional, - "File name for which the session should be ended"]]) -def h5_end_session(self, path): +@macro() +def h5_end_session(self): """End write session for HDF5 scan file(s) - Close previously opened HDF5 scan files with the use ``h5_start_session``. + Close previously opened HDF5 scan files with the use ``h5_start_session`` + or ``h5_start_session_path``. + + The session file path is obtained by inspecting ScanDir and ScanFile + environment variables. If you want to close a different file path, use + ``h5_end_session_path`` + """ + _h5_end_session(self, path=None) + + +@macro([["path", Type.String, Optional, + "File name for which the session should be ended"]]) +def h5_end_session_path(self, path): + """End write session for HDF5 file path - If the optional argument "path" is not passed, resolve configured H5 scan - file names by inspecting ScanDir and ScanFile environment variables. + Close previously opened HDF5 scan files with the use ``h5_start_session`` + or ``h5_start_session_path``. """ _h5_end_session(self, path) @@ -111,7 +150,7 @@ def h5_ls_session(self): @contextlib.contextmanager -def h5_write_session(macro, swmr_mode=False): +def h5_write_session(macro, path=None, swmr_mode=False): """Context manager for HDF5 file write session. Maintains HDF5 file opened for the context lifetime. @@ -130,14 +169,16 @@ def experiment(self): :param macro: macro object :type macro: `~sardana.macroserver.macro.Macro` + :param path: file path (or None to use ScanDir and ScanFile) + :type path: str :param swmr_mode: Use SWMR write mode :type swmr_mode: bool """ - _h5_start_session(macro, swmr_mode) + _h5_start_session(macro, path, swmr_mode) try: yield None finally: - _h5_end_session(macro) + _h5_end_session(macro, path) From 44bfa4f8c88bc5a24d9706882739f6faf97d1dfb Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 24 Mar 2021 23:06:30 +0100 Subject: [PATCH 218/279] doc: mention h5 session _path macros in scan user guide --- doc/source/users/scan.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/source/users/scan.rst b/doc/source/users/scan.rst index 0241ea1fe2..adcb0b8f89 100644 --- a/doc/source/users/scan.rst +++ b/doc/source/users/scan.rst @@ -239,8 +239,10 @@ The write sessions use case scenarios: * Manual session control with macros To start and end the session you can use - `~sardana.macroserver.macros.h5storage.h5_start_session` and - `~sardana.macroserver.macros.h5storage.h5_end_session` macros. + `~sardana.macroserver.macros.h5storage.h5_start_session` / + `~sardana.macroserver.macros.h5storage.h5_start_session_path` and + `~sardana.macroserver.macros.h5storage.h5_end_session` / + `~sardana.macroserver.macros.h5storage.h5_end_session_path` macros. You can list the active sessions with `~sardana.macroserver.macros.h5storage.h5_ls_session` macro. * Programmatic session control with context manager (for macro developers) From fdc6e204b01597591626488d30fa652fd02a7c79 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 24 Mar 2021 23:06:58 +0100 Subject: [PATCH 219/279] doc: Add h5 session macros to macro catalog --- doc/source/users/standard_macro_catalog.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/source/users/standard_macro_catalog.rst b/doc/source/users/standard_macro_catalog.rst index bd9f1e2731..b88289b125 100644 --- a/doc/source/users/standard_macro_catalog.rst +++ b/doc/source/users/standard_macro_catalog.rst @@ -224,3 +224,8 @@ scan related macros * :class:`~sardana.macroserver.macros.standard.where` * :class:`~sardana.macroserver.macros.standard.pic` * :class:`~sardana.macroserver.macros.standard.cen` + * :class:`~sardana.macroserver.macros.h5storage.h5_ls_session` + * :class:`~sardana.macroserver.macros.h5storage.h5_start_session` + * :class:`~sardana.macroserver.macros.h5storage.h5_start_session_path` + * :class:`~sardana.macroserver.macros.h5storage.h5_end_session` + * :class:`~sardana.macroserver.macros.h5storage.h5_end_session_path` From afc7a5ea2c31407dc8c2f0675963e8b2631dda97 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Wed, 24 Mar 2021 23:44:06 +0100 Subject: [PATCH 220/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dc90b78dc..a6a783199d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ This file follows the formats and conventions from [keepachangelog.com] e.g. pseudo counter's value, in controllers (#1440, #1446) * Storing string values in PreScanSnapshot in NXscanH5_FileRecorder (#1486) * Storing string values as custom data in NXscanH5_FileRecorder (#1485) +* Stopping/aborting grouped movement when backlash correction would be applied (#1421, #1474, #1539) * Fill parent_macro in case of executing XML hooks e.g. in sequencer (#1497) * Problems with macro id's when `sequencer` executes from _plain text_ files (#1215, #1216) * `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) From ae43eb9c1eaf39a680dabe506d90731d1c061884 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Thu, 25 Mar 2021 23:18:50 +0100 Subject: [PATCH 221/279] fix hookable import and add angles as self.motors in ubr and br --- src/sardana/macroserver/macros/hkl.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/sardana/macroserver/macros/hkl.py b/src/sardana/macroserver/macros/hkl.py index 23f97362e1..a4ac520714 100644 --- a/src/sardana/macroserver/macros/hkl.py +++ b/src/sardana/macroserver/macros/hkl.py @@ -48,7 +48,7 @@ import numpy as np from sardana.sardanautils import py2_round -from sardana.macroserver.macro import Macro, iMacro, Type +from sardana.macroserver.macro import Hookable, Macro, iMacro, Type from sardana.macroserver.macros.scan import aNscan from sardana.macroserver.msexception import UnknownEnv @@ -218,6 +218,9 @@ class br(Macro, _diffrac, Hookable): def prepare(self, H, K, L, AnglesIndex, FlagNotBlocking, FlagPrinting): _diffrac.prepare(self) + self.motors = [] + for name in self.angle_names: + self.motors.append(self.getMotor(name)) def run(self, H, K, L, AnglesIndex, FlagNotBlocking, FlagPrinting): h_idx = 0 @@ -286,6 +289,9 @@ class ubr(Macro, _diffrac, Hookable): def prepare(self, hh, kk, ll, AnglesIndex): _diffrac.prepare(self) + self.motors = [] + for name in self.angle_names: + self.motors.append(self.getMotor(name)) def run(self, hh, kk, ll, AnglesIndex): if ll != "Not set": From 4826f9b030cd29a3251b688279606ab51effec7d Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 25 Mar 2021 23:38:06 +0100 Subject: [PATCH 222/279] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6a783199d..c9e44346e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ This file follows the formats and conventions from [keepachangelog.com] ### Added +* _H5 write session_ to avoid file locking problems and to introduce SWMR mode support (#1124, #1457) + * `h5_start_session`, `h5_start_session_path`, `h5_end_session`, `h5_end_session_path` + and `h5_ls_session` macros + * `h5_write_session` context manager * `shape` controller axis parameter (plugin), `shape` experimental channel attribute (kernel) and `Shape` Tango attribute to the experimental channels (#1296, #1466) From 57c1d20848c164f89df6e188cb3caebdb5d79c10 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Fri, 26 Mar 2021 00:15:23 +0100 Subject: [PATCH 223/279] add angles as self.motors in ubr and br --- src/sardana/macroserver/macros/hkl.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/sardana/macroserver/macros/hkl.py b/src/sardana/macroserver/macros/hkl.py index a4ac520714..45e24b0ecb 100644 --- a/src/sardana/macroserver/macros/hkl.py +++ b/src/sardana/macroserver/macros/hkl.py @@ -120,10 +120,9 @@ def prepare(self): self.type = v self.angle_device_names = {} - i = 0 - for motor in motor_list: + + for i, motor in enumerate(motor_list): self.angle_device_names[self.angle_names[i]] = motor.split(' ')[0] - i = i + 1 # TODO: it should not be necessary to implement on_stop methods in the # macros in order to stop the moveables. Macro API should provide this kind @@ -219,8 +218,8 @@ class br(Macro, _diffrac, Hookable): def prepare(self, H, K, L, AnglesIndex, FlagNotBlocking, FlagPrinting): _diffrac.prepare(self) self.motors = [] - for name in self.angle_names: - self.motors.append(self.getMotor(name)) + for motor_name in self.angle_device_names.values(): + self.motors.append(self.getMotor(motor_name)) def run(self, H, K, L, AnglesIndex, FlagNotBlocking, FlagPrinting): h_idx = 0 @@ -290,8 +289,8 @@ class ubr(Macro, _diffrac, Hookable): def prepare(self, hh, kk, ll, AnglesIndex): _diffrac.prepare(self) self.motors = [] - for name in self.angle_names: - self.motors.append(self.getMotor(name)) + for motor_name in self.angle_device_names.values(): + self.motors.append(self.getMotor(motor_name)) def run(self, hh, kk, ll, AnglesIndex): if ll != "Not set": From 4f0ca9efab3223f23c143b6bcbb79d5da7f833b2 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Fri, 26 Mar 2021 12:05:54 +0100 Subject: [PATCH 224/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9e44346e6..149d2b9a02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Storing string values as custom data in NXscanH5_FileRecorder (#1485) * Stopping/aborting grouped movement when backlash correction would be applied (#1421, #1474, #1539) * Fill parent_macro in case of executing XML hooks e.g. in sequencer (#1497) +* Remove redundant print of positions at the end of umv (#1526) * Problems with macro id's when `sequencer` executes from _plain text_ files (#1215, #1216) * `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) * Allow running Spock without Qt bindings (#1462, #1463) From ea88211d53c33f1b7b2b59b5cbb22732607deb3c Mon Sep 17 00:00:00 2001 From: reszelaz Date: Fri, 26 Mar 2021 12:57:14 +0100 Subject: [PATCH 225/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 149d2b9a02..c8964f9b4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ This file follows the formats and conventions from [keepachangelog.com] and not part of the measurement group configuration (#1296, #1466) * Use `LatestDeviceImpl` (currently `Device_5Impl`) for as a base class of the Sardana Tango devices (#1214, #1301, #1531) +* Read experimental channel's `value` in serial mode to avoid involvement of a worker thread (#1512) ### Removed From e389f6018ddc8685ec18edddd4812183110ec1e2 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Mon, 29 Mar 2021 11:42:17 +0200 Subject: [PATCH 226/279] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8964f9b4d..eefa78bea6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Documentation on how to start Tango servers on fixed IP - ORBendPoint (#1470) * Documentation example on how to more efficiently access Tango with PyTango in macros/controllers (#1456) +* napoleon extension to the sphinx configuration (#1533) * LICENSE file to python source distribution (#1490) ### Fixed @@ -57,6 +58,8 @@ This file follows the formats and conventions from [keepachangelog.com] * Setting `Hookable.hooks` to empty list (#1522) * `Macro.hasResult()` and `Macro.hasParams()` what avoids adding empty _Parameters_ and _Result_ sections in the macro description in Spock (#1524) +* Apply position formatting (configured with `PosFormat` _view option_) + to the limits in the `wm` macro (#1529, #1530) * Use equality instead of identity checks for numbers and strings (#1491) * Docstring of QtSpockWidget (#1484) * Recorders tests helpers (#1439) From 7feacbdb9d38309bee52be99d856611f21f620f9 Mon Sep 17 00:00:00 2001 From: Alberto Date: Mon, 29 Mar 2021 16:36:39 +0200 Subject: [PATCH 227/279] add sardana_init_hook Add sardana_init_hook to SardanaDevice for allowing the subscribe to pool's elements attribute in the init of the MacroServer. Based on the response of Michal to https://gitlab.com/tango-controls/pytango/-/merge_requests/419. Modify the run_tango_server to replicate the behaviour of post_init_callback found in PyTango High-level API which is invoked before server enters event loop. Fixes #674. --- src/sardana/tango/core/SardanaDevice.py | 8 ++++++++ src/sardana/tango/core/util.py | 8 ++++++++ src/sardana/tango/macroserver/MacroServer.py | 3 +++ 3 files changed, 19 insertions(+) diff --git a/src/sardana/tango/core/SardanaDevice.py b/src/sardana/tango/core/SardanaDevice.py index 7d8d9ca70c..5539b084b8 100644 --- a/src/sardana/tango/core/SardanaDevice.py +++ b/src/sardana/tango/core/SardanaDevice.py @@ -170,6 +170,14 @@ def init_device(self): non_detect_evts = () self.set_change_events(detect_evts, non_detect_evts) + def sardana_init_hook(self): + """Hook that is called before the server event loop. + + The idea behind this hook is to be equivalent to server_init_hook from + Tango. Similar behaviour can be archived using post_init_callback. + """ + pass + def _get_nodb_device_info(self): """Internal method. Returns the device info when tango database is not being used (example: in demos)""" diff --git a/src/sardana/tango/core/util.py b/src/sardana/tango/core/util.py index 293aea9a8e..0bb258dd84 100644 --- a/src/sardana/tango/core/util.py +++ b/src/sardana/tango/core/util.py @@ -1218,12 +1218,20 @@ def prepare_rconsole(options, args, tango_args): def run_tango_server(tango_util=None, start_time=None): + # Import here to avoid circular import + from sardana.tango.core.SardanaDevice import SardanaDevice + try: if tango_util is None: tango_util = Util(sys.argv) util = Util.instance() SardanaServer.server_state = State.Init util.server_init() + + for device in util.get_device_list("*"): + if isinstance(device, SardanaDevice): + device.sardana_init_hook() + SardanaServer.server_state = State.Running if start_time is not None: import datetime diff --git a/src/sardana/tango/macroserver/MacroServer.py b/src/sardana/tango/macroserver/MacroServer.py index b21c0f14c6..7c651c22ee 100644 --- a/src/sardana/tango/macroserver/MacroServer.py +++ b/src/sardana/tango/macroserver/MacroServer.py @@ -131,6 +131,9 @@ def init_device(self): self.debug("Details:", exc_info=1) self.set_state(DevState.ON) + def sardana_init_hook(self): + self.macro_server.set_pool_names(self.PoolNames) + def _calculate_name(self, name): if name is None: return None From 5d32934c59556a0de8b028e2f6e85010634d6532 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 29 Mar 2021 22:58:02 +0200 Subject: [PATCH 228/279] doc: remove highlight from pseudo code --- .../devel/howto_controllers/howto_countertimercontroller.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst index ccd319b2af..2631aef696 100644 --- a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst @@ -333,7 +333,9 @@ implementation of all other start methods is optional and their default implementation does nothing (:meth:`~sardana.pool.controller.Startable.PreStartOne` actually returns ``True``). -So, actually, the algorithm for counter acquisition start in sardana is:: +So, actually, the algorithm for counter acquisition start in sardana is: + +.. code-block:: text /FOR/ Each controller(s) implied in the acquisition - Call PreStartAll() From 26d877869e7620e5eada0cc709f6adecf979cb1b Mon Sep 17 00:00:00 2001 From: reszelaz Date: Mon, 29 Mar 2021 23:06:44 +0200 Subject: [PATCH 229/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eefa78bea6..7b7a074e8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Allow running Spock without Qt bindings (#1462, #1463) * Fix getting macroserver from remote door in Sardana-Taurus Door extension (#1506) * MacroServer opening empty environment files used with dumb backend (#1425, #1514, #1517, #1520) +* Respect timer/monitor passed in measurement group configuration (#1516, #1521) * Setting `Hookable.hooks` to empty list (#1522) * `Macro.hasResult()` and `Macro.hasParams()` what avoids adding empty _Parameters_ and _Result_ sections in the macro description in Spock (#1524) From f6b0ebafb2642483ba82ce0662b59366337e6a18 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 29 Mar 2021 23:24:33 +0200 Subject: [PATCH 230/279] bug: remove redundant set of pools from init_device() Pools are now set in the sardana_init_hook(). Remove the redundant call from the init_device(). --- src/sardana/tango/macroserver/MacroServer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sardana/tango/macroserver/MacroServer.py b/src/sardana/tango/macroserver/MacroServer.py index 7c651c22ee..56b341534c 100644 --- a/src/sardana/tango/macroserver/MacroServer.py +++ b/src/sardana/tango/macroserver/MacroServer.py @@ -120,7 +120,6 @@ def init_device(self): macro_server.set_recorder_path(self.RecorderPath) macro_server.set_macro_path(self.MacroPath) - macro_server.set_pool_names(self.PoolNames) if self.RConsolePort: try: From 9f5e1a5eec6d48d66ffcffeed08fd2da43b2577f Mon Sep 17 00:00:00 2001 From: reszelaz Date: Mon, 29 Mar 2021 23:54:22 +0200 Subject: [PATCH 231/279] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b7a074e8a..6acb53a067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Allowed hooks to macro description in Spock (#1523) * Assert motor sign is -1 or 1 (#1345, #1507) * Documentation on how to write 1D and 2D controllers (#1494) +* Machanism to call `SardanaDevice.sardana_init_hook()` before entering in the server event loop (#674, #1545) * Missing documentation of SEP18 concepts to how-to counter/timer controller (#995, #1492) * Document how to properly deal with exceptions in macros in order to not interfer with macro stopping/aborting (#1461) @@ -39,6 +40,7 @@ This file follows the formats and conventions from [keepachangelog.com] ### Fixed +* Subscribing to Pool's Elements attribute at Sardana server startup (#674, #1545) * Execute per measurement preparation in `mesh` scan macro (#1437) * Continously read value references in hardware synchronized acquisition instead of reading only at the end (#1442, #1448) From 5ddbf01cd44b570f11eaf2601e30b0c716d82028 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Tue, 30 Mar 2021 22:31:51 +0200 Subject: [PATCH 232/279] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6acb53a067..c1569703c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ This file follows the formats and conventions from [keepachangelog.com] * *scan information* and *scan point* forms to the *showscan online* widget (#1386, #1477, #1479) * `ScanPlotWidget`, `ScanPlotWindow`, `ScanInfoForm`, `ScanPointForm` and `ScanWindow` widget classes for easier composition of custom GUIs involving online scan plotting (#1386) +* Handle `pre-move` and `post-move` hooks by: `mv`, `mvr`, `umv`, `umvr`, `br`, `ubr` (#1471, #1480) + * `motors` attribute to these macros which contains list of motors that will be moved + * `sardanacustomettings.PRE_POST_MOVE_HOOK_IN_MV` for disabling these hooks * Include trigger/gate elements in the per-measurement preparation (#1432, #1443, #1468) * Add `PrepareOne()` to TriggerGate controller. * Call TriggerGate controller preparation methods in the _acquision action_ From 196f811373cfb6793980ad7a119f33a37553fd9e Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 30 Mar 2021 22:47:27 +0200 Subject: [PATCH 233/279] Add 3.1.0 release in the CHANGELOG --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1569703c2..53c734ff64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). This file follows the formats and conventions from [keepachangelog.com] -## [Unreleased] +## [3.1.0] 2021-03-30 ### Added @@ -1030,7 +1030,7 @@ Main improvements since sardana 1.5.0 (aka Jan15): [keepachangelog.com]: http://keepachangelog.com -[Unreleased]: https://github.com/sardana-org/sardana/compare/3.0.3...HEAD +[3.1.0]: https://github.com/sardana-org/sardana/compare/3.1.0...3.0.3 [3.0.3]: https://github.com/sardana-org/sardana/compare/3.0.3...2.8.6 [2.8.6]: https://github.com/sardana-org/sardana/compare/2.8.6...2.8.5 [2.8.5]: https://github.com/sardana-org/sardana/compare/2.8.5...2.8.4 From 932532a69cf9c70fb13dfcb0957004db8a1cc339 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 30 Mar 2021 22:48:09 +0200 Subject: [PATCH 234/279] Bump version 3.0.4-alpha to 3.1.0-alpha --- .bumpversion.cfg | 2 +- src/sardana/release.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index d96215968e..acb599c6f1 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -3,7 +3,7 @@ commit = True message = Bump version {current_version} to {new_version} tag = False tag_name = {new_version} -current_version = 3.0.4-alpha +current_version = 3.1.0-alpha parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? serialize = {major}.{minor}.{patch}-{release} diff --git a/src/sardana/release.py b/src/sardana/release.py index d013fa377e..f2d84116c0 100644 --- a/src/sardana/release.py +++ b/src/sardana/release.py @@ -47,7 +47,7 @@ # we use semantic versioning (http://semver.org/) and we update it using the # bumpversion script (https://github.com/peritus/bumpversion) -version = '3.0.4-alpha' +version = '3.1.0-alpha' description = "instrument control and data acquisition system" From 028c2c472fe29ffa408da88971f888ea2f2dd80b Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 30 Mar 2021 22:48:09 +0200 Subject: [PATCH 235/279] Bump version 3.1.0-alpha to 3.1.0 --- .bumpversion.cfg | 2 +- src/sardana/release.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index acb599c6f1..be2e956cc8 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -3,7 +3,7 @@ commit = True message = Bump version {current_version} to {new_version} tag = False tag_name = {new_version} -current_version = 3.1.0-alpha +current_version = 3.1.0 parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? serialize = {major}.{minor}.{patch}-{release} diff --git a/src/sardana/release.py b/src/sardana/release.py index f2d84116c0..d9af23114a 100644 --- a/src/sardana/release.py +++ b/src/sardana/release.py @@ -47,7 +47,7 @@ # we use semantic versioning (http://semver.org/) and we update it using the # bumpversion script (https://github.com/peritus/bumpversion) -version = '3.1.0-alpha' +version = '3.1.0' description = "instrument control and data acquisition system" From 73800aa8502a92447399712ece2e23eaa2fbe842 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 30 Mar 2021 22:51:20 +0200 Subject: [PATCH 236/279] Use 3.1.0 version in deprecation warnings --- src/sardana/pool/poolbasechannel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sardana/pool/poolbasechannel.py b/src/sardana/pool/poolbasechannel.py index 0638805104..f35370881c 100644 --- a/src/sardana/pool/poolbasechannel.py +++ b/src/sardana/pool/poolbasechannel.py @@ -692,7 +692,7 @@ def read_shape(self): ElementType.TwoDExpChannel): self.warning( "not implementing shape axis parameter in 1D and 2D " - "controllers is deprecated since Jan21") + "controllers is deprecated since 3.1.0") value = self.value.value if value is None: self.debug("could not get shape from value") From 1844200835f9b3f86ed748a85b38114499505538 Mon Sep 17 00:00:00 2001 From: teresa Date: Wed, 7 Apr 2021 12:29:18 +0200 Subject: [PATCH 237/279] Using PosFormat in umv --- src/sardana/macroserver/macros/env.py | 4 ++-- src/sardana/macroserver/macros/standard.py | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/sardana/macroserver/macros/env.py b/src/sardana/macroserver/macros/env.py index e267ae432a..cdae90f1f6 100644 --- a/src/sardana/macroserver/macros/env.py +++ b/src/sardana/macroserver/macros/env.py @@ -86,7 +86,7 @@ class setvo(Macro): - **ShowDial**: used by macro wm, pwm and wa. Default value ``False`` - **ShowCtrlAxis**: used by macro wm, pwm and wa. Default value ``False`` - - **PosFormat**: used by macro wm, pwm and wa. Default value ``-1`` + - **PosFormat**: used by macro wm, pwm, wa and umv. Default value ``-1`` - **OutputBlock**: used by scan macros. Default value ``False`` - **DescriptionLength**: used by lsdef. Default value ``60`` @@ -113,7 +113,7 @@ class usetvo(Macro): - **ShowDial**: used by macro wm, pwm and wa. Default value ``False`` - **ShowCtrlAxis**: used by macro wm, pwm and wa. Default value ``False`` - - **PosFormat**: used by macro wm, pwm and wa. Default value ``-1`` + - **PosFormat**: used by macro wm, pwm, wa and umv. Default value ``-1`` - **OutputBlock**: used by scan macros. Default value ``False`` - **DescriptionLength**: used by lsdef. Default value ``60`` diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py index 9b9e7d6481..990bc62012 100755 --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -589,8 +589,12 @@ def positionChanged(self, motor, position): self.printAllPos() def printAllPos(self): - motor_width = 10 - table = Table(self.all_pos, elem_fmt=['%*.4f'], + motor_width = 10 + pos_format = self.getViewOption(ViewOption.PosFormat) + fmt = '%*.4f' + if pos_format > -1: + fmt = '%c*.%df' % ('%', int(pos_format)) + table = Table(self.all_pos, elem_fmt=[fmt], col_head_str=self.all_names, col_head_width=motor_width) self.outputBlock(table.genOutput()) self.flushOutput() From 057d67cb20c5b8d63ca5e86c1e976e90242fc63a Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 12 Apr 2021 22:35:33 +0200 Subject: [PATCH 238/279] (test) use os.pathsep as recorder paths separator in test. Fix test failing on Windows due to using wrong path separator. Use os.pathsep instead of hardcoding ":" in recorder paths (test input). For windows the separator must be ";". --- src/sardana/macroserver/test/test_msrecordermanager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sardana/macroserver/test/test_msrecordermanager.py b/src/sardana/macroserver/test/test_msrecordermanager.py index f9c91e328f..e6ca874a99 100644 --- a/src/sardana/macroserver/test/test_msrecordermanager.py +++ b/src/sardana/macroserver/test/test_msrecordermanager.py @@ -48,7 +48,8 @@ extra_recorders=1) @insertTest(helper_name='getRecorderPath', recorder_path=["/tmp/foo", "#/tmp/foo2"], expected_num_path=2) -@insertTest(helper_name='getRecorderPath', recorder_path=["/tmp/foo:/tmp/foo2"], +@insertTest(helper_name='getRecorderPath', + recorder_path=["/tmp/foo" + os.pathsep + "/tmp/foo2"], expected_num_path=3) @insertTest(helper_name='getRecorderPath', recorder_path=["/tmp/foo"], expected_num_path=2) From 64fa834e140134c56034fc2e34bea7c0d0d5c247 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 12 Apr 2021 22:47:45 +0200 Subject: [PATCH 239/279] (test) Fix Tango MeasuremengGroup tests on Windows Wrong full_name composition makes the test to fail (surprisingly only on Windows) TangoDeviceNameValidator is not capable to interpret the device names containing authority which does does not start with "//". Add this prefix to avoid the problems. --- src/sardana/tango/pool/test/test_measurementgroup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sardana/tango/pool/test/test_measurementgroup.py b/src/sardana/tango/pool/test/test_measurementgroup.py index 042db14541..069c97df61 100644 --- a/src/sardana/tango/pool/test/test_measurementgroup.py +++ b/src/sardana/tango/pool/test/test_measurementgroup.py @@ -52,7 +52,7 @@ def _get_full_name(device_proxy, logger=None): host = device_proxy.get_db_host() # this is FQDN port = device_proxy.get_db_port() db_name = host + ":" + port - full_name = db_name + "/" + device_proxy.name() + full_name = "//" + db_name + "/" + device_proxy.name() # try to use Taurus 4 to retrieve FQDN try: from taurus.core.tango.tangovalidator import TangoDeviceNameValidator From ec37ff53720bd2d233b13b3d9af76c81508ac0fd Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 12 Apr 2021 22:49:51 +0200 Subject: [PATCH 240/279] (test) Remove fallback code for Taurus 3 Sardana does not support Taurus 3. Remove unnecessary fallback. --- src/sardana/tango/pool/test/test_measurementgroup.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/sardana/tango/pool/test/test_measurementgroup.py b/src/sardana/tango/pool/test/test_measurementgroup.py index 069c97df61..b6c57879c2 100644 --- a/src/sardana/tango/pool/test/test_measurementgroup.py +++ b/src/sardana/tango/pool/test/test_measurementgroup.py @@ -35,6 +35,7 @@ import unittest from taurus.test import insertTest from taurus.core.util import CodecFactory +from taurus.core.tango.tangovalidator import TangoDeviceNameValidator from sardana import sardanacustomsettings from sardana.pool import AcqSynchType, SynchDomain, SynchParam @@ -53,13 +54,7 @@ def _get_full_name(device_proxy, logger=None): port = device_proxy.get_db_port() db_name = host + ":" + port full_name = "//" + db_name + "/" + device_proxy.name() - # try to use Taurus 4 to retrieve FQDN - try: - from taurus.core.tango.tangovalidator import TangoDeviceNameValidator - full_name, _, _ = TangoDeviceNameValidator().getNames(full_name) - # if Taurus3 in use just continue - except ImportError: - pass + full_name, _, _ = TangoDeviceNameValidator().getNames(full_name) return full_name From ad30546bb8c4f4aa2f0eec3dfeb433fb569127b1 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Mon, 12 Apr 2021 23:23:28 +0200 Subject: [PATCH 241/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53c734ff64..753b19fcc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ This file follows the formats and conventions from [keepachangelog.com] * `createMacro()` and `prepareMacro()` docstring (#1460, #1444) * Make write of MeasurementGroup (Taurus extension) integration time more robust (#1473) * String formatting when rising exceptions in pseudomotors (#1469) +* Recorder test on Windows - use `os.pathsep` as recorder paths separator (#1556) ### Changed From e4625acba44e5c494ae4b9ba6012fd37c5a0882b Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 12 Apr 2021 23:36:40 +0200 Subject: [PATCH 242/279] (test) Avoid crashes of tests at process exit Some combination of test (when not executed with other tests using Taurus >= 4.7.1) crash at exit due to PyTango#390. Register (at class level) atexit hook to call tango.ApiUtil.cleanup() in: - `MacroExecutor` (tango implementation) - used when testing macro execution - MeasurementGroup (tango) tests Multiple execution of ApiUtil.cleanup() does not harm. --- src/sardana/tango/macroserver/test/macroexecutor.py | 7 +++++++ src/sardana/tango/pool/test/test_measurementgroup.py | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/src/sardana/tango/macroserver/test/macroexecutor.py b/src/sardana/tango/macroserver/test/macroexecutor.py index 9459301336..3a43d2aa74 100644 --- a/src/sardana/tango/macroserver/test/macroexecutor.py +++ b/src/sardana/tango/macroserver/test/macroexecutor.py @@ -25,6 +25,7 @@ import copy import time +import atexit import threading import PyTango from sardana.macroserver.macros.test import BaseMacroExecutor @@ -127,6 +128,8 @@ class TangoMacroExecutor(BaseMacroExecutor): Macro executor implemented using Tango communication with the Door device ''' + _api_util_cleanup_registered = False + def __init__(self, door_name=None): super(TangoMacroExecutor, self).__init__() if door_name is None: @@ -134,6 +137,10 @@ def __init__(self, door_name=None): self._door = PyTango.DeviceProxy(door_name) self._done_event = None self._started_event = None + if not TangoMacroExecutor._api_util_cleanup_registered: + # remove whenever PyTango#390 gets fixed + atexit.register(PyTango.ApiUtil.cleanup) + TangoMacroExecutor._api_util_cleanup_registered = True def _clean(self): '''Recreates threading Events in case the macro executor is reused.''' diff --git a/src/sardana/tango/pool/test/test_measurementgroup.py b/src/sardana/tango/pool/test/test_measurementgroup.py index 042db14541..deb1bec09a 100644 --- a/src/sardana/tango/pool/test/test_measurementgroup.py +++ b/src/sardana/tango/pool/test/test_measurementgroup.py @@ -27,6 +27,7 @@ import json import os import time +import atexit import threading # TODO: decide what to use: taurus or PyTango @@ -108,10 +109,17 @@ def event_received(self, *args, **kwargs): class MeasSarTestTestCase(SarTestTestCase): """ Helper class to setup the need environmet for execute """ + _api_util_cleanup_registered = False + def setUp(self, pool_properties=None): SarTestTestCase.setUp(self, pool_properties) self.event_ids = {} self.mg_name = '_test_mg_1' + if not MeasSarTestTestCase._api_util_cleanup_registered: + # remove whenever PyTango#390 gets fixed + atexit.register(PyTango.ApiUtil.cleanup) + MeasSarTestTestCase._api_util_cleanup_registered = True + def create_meas(self, config): """ Create a meas with the given configuration From 388c91e1f75832ca7a40707cd481ddf0f45b7831 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Mon, 12 Apr 2021 23:46:15 +0200 Subject: [PATCH 243/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 753b19fcc0..3b282d7c6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Make write of MeasurementGroup (Taurus extension) integration time more robust (#1473) * String formatting when rising exceptions in pseudomotors (#1469) * Recorder test on Windows - use `os.pathsep` as recorder paths separator (#1556) +* Measurement group tango tests - wrong full name composition (#1557) ### Changed From e5f018aca84977cab0533527f359714a1ef24a22 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Mon, 12 Apr 2021 23:51:52 +0200 Subject: [PATCH 244/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b282d7c6c..3d1cec95fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Add `PrepareOne()` to TriggerGate controller. * Call TriggerGate controller preparation methods in the _acquision action_ * Add `ScanUser` environment variable (#1355) +* Support `PosFormat` _ViewOption_ in `umv` macro (#176, #1555) * Allow to programmatically disable *deterministic scan* optimization (#1426, #1427) * Initial delay in position domain to the synchronization description in *ct* like continuous scans (#1428) From 63dc5f7fb88e4c2b994c17581a0bffe8f900d870 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 14 Apr 2021 10:02:35 +0200 Subject: [PATCH 245/279] (fix) Avoid already finished macro being aborted at MS exit Macro which was already finished is considered as _running macro_ and at the MacroServer (MS) exit the running macro gets aborted. This abort is unnecessary and causes the MS to crash on Windows. - Introduce the concept of _last macro_. - When the macro finishes, update the _last macro_ information and at the same time clear the _running macro_ - this avoids the macro aborting. - Maintain the backwards compatibility when reading the macro data from the door: if it is read during the macro execution it reports the data of the macro currently being run and as soon as the macro finished it returns the data of the macro which just finished execution. --- src/sardana/macroserver/msdoor.py | 7 +++++++ src/sardana/macroserver/msmacromanager.py | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/sardana/macroserver/msdoor.py b/src/sardana/macroserver/msdoor.py index 1eb06fd24d..3d9659170f 100644 --- a/src/sardana/macroserver/msdoor.py +++ b/src/sardana/macroserver/msdoor.py @@ -129,8 +129,15 @@ def get_running_macro(self): running_macro = property(get_running_macro) + def get_last_macro(self): + return self.macro_executor.getLastMacro() + + last_macro = property(get_last_macro) + def get_macro_data(self): macro = self.running_macro + if macro is None: + macro = self.last_macro if macro is None: raise MacroServerException("No macro has run so far " + "or the macro data was not preserved.") diff --git a/src/sardana/macroserver/msmacromanager.py b/src/sardana/macroserver/msmacromanager.py index 43cc00d04c..73c96f7d34 100644 --- a/src/sardana/macroserver/msmacromanager.py +++ b/src/sardana/macroserver/msmacromanager.py @@ -1089,6 +1089,7 @@ def __init__(self, door): self._macro_stack = [] self._xml_stack = [] self._macro_pointer = None + self._last_macro = None self._abort_thread = None self._aborted = False self._stop_thread = None @@ -1399,6 +1400,9 @@ def prepareMacro(self, pars, init_opts={}, prepare_opts={}): def getRunningMacro(self): return self._macro_pointer + def getLastMacro(self): + return self._last_macro + def clearRunningMacro(self): """Clear pointer to the running macro. @@ -1726,11 +1730,13 @@ def runMacro(self, macro_obj): preserve_macro_data = macro_obj.getEnv(env_var_name) except UnknownEnv: preserve_macro_data = True - if not preserve_macro_data: + if preserve_macro_data: + self._last_macro = self._macro_pointer + else: self.debug('Macro data will not be preserved. ' + 'Set "%s" environment variable ' % env_var_name + 'to True in order to change it.') - self._macro_pointer = None + self._macro_pointer = None log_macro_manager.disable() From bfb6af36f2d8c462777a24f5c6c3e26352e8e037 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Wed, 14 Apr 2021 13:24:44 +0200 Subject: [PATCH 246/279] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d1cec95fe..1add98f1a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Avoid double printing of user units in PMTV: read widget and units widget (#1424) * Allowed hooks to macro description in Spock (#1523) * Assert motor sign is -1 or 1 (#1345, #1507) +* _last macro_ concept to the `MacroExecutor` (kernel) #1559 * Documentation on how to write 1D and 2D controllers (#1494) * Machanism to call `SardanaDevice.sardana_init_hook()` before entering in the server event loop (#674, #1545) * Missing documentation of SEP18 concepts to how-to counter/timer controller (#995, #1492) @@ -58,6 +59,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Remove redundant print of positions at the end of umv (#1526) * Problems with macro id's when `sequencer` executes from _plain text_ files (#1215, #1216) * `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) +* MacroServer crash at exit on Windows by avoiding the abort of the already finished macro (#1077, #1559) * Allow running Spock without Qt bindings (#1462, #1463) * Fix getting macroserver from remote door in Sardana-Taurus Door extension (#1506) * MacroServer opening empty environment files used with dumb backend (#1425, #1514, #1517, #1520) From 5c91b35c30398f39968bd672d07f5cfeecc25196 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 15 Apr 2021 13:45:48 +0200 Subject: [PATCH 247/279] (fix) Use h5py.string_dtype() if available (h5py >= 3) The recommended way of specifying string dtype changed in version 3 of h5py. Use the new recommendation. --- src/sardana/macroserver/recorders/h5storage.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/sardana/macroserver/recorders/h5storage.py b/src/sardana/macroserver/recorders/h5storage.py index 5fde90302b..3219d1670f 100644 --- a/src/sardana/macroserver/recorders/h5storage.py +++ b/src/sardana/macroserver/recorders/h5storage.py @@ -66,7 +66,11 @@ class NXscanH5_FileRecorder(BaseFileRecorder): """ formats = {'h5': '.h5'} # from http://docs.h5py.org/en/latest/strings.html - str_dt = h5py.special_dtype(vlen=str) # Variable-length UTF-8 + try: + str_dt = h5py.string_dtype() + except AttributeError: + # h5py < 3 + str_dt = h5py.special_dtype(vlen=str) # Variable-length UTF-8 byte_dt = h5py.special_dtype(vlen=bytes) # Variable-length UTF-8 supported_dtypes = ('float32', 'float64', 'int8', 'int16', 'int32', 'int64', 'uint8', From 5641f5d478ccc31f3be8838000fe499adfb13da0 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 15 Apr 2021 13:50:51 +0200 Subject: [PATCH 248/279] (fix) Use Dataset.asstr() instead of explicit decoding Since h5py version 3, reading string dataset returns bytes. a50a7c1e fixed it by explicit decoding. Use Dataset.asstr() according to the h5py docs. Also make a small refactoring of the dataset variable names. --- .../macroserver/recorders/h5storage.py | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/sardana/macroserver/recorders/h5storage.py b/src/sardana/macroserver/recorders/h5storage.py index 3219d1670f..944fe63dbb 100644 --- a/src/sardana/macroserver/recorders/h5storage.py +++ b/src/sardana/macroserver/recorders/h5storage.py @@ -366,10 +366,12 @@ def _endRecordList(self, recordlist): # If h5file scheme is used: Creation of a Virtual Dataset if dd.value_ref_enabled: measurement = nxentry['measurement'] - first_reference = measurement[label][0] - # in some versions of h5py we get bytes - if isinstance(first_reference, bytes): - first_reference = first_reference.decode() + try: + dataset = measurement[label].asstr() + except AttributeError: + # h5py < 3 + dataset = measurement[label] + first_reference = dataset[0] group = re.match(self.pattern, first_reference) if group is None: msg = 'Unsupported reference %s' % first_reference @@ -395,10 +397,7 @@ def _endRecordList(self, recordlist): dtype=dd_env.dtype) for i in range(nb_points): - reference = measurement[label][i] - # in some versions of h5py we get bytes - if isinstance(reference, bytes): - reference = reference.decode() + reference = dataset[i] group = re.match(self.pattern, reference) if group is None: msg = 'Unsupported reference %s' % first_reference @@ -406,10 +405,10 @@ def _endRecordList(self, recordlist): continue uri_groups = group.groupdict() filename = uri_groups["filepath"] - dataset = uri_groups["dataset"] - if dataset is None: - dataset = "dataset" - vsource = h5py.VirtualSource(filename, dataset, + remote_dataset_name = uri_groups["dataset"] + if remote_dataset_name is None: + remote_dataset_name = "dataset" + vsource = h5py.VirtualSource(filename, remote_dataset_name, shape=(dim_1, dim_2)) layout[i] = vsource From 3e2a8de15b108607a4bf87264e258a61695851d3 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 15 Apr 2021 13:52:18 +0200 Subject: [PATCH 249/279] (test) Adapt h5storage string tests to use Dataset.asstr() Since h5py version 3, reading string dataset returns bytes. Use Dataset.asstr() according to the h5py docs in order to fix the tests. --- .../recorders/test/test_h5storage.py | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/sardana/macroserver/recorders/test/test_h5storage.py b/src/sardana/macroserver/recorders/test/test_h5storage.py index d6f32cfcaf..84a91feea6 100644 --- a/src/sardana/macroserver/recorders/test/test_h5storage.py +++ b/src/sardana/macroserver/recorders/test/test_h5storage.py @@ -130,7 +130,12 @@ def test_value_ref(self): file_ = h5py.File(self.path) for i in range(nb_records): expected_data = "file:///tmp/test.edf" - data = file_["entry0"]["measurement"][COL1_NAME][i] + try: + dataset = file_["entry0"]["measurement"][COL1_NAME].asstr() + except AttributeError: + # h5py < 3 + dataset = file_["entry0"]["measurement"][COL1_NAME] + data = dataset[i] msg = "data does not match" self.assertEqual(data, expected_data, msg) @@ -202,9 +207,21 @@ def recorder(tmpdir): return NXscanH5_FileRecorder(filename=path) -@pytest.mark.parametrize("custom_data", ["str_custom_data", 8, True]) +@pytest.mark.parametrize("custom_data", [8, True]) def test_addCustomData(recorder, custom_data): name = "custom_data_name" recorder.addCustomData(custom_data, name) with h5py.File(recorder.filename) as fd: - assert fd["entry"]["custom_data"][name].value == custom_data + assert fd["entry"]["custom_data"][name][()] == custom_data + + +def test_addCustomData_str(recorder): + name = "custom_data_name" + custom_data = "str_custom_data" + recorder.addCustomData(custom_data, name) + with h5py.File(recorder.filename) as fd: + try: + dset = fd["entry"]["custom_data"][name].asstr() + except AttributeError: + dset = fd["entry"]["custom_data"][name] + assert dset[()] == custom_data \ No newline at end of file From d3570e914459f699cb134a8ca802a004ef783a4f Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 15 Apr 2021 22:44:52 +0200 Subject: [PATCH 250/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1add98f1a1..44d287b268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Storing string values in PreScanSnapshot in NXscanH5_FileRecorder (#1486) * Storing string values as custom data in NXscanH5_FileRecorder (#1485) * Stopping/aborting grouped movement when backlash correction would be applied (#1421, #1474, #1539) +* Storing string datasets with `h5py` > 3 (#1510) * Fill parent_macro in case of executing XML hooks e.g. in sequencer (#1497) * Remove redundant print of positions at the end of umv (#1526) * Problems with macro id's when `sequencer` executes from _plain text_ files (#1215, #1216) From c93de277beafdc7138a2c03aea6c0fb65f9f4515 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 16 Apr 2021 22:25:34 +0200 Subject: [PATCH 251/279] (test) Refactor h5storage tests to use pytest Tests from TestNXscanH5_FileRecorder class use the same file. If one of the tests from this class fails the this affects the rest of them. Refactor these tests to use pytest and avoid this problem. --- .../recorders/test/test_h5storage.py | 246 ++++++++---------- 1 file changed, 115 insertions(+), 131 deletions(-) diff --git a/src/sardana/macroserver/recorders/test/test_h5storage.py b/src/sardana/macroserver/recorders/test/test_h5storage.py index baa4bfe0b0..e18d8c06dd 100644 --- a/src/sardana/macroserver/recorders/test/test_h5storage.py +++ b/src/sardana/macroserver/recorders/test/test_h5storage.py @@ -63,14 +63,20 @@ def h5_write_session(fname, swmr_mode=False): COL1_NAME = "col1" ENV = { - "serialno": 0, - "starttime": None, - "title": "test", - "user": "user", - "datadesc": None, - "endtime": None + "serialno": 0, + "starttime": None, + "title": "test", + "user": "user", + "datadesc": None, + "endtime": None } +@pytest.fixture +def recorder(tmpdir): + path = str(tmpdir / "file.h5") + return NXscanH5_FileRecorder(filename=path) + + class RecordList(dict): def __init__(self, env): @@ -87,148 +93,126 @@ def __init__(self, data, recordno=0): self.recordno = recordno -class TestNXscanH5_FileRecorder(TestCase): +def test_dtype_float64(recorder): + """Test creation of dataset with float64 data type""" + nb_records = 1 + # create description of channel data + data_desc = [ + ColumnDesc(name=COL1_NAME, label=COL1_NAME, dtype="float64", + shape=tuple()) + ] + environ = ENV.copy() + environ["datadesc"] = data_desc - def setUp(self): - self.dir_name = tempfile.gettempdir() - self.path = os.path.join(self.dir_name, "test.h5") - try: - os.remove(self.path) # remove file just in case - except OSError: - pass + # simulate sardana scan + environ["starttime"] = datetime.now() + record_list = RecordList(environ) + recorder._startRecordList(record_list) + for i in range(nb_records): + record = Record({COL1_NAME: 0.1}, i) + recorder._writeRecord(record) + environ["endtime"] = datetime.now() + recorder._endRecordList(record_list) - self.env = ENV - self.record_list = RecordList(self.env) + # assert if reading datasets from the sardana file access to the + # dataset of the partial files + file_ = h5py.File(recorder.filename) + for i in range(nb_records): + expected_data = 0.1 + data = file_["entry0"]["measurement"][COL1_NAME][i] + msg = "data does not match" + assert data == expected_data, msg - def test_dtype_float64(self): - """Test creation of dataset with float64 data type""" - nb_records = 1 - # create description of channel data - data_desc = [ - ColumnDesc(name=COL1_NAME, label=COL1_NAME, dtype="float64", - shape=tuple()) - ] - self.env["datadesc"] = data_desc - # simulate sardana scan - recorder = NXscanH5_FileRecorder(filename=self.path) - self.env["starttime"] = datetime.now() - recorder._startRecordList(self.record_list) - for i in range(nb_records): - record = Record({COL1_NAME: 0.1}, i) - recorder._writeRecord(record) - self.env["endtime"] = datetime.now() - recorder._endRecordList(self.record_list) +def test_value_ref(recorder): + """Test creation of dataset with str data type""" + nb_records = 1 + # create description of channel data + data_desc = [ + ColumnDesc(name=COL1_NAME, label=COL1_NAME, dtype="float64", + shape=(1024, 1024), value_ref_enabled=True) + ] + environ = ENV.copy() + environ["datadesc"] = data_desc - # assert if reading datasets from the sardana file access to the - # dataset of the partial files - file_ = h5py.File(self.path) - for i in range(nb_records): - expected_data = 0.1 - data = file_["entry0"]["measurement"][COL1_NAME][i] - msg = "data does not match" - self.assertEqual(data, expected_data, msg) - - def test_value_ref(self): - """Test creation of dataset with str data type""" - nb_records = 1 + # simulate sardana scan + environ["starttime"] = datetime.now() + record_list = RecordList(environ) + recorder._startRecordList(record_list) + for i in range(nb_records): + record = Record({COL1_NAME: "file:///tmp/test.edf"}, i) + recorder._writeRecord(record) + environ["endtime"] = datetime.now() + recorder._endRecordList(record_list) + + # assert if reading datasets from the sardana file access to the + # dataset of the partial files + file_ = h5py.File(recorder.filename) + for i in range(nb_records): + expected_data = "file:///tmp/test.edf" + try: + dataset = file_["entry0"]["measurement"][COL1_NAME].asstr() + except AttributeError: + # h5py < 3 + dataset = file_["entry0"]["measurement"][COL1_NAME] + data = dataset[i] + msg = "data does not match" + assert data == expected_data, msg + + +@pytest.mark.skipif(not hasattr(h5py, "VirtualLayout"), + reason="VDS not available in this version of h5py") +def test_VDS(recorder): + """Test creation of VDS when channel reports URIs (str) of h5file + scheme in a simulated sardana scan (3 points). + """ + nb_records = 3 + # create partial files + part_file_name_pattern = "test_vds_part{0}.h5" + part_file_paths = [] + for i in range(nb_records): + path = os.path.join(os.path.dirname(recorder.filename), + part_file_name_pattern.format(i)) + part_file_paths.append(path) + part_file = h5py.File(path, "w") + img = numpy.array([[i, i], [i, i]]) + dataset = "dataset" + part_file.create_dataset(dataset, data=img) + part_file.flush() + part_file.close() + try: # create description of channel data data_desc = [ ColumnDesc(name=COL1_NAME, label=COL1_NAME, dtype="float64", - shape=(1024, 1024), value_ref_enabled=True) + shape=(2, 2), value_ref_enabled=True) ] - self.env["datadesc"] = data_desc + environ = ENV.copy() + environ["datadesc"] = data_desc # simulate sardana scan - recorder = NXscanH5_FileRecorder(filename=self.path) - self.env["starttime"] = datetime.now() - recorder._startRecordList(self.record_list) + environ["starttime"] = datetime.now() + record_list = RecordList(environ) + recorder._startRecordList(record_list) for i in range(nb_records): - record = Record({COL1_NAME: "file:///tmp/test.edf"}, i) + ref = "h5file://" + part_file_paths[i] + "::" + dataset + record = Record({COL1_NAME: ref}, i) recorder._writeRecord(record) - self.env["endtime"] = datetime.now() - recorder._endRecordList(self.record_list) + environ["endtime"] = datetime.now() + recorder._endRecordList(record_list) # assert if reading datasets from the sardana file access to the # dataset of the partial files - file_ = h5py.File(self.path) + file_ = h5py.File(recorder.filename) for i in range(nb_records): - expected_data = "file:///tmp/test.edf" - try: - dataset = file_["entry0"]["measurement"][COL1_NAME].asstr() - except AttributeError: - # h5py < 3 - dataset = file_["entry0"]["measurement"][COL1_NAME] - data = dataset[i] - msg = "data does not match" - self.assertEqual(data, expected_data, msg) - - def test_VDS(self): - """Test creation of VDS when channel reports URIs (str) of h5file - scheme in a simulated sardana scan (3 points). - """ - try: - h5py.VirtualLayout - except AttributeError: - self.skipTest("VDS not available in this version of h5py") - nb_records = 3 - # create partial files - part_file_name_pattern = "test_vds_part{0}.h5" - part_file_paths = [] - for i in range(nb_records): - path = os.path.join(self.dir_name, - part_file_name_pattern.format(i)) - part_file_paths.append(path) - part_file = h5py.File(path, "w") - img = numpy.array([[i, i], [i, i]]) - dataset = "dataset" - part_file.create_dataset(dataset, data=img) - part_file.flush() - part_file.close() - try: - # create description of channel data - data_desc = [ - ColumnDesc(name=COL1_NAME, label=COL1_NAME, dtype="float64", - shape=(2, 2), value_ref_enabled=True) - ] - self.env["datadesc"] = data_desc - - # simulate sardana scan - recorder = NXscanH5_FileRecorder(filename=self.path) - self.env["starttime"] = datetime.now() - recorder._startRecordList(self.record_list) - for i in range(nb_records): - ref = "h5file://" + part_file_paths[i] + "::" + dataset - record = Record({COL1_NAME: ref}, i) - recorder._writeRecord(record) - self.env["endtime"] = datetime.now() - recorder._endRecordList(self.record_list) - - # assert if reading datasets from the sardana file access to the - # dataset of the partial files - file_ = h5py.File(self.path) - for i in range(nb_records): - expected_img = numpy.array([[i, i], [i, i]]) - img = file_["entry0"]["measurement"][COL1_NAME][i] - msg = "VDS extracted image does not match" - # TODO: check if this assert works well - numpy.testing.assert_array_equal(img, expected_img, msg) - finally: - # remove partial files - for path in part_file_paths: - os.remove(path) - - def tearDown(self): - try: - os.remove(self.path) - except OSError: - pass - - -@pytest.fixture -def recorder(tmpdir): - path = str(tmpdir / "file.h5") - return NXscanH5_FileRecorder(filename=path) + expected_img = numpy.array([[i, i], [i, i]]) + img = file_["entry0"]["measurement"][COL1_NAME][i] + msg = "VDS extracted image does not match" + # TODO: check if this assert works well + numpy.testing.assert_array_equal(img, expected_img, msg) + finally: + # remove partial files + for path in part_file_paths: + os.remove(path) @pytest.mark.parametrize("custom_data", [8, True]) From 7d37add127ace978b7d7dd893740d80e4a8c8564 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 16 Apr 2021 23:03:02 +0200 Subject: [PATCH 252/279] (test) Fix SWMR tests on Windows multiprocessing.Process fails when used with local functions on Windows. Change these functions to a module level functions. --- .../recorders/test/test_h5storage.py | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/sardana/macroserver/recorders/test/test_h5storage.py b/src/sardana/macroserver/recorders/test/test_h5storage.py index e18d8c06dd..0ec9108bd6 100644 --- a/src/sardana/macroserver/recorders/test/test_h5storage.py +++ b/src/sardana/macroserver/recorders/test/test_h5storage.py @@ -259,13 +259,13 @@ def _scan(path, serialno=0): recorder._endRecordList(record_list) -def test_swmr_with_h5_session(tmpdir): +def read_file(path, ready, done): + with h5py.File(path, mode="r"): + ready.set() + done.wait() - def read_file(path, ready, done): - with h5py.File(path, mode="r"): - ready.set() - done.wait() +def test_swmr_with_h5_session(tmpdir): path = str(tmpdir / "file.h5") reader_is_ready = multiprocessing.Event() writer_is_done = multiprocessing.Event() @@ -283,23 +283,24 @@ def read_file(path, ready, done): reader.join() +@mock.patch.dict(os.environ, {"HDF5_USE_FILE_LOCKING": "FALSE"}) +def read_file_without_file_locking(path, ready, done): + with h5py.File(path, mode="r"): + ready.set() + done.wait() + + @pytest.mark.xfail( condition=h5py.version.hdf5_version_tuple < (1, 10, 1), reason="HDF5_USE_FILE_LOCKING not supported by hdf5<1.10.1" ) def test_swmr_without_h5_session(tmpdir): - - @mock.patch.dict(os.environ, {"HDF5_USE_FILE_LOCKING": "FALSE"}) - def read_file(path, ready, done): - with h5py.File(path, mode="r"): - ready.set() - done.wait() - path = str(tmpdir / "file.h5") reader_is_ready = multiprocessing.Event() writer_is_done = multiprocessing.Event() reader = multiprocessing.Process( - target=read_file, args=(path, reader_is_ready, writer_is_done) + target=read_file_without_file_locking, + args=(path, reader_is_ready, writer_is_done) ) _scan(path, serialno=0) From a427718143935be355c9351235b54c07ea16f340 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 16 Apr 2021 23:04:10 +0200 Subject: [PATCH 253/279] (test) mark test_VDS as xfail on Windows See more details in #1562 --- src/sardana/macroserver/recorders/test/test_h5storage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sardana/macroserver/recorders/test/test_h5storage.py b/src/sardana/macroserver/recorders/test/test_h5storage.py index 0ec9108bd6..65e5da7c26 100644 --- a/src/sardana/macroserver/recorders/test/test_h5storage.py +++ b/src/sardana/macroserver/recorders/test/test_h5storage.py @@ -160,6 +160,7 @@ def test_value_ref(recorder): assert data == expected_data, msg +@pytest.mark.xfail(os.name == "nt", reason="VDS are buggy on Windows") @pytest.mark.skipif(not hasattr(h5py, "VirtualLayout"), reason="VDS not available in this version of h5py") def test_VDS(recorder): From a2864015fe8729437c81fe27b7c3335db85e5e2e Mon Sep 17 00:00:00 2001 From: zreszela Date: Sat, 17 Apr 2021 17:47:34 +0200 Subject: [PATCH 254/279] (test) skip qtspock test_get_value if qtconsole >= 4.4.0 qtspock's test_get_value() fails with newer versions of qtconsole. This is because RichJupyterWidget.blocking_client (BaseFrontendMixin.blocking_client) was removed in qtconsole#174 and is not available in qtconsole >= 4.4.0. Skip the test for these versions. --- .../taurus/qt/qtgui/extra_sardana/test/test_qtspock.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/test/test_qtspock.py b/src/sardana/taurus/qt/qtgui/extra_sardana/test/test_qtspock.py index 126bb30a4c..10ed01f298 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/test/test_qtspock.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/test/test_qtspock.py @@ -1,13 +1,14 @@ import re import os import tempfile +import qtconsole import numpy as np try: from unittest.mock import patch except ImportError: from mock import patch -from taurus.external.unittest import TestCase, main +from taurus.external.unittest import TestCase, main, skipIf from taurus.external import qt from taurus.external.qt import Qt from sardana.spock.ipython_01_00.genutils import _create_config_file, \ @@ -130,6 +131,9 @@ def predicate(): class CorrectProfileTestCase(QtSpockTestCase, CorrectProfileOutputMixin): + + @skipIf(qtconsole.version_info >= (4, 4, 0), + "blocking_client was removed in qtconsole#174") def test_get_value(self): msg_id = self.widget.blocking_client.execute( "a = arange(3)", silent=True) From 6e503fdf843d03b069b3bec49cf023db66ebb55a Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 19 Apr 2021 13:42:28 +0200 Subject: [PATCH 255/279] (fix) prompt in qtspock when used with new traitlets Setting qtspock's prompt (door's alias) fails when traitlet >= 5.0.0 and Taurus models are used instead of configuration stored in the profile. This is because the traitlets library instead of returning a string door_alias now returns DeferredConfigString object. Fix it by obtaining a string value stored in this trait. --- src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py b/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py index e9a84ba2ed..a5b641ae8b 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py @@ -180,6 +180,7 @@ def __init__( self._macro_server_alias = None self._door_name = None self._door_alias = None + self._config_passed_as_extra_arguments = False self.append_stream("Waiting for kernel to start") @@ -207,6 +208,7 @@ def _extra_arguments(self): if not self.use_model_from_profile: if self._macro_server_name and self._door_name: + self._config_passed_as_extra_arguments = True extra_arguments.append("--Spock.macro_server={}".format( self._macro_server_name)) extra_arguments.append("--Spock.macro_server_alias={}".format( @@ -273,7 +275,14 @@ def _set_macro_server_name(self, door): self._macro_server_alias = None def _set_prompts(self): - var = "get_ipython().config.Spock.door_alias" + # If traitlets >= 5.0.0 then DeferredConfigString is used for values + # that are not listed in the configurable classes. Get its value. + if self._config_passed_as_extra_arguments: + self.kernel_client.execute( + "from sardana.spock.config import Spock", silent=True) + var = "get_ipython().config.Spock.door_alias.get_value(Spock.door_alias)" # noqa + else: + var = "get_ipython().config.Spock.door_alias" self._silent_exec_callback( var, self._set_prompts_callback) From 2c1efc80304682742a538c6e8b5c224c7a58620f Mon Sep 17 00:00:00 2001 From: reszelaz Date: Mon, 19 Apr 2021 13:55:55 +0200 Subject: [PATCH 256/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44d287b268..9fd469a224 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ This file follows the formats and conventions from [keepachangelog.com] * `createMacro()` and `prepareMacro()` docstring (#1460, #1444) * Make write of MeasurementGroup (Taurus extension) integration time more robust (#1473) * String formatting when rising exceptions in pseudomotors (#1469) +* h5storage tests so they pass on Windows and mark the `test_VDS` as xfail (#1562, #1563). * Recorder test on Windows - use `os.pathsep` as recorder paths separator (#1556) * Measurement group tango tests - wrong full name composition (#1557) From e19ddc81b9bc73fd2b35c0ee40f4d26d9d95968e Mon Sep 17 00:00:00 2001 From: reszelaz Date: Mon, 19 Apr 2021 13:59:47 +0200 Subject: [PATCH 257/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fd469a224..836081ab6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,7 @@ This file follows the formats and conventions from [keepachangelog.com] * h5storage tests so they pass on Windows and mark the `test_VDS` as xfail (#1562, #1563). * Recorder test on Windows - use `os.pathsep` as recorder paths separator (#1556) * Measurement group tango tests - wrong full name composition (#1557) +* Avoid crashes of certain combinations of tests on Windows at process exit (#1558) ### Changed From 2eabd7d2f80698b5e5301f3bfae4d55774165995 Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 19 Apr 2021 21:54:14 +0200 Subject: [PATCH 258/279] (fix) adapt only for traitlets >= 5 --- src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py b/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py index a5b641ae8b..2dfdfc41a3 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py @@ -36,6 +36,7 @@ import pickle import ast +import traitlets from IPython.core.profiledir import ProfileDirError, ProfileDir from taurus.external.qt import Qt @@ -277,7 +278,8 @@ def _set_macro_server_name(self, door): def _set_prompts(self): # If traitlets >= 5.0.0 then DeferredConfigString is used for values # that are not listed in the configurable classes. Get its value. - if self._config_passed_as_extra_arguments: + if (traitlets.version_info >= (5, 0, 0) + and self._config_passed_as_extra_arguments): self.kernel_client.execute( "from sardana.spock.config import Spock", silent=True) var = "get_ipython().config.Spock.door_alias.get_value(Spock.door_alias)" # noqa From 11bb616576995e87080f47f411516d609b90d97c Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 20 Apr 2021 21:53:34 +0200 Subject: [PATCH 259/279] (tests) relax funcgenerator tests on Windows funcgenerator tests on Windows are flaky. It has to do with the worst time precision on Windows and that we test on a poor VM. --- src/sardana/util/test/test_funcgenerator.py | 55 +++++++++++---------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/sardana/util/test/test_funcgenerator.py b/src/sardana/util/test/test_funcgenerator.py index d816b8e76c..d76d3d8b37 100644 --- a/src/sardana/util/test/test_funcgenerator.py +++ b/src/sardana/util/test/test_funcgenerator.py @@ -37,18 +37,18 @@ configuration_negative = [{SynchParam.Initial: {SynchDomain.Position: 0.}, SynchParam.Delay: {SynchDomain.Time: 0.1}, - SynchParam.Active: {SynchDomain.Position: -.1, - SynchDomain.Time: .01, }, - SynchParam.Total: {SynchDomain.Position: -.2, + SynchParam.Active: {SynchDomain.Position: -.5, + SynchDomain.Time: .05, }, + SynchParam.Total: {SynchDomain.Position: -1, SynchDomain.Time: 0.1}, SynchParam.Repeats: 10}] configuration_positive = [{SynchParam.Initial: {SynchDomain.Position: 0.}, SynchParam.Delay: {SynchDomain.Time: 0.3}, - SynchParam.Active: {SynchDomain.Position: .1, - SynchDomain.Time: .01, }, - SynchParam.Total: {SynchDomain.Position: .2, - SynchDomain.Time: .02}, + SynchParam.Active: {SynchDomain.Position: .5, + SynchDomain.Time: .05, }, + SynchParam.Total: {SynchDomain.Position: 1, + SynchDomain.Time: 0.1}, SynchParam.Repeats: 10}] @@ -107,18 +107,23 @@ def _done(self, _): self.event.clear() def test_sleep(self): - delta = 0 if os.name == "nt": - delta = 0.02 - for i in [0.01, 0.13, 1.2]: - stmt = "fg.sleep(%f)" % i + # (period, delta) + tests = [(0.01, 0.02), (0.13, 0.05), (1.2, 0.2)] + else: + tests = [(0.01, 0.02), (0.13, 0.02), (1.2, 0.02)] + for period, delta in tests: + stmt = "fg.sleep(%f)" % period setup = "from sardana.util.funcgenerator import FunctionGenerator;\ fg = FunctionGenerator()" - period = timeit.timeit(stmt, setup, number=1) - period_ok = i - msg = "sleep period: %f, expected: %f +/- %f" % (period, period_ok, + period_measured = timeit.timeit(stmt, setup, number=1) + msg = "sleep period: %f, expected: %f +/- %f" % (period_measured, + period, delta) - self.assertAlmostEqual(period, period_ok, delta=0.02, msg=msg) + self.assertAlmostEqual(period_measured, + period, + delta=delta, + msg=msg) def test_run_time(self): self.func_generator.initial_domain = SynchDomain.Time @@ -159,8 +164,8 @@ def test_run_position_negative(self): self.thread_pool.add(self.func_generator.run, self._done) while not self.func_generator.is_running(): time.sleep(0.1) - self.thread_pool.add(position.run, None, 0, -2, -.01) - self.event.wait(3) + self.thread_pool.add(position.run, None, 0, -10, -.05) + self.event.wait(4) position.remove_listener(self.func_generator) active_event_ids = self.listener.active_event_ids active_event_ids_ok = list(range(0, 10)) @@ -179,8 +184,8 @@ def test_run_position_positive(self): self.thread_pool.add(self.func_generator.run, self._done) while not self.func_generator.is_running(): time.sleep(0.1) - self.thread_pool.add(position.run, None, 0, 2, .01) - self.event.wait(3) + self.thread_pool.add(position.run, None, 0, 10, .05) + self.event.wait(4) position.remove_listener(self.func_generator) active_event_ids = self.listener.active_event_ids active_event_ids_ok = list(range(0, 10)) @@ -193,12 +198,12 @@ def test_configuration_position(self): self.func_generator.active_domain = SynchDomain.Position self.func_generator.set_configuration(configuration_negative) active_events = self.func_generator.active_events - active_events_ok = numpy.arange(0, -2, -0.2).tolist() + active_events_ok = numpy.arange(0, -9, -1).tolist() msg = "Active events are wrong: %s" % active_events for a, b in zip(active_events, active_events_ok): self.assertAlmostEqual(a, b, 10, msg) passive_events = self.func_generator.passive_events - passive_events_ok = numpy.arange(-.1, -2.1, -0.2).tolist() + passive_events_ok = numpy.arange(-.5, -9.5, -1).tolist() msg = "Passive events are wrong: %s" % passive_events for a, b in zip(passive_events, passive_events_ok): self.assertAlmostEqual(a, b, 10, msg) @@ -208,13 +213,13 @@ def test_configuration_time(self): self.func_generator.active_domain = SynchDomain.Time self.func_generator.set_configuration(configuration_positive) active_events = self.func_generator.active_events - active_events_ok = numpy.arange(.3, .5, 0.02).tolist() + active_events_ok = numpy.arange(.3, 1.2, 0.1).tolist() msg = ("Active events mismatch, received: %s, expected: %s" % (active_events, active_events_ok)) for a, b in zip(active_events, active_events_ok): self.assertAlmostEqual(a, b, 10, msg) passive_events = self.func_generator.passive_events - passive_events_ok = numpy.arange(.31, 0.51, 0.02).tolist() + passive_events_ok = numpy.arange(.35, 1.25, 0.1).tolist() msg = ("Passive events mismatch, received: %s, expected: %s" % (passive_events, passive_events_ok)) for a, b in zip(passive_events, passive_events_ok): @@ -223,13 +228,13 @@ def test_configuration_time(self): def test_configuration_default(self): self.func_generator.set_configuration(configuration_positive) active_events = self.func_generator.active_events - active_events_ok = numpy.arange(0, 2, 0.2).tolist() + active_events_ok = numpy.arange(0, 9, 1).tolist() msg = ("Active events mismatch, received: %s, expected: %s" % (active_events, active_events_ok)) for a, b in zip(active_events, active_events_ok): self.assertAlmostEqual(a, b, 10, msg) passive_events = self.func_generator.passive_events - passive_events_ok = numpy.arange(.31, .51, 0.02).tolist() + passive_events_ok = numpy.arange(.35, 1.25, 0.1).tolist() msg = ("Passive events mismatch, received: %s, expected: %s" % (passive_events, passive_events_ok)) for a, b in zip(passive_events, passive_events_ok): From 24b5f186eec5be955e1e176fe28027c66c311e65 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 20 Apr 2021 22:28:18 +0200 Subject: [PATCH 260/279] (tests) skip stress count with CT (HW trigger) and 0D on Windows This configuration regularly fails on Windows. There is no way to mark a test generated with @insertTest as flaky or expected failure. Hardcode the skip in the helper method directly. --- .../taurus/core/tango/sardana/test/test_measgrpstress.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py b/src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py index 5f751c7d4c..06de08e789 100644 --- a/src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py +++ b/src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py @@ -67,6 +67,11 @@ def setUp(self): registerExtensions() def stress_count(self, elements, repeats, synchronizer, synchronization): + if (elements == ["_test_ct_1_1", "_test_0d_1_1"] + and synchronizer == "_test_tg_1_1" + and synchronization == AcqSynchType.Trigger + and os.name == "nt"): + self.skipTest("fails on Windows") mg_name = str(uuid.uuid1()) argin = [mg_name] + elements self.pool.CreateMeasurementGroup(argin) From 6df1ee09481e159c0f8f835188b432b98973788c Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 20 Apr 2021 22:49:01 +0200 Subject: [PATCH 261/279] (test) skip some configuration tests Some measurement group configuration tests - the ones setting a timer/monitor/synchronizer for an external channel causes troubles on Windows. Skip these configurations only. --- .../core/tango/sardana/test/test_measgrpconf.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/sardana/taurus/core/tango/sardana/test/test_measgrpconf.py b/src/sardana/taurus/core/tango/sardana/test/test_measgrpconf.py index d7addfc2de..6f193e385a 100644 --- a/src/sardana/taurus/core/tango/sardana/test/test_measgrpconf.py +++ b/src/sardana/taurus/core/tango/sardana/test/test_measgrpconf.py @@ -1,3 +1,4 @@ +import os import uuid import unittest from taurus import Device @@ -279,8 +280,9 @@ def test_Timer(self, elements=["_test_ct_1_1", "_test_ct_1_2", mg = Device(mg_name) result = mg.getTimer("_test_mt_1_3/position") - with self.assertRaises(Exception): - mg.setTimer("_test_mt_1_3/position") + if os.name != "nt": + with self.assertRaises(Exception): + mg.setTimer("_test_mt_1_3/position") self._assertResult(result, ["_test_mt_1_3/position"], None) mg.setTimer('_test_ct_1_3') result = mg.getTimer(*elements) @@ -323,8 +325,9 @@ def test_Monitor(self, elements=["_test_ct_1_1", "_test_ct_1_2", try: mg = Device(mg_name) - with self.assertRaises(Exception): - mg.setMonitor("_test_mt_1_3/position") + if os.name != "nt": + with self.assertRaises(Exception): + mg.setMonitor("_test_mt_1_3/position") mg.setMonitor('_test_2d_1_2') mg.setMonitor("_test_ct_1_3") @@ -365,8 +368,10 @@ def test_Synchronizer(self, elements=["_test_ct_1_1", "_test_ct_1_2", result = mg.getSynchronizer() expected = ['software', 'software', 'software', 'software', None] self._assertMultipleResults(result, elements, expected) - with self.assertRaises(Exception): - mg.setSynchronizer('_test_tg_1_2', "_test_mt_1_3/position") + + if os.name != "nt": + with self.assertRaises(Exception): + mg.setSynchronizer('_test_tg_1_2', "_test_mt_1_3/position") mg.setSynchronizer('_test_tg_1_2', "_test_ct_ctrl_1", "_test_2d_ctrl_1") From 7e7f08bf58085ab347fa5d613fd90def4d613c20 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 20 Apr 2021 22:55:48 +0200 Subject: [PATCH 262/279] (fix) add missing import of os --- src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py b/src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py index 06de08e789..710810fb99 100644 --- a/src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py +++ b/src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py @@ -23,6 +23,7 @@ ## ############################################################################## +import os import uuid from unittest import TestCase From e43d6f533339b24a7dcc024d8c960f3f3e431014 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 20 Apr 2021 23:11:27 +0200 Subject: [PATCH 263/279] (test) avoid executing Pool's DeleteElement() cmd on Windows Calling Pool's DeleteElement() command at the end of the tests very often crashes the server on Windows. This deletion is not necessary cause at the end the device server will be deleted anyway. Skip execution of this command on Windows. Fixes #540. --- src/sardana/tango/pool/test/base_sartest.py | 8 +++-- src/sardana/tango/pool/test/test_Motor.py | 7 +++-- .../tango/pool/test/test_measurementgroup.py | 3 +- .../tango/pool/test/test_persistence.py | 7 +++-- .../tango/sardana/test/test_measgrpconf.py | 30 ++++++++++++------- .../tango/sardana/test/test_measgrpstress.py | 3 +- .../core/tango/sardana/test/test_pool.py | 11 ++++--- 7 files changed, 47 insertions(+), 22 deletions(-) diff --git a/src/sardana/tango/pool/test/base_sartest.py b/src/sardana/tango/pool/test/base_sartest.py index cbd7901501..28ff386303 100644 --- a/src/sardana/tango/pool/test/base_sartest.py +++ b/src/sardana/tango/pool/test/base_sartest.py @@ -23,6 +23,8 @@ ## ############################################################################## +import os + import PyTango import taurus @@ -168,7 +170,8 @@ def tearDown(self): if elem_name in f.tango_alias_devs: _cleanup_device(elem_name) try: - self.pool.DeleteElement(elem_name) + if os.name != "nt": + self.pool.DeleteElement(elem_name) except Exception as e: print(e) dirty_elems.append(elem_name) @@ -182,7 +185,8 @@ def tearDown(self): if ctrl_name in f.tango_alias_devs: _cleanup_device(ctrl_name) try: - self.pool.DeleteElement(ctrl_name) + if os.name != "nt": + self.pool.DeleteElement(ctrl_name) except: dirty_ctrls.append(ctrl_name) diff --git a/src/sardana/tango/pool/test/test_Motor.py b/src/sardana/tango/pool/test/test_Motor.py index 52f1d8f683..6c113bf4c3 100644 --- a/src/sardana/tango/pool/test/test_Motor.py +++ b/src/sardana/tango/pool/test/test_Motor.py @@ -24,6 +24,8 @@ ############################################################################## """Tests Read Position from Sardana using PyTango""" +import os + import PyTango import unittest from sardana.tango.pool.test import BasePoolTestCase @@ -70,6 +72,7 @@ def test_read_position_outside_sw_lim(self): def tearDown(self): """Remove motor element and motor controller """ - self.pool.DeleteElement(self.elem_name) - self.pool.DeleteElement(self.ctrl_name) + if os.name != "nt": + self.pool.DeleteElement(self.elem_name) + self.pool.DeleteElement(self.ctrl_name) super(ReadMotorPositionOutsideLim, self).tearDown() diff --git a/src/sardana/tango/pool/test/test_measurementgroup.py b/src/sardana/tango/pool/test/test_measurementgroup.py index 8b6bb4a946..6873aed7b0 100644 --- a/src/sardana/tango/pool/test/test_measurementgroup.py +++ b/src/sardana/tango/pool/test/test_measurementgroup.py @@ -282,7 +282,8 @@ def tearDown(self): channel.unsubscribe_event(event_id) try: # Delete the meas - self.pool.DeleteElement(self.mg_name) + if os.name != "nt": + self.pool.DeleteElement(self.mg_name) except Exception as e: print('Impossible to delete MeasurementGroup: %s' % self.mg_name) diff --git a/src/sardana/tango/pool/test/test_persistence.py b/src/sardana/tango/pool/test/test_persistence.py index cac52dc997..c2a2134bbf 100644 --- a/src/sardana/tango/pool/test/test_persistence.py +++ b/src/sardana/tango/pool/test/test_persistence.py @@ -22,6 +22,7 @@ # along with Sardana. If not, see . ## ############################################################################## +import os import PyTango import unittest @@ -86,11 +87,13 @@ def tearDown(self): cleanup_success = True if self.do_element_cleanup: try: - self.pool.DeleteElement(self.elem_name) + if os.name != "nt": + self.pool.DeleteElement(self.elem_name) except: cleanup_success = False try: - self.pool.DeleteElement(self.ctrl_name) + if os.name != "nt": + self.pool.DeleteElement(self.ctrl_name) except: cleanup_success = False BasePoolTestCase.tearDown(self) diff --git a/src/sardana/taurus/core/tango/sardana/test/test_measgrpconf.py b/src/sardana/taurus/core/tango/sardana/test/test_measgrpconf.py index 6f193e385a..46f30d6eb3 100644 --- a/src/sardana/taurus/core/tango/sardana/test/test_measgrpconf.py +++ b/src/sardana/taurus/core/tango/sardana/test/test_measgrpconf.py @@ -91,7 +91,8 @@ def test_enabled(self, elements=["_test_ct_1_1", "_test_ct_1_2", self._assertResult(resutl, full_names, True) finally: mg.cleanUp() - self.pool.DeleteElement(mg_name) + if os.name != "nt": + self.pool.DeleteElement(mg_name) def test_output(self, elements=["_test_ct_1_1", "_test_ct_1_2", "_test_2d_1_3", @@ -146,7 +147,8 @@ def test_output(self, elements=["_test_ct_1_1", "_test_ct_1_2", self._assertResult(is_output, full_names, True) finally: mg.cleanUp() - self.pool.DeleteElement(mg_name) + if os.name != "nt": + self.pool.DeleteElement(mg_name) def test_PlotType(self, elements=["_test_ct_1_1", "_test_ct_1_2", "_test_ct_1_3", "_test_2d_1_3", @@ -194,7 +196,8 @@ def test_PlotType(self, elements=["_test_ct_1_1", "_test_ct_1_2", finally: mg.cleanUp() - self.pool.DeleteElement(mg_name) + if os.name != "nt": + self.pool.DeleteElement(mg_name) def test_PlotAxes(self, elements=["_test_ct_1_1", "_test_ct_1_2", "_test_ct_1_3", "_test_2d_1_3", @@ -268,7 +271,8 @@ def test_PlotAxes(self, elements=["_test_ct_1_1", "_test_ct_1_2", self._assertMultipleResults(result, full_names, expected_result) finally: mg.cleanUp() - self.pool.DeleteElement(mg_name) + if os.name != "nt": + self.pool.DeleteElement(mg_name) def test_Timer(self, elements=["_test_ct_1_1", "_test_ct_1_2", "_test_ct_1_3", @@ -313,7 +317,8 @@ def test_Timer(self, elements=["_test_ct_1_1", "_test_ct_1_2", self._assertResult(result, full_names, "_test_ct_1_2") finally: mg.cleanUp() - self.pool.DeleteElement(mg_name) + if os.name != "nt": + self.pool.DeleteElement(mg_name) def test_Monitor(self, elements=["_test_ct_1_1", "_test_ct_1_2", "_test_ct_1_3", "_test_2d_1_1", @@ -355,7 +360,8 @@ def test_Monitor(self, elements=["_test_ct_1_1", "_test_ct_1_2", self._assertMultipleResults(result, full_names, expected) finally: mg.cleanUp() - self.pool.DeleteElement(mg_name) + if os.name != "nt": + self.pool.DeleteElement(mg_name) def test_Synchronizer(self, elements=["_test_ct_1_1", "_test_ct_1_2", "_test_ct_1_3", "_test_2d_1_1", @@ -405,7 +411,8 @@ def test_Synchronizer(self, elements=["_test_ct_1_1", "_test_ct_1_2", finally: mg.cleanUp() - self.pool.DeleteElement(mg_name) + if os.name != "nt": + self.pool.DeleteElement(mg_name) def test_Synchronization(self, elements=["_test_ct_1_1", "_test_ct_1_2", "_test_ct_1_3", "_test_2d_1_1", @@ -457,7 +464,8 @@ def test_Synchronization(self, elements=["_test_ct_1_1", "_test_ct_1_2", finally: mg.cleanUp() - self.pool.DeleteElement(mg_name) + if os.name != "nt": + self.pool.DeleteElement(mg_name) def test_ValueRefEnabled(self, elements=["_test_2d_1_1", "_test_2d_1_2", "_test_ct_1_3", @@ -517,7 +525,8 @@ def test_ValueRefEnabled(self, elements=["_test_2d_1_1", "_test_2d_1_2", self._assertResult(enabled, full_names, True) finally: mg.cleanUp() - self.pool.DeleteElement(mg_name) + if os.name != "nt": + self.pool.DeleteElement(mg_name) def test_ValueRefPattern(self, elements=["_test_2d_1_1", "_test_2d_1_2", "_test_ct_1_3", @@ -570,4 +579,5 @@ def test_ValueRefPattern(self, elements=["_test_2d_1_1", "_test_2d_1_2", finally: mg.cleanUp() - self.pool.DeleteElement(mg_name) + if os.name != "nt": + self.pool.DeleteElement(mg_name) diff --git a/src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py b/src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py index 710810fb99..3c03c6604a 100644 --- a/src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py +++ b/src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py @@ -90,7 +90,8 @@ def stress_count(self, elements, repeats, synchronizer, synchronization): self.assertTrue(is_numerical(value), msg) finally: mg.cleanUp() - self.pool.DeleteElement(mg_name) + if os.name != "nt": + self.pool.DeleteElement(mg_name) def tearDown(self): SarTestTestCase.tearDown(self) diff --git a/src/sardana/taurus/core/tango/sardana/test/test_pool.py b/src/sardana/taurus/core/tango/sardana/test/test_pool.py index 292b4ef2aa..8166b01689 100644 --- a/src/sardana/taurus/core/tango/sardana/test/test_pool.py +++ b/src/sardana/taurus/core/tango/sardana/test/test_pool.py @@ -23,7 +23,7 @@ ## ############################################################################## - +import os import uuid import numpy @@ -78,7 +78,8 @@ def count(self, elements): self.assertTrue(is_numerical(value), msg) finally: mg.cleanUp() - self.pool.DeleteElement(mg_name) + if os.name != "nt": + self.pool.DeleteElement(mg_name) def tearDown(self): SarTestTestCase.tearDown(self) @@ -107,7 +108,8 @@ def test_value_ref_enabled(self): finally: channel.cleanUp() mg.cleanUp() - self.pool.DeleteElement(mg_name) + if os.name != "nt": + self.pool.DeleteElement(mg_name) def test_value_ref_disabled(self): mg_name = str(uuid.uuid1()) @@ -126,7 +128,8 @@ def test_value_ref_disabled(self): finally: channel.cleanUp() mg.cleanUp() - self.pool.DeleteElement(mg_name) + if os.name != "nt": + self.pool.DeleteElement(mg_name) def tearDown(self): SarTestTestCase.tearDown(self) From de20fdc796e86afd7dd38e440b112c96dc927c84 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 22 Apr 2021 10:00:05 +0200 Subject: [PATCH 264/279] (test) increase timeout for qtspock tests On Windows it was observed that the qtspock tests that have timeout set to 5 s sporadically fail. The ones with the timeout set to 10 s works well. This happened only when executed within the testsuite, when executed separately it was not observed. As an immediate solution, unify all timeouts in all tests to 10 s. --- .../qt/qtgui/extra_sardana/test/test_qtspock.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/test/test_qtspock.py b/src/sardana/taurus/qt/qtgui/extra_sardana/test/test_qtspock.py index 126bb30a4c..c85873a2ba 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/test/test_qtspock.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/test/test_qtspock.py @@ -151,21 +151,21 @@ def predicate(): text = self.widget._control.toPlainText() matches = re.findall(r"^IPython \d\.\d", text, re.MULTILINE) return len(matches) == 1 - self.assertTrue(waitFor(predicate, 5000)) + self.assertTrue(waitFor(predicate, 10000)) def test_ipython_prompt(self): def predicate(): text = self.widget._control.toPlainText() matches = re.findall(r"^In.*\[(\d)\]", text, re.MULTILINE) return len(matches) == 1 and matches[0] == "1" - self.assertTrue(waitFor(predicate, 5000)) + self.assertTrue(waitFor(predicate, 10000)) def test_profile_error_info(self): def predicate(): text = self.widget._control.toPlainText() matches = re.findall(r"^Spock profile error", text, re.MULTILINE) return len(matches) == 1 - self.assertTrue(waitFor(predicate, 5000)) + self.assertTrue(waitFor(predicate, 10000)) class MissingProfileTestCase(QtSpockTestCase, ProfileErrorOutputMixin): @@ -193,7 +193,7 @@ def predicate(): text = cls.widget._control.toPlainText() matches = re.findall(r"\[1\]: $", text, re.MULTILINE) return len(matches) == 1 - assert waitFor(predicate, 5000) + assert waitFor(predicate, 10000) cls._create_profile() @@ -224,7 +224,7 @@ def predicate(): text = cls.widget._control.toPlainText() matches = re.findall(r"\[1\]: $", text, re.MULTILINE) return len(matches) == 1 - assert waitFor(predicate, 5000) + assert waitFor(predicate, 10000) # "Update" profile config_file = os.path.join( @@ -261,7 +261,7 @@ def predicate(): text = self.widget._control.toPlainText() matches = re.findall(r"^No door selected", text, re.MULTILINE) return len(matches) == 1 - self.assertTrue(waitFor(predicate, 5000)) + self.assertTrue(waitFor(predicate, 10000)) class QtSpockModelTestCase(QtSpockBaseTestCase, CorrectProfileOutputMixin): @@ -296,7 +296,7 @@ def predicate(): text = cls.widget._control.toPlainText() matches = re.findall(r"\[1\]: $", text, re.MULTILINE) return len(matches) == 1 - assert waitFor(predicate, 5000) + assert waitFor(predicate, 10000) cls.widget.setModel(UNITTEST_DOOR_NAME) @@ -318,7 +318,7 @@ def predicate(): text = cls.widget._control.toPlainText() matches = re.findall(r"\[1\]: $", text, re.MULTILINE) return len(matches) == 1 - assert waitFor(predicate, 5000) + assert waitFor(predicate, 10000) cls.widget.setModel("") From 53b3779d9a3506dcab6a168225dcbfedef7b11b4 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 22 Apr 2021 10:49:07 +0200 Subject: [PATCH 265/279] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 836081ab6b..0ff14e8ad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,8 @@ This file follows the formats and conventions from [keepachangelog.com] * Recorder test on Windows - use `os.pathsep` as recorder paths separator (#1556) * Measurement group tango tests - wrong full name composition (#1557) * Avoid crashes of certain combinations of tests on Windows at process exit (#1558) +* Skip execution of Pool's `DeleteElement` Tango command in tests for Windows in order to + avoid server crashes (#540, #1567) ### Changed From 6839261686ab2f6cf23ea8450e16f9fc88e1411b Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 22 Apr 2021 10:50:55 +0200 Subject: [PATCH 266/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ff14e8ad0..066fc553ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Avoid crashes of certain combinations of tests on Windows at process exit (#1558) * Skip execution of Pool's `DeleteElement` Tango command in tests for Windows in order to avoid server crashes (#540, #1567) +* Skip qtspock's `test_get_value` if qtconsole >= 4.4.0 (#1564) ### Changed From 4dd35216694651aae391325dc2c3b6151d639a67 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 22 Apr 2021 10:58:31 +0200 Subject: [PATCH 267/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 066fc553ff..b4f1fcd117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ This file follows the formats and conventions from [keepachangelog.com] sections in the macro description in Spock (#1524) * Apply position formatting (configured with `PosFormat` _view option_) to the limits in the `wm` macro (#1529, #1530) +* Prompt in QtSpock when used with new versions of the `traitlets` package (#1566) * Use equality instead of identity checks for numbers and strings (#1491) * Docstring of QtSpockWidget (#1484) * Recorders tests helpers (#1439) From 086c0fba0a503e7633367a2df0d31ec149dac8c2 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 22 Apr 2021 11:02:00 +0200 Subject: [PATCH 268/279] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4f1fcd117..748ca512c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,7 +84,8 @@ This file follows the formats and conventions from [keepachangelog.com] * Avoid crashes of certain combinations of tests on Windows at process exit (#1558) * Skip execution of Pool's `DeleteElement` Tango command in tests for Windows in order to avoid server crashes (#540, #1567) -* Skip qtspock's `test_get_value` if qtconsole >= 4.4.0 (#1564) +* Skip QtSpock `test_get_value` test if qtconsole >= 4.4.0 (#1564) +* Increase timeout for QtSpock tests (#1568) ### Changed From d67df23fdccd25abf4991669d25a433bc7b8c955 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 22 Apr 2021 16:14:25 +0200 Subject: [PATCH 269/279] (fix) dummy 2D reaction on "hardware" trigger Very demanding (fast) synchronizations may not emit active events with 0 index. It is better to react on the start event. Similar implementation was already done for the C/T in 391c1102. --- src/sardana/pool/poolcontrollers/DummyTwoDController.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sardana/pool/poolcontrollers/DummyTwoDController.py b/src/sardana/pool/poolcontrollers/DummyTwoDController.py index f48f63cb4b..d648a3f5dc 100644 --- a/src/sardana/pool/poolcontrollers/DummyTwoDController.py +++ b/src/sardana/pool/poolcontrollers/DummyTwoDController.py @@ -497,7 +497,7 @@ def event_received(self, src, type_, value): e.g. start, active passive """ # for the moment only react on first trigger - if type_.name.lower() == "active" and value == 0: + if type_.name.lower() == "start": self._armed = False for axis, channel in self.counting_channels.items(): channel.is_counting = True From bc1a9d025551427215688d5d0a07777b86bd32f9 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 22 Apr 2021 23:36:38 +0200 Subject: [PATCH 270/279] (fix) sardana GUIs launchers on Windows During the Jan21 release it was observed that the Sardana GUIs launchers, when sardana is installed on Windows with pip, does not launch the applications at all. In the previous releases Sardana was never tested on Windows when installing with pip, but with MSI, and in this case the launchers were working correctly. Since version Jan21, the recommendation is to use Sardana on Windows with pip. In order to overcome this problem change the GUIs launchers from gui_script to console_scripts entry points. Similar solution was already applied for Taurus in taurus-org/taurus#541. --- setup.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/setup.py b/setup.py index faa673efb5..1ba8bfb651 100644 --- a/setup.py +++ b/setup.py @@ -72,9 +72,6 @@ def get_release_info(): "Sardana = sardana.tango:main", "sardanatestsuite = sardana.test.testsuite:main", "spock = sardana.spock:main", -] - -gui_scripts = [ "diffractometeralignment = sardana.taurus.qt.qtgui.extra_hkl.diffractometeralignment:main", "hklscan = sardana.taurus.qt.qtgui.extra_hkl.hklscan:main", "macroexecutor = sardana.taurus.qt.qtgui.extra_macroexecutor.macroexecutor:main", @@ -89,7 +86,6 @@ def get_release_info(): entry_points = { 'console_scripts': console_scripts, - 'gui_scripts': gui_scripts, 'taurus.form.item_factories': form_factories, } From ed3dfdb19442be1088c6b638f24f7cc56086fc25 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 13 May 2021 15:10:07 +0200 Subject: [PATCH 271/279] Improve installation instructions (#1580) * docs: remove instructions on installing with pip3 --user, etc. pip3 installs with --user option automatically when there are no privileges to install system wide. * docs: remove windows specific installation instructions * docs: update Debian installation instructions * docs: add info on building PyTango on Linux * docs: add installation instructions for conda * docs: remove installation from PyPI * docs: add note on installing with conda on Windows * docs: rewrite instructions on installing from git * docs: rewrite dependencies * docs: minor corrections --- .../users/getting_started/installing.rst | 145 ++++++------------ 1 file changed, 48 insertions(+), 97 deletions(-) diff --git a/doc/source/users/getting_started/installing.rst b/doc/source/users/getting_started/installing.rst index 6e2108a799..2201c05a91 100644 --- a/doc/source/users/getting_started/installing.rst +++ b/doc/source/users/getting_started/installing.rst @@ -5,8 +5,8 @@ Installing ========== -Installing with pip [1]_ (platform-independent) --------------------------------------------------------- +Installing with pip (platform-independent) +------------------------------------------ Sardana can be installed using pip. The following command will automatically download and install the latest release of Sardana (see @@ -18,86 +18,53 @@ You can test the installation by running:: python3 -c "import sardana; print(sardana.Release.version)" +Note: Installing sardana with pip3 on Linux requires building PyTango (one of +the sardana's dependencies). You could use :ref:`sardana-getting-started-installing-in-conda` +to avoid this. If you decide to continue with pip3, please refer to +`PyTango's installation guide `_. +On Debian this should work to prepare the build environment:: -Installing from PyPI manually [2]_ (platform-independent) ---------------------------------------------------------- - -You may alternatively install from a downloaded release package: - -#. Download the latest release of Sardana from http://pypi.python.org/pypi/sardana -#. Extract the downloaded source into a temporary directory and change to it -#. run:: - - python3 setup.py install - -You can test the installation by running:: - - python3 -c "import sardana; print(sardana.Release.version)" + apt-get install pkg-config libboost-python-dev libtango-dev Linux (Debian-based) -------------------- -Since v1.4, Sardana is part of the official repositories of Debian (and Ubuntu +Sardana is part of the official repositories of Debian (and Ubuntu and other Debian-based distros). You can install it and all its dependencies by doing (as root):: - aptitude install python-sardana - -You can test the installation by running:: - - python3 -c "import sardana; print(sardana.Release.version)" - -(see more detailed instructions in `this step-by-step howto -`__) - + apt-get install python3-sardana -Windows -------- +Note: `python3-sardana` package is available starting from the Debian 11 +(Bullseye) release. For previous releases you can use `python-sardana` +(compatible with Python 2 only). -#. Download the latest windows binary from https://github.com/sardana-org/sardana/releases -#. Run the installation executable -#. test the installation:: +.. _sardana-getting-started-installing-in-conda: - C:\Python35\python3 -c "import sardana; print(sardana.Release.version)" - -Windows installation shortcut -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This chapter provides a quick shortcut to all windows packages which are -necessary to run Sardana on your windows machine +Installing in a conda environment (platform-independent) +-------------------------------------------------------- -#. Install all dependencies: +In a conda environment (we recommend creating one specifically for sardana):: - #. Download and install latest `PyTango`_ from `PyTango downdoad page `_ - #. Download and install latest `Taurus`_ from `Taurus downdoad page `_ - #. Download and install latest `lxml`_ from `lxml downdoad page `_ - #. Download and install latest itango from `itango download page `_ + conda install -c conda-forge sardana -#. Finally download and install latest Sardana from `Sardana downdoad page `_ +Note: for Windows, until PyTango is available on conda-forge, you may need to use +`pip3 install pytango` for installing it. -========================= -Working directly from Git -========================= +Working from Git source directly (in develop mode) +-------------------------------------------------- If you intend to do changes to Sardana itself, or want to try the latest developments, it is convenient to work directly from the git source in "develop" (aka "editable") mode, so that you do not need to re-install -on each change. - -You can clone sardana from the main git repository:: - - git clone https://github.com/sardana-org/sardana.git sardana - -Then, to work in editable mode, just do:: - - pip3 install -e ./sardana +on each change:: -Note that you can also fork the git repository in github to get your own -github-hosted clone of the sardana repository to which you will have full -access. This will create a new git repository associated to your personal account in -github, so that your changes can be easily shared and eventually merged -into the official repository. + # optional: if using a conda environment, pre-install dependencies with: + conda install --only-deps -c conda-forge sardana + # install sardana in develop mode + git clone https://github.com/sardana-org/sardana.git + pip3 install -e ./sardana # <-- Note the -e !! .. _dependencies: @@ -105,49 +72,33 @@ into the official repository. Dependencies ============ -Sardana has dependencies on some python libraries: +Sardana depends on PyTango_, Taurus_, lxml_, itango_ and click_. +However some Sardana features require additional dependencies. For example: -- Sardana uses Tango as the middleware so you need PyTango_ 9.2.5 or later - installed. You can check it by doing:: +- Using the Sardana Qt_ widgets, requires either PyQt_ (v4 or v5) + or PySide_ (v1 or v2). - python3 -c 'import tango; print(tango.__version__)' +- The macro plotting feature requires matplotlib_ -- Sardana clients are developed with Taurus so you need Taurus_ 4.5.4 or later - installed. You can check it by doing:: +- The showscan online widget requires pyqtgraph_ - python3 -c 'import taurus; print(taurus.Release.version)' +- The showscan offline widget requires PyMca5_. -- Sardana operate some data in the XML format and requires lxml_ library 2.3 or - later. You can check it by doing:: +- The HDF5 NeXus recorder requires h5py_ - python3 -c 'import lxml.etree; print(lxml.etree.LXML_VERSION)' +- The sardana editor widget requires spyder_. -- spock (Sardana CLI) requires itango 0.1.6 or later [3]_. - -.. rubric:: Footnotes - -.. [1] This command requires super user previledges on linux systems. If your - user has them you can usually prefix the command with *sudo*: - ``sudo pip3 -U sardana``. Alternatively, if you don't have - administrator previledges, you can install locally in your user - directory with: ``pip3 --user sardana`` - In this case the executables are located at /.local/bin. Make - sure the PATH is pointing there or you execute from there. - -.. [2] *setup.py install* requires user previledges on linux systems. If your - user has them you can usually prefix the command with *sudo*: - ``sudo python3 setup.py install``. Alternatively, if you don't have - administrator previledges, you can install locally in your user directory - with: ``python3 setup.py install --user`` - In this case the executables are located at /.local/bin. Make - sure the PATH is pointing there or you execute from there. - -.. [3] PyTango < 9 is compatible with itango >= 0.0.1 and < 0.1.0, - while higher versions with itango >= 0.1.6. - -.. _lxml: http://lxml.de -.. _SardanaPypi: http://pypi.python.org/pypi/sardana/ -.. _Tango: http://www.tango-controls.org/ .. _PyTango: http://pytango.readthedocs.io/ .. _Taurus: http://www.taurus-scada.org/ +.. _lxml: http://lxml.de +.. _itango: https://pytango.readthedocs.io/en/stable/itango.html +.. _click: https://pypi.org/project/click/ +.. _Qt: http://qt.nokia.com/products/ +.. _PyQt: http://www.riverbankcomputing.co.uk/software/pyqt/ +.. _PySide: https://wiki.qt.io/Qt_for_Python/ +.. _matplotlib: https://matplotlib.org/ +.. _pyqtgraph: http://www.pyqtgraph.org/ +.. _PyMca5: http://pymca.sourceforge.net/ +.. _h5py: https://www.h5py.org/ +.. _spyder: http://pythonhosted.org/spyder/ \ No newline at end of file From 89c390744b58e605c37236ae1d04fc737199db5a Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 13 May 2021 15:19:45 +0200 Subject: [PATCH 272/279] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 748ca512c2..426b860a02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Documentation on how to start Tango servers on fixed IP - ORBendPoint (#1470) * Documentation example on how to more efficiently access Tango with PyTango in macros/controllers (#1456) +* More clear installation instructions (#727, #1580) * napoleon extension to the sphinx configuration (#1533) * LICENSE file to python source distribution (#1490) @@ -85,6 +86,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Skip execution of Pool's `DeleteElement` Tango command in tests for Windows in order to avoid server crashes (#540, #1567) * Skip QtSpock `test_get_value` test if qtconsole >= 4.4.0 (#1564) +* More clear installation instructions (#727, #1580) * Increase timeout for QtSpock tests (#1568) ### Changed From 276b862988981f0884a26efce220ce76f659e258 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 13 May 2021 15:32:51 +0200 Subject: [PATCH 273/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 426b860a02..d46d748766 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ This file follows the formats and conventions from [keepachangelog.com] * `sequencer` loading of plain text sequences in spock syntax with macro functions (#1422) * MacroServer crash at exit on Windows by avoiding the abort of the already finished macro (#1077, #1559) * Allow running Spock without Qt bindings (#1462, #1463) +* Spock issues at startup on Windows (#536) * Fix getting macroserver from remote door in Sardana-Taurus Door extension (#1506) * MacroServer opening empty environment files used with dumb backend (#1425, #1514, #1517, #1520) * Respect timer/monitor passed in measurement group configuration (#1516, #1521) From 2ec29f7765fab68797f41df779a3f290fa5284bc Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 13 May 2021 15:53:05 +0200 Subject: [PATCH 274/279] bump taurus requirement to >= 4.7.1.1 on windows #724, #540 requires taurus >= 4.7.1 #1077, #540 requires taurus >= 4.7.1.1 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 1ba8bfb651..1144ecc817 100644 --- a/setup.py +++ b/setup.py @@ -61,6 +61,7 @@ def get_release_info(): 'PyTango>=9.2.5', 'itango>=0.1.6', 'taurus>=4.7.0', + 'taurus>=4.7.1.1 ; platform_system=="Windows"', 'lxml>=2.3', 'click', ] From 55794863fdfe4bd0cded45bb1f27c7e858d28a7f Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 13 May 2021 21:39:44 +0200 Subject: [PATCH 275/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d46d748766..fda369ab69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,6 +97,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Use `LatestDeviceImpl` (currently `Device_5Impl`) for as a base class of the Sardana Tango devices (#1214, #1301, #1531) * Read experimental channel's `value` in serial mode to avoid involvement of a worker thread (#1512) +* Bump taurus requirement to >= 4.7.1.1 on Windows (#1583) ### Removed From 3d901ac0bdc3637f6f07ec96e03bbf56edfeebc9 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 14 May 2021 13:44:30 +0200 Subject: [PATCH 276/279] docs: add "what's new" section --- doc/source/docs.rst | 1 + doc/source/news.rst | 99 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 doc/source/news.rst diff --git a/doc/source/docs.rst b/doc/source/docs.rst index b5ca06a091..f1f14e7003 100644 --- a/doc/source/docs.rst +++ b/doc/source/docs.rst @@ -17,6 +17,7 @@ scientific installations. users/index devel/index sep/index.rst + What's new? Glossary other_versions diff --git a/doc/source/news.rst b/doc/source/news.rst new file mode 100644 index 0000000000..97bef3f18d --- /dev/null +++ b/doc/source/news.rst @@ -0,0 +1,99 @@ +########### +What's new? +########### + +Below you will find the most relevant news that brings the Sardana releases. +For a complete list of changes consult the Sardana `CHANGELOG.md \ +`_ file. + +************************** +What's new in Sardana 3.1? +************************** + +Date: 2021-05-17 (*Jan21* milestone) + +Type: biannual stable release + +It is backwards compatible and comes with new features, changes and bug fixes. + +.. note:: + + This release, in comparison to the previous ones, brings significant + user experience improvements when used on Windows. + +Added +===== + +- *HDF5 write session*, in order to avoid the file locking problems and to introduce + the SWMR mode support. It enables safe introspection e.g.: using data + analysis tools like PyMCA or silx, custom scripts, etc. of the scan data files + written in the `HDF5 data format `_ + while scanning. + You can control the session using e.g.: + `~sardana.macroserver.macros.h5storage.h5_start_session` and + `~sardana.macroserver.macros.h5storage.h5_end_session` macros + or the `~sardana.macroserver.macros.h5storage.h5_write_session` + context manager. + More information in the :ref:`NXscanH5_FileRecorder documentation \ + ` +- *scan information* and *scan point* forms to the *showscan online* widget. + See example in the :ref:`showscan online screenshot \ + `. +- Handle `pre-move` and `post-move` hooks by: `mv`, `mvr`, `umv`, `umvr`, + `br` and `ubr` macros. + You may use `~sardana.sardanacustomsettings.PRE_POST_MOVE_HOOK_IN_MV` + for disabling these hooks. +- Include trigger/gate (synchronizer) elements in the per-measurement preparation. + This enables possible dead time optimization in hardware synchronized step scans. + More information in the :ref:`How to write a trigger/gate controller documentation \ + `. +- :ref:`scanuser` environment variable. +- Support to `PosFormat` :ref:`ViewOption ` in `umv` macro. +- Avoid double printing of user units in :ref:`pmtv`: read widget and + units widget. +- Print of allowed :ref:`sardana-macros-hooks` when :ref:`sardana-spock-gettinghelp` + on macros in Spock. +- Documentation: + + - :ref:`sardana-1dcontroller-howto` and :ref:`sardana-2dcontroller-howto` + - :ref:`sardana-countertimercontroller` now contains the `SEP18 \ + `_ concepts. + - Properly :ref:`sardana-macro-exception-handling` in macros in order + to not interfere with macro stopping/aborting + - :ref:`faq_how_to_access_tango_from_macros_and_controllers` + - Update :ref:`Installation instructions ` + +Changed +======= + +- Experimental channel's shape is now considered as a result of the configuration + e.g. RoI, binning, etc. and not part of the measurement group configuration: + + - Added :ref:`shape controller axis parameter (plugin) `, + `shape` experimental channel attribute (kernel) + and `Shape` Tango attribute to the experimental channels + - **Removed** the *shape* column from the measurement group's configuration panel + in :ref:`expconf_ui`. + +Fixed +===== + +- Sardana server (standalone) startup is more robust. +- Storing string values in *datasets*, *pre-scan snapshot* and *custom data* + in :ref:`sardana-users-scan-data-storage-nxscanh5_filerecorder`. +- Stopping/aborting grouped movement when backlash correction would be applied. +- Randomly swapping target positions in grouped motion when moveables proceed + from various Device Pool's. +- Enables possible dead time optimization in `mesh` scan macro by executing + :ref:`per measurement preparation `. +- Continuously read experimental channel's value references in hardware + synchronized acquisition instead of reading only once at the end. +- Problems when :ref:`sardana-controller-howto-change-default-interface` of standard attributes + in controllers e.g. shape of the pseudo counter's Value attribute. +- :ref:`sequencer_ui` related bugs: + + * Fill Macro's `parent_macro` in case of executing XML hooks in sequencer + * Problems with macro id's when executing sequences loaded from *plain text* files with spock syntax + * Loading of sequences using macro functions from *plain text* files with spock syntax +- Apply position formatting (configured with `PosFormat` + :ref:`ViewOption `) to the limits in the `wm` macro. From fbf23909126e8e7a8d8e0bb0534cfd930ff2224f Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 14 May 2021 13:54:56 +0200 Subject: [PATCH 277/279] docs: update internal references --- doc/source/devel/howto_controllers/howto_1dcontroller.rst | 5 +---- doc/source/users/faq.rst | 2 ++ doc/source/users/spock.rst | 4 ++++ doc/source/users/taurus/poolchanneltv.rst | 2 ++ doc/source/users/taurus/poolmotortv.rst | 2 ++ doc/source/users/taurus/showscan.rst | 2 ++ 6 files changed, 13 insertions(+), 4 deletions(-) diff --git a/doc/source/devel/howto_controllers/howto_1dcontroller.rst b/doc/source/devel/howto_controllers/howto_1dcontroller.rst index 90f5a341d5..f044c39de5 100644 --- a/doc/source/devel/howto_controllers/howto_1dcontroller.rst +++ b/doc/source/devel/howto_controllers/howto_1dcontroller.rst @@ -1,14 +1,11 @@ .. currentmodule:: sardana.pool.controller -.. _sardana-1dcontroller-howto-basics: +.. _sardana-1dcontroller-howto: ============================ How to write a 1D controller ============================ -The basics ----------- - This chapter provides the necessary information to write a one dimensional (1D) experimental channel controller in Sardana. diff --git a/doc/source/users/faq.rst b/doc/source/users/faq.rst index 221d85564d..77f69a8cc5 100644 --- a/doc/source/users/faq.rst +++ b/doc/source/users/faq.rst @@ -92,6 +92,8 @@ write Sardana controllers and pseudo controllers can be found This documentation also includes the :term:`API` which can be used to interface to the specific hardware item. +.. _faq_how_to_access_tango_from_macros_and_controllers: + How to access Tango from within macros or controllers -------------------------------------------------------------------------------- In your macros and controllers almost certainly you will need to access Tango diff --git a/doc/source/users/spock.rst b/doc/source/users/spock.rst index 13652d1125..72abfbc6d6 100644 --- a/doc/source/users/spock.rst +++ b/doc/source/users/spock.rst @@ -231,6 +231,8 @@ Exiting spock To exit spock type :kbd:`Control+d` or :samp:`exit()` inside a spock console. +.. _sardana-spock-gettinghelp: + Getting help ------------ @@ -479,6 +481,8 @@ Example accesing scan data:
+.. _sardana-spock-viewoptions: + Changing appearance with View Options ------------------------------------- diff --git a/doc/source/users/taurus/poolchanneltv.rst b/doc/source/users/taurus/poolchanneltv.rst index 848c042238..8e3c7c139c 100644 --- a/doc/source/users/taurus/poolchanneltv.rst +++ b/doc/source/users/taurus/poolchanneltv.rst @@ -1,3 +1,5 @@ +.. _pctv: + PoolChannelTV User’s Interface ------------------------------ diff --git a/doc/source/users/taurus/poolmotortv.rst b/doc/source/users/taurus/poolmotortv.rst index 6a7d7ef772..9aeecf70ca 100644 --- a/doc/source/users/taurus/poolmotortv.rst +++ b/doc/source/users/taurus/poolmotortv.rst @@ -1,3 +1,5 @@ +.. _pmtv: + PoolMotorTV User’s Interface ----------------------------- diff --git a/doc/source/users/taurus/showscan.rst b/doc/source/users/taurus/showscan.rst index e2f208ae70..1556278968 100644 --- a/doc/source/users/taurus/showscan.rst +++ b/doc/source/users/taurus/showscan.rst @@ -53,6 +53,8 @@ and offer online updates on the channel values of the current scan point and some general scan information e.g. scan file, start and end time, etc. respectively. +.. _showscan-online-infopanels-figure: + .. figure:: /_static/showscan-online-infopanels.png Showscan online plotting with separate plots and information panels. From 521abab28f8c445aa257c7bdfb77f6225dfee6c3 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 14 May 2021 13:55:20 +0200 Subject: [PATCH 278/279] update release date in the CHANGELOG.md --- CHANGELOG.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d46d748766..4959fbe88c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). This file follows the formats and conventions from [keepachangelog.com] -## [3.1.0] 2021-03-30 +## [3.1.0] 2021-05-17 ### Added @@ -33,7 +33,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Assert motor sign is -1 or 1 (#1345, #1507) * _last macro_ concept to the `MacroExecutor` (kernel) #1559 * Documentation on how to write 1D and 2D controllers (#1494) -* Machanism to call `SardanaDevice.sardana_init_hook()` before entering in the server event loop (#674, #1545) +* Mechanism to call `SardanaDevice.sardana_init_hook()` before entering in the server event loop (#674, #1545) * Missing documentation of SEP18 concepts to how-to counter/timer controller (#995, #1492) * Document how to properly deal with exceptions in macros in order to not interfer with macro stopping/aborting (#1461) @@ -44,6 +44,18 @@ This file follows the formats and conventions from [keepachangelog.com] * napoleon extension to the sphinx configuration (#1533) * LICENSE file to python source distribution (#1490) +### Changed + +* Experimental channel shape is now considered as a result of the configuration + and not part of the measurement group configuration (#1296, #1466) +* Use `LatestDeviceImpl` (currently `Device_5Impl`) for as a base class of the Sardana Tango + devices (#1214, #1301, #1531) +* Read experimental channel's `value` in serial mode to avoid involvement of a worker thread (#1512) + +### Removed + +* `shape` from the measurement group configuration and `expconf` (#1296, #1466) + ### Fixed * Subscribing to Pool's Elements attribute at Sardana server startup (#674, #1545) @@ -87,20 +99,8 @@ This file follows the formats and conventions from [keepachangelog.com] * Skip execution of Pool's `DeleteElement` Tango command in tests for Windows in order to avoid server crashes (#540, #1567) * Skip QtSpock `test_get_value` test if qtconsole >= 4.4.0 (#1564) -* More clear installation instructions (#727, #1580) * Increase timeout for QtSpock tests (#1568) -### Changed - -* Experimental channel shape is now considered as a result of the configuration - and not part of the measurement group configuration (#1296, #1466) -* Use `LatestDeviceImpl` (currently `Device_5Impl`) for as a base class of the Sardana Tango - devices (#1214, #1301, #1531) -* Read experimental channel's `value` in serial mode to avoid involvement of a worker thread (#1512) - -### Removed - -* `shape` from the measurement group configuration and `expconf` (#1296, #1466) ## [3.0.3] 2020-09-18 From 3e89ecbc1aedb68685fdf536a31e5c5f8ea4ef66 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Sat, 15 May 2021 21:51:16 +0200 Subject: [PATCH 279/279] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7398b3451..6cb2ee1b98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ This file follows the formats and conventions from [keepachangelog.com] * Documentation on how to start Tango servers on fixed IP - ORBendPoint (#1470) * Documentation example on how to more efficiently access Tango with PyTango in macros/controllers (#1456) +* "What's new?" section to docs (#1584) * More clear installation instructions (#727, #1580) * napoleon extension to the sphinx configuration (#1533) * LICENSE file to python source distribution (#1490)