Exemplo n.º 1
0
def amfe():
    print(bold_msg("%-12s : %s / %s" % ('motor', 'AMFE', 'AMFAE')))
    for m in mcs8_motors:
        if m.amfe.get():
            fe = warning_msg(m.amfe.enum_strs[m.amfe.get()])
        else:
            fe = m.amfe.enum_strs[m.amfe.get()]
        if m.amfae.get():
            fae = warning_msg(m.amfae.enum_strs[m.amfae.get()])
        else:
            fae = m.amfae.enum_strs[m.amfae.get()]
        print("%-12s : %s / %s" % (m.name, fe, fae))
Exemplo n.º 2
0
def ampen():
    for m in mcs8_motors:
        if m.ampen.get():
            print("%-12s : %s" %
                  (m.name, warning_msg(m.ampen.enum_strs[m.ampen.get()])))
        else:
            print("%-12s : %s" % (m.name, m.ampen.enum_strs[m.ampen.get()]))
Exemplo n.º 3
0
 def _pressure(self):
     if self.pressure.get() == 'OFF':
         return (disconnected_msg(-1.1e-15))
     if float(self.pressure.get()) > 1e-1:
         return warning_msg(self.pressure.get())
     if float(self.pressure.get()) > 6e-3:
         return error_msg(self.pressure.get())
     return (self.pressure.get())
Exemplo n.º 4
0
 def _state(self, info=False):
     t = "%.1f" % self.temperature.get()
     if self.temperature.get() > self.alarm.get():
         return(error_msg(t))
     if self.temperature.get() > self.warning.get():
         return(warning_msg(t))
     if info is True and self.temperature.get() > (0.5 * self.warning.get()):
         return(info_msg(t))
     return(t)
Exemplo n.º 5
0
 def on(self, number):
     '''Turn on the specified spinner'''
     if self.spin is False:
         print(warning_msg('The spinners are currently disabled.  do "ga.spin = True" to re-enable.'))
         return
     if not self.valid(number):
         print(error_msg('The fans are numbered from 1 to 8'))
         return
     this = getattr(self, f'spinner{number}')
     this.put(1)
Exemplo n.º 6
0
 def _current(self):
     curr = float(self.current.get())
     if curr > 2e-3:
         out = '%.1f' % (1e3 * curr)
         return (error_msg(out))
     if curr > 5e-4:
         out = '%.1f' % (1e3 * curr)
         return (warning_msg(out))
     out = '%.1f' % (1e6 * curr)
     return (out)
Exemplo n.º 7
0
    def _pressure(self):
        #print(self.pressure.get())
        #print(type(self.pressure.get()))
        if self.pressure.get() == 'OFF':
            return (disconnected_msg(-1.1E-15))

        if float(self.pressure.get()) > 1e-6:
            return error_msg(self.pressure.get())
        if float(self.pressure.get()) > 1e-8:
            return warning_msg(self.pressure.get())
        return (self.pressure.get())
Exemplo n.º 8
0
 def _current(self):
     if self.connected is False:
         return(disconnected_msg('?????'))
     curr = float(self.current.get())
     if curr > 2e-3:
         out = '%.1f' % (1e3*curr)
         return(error_msg(out))
     if curr > 5e-4:
         out = '%.1f' % (1e3*curr)
         return(warning_msg(out))
     out = '%.1f' % (1e6*curr)
     return(out)
Exemplo n.º 9
0
    def _pressure(self):
        #print(self.pressure.get())
        #print(type(self.pressure.get()))
        if self.connected is False:
            return(disconnected_msg('?????'))
        if self.pressure.get() == 'OFF':
            return(disconnected_msg(-1.1E-15))

        if type(self.pressure.get()) is str and self.pressure.get() == 'LO<E-11':
            return whisper('1.00e-11')
        if float(self.pressure.get()) > 1e-6:
            return error_msg(self.pressure.get())
        if float(self.pressure.get()) > 1e-8:
            return warning_msg(self.pressure.get())
        return(self.pressure.get())
