def two_points(dx=100, dy=100, nsamples=1):
    """To calibrate, mechanically block the head of the cantilever at least from the bottom and the positive x direction (left). The resulting matrix allows to convert sensor measurements into micromanipulator coordinates."""
    with closing(DT3100('169.254.3.100')) as sensorA, closing(
            DT3100('169.254.4.100')) as sensorB, closing(MPC385()) as mpc:
        sensors = [sensorA, sensorB]
        #setting up sensors
        for sensor in sensors:
            sensor.set_averaging_type(3)
            sensor.set_averaging_number(3)
        #remember original positions of the sensors and actuator
        ab0 = np.mean([[sensor.readOne().m for sensor in sensors]
                       for i in range(nsamples)], 0)
        assert ab0.min() > 0 and ab0.max() < 800
        x0, y0, z0 = mpc.update_current_position()[1:]
        #move along x (width)
        mpc.move_to(x0 + mpc.um2integer_step(dx), y0, z0)
        abx = np.mean([[sensor.readOne().m for sensor in sensors]
                       for i in range(nsamples)], 0)
        assert abx.min() > 0 and abx.max() < 800
        #move along y (depth)
        mpc.move_to(x0, y0 + mpc.um2integer_step(dy), z0)
        abz = np.mean([[sensor.readOne().m for sensor in sensors]
                       for i in range(nsamples)], 0)
        assert abz.min() > 0 and abz.max() < 800
        #move back to original position
        mpc.move_to(x0, y0, z0)
    #the transfer matrix from actuator to sensor coordinates is the dispacements
    #we just measured as column vectors
    xy2ab = ((np.array([abx, abz]) - ab0).T / [dx, dy])
    return np.linalg.inv(xy2ab)
