async def test_correct_hotspots(): config = robot_configs.build_config([], {}) tip_length = 47 switch_clearance = 7.5 x_switch_offset = 2.0 y_switch_offset = 5.0 z_switch_offset = 5.0 deck_clearance = 5.0 z_probe_clearance = 5.0 z_start_clearance = 20.0 size_x, size_y, size_z = config.tip_probe.dimensions rel_x_start = (size_x / 2) + switch_clearance rel_y_start = (size_y / 2) + switch_clearance center = [293.03, 301.27, 74.3] nozzle_safe_z = round((size_z - tip_length) + z_probe_clearance, 3) z_start = max(deck_clearance, nozzle_safe_z) expected = [robot_configs.HotSpot('x', -rel_x_start, x_switch_offset, z_start, size_x), robot_configs.HotSpot('x', rel_x_start, x_switch_offset, z_start, -size_x), robot_configs.HotSpot('y', y_switch_offset, -rel_y_start, z_start, size_y), robot_configs.HotSpot('y', y_switch_offset, rel_y_start, z_start, -size_y), robot_configs.HotSpot('z', 0, z_switch_offset, center[2] + z_start_clearance, -size_z)] actual = robot_configs.calculate_tip_probe_hotspots( tip_length, config.tip_probe) assert expected == actual
def probe_instrument(instrument, robot, tip_length=None) -> Point: robot.home() tp = robot.config.tip_probe if tip_length is None: tip_length = robot.config.tip_length[instrument.model] instrument._add_tip(tip_length) # probe_center is the point at the center of the switch pcb center = Point(*tp.center) hot_spots = robot_configs.calculate_tip_probe_hotspots(tip_length, tp) # The saved axis positions from limit switch response axis_pos = [] safe_height = _calculate_safeheight(robot, tp.z_clearance.crossover) log.info("Moving to safe z: {}".format(safe_height)) robot.poses = instrument._move(robot.poses, z=safe_height) for hs in hot_spots: x0 = tp.center[0] + hs.x_start_offs y0 = tp.center[1] + hs.y_start_offs z0 = hs.z_start_abs log.info("Moving to {}".format((x0, y0, z0))) robot.poses = instrument._move(robot.poses, x=x0, y=y0) robot.poses = instrument._move(robot.poses, z=z0) axis_index = 'xyz'.index(hs.axis) robot.poses = instrument._probe(robot.poses, hs.axis, hs.probe_distance) # Tip position is stored in accumulator and averaged for each axis # to be used for more accurate positioning for the next axis value = absolute(robot.poses, instrument)[axis_index] axis_pos.append(value) # after probing two points along the same axis # average them out, update center and clear accumulator # except Z, we're only probing that once if hs.axis == 'z': center = center._replace(**{hs.axis: axis_pos[0]}) axis_pos.clear() elif len(axis_pos) == 2: center = center._replace( **{hs.axis: (axis_pos[0] + axis_pos[1]) / 2.0}) axis_pos.clear() log.debug("Current axis positions for {}: {}".format( hs.axis, axis_pos)) # Bounce back to release end stop sgn = hs.probe_distance / abs(hs.probe_distance) bounce = value + (tp.bounce_distance * -sgn) robot.poses = instrument._move(robot.poses, **{hs.axis: bounce}) robot.poses = instrument._move(robot.poses, z=safe_height) log.debug("Updated center point tip probe {}".format(center)) instrument._remove_tip(tip_length) return center
async def test_moves_to_hotspot(hardware_api, monkeypatch, mount, pipette_model): move_calls = [] rel_calls = [] probe_calls = [] old_move_to = hardware_api.move_to old_move_rel = hardware_api.move_rel old_probe = hardware_api._backend.probe async def fake_move_to(which_mount, point): move_calls.append((which_mount, point)) return await old_move_to(which_mount, point) async def fake_move_rel(which_mount, delta): rel_calls.append((which_mount, delta)) return await old_move_rel(which_mount, delta) def fake_probe(ax, dist): probe_calls.append((ax, dist)) return old_probe(ax, dist) monkeypatch.setattr(hardware_api, 'move_to', fake_move_to) monkeypatch.setattr(hardware_api, 'move_rel', fake_move_rel) monkeypatch.setattr(hardware_api._backend, 'probe', fake_probe) await hardware_api.cache_instruments( {mount: name_for_model(pipette_model)}) await hardware_api.home() center = await hardware_api.locate_tip_probe_center(mount, 30) hotspots = robot_configs.calculate_tip_probe_hotspots( 30, hardware_api._config.tip_probe) assert len(move_calls) == len(hotspots) * 4 assert len(rel_calls) == len(hotspots) move_iter = iter(move_calls) rel_iter = iter(rel_calls) probe_iter = iter(probe_calls) bounce_base = hardware_api._config.tip_probe.bounce_distance old_center = hardware_api._config.tip_probe.center for hs in hotspots: x0 = old_center[0] + hs.x_start_offs y0 = old_center[1] + hs.y_start_offs z0 = hs.z_start_abs next(move_iter) next(move_iter) rel = next(rel_iter) if hs.probe_distance < 0: bounce = bounce_base else: bounce = -bounce_base assert rel[1] == Point(**{hs.axis: bounce}) prep_point = next(move_iter) assert prep_point[1] == Point(x0, y0, z0) probe = next(probe_iter) assert probe[0] == hs.axis if hs.axis != 'z' else 'a' assert probe[1] == hs.probe_distance next(move_iter) targ = Point(*hardware_api._config.tip_probe.center) # The x and y are the same because the offsets from our naive mock of # probe() should cancel out, but the z only has one side so we need # to figure out what it will be targ = targ._replace(z=hotspots[-1][3] + hotspots[-1][4]) assert list(center) == pytest.approx(targ)