Exemplo n.º 10
0
def report(text, level=None, slack=False):
    '''Print a string to:
      * the log file
      * the screen
      * the BMM beamtime slack channel

    Report level decorations  on screen:

      * 'error' (red)
      * 'warning' (yellow)
      * 'info' (brown)
      * 'url' (undecorated)
      * 'bold' (bright white)
      * 'verbosebold' (bright cyan)
      * 'list' (cyan)
      * 'disconnected' (purple)
      * 'whisper' (gray)

    not matching a report level will be undecorated
    '''
    BMMuser = user_ns['BMMuser']
    BMM_log_info(text)
    if color:  # test that color is sensible...
        if level == 'error':
            print(error_msg(text))
        elif level == 'warning':
            print(warning_msg(text))
        elif level == 'info':
            print(info_msg(text))
        elif level == 'url':
            print(url_msg(text))
        elif level == 'bold':
            print(bold_msg(text))
        elif level == 'verbosebold':
            print(verbosebold_msg(text))
        elif level == 'disconnected':
            print(disconnected_msg(text))
        elif level == 'list':
            print(list_msg(text))
        elif level == 'whisper':
            print(whisper(text))
        else:
            print(text)
    else:
        print(text)
    if BMMuser.use_slack and slack:
        post_to_slack(text)
Exemplo n.º 11
0
 def select(self, el):
     '''Choose the ROI configured for element el'''
     if type(el) is int:
         if el < 1 or el > 3:
             print(error_msg('\n%d is not a valid ROI channel\n' % el))
             return (yield from null())
         el = self.slots[el - 1]
     if el is None:
         print(error_msg('\nThat ROI is not configured\n'))
         return (yield from null())
     if Z_number(el) is None:
         print(error_msg('\n%s is not an element\n' % el))
         return (yield from null())
     selected = False
     vor = user_ns['vor']
     BMMuser = user_ns['BMMuser']
     for i in range(3):
         if element_symbol(el) == self.slots[i]:
             BMMuser.roi_channel = i + 1
             if i == 0:  # help out the best effort callback
                 (BMMuser.roi1, BMMuser.roi2, BMMuser.roi3,
                  BMMuser.roi4) = ('ROI1', 'ROI2', 'ROI3', 'ROI4')
                 (BMMuser.dtc1, BMMuser.dtc2, BMMuser.dtc3,
                  BMMuser.dtc4) = ('DTC1', 'DTC2', 'DTC3', 'DTC4')
                 vor.set_hints(1)
             elif i == 1:
                 (BMMuser.roi1, BMMuser.roi2, BMMuser.roi3,
                  BMMuser.roi4) = ('ROI2_1', 'ROI2_2', 'ROI2_3', 'ROI2_4')
                 (BMMuser.dtc1, BMMuser.dtc2, BMMuser.dtc3,
                  BMMuser.dtc4) = ('DTC2_1', 'DTC2_2', 'DTC2_3', 'DTC2_4')
                 vor.set_hints(2)
             elif i == 2:
                 (BMMuser.roi1, BMMuser.roi2, BMMuser.roi3,
                  BMMuser.roi4) = ('ROI3_1', 'ROI3_2', 'ROI3_3', 'ROI3_4')
                 (BMMuser.dtc1, BMMuser.dtc2, BMMuser.dtc3,
                  BMMuser.dtc4) = ('DTC3_1', 'DTC3_2', 'DTC3_3', 'DTC3_4')
                 vor.set_hints(3)
             report('Set ROI channel to %s at channel %d' %
                    (el.capitalize(), i + 1))
             selected = True
     if not selected:
         print(
             warning_msg(
                 '%s is not in a configured channel, not changing BMMuser.roi_channel'
                 % el.capitalize()))
         yield from null()
Exemplo n.º 12
0
 def _current(self, num=None):
     if num is None:
         num = 1
     if num < 1:
         num = 1
     if num > 6:
         num = 6
     sgnl = getattr(self, 'c' + str(num))
     curr = float(sgnl.get())
     if curr > 2e-3:
         out = '%.1f' % (1e3 * curr)
         return (error_msg(out))
     if curr > 5e-4:
         out = '%.1f' % (1e3 * curr)
         return (warning_msg(out))
     out = '%.1f' % (1e6 * curr)
     return (out)
Exemplo n.º 13
0
    def _pressure(self, num=None):
        if num is None:
            num = 1
        if num < 1:
            num = 1
        if num > 6:
            num = 6
        sgnl = getattr(self, 'p' + str(num))
        #print(self.pressure.get())
        #print(type(self.pressure.get()))
        if sgnl.get() == 'OFF':
            return (disconnected_msg(-1.1E-15))

        if float(sgnl.get()) > 1e-6:
            return error_msg(self.pressure.get())
        if float(sgnl.get()) > 1e-8:
            return warning_msg(self.pressure.get())
        return (sgnl.get())