def step_response(outname,
                  AB2XY,
                  kp,
                  ki=0,
                  kd=0,
                  dx=10.,
                  dy=10.,
                  originalsetpoint=None):
    """Perform a single step of (dx,dy) microns and record the PID response to a file named outname."""
    if np.isscalar(kp):
        kp = [kp, kp]
    if np.isscalar(ki):
        ki = [ki, ki]
    if np.isscalar(kd):
        kd = [kd, kd]
    with closing(DT3100('169.254.3.100')) as sensorA, closing(
            DT3100('169.254.4.100')) as sensorB, closing(MPC385()) as actuator:
        sensors = [sensorA, sensorB]
        #setting up sensors
        for sensor in sensors:
            sensor.set_averaging_type(3)
            sensor.set_averaging_number(3)
        #remember original positions of the sensors and actuator
        if originalsetpoint is None:
            originalsetpoint = np.array(
                [sensor.readOne().m for sensor in sensors])
            originalsetpoint = np.matmul(AB2XY, originalsetpoint)
        x0, y0, z0 = actuator.update_current_position()[1:]
        #setting up PID
        pids = []
        for p, i, d, s in zip(kp, ki, kd, originalsetpoint):
            pid = PID(p, i, d)
            pid.setPoint = s
            pids.append(pid)
        with open(outname, "wb") as fout:
            m = MoverPID_XY(sensors, actuator, AB2XY, pids, outputFile=fout)
            m.start()
            time.sleep(1)
            pids[0].setPoint += dx
            pids[1].setPoint += dy
            time.sleep(20)
            #stop PID
            m.go = False
            m.join()
        #move the actuator back to its original position
        actuator.move_to(x0, y0, z0)
    #read back the data and print some metrics
    meas = np.fromfile(outname)
    ts, mx, my, ox, oy = meas.reshape((meas.shape[0] // 5, 5)).T
    measures = np.column_stack([mx, my])
    rms = np.sqrt(np.sum((originalsetpoint + [dx, dy] - measures)**2, -1))
    if not np.any(rms < actuator.step2um(1)):
        print("does not converge")
    else:
        print("cverge\teRMS\t%overshoot")
        print('%g\t%g\t%g' %
              (ts[np.where(rms < actuator.step2um(1))[0][0] - 1],
               np.sqrt(np.mean(rms[np.where(ts > 2)[0][0]:]**2)),
               (100 *
                (measures - originalsetpoint - [dx, dy]) / [dx, dy]).max()))
def measure_state():
    with ExitStack() as stack:
        sensors = [
            stack.enter_context(closing(DT3100(f'169.254.{i+3:d}.100')))
            for i in range(3)
        ]
        mpc = stack.enter_context(closing(MPC385()))
        return mechtest3sensors.State().read(sensors, mpc, ab2xy)
def sampled_direction(direction='y', samples=None, repeat=10):
    """To calibrate along z (resp y) the 3rd sensor, By default, test 11 dispacements sampled in log scale.
    Added the option for direction to check how movement in other direction affects the sensor reading
    The resulting coefficients allows to convert micromanipulator coordinates to sensor measurements coordinates.
    Returns coefficients and figure testing linearity."""
    if samples is None:
        samples = 2**np.arange(11)
    measures = np.zeros((len(samples)))
    with closing(DT3100('169.254.5.100')) as sensorC, closing(
            MPC385()) as actuator:
        sensors = [sensorC]
        for sensor in sensors:
            sensor.set_averaging_type(3)
            sensor.set_averaging_number(3)
        #remember original positions of the sensors and actuator
        x0, y0, z0 = actuator.update_current_position()[1:]
        try:
            initial = np.zeros(1)
            for j in range(repeat):
                initial += sensorC.readOne().m  #why .m
            initial /= repeat
            #move along z
            for i, sample in enumerate(samples):
                #manipulator going up
                if direction == "y":
                    actuator.move_to(x0, y0 + sample, z0)
                elif direction == "x":
                    actuator.move_to(x0 + sample, y0, z0)
                else:
                    actuator.move_to(x0, y0, z0 + sample)
                for j in range(repeat):
                    m = sensorC.readOne().m
                    if np.any(m == 800) or np.any(m == 0):
                        raise ValueError('Sensor out of range')
                    measures[i] += m
        finally:
            actuator.move_to(x0, y0, z0)
    measures /= repeat
    measures -= initial
    np.save(
        'calib_sensorC_' + '_' +
        datetime.now().strftime('%Y%m%d_%H%M_calib.npy'), measures)
    #fit the results to get coefficients
    func = lambda x, p: p * x
    dxs = actuator.step2um(samples)
    fig, axs = plt.subplots(1, 1)
    x2c = []
    #for ax,m in zip(axs, (measures).T):
    axs.plot(dxs, measures, 'o')
    param = curve_fit(func, dxs, measures)[0][0]
    print(param)
    x2c.append(param)
    axs.plot(dxs, func(dxs, param), ':')
    axs.set_xlabel(direction + ' manip (µm)')
    axs.set_ylabel('Sensor c (µm)')
    axs.set_title(direction + '_C')
    return np.array(x2c), fig
def interactive(samples=None, repeat=10, axs=None):
    """To calibrate, mechanically block the head of the cantilever from the right (looking to the micromanipulator), then from the bottom. By default, test 11 dispacements in each direction, sampled in log scale.
    The resulting matrix allows to convert sensor measurements into micromanipulator coordinates."""
    if axs is None:
        fig, axs = plt.subplots(2, 2, sharex='col', sharey='row')
    else:
        fig = axs[0, 0].figure
    plt.close(fig)
    xy2ab = []
    for direction, axss, dirname in zip('xy', axs.T, ['lateral', 'depth']):
        while True:
            for i in range(3):
                recover(f'169.254.{3+i}.100')
            print(
                f"Please block {dirname} direction. Close figure window when OK."
            )
            with ExitStack() as stack:
                sensors = [
                    stack.enter_context(closing(DT3100(f'169.254.{3+i}.100')))
                    for i in range(3)
                ]
                for sensor in sensors:
                    sensor.set_averaging_type(3)
                    sensor.set_averaging_number(3)
                show_measurement(sensors, 1, ymin=-80)
            print(f"Performing measurments in {dirname} direction.")
            for ax in axss:
                ax.clear()
            dir2ab = sampled_single_direction(direction,
                                              samples,
                                              repeat,
                                              axs=axss)
            dummy = plt.figure()
            new_manager = dummy.canvas.manager
            new_manager.canvas.figure = fig
            fig.set_canvas(new_manager.canvas)
            plt.show(block=False)
            if (input('Are you happy with the results? (N)')
                    in ['Y', 'y', 'yes', 'Yes', 'oui']):
                xy2ab.append(dir2ab)
                break
            plt.close(dummy)
    return np.linalg.inv(np.column_stack((xy2ab)))
def step_response_Y(outname, kp, ki=0, kd=0, dy=10., originalsetpoint=None):
    """Perform a single depthwise step of dy microns and record the PID response to a file named outname."""
    with closing(DT3100('169.254.3.100')) as sensor, closing(
            MPC385()) as actuator:
        #setting up sensor
        sensor.set_averaging_type(3)
        sensor.set_averaging_number(3)
        #remember original positions of the sensor and actuator
        if originalsetpoint is None:
            originalsetpoint = sensor.readOne().m
        x0, y0, z0 = actuator.update_current_position()[1:]
        #setting up PID
        pid = PID(kp, ki, kd)
        pid.setPoint = originalsetpoint
        with open(outname, "wb") as fout:
            m = MoverPID_Y(sensor, actuator, pid, outputFile=fout)
            m.start()
            time.sleep(1)
            pid.setPoint += dy
            time.sleep(20)
            #stop PID
            m.go = False
            m.join()
        #move the actuator back to its original position
        actuator.move_to(x0, y0, z0)
    #read back the data and print some metrics
    meas = np.fromfile(outname)
    ts, measured, out = meas.reshape((meas.shape[0] // 3, 3)).T
    if not np.any(
            np.abs(measured - originalsetpoint - dy) < actuator.step2um(1)):
        print("does not converge")
    else:
        print("cverge\teRMS\t%overshoot")
        print('%g\t%g\t%g' % (ts[np.where(
            np.abs(measured - originalsetpoint -
                   dy) < actuator.step2um(1))[0][0] - 1],
                              np.sqrt(
                                  np.mean((measured[np.where(ts > 2)[0][0]:] -
                                           originalsetpoint - dy)**2)), 100 *
                              (measured.max() - originalsetpoint - dy) / dy))
def timedep_armX_positionY(ab2xy,
                           outname,
                           functions,
                           duration=None,
                           kp=0.9,
                           ki=0.0,
                           kd=0.0,
                           moveback=False,
                           state0=None):
    """Moving the absolute position of the head in depth (Y) and the arm position in Y following two
    time-dependent functions around state0.
    Need ab2xy calibration matrix.
    Duration is in seconds. If None specified, continues until stopped."""
    if not hasattr(kp, "__len__"):
        kps = [kp, kp]
    else:
        kps = kp
    if not hasattr(ki, "__len__"):
        kis = [ki, ki]
    else:
        kis = ki
    if not hasattr(kd, "__len__"):
        kds = [kd, kd]
    else:
        kds = kd
    assert len(kps) == 2
    assert len(kis) == 2
    assert len(kds) == 2
    assert len(functions) == 2
    with ExitStack() as stack:
        sensors = [
            stack.enter_context(closing(DT3100(f'169.254.{3+i}.100')))
            for i in range(3)
        ]
        actuator = stack.enter_context(closing(MPC385()))
        #setting up sensors
        for sensor in sensors:
            sensor.set_averaging_type(3)
            sensor.set_averaging_number(3)
        if state0 is None:
            #remember original positions of the sensors and actuator
            state0 = State().read(sensors, actuator, ab2xy)
        #setting up PID, in microns
        pids = []
        initialposition = [state0.arm_to_ground[0], state0.head_to_ground[1]]
        for s, kp, ki, kd in zip(initialposition, kps, kis, kds):
            pid = PID(kp, ki, kd)
            pid.setPoint = s
            pids.append(pid)
        try:
            with open(outname, "wb") as fout:
                m = moverPID.armX_position_Y_3sensors(sensors,
                                                      actuator,
                                                      ab2xy,
                                                      pids,
                                                      outputFile=fout)
                t0 = time.time()
                m.start()
                try:
                    t = time.time() - t0
                    while (duration is None) or (t < duration):
                        t = time.time() - t0
                        for pid, p0, function in zip(pids, initialposition,
                                                     functions):
                            pid.setPoint = p0 + function(t)
                        time.sleep(0.001)  #no use to update above 1kHz

                except KeyboardInterrupt:
                    pass
                finally:
                    #stop PID thread
                    m.go = False
                    m.join()
        finally:
            if moveback:
                #move the actuator back to its original position
                actuator.move_to(
                    *actuator.um2integer_step(state0.actuator_pos))
def move_to_constant_positions(ab2xy,
                               outnames,
                               dxs,
                               dys,
                               durations,
                               kp=0.9,
                               ki=0.0,
                               kd=0.0,
                               moveback=False,
                               state0=None):
    """Moving the absolute position of the head by dy (depth) and dx (width)
    and stay at that position using PID feedback.
    Need ab2xy calibration matrix.
    Duration is in seconds. If None specified, continues until stopped."""
    if not hasattr(kp, "__len__"):
        kps = [kp, kp]
    else:
        kps = kp
    if not hasattr(ki, "__len__"):
        kis = [ki, ki]
    else:
        kis = ki
    if not hasattr(kd, "__len__"):
        kds = [kd, kd]
    else:
        kds = kd
    assert len(kps) == 2
    assert len(kis) == 2
    assert len(kds) == 2
    with ExitStack() as stack:
        sensors = [
            stack.enter_context(closing(DT3100(f'169.254.{3+i}.100')))
            for i in range(3)
        ]
        actuator = stack.enter_context(closing(MPC385()))
        #setting up sensors
        for sensor in sensors:
            sensor.set_averaging_type(3)
            sensor.set_averaging_number(3)
        if state0 is None:
            #remember original positions of the sensors and actuator
            state0 = State().read(sensors, actuator, ab2xy)
        #setting up PID, in microns
        pids = []
        initialposition = state0.head_to_ground
        for s, kp, ki, kd in zip(initialposition, kps, kis, kds):
            pid = PID(kp, ki, kd)
            pid.setPoint = s
            pids.append(pid)
        try:
            for outname, dx, dy, duration in zip(outnames, dxs, dys,
                                                 durations):
                for s, pid in zip(initialposition + np.array([dx, dy]), pids):
                    pid.setPoint = s
                with open(outname, "wb") as fout:
                    m = moverPID.constant_position_XY_3sensors(sensors,
                                                               actuator,
                                                               ab2xy,
                                                               pids,
                                                               outputFile=fout)
                    t0 = time.time()
                    m.start()
                    try:
                        while (duration is None) or (time.time() <
                                                     t0 + duration):
                            time.sleep(1)
                    except KeyboardInterrupt:
                        pass
                    finally:
                        #stop PID thread
                        m.go = False
                        m.join()
        finally:
            if moveback:
                #move the actuator back to its original position
                actuator.move_to(
                    *actuator.um2integer_step(state0.actuator_pos))
def add_constant_deflectionX_move_to_constant_positiony(
        ab2xy,
        outnames,
        dXs,
        dys,
        durations,
        kp=0.9,
        ki=0.0,
        kd=0.0,
        moveback=False,
        maxYdispl=None,
        state0=None):
    """Considering initial deflection is (0,0),
    successively add dX to the deflection and dy to absolute position
    and stay at this constant X deflection and constant y position during duration.
    Stay at this deflection and positon using using PID feedback (constant stress and constant strain respectively).
    Iterate on next dX, duration.
    Need ab2xy calibration matrix.
    Duration is in seconds. If None specified, continues until stopped."""
    if not hasattr(kp, "__len__"):
        kps = [kp, kp]
    else:
        kps = kp
    if not hasattr(ki, "__len__"):
        kis = [ki, ki]
    else:
        kis = ki
    if not hasattr(kd, "__len__"):
        kds = [kd, kd]
    else:
        kds = kd

    #remember original positions of the sensors and actuator
    with ExitStack() as stack:
        sensors = [
            stack.enter_context(closing(DT3100(f'169.254.{3+i}.100')))
            for i in range(3)
        ]
        actuator = stack.enter_context(closing(MPC385()))
        #setting up sensors
        for sensor in sensors:
            sensor.set_averaging_type(3)
            sensor.set_averaging_number(3)
        if state0 is None:
            #remember original positions of the sensors and actuator
            state0 = State().read(sensors, actuator, ab2xy)
        #setting up PID
        pids = []
        for s, kp, ki, kd in zip(
            [state0.deflection[0], state0.head_to_ground[1]], kps, kis, kds):
            pid = PID(kp, ki, kd)
            pid.setPoint = s
            pids.append(pid)
        try:
            for outname, dX, dy, duration in zip(outnames, dXs, dys,
                                                 durations):

                ip = [state0.deflection[0] + dX, state0.head_to_ground[1] + dy]
                for s, pid in zip(ip, pids):
                    pid.setPoint = s
                with open(outname, "wb") as fout:
                    m = moverPID.constant_deflectionX_positionY_3sensors(
                        sensors, actuator, ab2xy, pids, outputFile=fout)
                    t0 = time.time()
                    m.start()
                    try:
                        while (duration is None) or (time.time() <
                                                     t0 + duration):
                            time.sleep(1)  #is it too long ?
                            x, y, z = m.xyz
                            y0 = actuator.um2integer_step(
                                state0.actuator_pos[1])
                            if maxYdispl is not None and abs(
                                    y - y0) > actuator.um2step(maxYdispl):
                                break
                    except KeyboardInterrupt:
                        pass
                    finally:
                        #stop PID thread
                        m.go = False
                        m.join()
        finally:
            if moveback:
                #move the actuator back to its original position
                actuator.move_to(
                    *actuator.um2integer_step(state0.actuator_pos))
def add_constant_deflection(ab2xy,
                            outnames,
                            dXs,
                            dYs,
                            durations,
                            kp=0.9,
                            ki=0.0,
                            kd=0.0,
                            moveback=False,
                            delay=0,
                            state0=None):
    """Considering initial deflection is 0 in x
    and successively add dX to the deflection during duration
    and stay at this deflection using PID feedback (constant stress).
    Iterate on next dX, dY, duration.
    Need ab2xy calibration matrix.
    Duration is in seconds. If None specified, continues until stopped."""

    #remember original positions of the sensors and actuator
    if not hasattr(kp, "__len__"):
        kps = [kp, kp]
    else:
        kps = kp
    if not hasattr(ki, "__len__"):
        kis = [ki, ki]
    else:
        kis = ki
    if not hasattr(kd, "__len__"):
        kds = [kd, kd]
    else:
        kds = kd
    assert len(kps) == 2
    assert len(kis) == 2
    assert len(kds) == 2
    with ExitStack() as stack:
        sensors = [
            stack.enter_context(closing(DT3100(f'169.254.{3+i}.100')))
            for i in range(3)
        ]
        actuator = stack.enter_context(closing(MPC385()))
        #setting up sensors
        for sensor in sensors:
            sensor.set_averaging_type(3)
            sensor.set_averaging_number(3)
        if state0 is None:
            #remember original positions of the sensors and actuator
            state0 = State().read(sensors, actuator, ab2xy)
        #setting up PID, in microns
        pids = []
        initialposition = state0.deflection
        for s, kp, ki, kd in zip(initialposition, kps, kis, kds):
            pid = PID(kp, ki, kd)
            pid.setPoint = s
            pids.append(pid)
        try:
            for dX, dY, duration, outname in zip(dXs, dYs, durations,
                                                 outnames):
                for s, pid in zip(state0.deflection + np.array([dX, dY]),
                                  pids):
                    pid.setPoint = s
                with open(outname, "wb") as fout:
                    m = moverPID.constant_deflection_XY_3sensors(
                        sensors,
                        actuator,
                        ab2xy,
                        pids,
                        outputFile=fout,
                        delay=delay,
                    )
                    t0 = time.time()
                    m.start()
                    try:
                        while (duration is None) or (time.time() <
                                                     t0 + duration):
                            time.sleep(1)
                    except KeyboardInterrupt:
                        pass
                    finally:
                        #stop PID
                        m.go = False
                        m.join()
        finally:
            if moveback:
                #move the actuator back to its original position
                actuator.move_to(
                    *actuator.um2integer_step(state0.actuator_pos))
def sampled_single_direction(direction='x', samples=None, repeat=10, axs=None):
    """To calibrate along x (resp y), mechanically block the head of the cantilever from the left (resp bottom). By default, test 11 dispacements sampled in log scale.
    The resulting coefficients allows to convert micromanipulator coordinates to sensor measurements coordinates. Returns coefficients."""
    if samples is None:
        samples = 2**np.arange(11)
    measures = np.zeros((len(samples), 2))
    with closing(DT3100('169.254.3.100')) as sensorA, closing(
            DT3100('169.254.4.100')) as sensorB, closing(MPC385()) as actuator:
        sensors = [sensorA, sensorB]
        for sensor in sensors:
            sensor.set_averaging_type(3)
            sensor.set_averaging_number(3)
        #remember original positions of the sensors and actuator
        x0, y0, z0 = actuator.update_current_position()[1:]
        try:
            initial = np.zeros(2)
            for j in range(repeat):
                initial += read_both(sensorA, sensorB)
            initial /= repeat
            #move along z
            for i, sample in enumerate(samples):
                if direction == "x":
                    actuator.move_to(x0 + sample, y0, z0)
                elif direction == "y":
                    #manipulator going down by sample
                    #head going up by sample since blocked
                    actuator.move_to(x0, y0 - sample, z0)
                else:
                    raise ValueError('direction should be x or y')
                for j in range(repeat):
                    m = np.array(read_both(sensorA, sensorB))
                    if np.any(m == 800) or np.any(m == 0):
                        raise ValueError('Sensor out of range')
                    measures[i] += m
        finally:
            actuator.move_to(x0, y0, z0)
    measures /= repeat
    measures -= initial
    np.save(
        'calib_' + direction + '_' +
        datetime.now().strftime('%Y%m%d_%H%M_calib.npy'), measures)
    #fit the results to get coefficients
    func = lambda x, p: p * x
    if direction == "x":
        dxs = -actuator.step2um(samples)
    elif direction == "y":
        dxs = actuator.step2um(samples)
    else:
        raise ValueError('direction should be x or y')
    if axs is None:
        fig, axs = plt.subplots(1, 2, sharex=True)
    x2ab = []
    for ax, m in zip(axs.ravel(), (measures).T):
        ax.plot(dxs, m, 'o')
        param = curve_fit(func, dxs, m)[0][0]
        print(param)
        x2ab.append(param)
        ax.plot(dxs, func(dxs, param), ':')
    #axs[0].set_title(direction+'a')
    #axs[1].set_title(direction+'b')
    axs[0].set_xlabel(direction)
    axs[0].set_ylabel('a')
    axs[1].set_xlabel(direction)
    axs[1].set_ylabel('b')
    return np.array(x2ab)
def find_wall(direction='y',
              repeat=10,
              precision=1,
              verbose=False,
              backup=True,
              settle_time=None,
              k=100 * 16):
    """Find the position at which the head encounters a wall"""
    with closing(DT3100('169.254.3.100')) as sensorA, closing(
            DT3100('169.254.4.100')) as sensorB, closing(MPC385()) as actuator:
        sensors = [sensorA, sensorB]
        for sensor in sensors:
            sensor.set_averaging_type(3)
            sensor.set_averaging_number(3)
        #remember original positions of the sensors and actuator
        x0, y0, z0 = actuator.update_current_position()[1:]
        try:
            initial = np.zeros(2)
            for j in range(repeat):
                initial += read_both(*sensors)
            initial /= repeat
            #estimate the maximum range
            lbound = 0
            ubound = actuator.um2integer_step(initial.min()) // 2
            #if sensors in the middle of their range, should be 200um
            #try to expand upper bound
            moved = False
            while not moved:
                if direction == "x":
                    if verbose:
                        print("Looking between %d and %d in width" %
                              (x0 + lbound, x0 + ubound))
                    actuator.move_to(x0 + ubound, y0, z0)
                elif direction == "y":
                    if verbose:
                        print("Looking between %d and %d in depth" %
                              (y0 - ubound, y0 - lbound))
                    actuator.move_to(x0, y0 - ubound, z0)
                moved = have_moved(initial, sensors, repeat, precision,
                                   settle_time)
                if not moved:
                    lbound = ubound
                    ubound += actuator.um2integer_step(initial.min()) // 2
            #move back to lower bound
            if direction == "x":
                print("excessive touching")
                actuator.move_to(x0 + lbound, y0, z0)
            elif direction == "y":
                print("excessive touching")
                actuator.move_to(x0, y0 - lbound, z0)
            #actuator.move_to(x0, y0+lbound, z0)
            while ubound - lbound > actuator.um2integer_step(precision):
                midrange = (ubound + lbound) // 2
                if direction == "x":
                    if verbose:
                        print("Looking between %d and %d in width" %
                              (x0 + lbound, x0 + ubound))
                    actuator.move_to(x0 + midrange, y0, z0)
                elif direction == "y":
                    if verbose:
                        print("Looking between %d and %d in depth" %
                              (y0 - ubound, y0 - lbound))
                    actuator.movadjustmente_to(x0, y0 - midrange, z0)
                moved = have_moved(initial, sensors, repeat, precision,
                                   settle_time)
                if moved:
                    ubound = midrange
                else:
                    lbound = midrange
            touching = (ubound + lbound) // 2
            if direction == "x":
                touching += x0
                actuator.move_to(touching, y0, z0)
            elif direction == "y":
                touching = y0 - touching
                actuator.move_to(x0, touching, z0)
                #k += touching
                #actuator.move_to(x0, k, z0)
            print("touching at %d" % touching)
            #print("moved actuator to %d"%k)
        finally:
            if backup:
                print("Backing up to original position")
                actuator.move_to(x0, y0, z0)
    return touching
                        help='minimum distance')
    parser.add_argument('--ymax',
                        default=None,
                        type=float,
                        help='maximum distance')
    parser.add_argument('--tw',
                        default=1.0,
                        type=float,
                        help='Width of the time window, in seconds.')
    parser.add_argument(
        '--fast',
        default=False,
        type=bool,
        help='Whether to use continuous reading of the sensors.')
    args = parser.parse_args()
    if args.ip is None or len(args.ip) == 0:
        args.ip = ['169.254.3.100']
    for ip in args.ip:
        recover(ip)
    with ExitStack() as stack:
        sensors = [stack.enter_context(closing(DT3100(ip))) for ip in args.ip]
        for sensor in sensors:
            sensor.set_averaging_type(args.avt)
            sensor.set_averaging_number(args.avn)
        show_measurement(sensors,
                         args.tw,
                         ymin=args.ymin,
                         ymax=args.ymax,
                         names=args.ip,
                         fast=args.fast)