Source code for apstools.devices.aps_cycle

"""
APS cycles
+++++++++++++++++++++++++++++++++++++++

.. autosummary::

   ~ApsCycleDM
"""

import datetime
import json
import logging
import pathlib
import time

import yaml
from ophyd.sim import SynSignalRO

logger = logging.getLogger(__name__)
_PATH = pathlib.Path(__file__).parent
YAML_CYCLE_FILE = _PATH / "aps_cycle_info.yml"


[docs]class _ApsCycleDB: """ Python representation of the APS run cycle schedule table. """ def __init__(self): self.db = self._read_cycle_data()
[docs] def get_cycle_name(self, ts=None): """ Get the name of the current APS run cycle. By default, the name of the current run cycle (based on the current timestamp) will be returned. PARAMETERS ts float: Absolute time stamp (such as from ``time.time()``). Default: current time stamp. RETURNS Returns cycle name (str) or ``None`` if timestamp is not in data table. """ ts = ts or time.time() for cycle, span in self.db.items(): if span["start"] <= ts < span["end"]: return cycle return None # not found
[docs] def _read_cycle_data(self): """ Read the list of APS run cycles from a local file. The file is formatted in YAML after reformatting content received from the APS Data Management package (*aps-dm-api*). The YAML format is easily updated and human-readable. """ _cycles_yml = YAML_CYCLE_FILE.open().read() _cycles = yaml.load(_cycles_yml, Loader=yaml.BaseLoader) def iso2ts(isodatetime): return datetime.datetime.timestamp(datetime.datetime.fromisoformat(isodatetime)) db = { run_name: dict(start=iso2ts(span["begin"]), end=iso2ts(span["end"])) for run_name, span in _cycles.items() } return db
[docs] def _write_cycle_data(self, output_file: str = None): """ Write the list of APS run cycles to a local file. The content of this file is received from the APS Data Management package (*aps-dm-api*) and reformatted here for readbility. This allows automatic updates as needed. MANUAL UPDATE OF CYCLE YAML FILE To update the LOCAL_FILE, run this code (on a workstation at the APS configured to use the DM tools):: from apstools.devices.aps_cycle import cycle_db from apstools.utils import dm_setup dm_setup("/path/to/dm.setup.sh") cycle_db._write_cycle_data() """ runs = self._bss_list_runs if runs is not None: cycle_data = runs.replace("'", '"') cycles = json.loads(cycle_data) def sorter(line): return line["name"] db = { line["name"]: { "begin": line["startTime"], "end": line["endTime"], } for line in sorted(cycles, key=sorter) } if output_file is None: output_file = YAML_CYCLE_FILE else: # Allow caller to override default location. output_file = pathlib.Path(output_file) with output_file.open("w") as f: f.write(yaml.dump(db))
@property def _bss_list_runs(self): """Get the full list of APS runs via a Data Management API or 'None'.""" from dm import ApsDbApiFactory from dm.common.exceptions.dmException import DmException api = ApsDbApiFactory.getBssApsDbApi() # Only succeeds on workstations with DM tools installed. try: return api.listRuns() except (DmException, ValueError) as reason: logger.info(reason) return None
cycle_db = _ApsCycleDB()
[docs]class ApsCycleDM(SynSignalRO): """ Get the APS cycle name from the APS Data Management system or a local file. .. index:: Ophyd Signal; ApsCycleDM This signal is read-only. """ _cycle_ends = "1980" # force a read from DM on first get() _cycle_name = "unknown" def get(self): self._cycle_name = cycle_db.get_cycle_name() if datetime.datetime.now().isoformat(sep=" ") >= self._cycle_ends: ts_cycle_end = cycle_db.db[self._cycle_name]["end"] dt_cycle_ends = datetime.datetime.fromtimestamp(ts_cycle_end) self._cycle_ends = dt_cycle_ends.isoformat(sep=" ") return self._cycle_name
# ----------------------------------------------------------------------------- # :author: Pete R. Jemian # :email: jemian@anl.gov # :copyright: (c) 2017-2024, UChicago Argonne, LLC # # Distributed under the terms of the Argonne National Laboratory Open Source License. # # The full license is in the file LICENSE.txt, distributed with this software. # -----------------------------------------------------------------------------