Exemplo n.º 14
0
def change_edge(el,
                focus=False,
                edge='K',
                energy=None,
                slits=True,
                target=300.,
                xrd=False,
                bender=True):
    '''Change edge energy by:
    1. Moving the DCM above the edge energy
    2. Moving the photon delivery system to the correct mode
    3. Running a rocking curve scan
    4. Running a slits_height scan

    Parameters
    ----------
    el : str
        one- or two-letter symbol
    focus : bool, optional
        T=focused or F=unfocused beam [False, unfocused]
    edge : str, optional
        edge symbol ['K']
    energy : float, optional
        e0 value [None, determined from el/edge]
    slits : bool, optional
        perform slit_height() scan [False]
    target : float, optional
        energy where rocking curve is measured [300]
    xrd : boolean, optional
        force photon delivery system to XRD [False]

    Examples
    --------
    Normal use, unfocused beam:
       
    >>> RE(change_edge('Fe'))

    Normal use, focused beam:
       
    >>> RE(change_edge('Fe', focus=True))

    L2 or L1 edge:
       
    >>> RE(change_edge('Re', edge='L2'))

    Measure rocking curve at edge energy:
      
    >>> RE(change_edge('Fe', target=0))

    XRD, new energy:
       
    >>> RE(change_edge('Fe', xrd=True, energy=8600))
        
    note that you must specify an element, but it doesn't matter which
    one the energy will be moved to the specified energy xrd=True
    implies focus=True and target=0

    '''
    BMMuser, RE, dcm, dm3_bct = user_ns['BMMuser'], user_ns['RE'], user_ns[
        'dcm'], user_ns['dm3_bct']
    dcm_pitch, dcm_bragg = user_ns['dcm_pitch'], user_ns['dcm_bragg']
    rkvs = user_ns['rkvs']
    try:
        xs = user_ns['xs']
    except:
        pass
    #BMMuser.prompt = True
    el = el.capitalize()

    ######################################################################
    # this is a tool for verifying a macro.  this replaces an xafsmod scan  #
    # with a sleep, allowing the user to easily map out motor motions in #
    # a macro                                                            #
    if BMMuser.macro_dryrun:
        print(
            info_msg(
                '\nBMMuser.macro_dryrun is True.  Sleeping for %.1f seconds rather than changing to the %s edge.\n'
                % (BMMuser.macro_sleep, el)))
        countdown(BMMuser.macro_sleep)
        return (yield from null())
    ######################################################################

    if pds_motors_ready() is False:
        print(
            error_msg(
                '\nOne or more motors are showing amplifier faults.\nToggle the correct kill switch, then re-enable the faulted motor.'
            ))
        return (yield from null())

    (ok, text) = BMM_clear_to_start()
    if ok is False:
        print(
            error_msg('\n' + text) +
            bold_msg('Quitting change_edge() macro....\n'))
        return (yield from null())

    if energy is None:
        energy = edge_energy(el, edge)

    if energy is None:
        print(
            error_msg('\nEither %s or %s is not a valid symbol\n' %
                      (el, edge)))
        return (yield from null())
    if energy > 23500:
        edge = 'L3'
        energy = edge_energy(el, 'L3')

    if energy < 4000:
        print(warning_msg('The %s edge energy is below 4950 eV' % el))
        print(warning_msg('You have to change energy by hand.'))
        return (yield from null())

    if energy > 23500:
        print(
            warning_msg(
                'The %s edge energy is outside the range of this beamline!' %
                el))
        return (yield from null())

    BMMuser.edge = edge
    BMMuser.element = el
    BMMuser.edge_energy = energy
    rkvs.set('BMM:pds:edge', edge)
    rkvs.set('BMM:pds:element', el)
    rkvs.set('BMM:pds:edge_energy', energy)

    if energy > 8000:
        mode = 'A' if focus else 'D'
    elif energy < 6000:
        #mode = 'B' if focus else 'F'   ## mode B currently is inaccessible :(
        mode = 'C' if focus else 'F'
    else:
        mode = 'C' if focus else 'E'
    if xrd:
        mode = 'XRD'
        focus = True
        target = 0.0
    current_mode = get_mode()
    if mode in ('D', 'E', 'F') and current_mode in ('D', 'E', 'F'):
        with_m2 = False
    elif mode in ('A', 'B',
                  'C') and current_mode in ('A', 'B',
                                            'C'):  # no need to move M2
        with_m2 = False
    else:
        with_m2 = True
    if all_connected(with_m2) is False:
        print(warning_msg('Ophyd connection failure' % el))
        return (yield from null())

    ################################
    # confirm configuration change #
    ################################
    print(bold_msg('\nEnergy change:'))
    print('   %s: %s %s' %
          (list_msg('edge'), el.capitalize(), edge.capitalize()))
    print('   %s: %.1f' % (list_msg('edge energy'), energy))
    print('   %s: %.1f' % (list_msg('target energy'), energy + target))
    print('   %s: %s' % (list_msg('focus'), str(focus)))
    print('   %s: %s' % (list_msg('photon delivery mode'), mode))
    print('   %s: %s' % (list_msg('optimizing slits height'), str(slits)))
    if BMMuser.prompt:
        action = input("\nBegin energy change? [Y/n then Enter] ")
        if action.lower() == 'q' or action.lower() == 'n':
            return (yield from null())
        if mode == 'C' and energy < 6000:
            print(
                warning_msg(
                    '\nMoving to mode C for focused beam and an edge energy below 6 keV.'
                ))
            action = input(
                "You will not get optimal harmonic rejection.  Continue anyway?  [Y/n then Enter] "
            )
            if action.lower() == 'q' or action.lower() == 'n':
                return (yield from null())

    start = time.time()
    if mode == 'XRD':
        report('Configuring beamline for XRD', level='bold', slack=True)
    else:
        report(
            f'Configuring beamline for {el.capitalize()} {edge.capitalize()} edge',
            level='bold',
            slack=True)
    yield from dcm.kill_plan()

    ################################################
    # change to the correct photon delivery mode   #
    #      + move mono to correct energy           #
    #      + move reference holder to correct slot #
    ################################################
    # if not calibrating and mode != current_mode:
    #     print('Moving to photon delivery mode %s...' % mode)
    yield from mv(dcm_bragg.acceleration, BMMuser.acc_slow)
    yield from change_mode(mode=mode,
                           prompt=False,
                           edge=energy + target,
                           reference=el,
                           bender=bender)
    yield from mv(dcm_bragg.acceleration, BMMuser.acc_fast)
    if arrived_in_mode(mode=mode) is False:
        print(error_msg(f'\nFailed to arrive in Mode {mode}'))
        print(
            'Fixing this is often as simple as re-running the change_mode() command.'
        )
        print('Or try dm3_bct.kill() the re-run the change_mode() command.')
        print('If that doesn\'t work, call for help')
        return (yield from null())

    yield from user_ns['kill_mirror_jacks']()
    yield from sleep(1)
    if BMMuser.motor_fault is not None:
        print(
            error_msg('\nSome motors are reporting amplifier faults: %s' %
                      BMMuser.motor_fault))
        print(
            'Clear the faults and try running the same change_edge() command again.'
        )
        print('Troubleshooting: ' + url_msg(
            'https://nsls-ii-bmm.github.io/BeamlineManual/trouble.html#amplifier-fault'
        ))
        BMMuser.motor_fault = None
        return (yield from null())
    BMMuser.motor_fault = None

    ############################
    # run a rocking curve scan #
    ############################
    print('Optimizing rocking curve...')
    yield from abs_set(dcm_pitch.kill_cmd, 1, wait=True)
    yield from mv(dcm_pitch, approximate_pitch(energy + target))
    yield from sleep(1)
    yield from abs_set(dcm_pitch.kill_cmd, 1, wait=True)
    yield from rocking_curve()
    close_last_plot()

    ##########################
    # run a slit height scan #
    ##########################
    if slits:
        print('Optimizing slits height...')
        yield from slit_height(move=True)
        close_last_plot()
        ## redo rocking curve?

    ##################################
    # set reference and roi channels #
    ##################################
    if not xrd:
        ## reference channel
        rois = user_ns['rois']
        print('Moving reference foil...')
        yield from rois.select_plan(el)
        ## Xspress3
        BMMuser.verify_roi(xs, el, edge)
        ## feedback
        show_edges()

    if mode == 'XRD':
        report('Finished configuring for XRD', level='bold', slack=True)
    else:
        report(
            f'Finished configuring for {el.capitalize()} {edge.capitalize()} edge',
            level='bold',
            slack=True)
    if slits is False:
        print(
            '  * You may need to verify the slit position:  RE(slit_height())')
    self.to_json(os.path.join(self.DATA, '.BMMuser'))
    yield from dcm.kill_plan()
    end = time.time()
    print('\n\nThat took %.1f min' % ((end - start) / 60))
    return ()
def sanitize_step_scan_parameters(bounds, steps, times):
    '''Attempt to identify and flag/correct some common scan parameter mistakes.'''
    problem = False
    text = ''

    ############################################################################
    # bounds is one longer than steps/times, length of steps = length of times #
    ############################################################################
    if (len(bounds) - len(steps)) != 1:
        text += error_msg('\nbounds must have one more item than steps\n')
        text += error_msg('\tbounds = %s\n' % ' '.join(map(str, bounds)))
        text += error_msg('\tsteps = %s\n'  % ' '.join(map(str, steps)))
        problem = True
    if (len(bounds) - len(times)) != 1:
        text += error_msg('\nbounds must have one more item than times\n')
        text += error_msg('\tbounds = %s\n' % ' '.join(map(str, bounds)))
        text += error_msg('\ttimes = %s\n'  % ' '.join(map(str, times)))
        problem = True

    ############################
    # tests of boundary values #
    ############################
    for b in bounds:
        if not isfloat(b) and b[-1:].lower() == 'k':
            if not isfloat(b[:-1]):
                text += error_msg('\n%s is not a valid scan boundary value\n' % b)
                problem = True
        elif not isfloat(b):
            text += error_msg('\n%s is not a valid scan boundary value\n' % b)
            problem = True

        if not isfloat(b) and b[:1] == '-' and b[-1:].lower() == 'k':
            text += error_msg('\nNegative bounds must be energy-valued, not k-valued (%s)\n' % b) 
            problem = True
               
    #############################
    # tests of step size values #
    #############################
    for s in steps:
        if not isfloat(s) and s[-1:].lower() == 'k':
            if not isfloat(s[:-1]):
                text += error_msg('\n%s is not a valid scan step size value\n' % s)
                problem = True
            elif float(s[:-1]) < 0:
                text += error_msg('\nStep sizes cannot be negative (%s)\n' % s)
                problem = True
        elif not isfloat(s):
            text += error_msg('\n%s is not a valid scan step size value\n' % s)
            problem = True

        if isfloat(s) and float(s) < 0:
            text += error_msg('\nStep sizes cannot be negative (%s)\n' % s)
            problem = True
        elif isfloat(s) and float(s) <= 0.09:
            text += warning_msg('\n%s is a very small step size!\n' % s)
        elif not isfloat(s) and s[-1:].lower() == 'k' and isfloat(s[-1:]) and float(s[:-1]) < 0.01:
            text += warning_msg('\n%s is a very small step size!\n' % s)
            
                
    ####################################
    # tests of integration time values #
    ####################################
    for t in times:
        if not isfloat(t) and t[-1:].lower() == 'k':
            if not isfloat(t[:-1]):
                text += error_msg('\n%s is not a valid integration time value\n' % t)
                problem = True
            elif float(t[:-1]) < 0:
                text += error_msg('\nIntegration times cannot be negative (%s)\n' % t)
                problem = True
        elif not isfloat(t):
            text += error_msg('\n%s is not a valid integration time value\n' % t)
            problem = True

        if isfloat(t) and float(t) < 0:
            text += error_msg('\nIntegration times cannot be negative (%s)\n' % t)
            problem = True
        elif isfloat(t) and float(t) <= 0.1:
            text += warning_msg('\n%s is a very short integration time!\n' % t)
        elif not isfloat(t) and t[-1:].lower() == 'k' and isfloat(t[-1:]) and float(t[:-1]) < 0.05:
            text += warning_msg('\n%s is a very short integration time!\n' % t)


    reference = 'https://nsls-ii-bmm.github.io/BeamlineManual/xafs.html#scan-regions\n'

    return problem, text, reference
Exemplo n.º 16
0
    def main_plan(inifile, force, **kwargs):
        ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--
        ## read and check INI content
        orig = inifile
        if not os.path.isfile(inifile):
            inifile = DATA + inifile
            if not os.path.isfile(inifile):
                print(
                    warning_msg('\n%s does not exist!  Bailing out....\n' %
                                orig))
                return (orig, -1)
        print(bold_msg('reading ini file: %s' % inifile))
        (p, f) = scan_metadata(inifile=inifile, **kwargs)
        if not any(
                p):  # scan_metadata returned having printed an error message
            return (yield from null())
        #if not os.path.isdir(p['folder']):
        #    print(error_msg('\n%s is not a folder\n' % p['folder']))
        #    return(yield from null())

        detector = 'It'
        if 'trans' in p['mode']:
            detector = 'It'
        elif 'fluo' in p['mode']:
            detector = 'If'

        ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--
        ## verify output file name won't be overwritten
        outfile = '%s.%3.3d' % (os.path.join(p['folder'],
                                             p['filename']), p['start'])
        if os.path.isfile(outfile):
            print(error_msg('%s already exists!  Bailing out....' % outfile))
            return (yield from null())

        ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--
        ## prompt user and verify that we are clear to start
        text = '\n'
        for k in ('folder', 'filename', 'experimenters', 'e0', 'npoints',
                  'dwell', 'delay', 'sample', 'prep', 'comment', 'mode',
                  'snapshots'):
            text = text + '      %-13s : %-50s\n' % (k, p[k])
        if BMMuser.prompt:
            boxedtext('How does this look?',
                      text + '\n      %-13s : %-50s\n' %
                      ('output file', outfile),
                      'green',
                      width=len(outfile) + 25)  # see 05-functions
            action = input("\nBegin time scan? [Y/n then Enter] ")
            if action.lower() == 'q' or action.lower() == 'n':
                return (yield from null())

        (ok, ctstext) = BMM_clear_to_start()
        if force is False and ok is False:
            print(error_msg(ctstext))
            yield from null()
            return

        ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--
        # organize metadata for injection into database and XDI output
        print(bold_msg('gathering metadata'))
        md = bmm_metadata(
            measurement=p['mode'],
            experimenters=p['experimenters'],
            edge=p['edge'],
            element=p['element'],
            edge_energy=p['e0'],
            direction=0,
            scantype='fixed',
            channelcut=p['channelcut'],
            mono='Si(%s)' % dcm._crystal,
            i0_gas='N2',  #\
            it_gas='N2',  # > these three need to go into INI file
            ir_gas='N2',  #/
            sample=p['sample'],
            prep=p['prep'],
            stoichiometry=None,
            mode=p['mode'],
            comment=p['comment'],
        )
        del (md['XDI']['Element']['edge'])
        del (md['XDI']['Element']['symbol'])
        md['XDI']['Column']['01'] = 'time seconds'
        md['XDI']['Column']['02'] = md.copy()['XDI']['Column']['03']
        md['XDI']['Column']['03'] = md.copy()['XDI']['Column']['04']
        md['XDI']['Column']['04'] = md['XDI']['Column']['05']
        del (md['XDI']['Column']['05'])
        md['_kind'] = 'sead'

        rightnow = metadata_at_this_moment()  # see 62-metadata.py
        for family in rightnow.keys():  # transfer rightnow to md
            if type(rightnow[family]) is dict:
                if family not in md:
                    md[family] = dict()
                for k in rightnow[family].keys():
                    md[family][k] = rightnow[family][k]
        xdi = {'XDI': md}

        BMM_log_info(
            'Starting single-energy absorption detection time scan using\n%s:\n%s\nCommand line arguments = %s\nMoving to measurement energy: %.1f eV'
            % (inifile, text, str(kwargs), p['e0']))

        ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--
        ## move to the energy specified in the INI file
        print(bold_msg('Moving to measurement energy: %.1f eV' % p['e0']))
        dcm.mode = 'fixed'
        yield from mv(dcm.energy, p['e0'])

        ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--
        ## snap photos
        if p['snapshots']:
            image = os.path.join(
                p['folder'], 'snapshots',
                "%s_XASwebcam_%s.jpg" % (p['filename'], now()))
            snap('XAS', filename=image)
            image = os.path.join(p['folder'], 'snapshots',
                                 "%s_analog_%s.jpg" % (p['filename'], now()))
            snap('analog', filename=image)

        ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--
        ## engage suspenders right before starting measurement
        if not force: BMM_suspenders()

        ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--
        ## perform the actual time scan
        uid = yield from timescan(detector,
                                  p['npoints'],
                                  p['dwell'],
                                  p['delay'],
                                  force=force,
                                  md={**xdi})

        ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--
        ## write the output file
        header = db[uid]
        write_XDI(outfile, header)  # yield from ?
        report('wrote time scan to %s' % outfile)