Exemple #1
0
def action_capture_key_position_images():
    """
    Captures the key position and returns positional data from the calibration grid.
    """
    _ = gantry.calibrate()
    for key_position in key_positions:
        x, y = key_position.gantry_position
        gantry.set_position(x, y)
        frame = camera.capture_frame()
        markers = Marker.extract_markers(frame,
                                         marker_family=Marker.FAMILY_tag36h11)
        sid_center_positions = {}
        for sid in key_position.sid_fid_mapping:
            fid = key_position.sid_fid_mapping[sid]
            found_markers = list(filter(lambda m: m.id == fid, markers))
            if len(found_markers) != 1:
                log.error(
                    f"Found {len(found_markers)} markers with fid '{fid}'.")
                continue
            marker = found_markers[0]
            sid_center_positions[sid] = list([int(v) for v in marker.center])
            draw_markers(frame, [marker])
        save_frame_to_runtime_dir(frame,
                                  camera,
                                  calibration=True,
                                  name=f"key-position-{x}x{y}")
        log.info(
            f"Visible squares for key position at {key_position.gantry_position}:\n{json.dumps(sid_center_positions)}"
        )
    gantry.set_position(0, 0)
Exemple #2
0
def setup_board():
    log.info('Setting up board.')
    start_state = Board.get_starting_board_state()
    board_state = get_board_state()
    # For each sid, move a piece that is in an incorrect location to the correct location
    for s_sid, s_piece in start_state.items():
        # Get sids that do not have pieces in them
        free_sids = [
            sid for sid in Board.get_all_sids() if sid not in board_state
        ]
        # s_piece = correct piece for s_sid, b_piece = current piece in sid
        b_piece = board_state[s_sid] if s_sid in board_state else None
        # If the board piece is the same as the start piece, continue
        if b_piece == s_piece:
            continue
        # If there is an incorrect piece in the sid, move it to a free space
        if b_piece is not None:
            f_sid = free_sids.pop(0)
            make_move(f"{s_sid}{f_sid}", board_state)
            del board_state[s_sid]
            board_state[f_sid] = b_piece
        # Get sids of pieces incorrectly located
        i_sids = [
            sid for sid, piece in board_state.items() if piece == s_piece and (
                sid not in start_state or start_state[sid] != piece)
        ]
        # If there are none on the board, continue
        if len(i_sids) == 0:
            continue
        # Make the move
        i_sid = i_sids[0]
        make_move(f"{i_sid}{s_sid}", board_state)
        del board_state[i_sid]
        board_state[s_sid] = s_piece
def calculate_fid_correction_coefficients(frame_center):
    top_img = CALIBRATION_DIR.joinpath('fcc-top.jpg')
    base_img = CALIBRATION_DIR.joinpath('fcc-base.jpg')
    if not top_img.exists() or not base_img.exists():
        log.error('Missing calibration images.')
        return
    top_frame = cv2.imread(str(top_img.absolute()))
    base_frame = cv2.imread(str(base_img.absolute()))
    top_markers = Marker.extract_markers(top_frame, marker_family=Marker.FAMILY_tag16h5, scan_for_inverted_markers=True)
    valid_markers = ['0', '1', '2', '3', '4', '6', '14', '15', '16', '17', '19', '24']
    top_markers = list(filter(lambda m: np.linalg.norm(frame_center - m.center) < 600 and m.id in valid_markers, top_markers))
    base_markers = Marker.extract_markers(base_frame, marker_family=Marker.FAMILY_tag16h5, scan_for_inverted_markers=True)
    base_markers = list(filter(lambda m: np.linalg.norm(frame_center - m.center) < 600 and m.id in valid_markers, base_markers))
    draw_markers(top_frame, top_markers)
    draw_markers(base_frame, base_markers)
    cv2.imshow('top', top_frame)
    cv2.imshow('base', base_frame)
    cv2.waitKey()
    present_top_marker_ids = [m.id for m in top_markers]
    present_base_marker_ids = [m.id for m in base_markers]
    if len(present_top_marker_ids) == 0 \
            or len(set(present_top_marker_ids)) != len(present_top_marker_ids)\
            or set(present_base_marker_ids) != set(present_top_marker_ids):
        log.error('Marker images are not valid or do not appear consistent.')
        return
    fcc = {}
    for tm in top_markers:
        bm = [m for m in base_markers if m.id == tm.id][0]
        bv = bm.center - frame_center
        tv = tm.center - frame_center
        x = bv[0] / tv[0]
        y = bv[1] / tv[1]
        fcc[tm.id] = (x + y) / 2
    log.info(f"FCC:\n{json.dumps(fcc)}")
Exemple #4
0
def get_board_state(save_images=False):
    """
    Analyzes the board to find where all the pieces are.
    For each key position, move the gantry to that position, take a
    snapshot and locate the position of each of the visible pieces.
    :return: [square_id: piece.id] A map of square ids to piece ids.
    """
    log.info('Analyzing board.')
    board_state = {}
    for key_position in key_positions:
        x, y = key_position.gantry_position
        gantry.set_position(x, y)
        markers, frame = take_snapshot()
        markers = filter_markers_by_range(markers,
                                          x_range=key_position.x_range,
                                          y_range=key_position.y_range)
        markers = filter_markers_by_id(markers, valid_ids=board.piece_fids)
        if save_images:
            draw_markers(frame, markers, board=board)
            save_frame_to_runtime_dir(frame, camera)
        for marker in markers:
            piece_id = board.translate_fid_to_piece(marker.id)
            sid = key_position.get_closest_sid(marker.center)
            if sid in board_state and board_state[sid] != piece_id:
                raise BoardPieceViolation(
                    f"Two pieces ({board_state[sid]}, {piece_id}) found in the same square: {sid}"
                )
            board_state[sid] = piece_id
    log.info(f"Board state: {board_state}")
    return board_state
Exemple #5
0
 def capture_frame(self, correct_distortion=True, exposure=None):
     """
     Captures a raw RGB color frame from the camera. Corrects distortion if necessary.
     :param correct_distortion: Tell the function if it should correct for distortion.
     :param exposure: Exposure to capture.
     :return: np array of pixel data
     """
     if self.mock_frame_path is not None:
         log.debug(
             'Camera returning a mock frame in place of the captured image.'
         )
         frame = cv2.imread(self.mock_frame_path)
         self.latest_frame = frame
         return frame
     log.info('Warming camera up.')
     camera = self.generate_camera(exposure)
     log.info(
         f"Capturing frame from camera with"
         f"{'' if correct_distortion else ' no'} distortion correction.")
     ret, frame = camera.read()
     if not ret:
         raise CameraError('Failed to read from from camera.')
     camera.release()
     frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
     if correct_distortion:
         frame = self.correct_distortion(frame)
     self.latest_frame = frame
     return frame
Exemple #6
0
 def set_z_position(self, p, delay=None):
     """
     Sets the Z position based on the input {p}. If p == 1 the z servo will
     be fully extended, if p == 0 the z servo will be fully retracted.
     """
     p = max(0, min(p, 1))
     log.info(f"Setting z to {int(p * 100)}% extension.")
     self.z_servo.set_angle(
         180 * (1 - p),
         delay=max(self.z_delay, self.z_delay *
                   abs(self.z_position - p)) if delay is None else delay)
     self.z_position = p
Exemple #7
0
def take_snapshot():
    """
    Captures a frame and returns a map of all markers present in the frame
    as well as the coordinate in the center of the frame.
    :return: [Marker] A list of markers
    """
    log.info('Taking snapshot from camera.')
    frame = camera.capture_frame()
    markers = Marker.extract_markers(frame,
                                     marker_family=Marker.FAMILY_tag16h5,
                                     scan_for_inverted_markers=True)
    adjust_markers(markers)
    return markers, frame
Exemple #8
0
def action_play_self():
    _ = gantry.calibrate()
    moves = []
    chess_engine = Board.generate_chess_engine_instance()
    while True:
        chess_engine.set_position(moves)
        move = chess_engine.get_best_move_time(1)
        if move is None:
            break
        log.info(f"Making move: {move}")
        make_move(move,
                  board_state=Board.fen_to_board_state(
                      chess_engine.get_fen_position()))
        moves.append(move)
Exemple #9
0
def action_main():
    gantry.set_position(100, 100, rel=True, slow=True)
    play_audio_ids(AUDIO_IDS.START_MESSAGE, AUDIO_IDS.PAUSE_HALF_SECOND,
                   AUDIO_IDS.WAKEUP)
    # Perform mechanical calibration
    log.info('Performing gantry calibration.')
    _ = gantry.calibrate()
    play_audio_ids(AUDIO_IDS.CALIBRATION_COMPLETE, AUDIO_IDS.PAUSE_HALF_SECOND,
                   AUDIO_IDS.SASS_0, AUDIO_IDS.PAUSE_HALF_SECOND,
                   AUDIO_IDS.HAHA, AUDIO_IDS.PAUSE_HALF_SECOND)
    # Start playing sequence
    while True:
        gantry.set_position(200, 200)
        check_for_game_options()
        play_game()
Exemple #10
0
 def extract_markers(frame, marker_family, scan_for_inverted_markers=False):
     """
     Takes in a RGB color frame and extracts all of the apriltag markers present. Returns a list of markers.
     :param frame: The frame to search.
     :param marker_family: The marker family to search for.
     :param scan_for_inverted_markers: Determines if the image should be checked for markers that are inverted.
     :return: {[Marker]} List of markers
     """
     log.info('Extracting apriltag markers from camera frame.'
              + (' Checking for inverted markers as well.' if scan_for_inverted_markers else ''))
     markers = []
     options = apriltag.DetectorOptions(families=marker_family)
     detector = apriltag.Detector(options)
     gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
     gray = Camera.blur_frame(gray, 2)
     results = detector.detect(gray)
     if scan_for_inverted_markers:
         results += detector.detect(Camera.invert_colors(gray))
     for r in results:
         c0, c1, c2, c3 = r.corners
         c0 = np.array([
             int(c0[0]),
             int(c0[1])
         ])
         c1 = np.array([
             int(c1[0]),
             int(c1[1])
         ])
         c2 = np.array([
             int(c2[0]),
             int(c2[1])
         ])
         c3 = np.array([
             int(c3[0]),
             int(c3[1])
         ])
         fid = str(r.tag_id)
         markers.append(
             Marker(
                 fid,
                 [c0, c1, c2, c3]
             )
         )
     log.info(f"Found {len(markers)} markers: {Marker.get_fids_from_list(markers)}")
     return markers
Exemple #11
0
 def correct_distortion(self, frame):
     """
     Corrects distortion due to curved lenses using the distortion variables 'k' and 'd'.
     :param frame: The frame to correct.
     :return: The corrected frame.
     """
     log.info('Correcting camera distortion on frame.')
     new_k = cv2.fisheye.estimateNewCameraMatrixForUndistortRectify(
         self.k, self.d, self.frame_size, np.eye(3), balance=1)
     m1, m2 = cv2.fisheye.initUndistortRectifyMap(self.k, self.d, np.eye(3),
                                                  new_k, self.frame_size,
                                                  cv2.CV_32FC1)
     undistorted_img = cv2.remap(frame,
                                 m1,
                                 m2,
                                 interpolation=cv2.INTER_LINEAR,
                                 borderMode=cv2.BORDER_CONSTANT)
     return undistorted_img
Exemple #12
0
def check_for_game_options():
    return
    x, y = key_positions[0].gantry_position
    gantry.set_position(x, y)
    play_audio_ids(AUDIO_IDS.OPTIONS_CHECK)
    wait_for_player_button_press()
    frame = camera.capture_frame()
    markers = Marker.extract_markers(frame, Marker.FAMILY_tag36h11)
    for marker in markers:
        if marker.id in game_options_map:
            option = game_options_map[marker.id]
            log.info(f"Game option found '{option}'")
            if option == 'level-easy':
                pass
            elif option == 'level-medium':
                pass
            elif option == 'level-hard':
                pass
            elif option == 'level-advanced':
                pass
Exemple #13
0
def save_frame_to_runtime_dir(frame,
                              camera=None,
                              calibration=False,
                              name=None,
                              name_only=False):
    """
    Saves a frame to the runtime dir.
    :param calibration: If true the file will be saved to the calibration dir
    :param name: Name of the file
    :param frame: The frame to save
    :param camera: The camera object used to capture the frame.
    """
    data = Image.fromarray(frame)
    image_name = f"{Log.current_time_in_milliseconds()}"
    if camera is not None:
        image_name += f"-e{str(camera.exposure)[:6]}"
    if name is not None:
        image_name += f"-{name}"
        if name_only:
            image_name = name
    path = f"{CALIBRATION_DIR if calibration else IMAGES_DIR}/{image_name}.jpg"
    log.info(f"Saving frame to {path}")
    data.save(path)
Exemple #14
0
 def set_position(self, x, y, rel=False, slow=False):
     """
     Sets the absolute position of the gantry to the given position.
     :param rel: Set position relatively instead of absolutely.
     :param x: {int} The x coordinate.
     :param y: {int} The y coordinate.
     """
     log.info(f"Setting position to ({x}, {y})")
     if rel:
         self.x_stepper.set_position_rel(int(x))
         self.y0_stepper.set_position_rel(int(y))
         self.y1_stepper.set_position_rel(int(y))
     else:
         self.x_stepper.set_position_abs(int(x))
         self.y0_stepper.set_position_abs(int(y))
         self.y1_stepper.set_position_abs(int(y))
     if slow:
         Stepper.move(self.x_stepper,
                      self.y0_stepper,
                      self.y1_stepper,
                      max_delay=0.0012,
                      min_delay=0.0008)
     else:
         Stepper.move(self.x_stepper, self.y0_stepper, self.y1_stepper)
Exemple #15
0
 def calibrate(self, test_size=False):
     """
     Calibrates the gantry and sets the current position to [0, 0]. Returns
     """
     base_distance = 150
     log.info('Starting calibration sequence.')
     self.x_stepper.set_position_rel(base_distance)
     Stepper.move(self.x_stepper,
                  acceleration_function=Stepper.ACCELERATION_SIN)
     while not self.x_stop.is_pressed():
         self.x_stepper.set_position_rel(-3)
         Stepper.move(self.x_stepper,
                      acceleration_function=Stepper.ACCELERATION_CONST,
                      min_delay=0.004,
                      max_delay=0.004)
     log.info('X stop found.')
     x_pos = -self.x_stepper.get_current_position()
     self.x_stepper.reset()
     self.y0_stepper.set_position_rel(base_distance)
     self.y1_stepper.set_position_rel(base_distance)
     Stepper.move(self.y0_stepper,
                  self.y1_stepper,
                  acceleration_function=Stepper.ACCELERATION_SIN)
     while True:
         if self.y0_stop.is_pressed() and self.y1_stop.is_pressed():
             break
         if not self.y0_stop.is_pressed():
             self.y0_stepper.set_position_rel(-3)
         if not self.y1_stop.is_pressed():
             self.y1_stepper.set_position_rel(-3)
         Stepper.move(self.y0_stepper,
                      self.y1_stepper,
                      acceleration_function=Stepper.ACCELERATION_CONST,
                      min_delay=0.004,
                      max_delay=0.004)
     log.info('Y stops found.')
     y0_pos = -self.y0_stepper.get_current_position()
     y1_pos = -self.y1_stepper.get_current_position()
     y_pos = int(round((y0_pos + y1_pos) / 2))
     self.y0_stepper.reset()
     self.y1_stepper.reset()
     if test_size:
         self.y0_stepper.set_position_abs(self.y_size)
         self.y1_stepper.set_position_abs(self.y_size)
         self.x_stepper.set_position_abs(self.x_size)
         Stepper.move(self.y0_stepper, self.y1_stepper, self.x_stepper)
         self.y0_stepper.set_position_abs(0)
         self.y1_stepper.set_position_abs(0)
         self.x_stepper.set_position_abs(0)
         Stepper.move(self.y0_stepper, self.y1_stepper, self.x_stepper)
     return x_pos, y_pos
Exemple #16
0
def get_shortest_clear_path(move, board_state):
    """
    Returns the shortest path with no pieces in the way or None if there is not a clear path.
    """
    log.info('Searching for a clear path.')
    s_sid, e_sid = move[:2], move[2:4]
    explored = [s_sid]
    paths = [[s_sid]]
    # For a max of 14 steps
    search_max = 14
    while search_max > 0:
        search_max -= 1
        # For each path
        for i in range(len(paths) - 1, -1, -1):
            c_sid = paths[i][-1]
            # If the path has reached the target, skip over the path
            if c_sid == e_sid:
                continue
            # Get the surrounding empty and unexplored sids
            surrounding = [
                sid for sid in board.get_surrounding_sids(c_sid)
                if (sid not in board_state or sid == e_sid)
                and sid not in explored
            ]
            # Generate new paths for each and remove the older path
            if len(surrounding) > 0:
                for sid in surrounding:
                    sid_distance = np.linalg.norm(
                        np.array(board.get_square_location(sid)) -
                        np.array(board.get_square_location(e_sid)))
                    c_sid_distance = np.linalg.norm(
                        np.array(board.get_square_location(c_sid)) -
                        np.array(board.get_square_location(e_sid)))
                    # Mark sid as explored if it is further away from the target than the current sid
                    # This is done to reduce jagged paths by allowing alternate equal length routes
                    if sid_distance > c_sid_distance:
                        explored.append(sid)
                    paths.append(paths[i] + [sid])
            paths.pop(i)
    refined_paths = [refine_path(p) for p in paths if p[-1] == e_sid]
    if len(refined_paths) == 0:
        log.info('Could not find a clear path.')
        return None
    clear_path = sorted(refined_paths, key=lambda x: len(x)).pop(0)
    # If path has too many turns, return None
    if len(clear_path) > 3:
        return None
    log.info(f"Found clear path {clear_path}")
    return clear_path
Exemple #17
0
def cleanup_runtime_dir():
    log.info('Cleaning up runtime directory')
    image_retain_milliseconds = 1000 * 30
    for filename in os.listdir(IMAGES_DIR):
        timestamp = filename.split('.')[0].split('-')[0]
        if timestamp.isnumeric():
            timestamp = int(timestamp)
            if Log.current_time_in_milliseconds(
            ) - timestamp < image_retain_milliseconds:
                continue
        image_path = str(IMAGES_DIR.joinpath(filename).absolute())
        os.remove(image_path)
        log.info(f"Removed image {filename} from runtime/images")
    log_retain_milliseconds = 60 * 60 * 12 * 1000
    for filename in os.listdir(LOG_DIR):
        timestamp = filename.split('.')[0]
        if timestamp.isnumeric():
            timestamp = int(timestamp)
            if Log.current_time_in_milliseconds(
            ) - timestamp < log_retain_milliseconds:
                continue
        log_path = str(LOG_DIR.joinpath(filename).absolute())
        os.remove(log_path)
        log.info(f"Removed image {filename} from runtime/logs")
Exemple #18
0
 def release_grip(self):
     log.info('Releasing grip.')
     self.gripper.demagnetize()
Exemple #19
0
 def engage_grip(self):
     log.info('Engaging grip.')
     self.gripper.magnetize()
Exemple #20
0
def play_game():
    """
    Game play logic.
    """
    # Initialize state history, move list, and chess engine. Then verify the board is in starting position.
    state_history = [Board.get_starting_board_state()]
    moves = []
    chess_engine = Board.generate_chess_engine_instance()
    verify_initial_state()
    # Begin the game
    play_audio_ids(AUDIO_IDS.BEFORE_GAME, AUDIO_IDS.GOOD_LUCK)
    best_player_move = None
    while True:
        log.info('Waiting for player move')
        wait_for_player_button_press()
        # Analyze the board to get the board state. If it fails, retry once before asking for the user
        # to intervene. Repeat until board state can be captured.
        attempts = 0
        should_request_user_intervention = False
        while True:
            attempts += 1
            if should_request_user_intervention:
                play_audio_ids(AUDIO_IDS.USER_CHECK_BOARD)
                log.info('Requesting user intervention.')
                wait_for_player_button_press()
            try:
                board_state = get_board_state(save_images=True)
                break
            except BoardPieceViolation as error:
                log.error(error)
                should_request_user_intervention = attempts % 2 == 0
        # Get previous state in order to extract the move made by the player and add it to the move list
        previous_state = state_history[-1]
        log.debug(
            f"Previous state: {Board.board_state_to_fen(previous_state)}")
        log.debug(f"Board state: {Board.board_state_to_fen(board_state)}")
        try:
            detected_move = Board.get_move_from_board_states(
                previous_state, board_state, moves, chess_engine)
        except InvalidMove as err:
            # If an invalid move was detected, notify the payer and try again
            log.error(f"Invalid move detected: {err}")
            play_audio_ids(AUDIO_IDS.INVALID_MOVE)
            continue
        except NoMoveFound:
            # If no move was found, notify the player and try again
            log.error('No move found.')
            play_audio_ids(AUDIO_IDS.NO_MOVE_FOUND)
            continue
        log.info(f"Detected move {detected_move} from player")
        state_history.append(board_state)
        moves.append(detected_move)
        log.debug(f"Previous moves: {','.join(moves)}")
        # Update the chess engine with the latest moves
        chess_engine.set_position(moves)
        log.debug(
            f"Making move from current board:\n{chess_engine.get_board_visual()}{chess_engine.get_fen_position()}"
        )
        # Generate the best move, append it to the moves list
        generated_move = chess_engine.get_best_move_time(2)
        # If the move is None, the player won
        if generated_move is None:
            play_audio_ids(AUDIO_IDS.LOST)
            log.info('Player won.')
            break
        # If the move is a promotion, make sure that it has the reserve piece
        if len(generated_move) == 5:
            piece_to_promote = generated_move[-1]
            black_pieces_on_board = ''.join([
                board_state[sid] for sid in board_state
                if board_state[sid] in Board.get_black_pieces()
            ])
            promotion_candidates = Board.get_full_black_pieces()
            promotion_candidates.replace('p', '')
            promotion_candidates.replace('k', '')
            for piece in black_pieces_on_board:
                promotion_candidates.replace(piece, '', 1)
            log.info(f"Promotion candidates {promotion_candidates}")
            if piece_to_promote not in promotion_candidates:
                if len(promotion_candidates) == 0:
                    log.error('No promotion candidates')
                    break
                generated_move = generated_move[:4] + random.choice(
                    promotion_candidates)
        moves.append(generated_move)
        log.info(f"Moves: {','.join(moves)}")
        chess_engine.set_position(moves)
        state_history.append(
            Board.fen_to_board_state(chess_engine.get_fen_position()))
        # Generate the best move for the player to take next
        best_player_move = chess_engine.get_best_move_time(1)
        # Make the move TODO: handle move failure
        log.info(f"Making move {generated_move}")
        try:
            make_move(generated_move, board_state)
        except InvalidMove or InconsistentBoardState as err:
            log.error(f"Move failed due to: {err}")
            break
        # If the player has no valid moves, beth wins
        if best_player_move is None:
            play_audio_ids(AUDIO_IDS.WON)
            break
        # Reset to the key position
        x, y = key_positions[0].gantry_position
        gantry.set_position(x, y)
Exemple #21
0
def action_determine_current_position():
    """
    Logs the current position of the gantry.
    """
    x, y = gantry.calibrate()
    log.info(f"Gantry was at position {x}, {y}")
Exemple #22
0
def action_show_board_state():
    gantry.calibrate()
    state = get_board_state(save_images=True)
    chess_engine = Board.generate_chess_engine_instance()
    chess_engine.set_fen_position(Board.board_state_to_fen(state))
    log.info('Board state:\n' + chess_engine.get_board_visual())
Exemple #23
0
def make_move(move, board_state):
    """
    Given a FEN move, execute the move on the board.
    :throws: InvalidMove, InconsistentBoardState
    """
    # TODO: castling, pawn promotion
    # Raise exception if the move was invalid
    if len(move) < 4 or len(move) > 5:
        raise InvalidMove(f"{move} is invalid")
    # Split the move into 2 sids
    s_sid, e_sid, promotion_piece = move[:2], move[2:4], None if len(
        move) == 4 else move[-1]
    # Check is castling move
    is_castling = s_sid == 'e8' and (e_sid == 'g8' or e_sid
                                     == 'b8') and board_state[s_sid] == 'k'
    # Retrieve the sid positions for the gantry
    sx, sy = board.get_square_location(s_sid)
    ex, ey = board.get_square_location(e_sid)
    # Search for a clear path to take
    shortest_clear_path = get_shortest_clear_path(move, board_state)
    # If move captures a piece, remove the captured piece from the board
    if e_sid in board_state:
        extension_amount = get_extension_amount(board_state[e_sid])
        gantry.set_position(ex, ey)
        gantry.set_z_position(extension_amount)
        gantry.engage_grip()
        gantry.set_z_position(min_extension)
        # TODO: have the machine place pieces in an area they can be retrieved
        gantry.set_position(100, 100)
        gantry.set_z_position(max_extension)
        gantry.release_grip()
        gantry.set_z_position(min_extension)
    # Raise an exception if the board state is inconsistent with the move
    if s_sid not in board_state:
        raise InconsistentBoardState(f"Could not find piece in sid {s_sid}")
    # Move the target piece to its destination
    gantry.set_position(sx, sy)
    extension_amount = get_extension_amount(board_state[s_sid])
    gantry.set_z_position(extension_amount)
    gantry.engage_grip()
    gantry.set_z_position(
        min_extension if shortest_clear_path is None else extension_amount -
        0.3,
        delay=None if shortest_clear_path is None else 0.05)
    # If a clear path exists, use it
    if shortest_clear_path is not None:
        log.info(f"Using shortest clear path {shortest_clear_path}")
        for sid in shortest_clear_path:
            tx, ty = board.get_square_location(sid)
            gantry.set_position(tx, ty)
    gantry.set_position(ex, ey)
    gantry.set_z_position(extension_amount,
                          delay=None if shortest_clear_path is None else 0.5)
    gantry.release_grip()
    gantry.set_z_position(min_extension)
    # Ask for piece promotion
    if promotion_piece is not None:
        play_audio_ids(AUDIO_IDS.PIECE_PROMOTION, promotion_piece)
    # Perform castling if required
    if is_castling:
        rx, ry = 0, 0
        nx, ny = 0, 0
        if e_sid == 'g8':
            rx, ry = board.get_square_location('h8')
            nx, ny = board.get_square_location('f8')
        elif e_sid == 'b8':
            rx, ry = board.get_square_location('a8')
            nx, ny = board.get_square_location('c8')
        gantry.set_position(rx, ry)
        gantry.set_z_position(get_extension_amount('r'))
        gantry.engage_grip()
        gantry.set_z_position(min_extension)
        gantry.set_position(nx, ny)
        gantry.set_z_position(get_extension_amount('r'))
        gantry.release_grip()
        gantry.set_z_position(min_extension)
Exemple #24
0
import sys
from src.misc.Log import log

mock_gpio_enabled = "--mock-gpio" in sys.argv
if mock_gpio_enabled:
    log.info("--mock-gpio enabled. All GPIO setup and output will be mocked.")
    from src.misc.MockGPIO import MockGPIO as gpio
else:
    import RPi.GPIO as gpio
    gpio.setwarnings(False)

import time
import math


gpio.setmode(gpio.BCM)


def cleanup():
    """
    Cleans up gpio
    """
    gpio.cleanup()


def p_out(pin, val):
    """
    Validates the pin is not None and then writes the value.
    :param pin: Pin to output to.
    :param val: Value to output.
    """
Exemple #25
0
 def get_move_from_board_states(board_state_before, board_state_after,
                                previous_moves, chess_engine: Stockfish):
     """
     Determine the move given the state before the move and the state after the move.
     """
     # Determine the sids in the prev state that differ in the current state
     changed_before = [
         sid for sid in board_state_before if sid not in board_state_after
         or board_state_after[sid] != board_state_before[sid]
     ]
     # Determine the sids in the current state that differ from the prev state
     changed_after = [
         sid for sid in board_state_after if sid not in board_state_before
         or board_state_after[sid] != board_state_before[sid]
     ]
     # Check for promoted pieces
     promoted_piece = None
     pieces_before = ''.join(
         sorted([
             p for p in board_state_before.values()
             if p in Board.get_white_pieces()
         ]))
     pieces_after = ''.join(
         sorted([
             p for p in board_state_after.values()
             if p in Board.get_white_pieces()
         ]))
     if pieces_before != pieces_after:
         differing_pieces = pieces_before + pieces_after
         for p in pieces_before:
             if p in pieces_after:
                 differing_pieces.replace(p, '', 2)
         log.info(
             f"Found differing pieces between states: {differing_pieces}")
         potential_piece = differing_pieces.replace('p', '')
         if len(differing_pieces
                ) != 2 or 'p' not in differing_pieces or len(
                    potential_piece) != 1:
             raise InvalidMove('Piece promotion is invalid.')
         promoted_piece = potential_piece
     # If no change raise exception
     if len(changed_before) == 0 or len(changed_after) == 0:
         raise NoMoveFound()
     # If more than one sid was changed in the after board state this could be a castling move
     if len(changed_after) != 1:
         # Construct a set of all the pieces involved in the move
         pieces_moved = set([board_state_before[k]
                             for k in changed_before] +
                            [board_state_after[k] for k in changed_after])
         # Check if the move is a castling move
         is_castling_move = len(pieces_moved) == 2 and (
             ('r' in pieces_moved and 'k' in pieces_moved) or
             ('R' in pieces_moved and 'K' in pieces_moved))
         # If it is a castling move, filter out board state changes that are not related to the king
         if is_castling_move:
             log.info('Castling move detected.')
             board_state_before = {
                 k: board_state_before[k]
                 for k in board_state_before
                 if (board_state_before[k] == 'k'
                     or board_state_before[k] == 'K')
             }
             board_state_after = {
                 k: board_state_after[k]
                 for k in board_state_after
                 if (board_state_after[k] == 'k'
                     or board_state_after[k] == 'K')
             }
             changed_before = [
                 sid for sid in board_state_before
                 if sid not in board_state_after
                 or board_state_after[sid] != board_state_before[sid]
             ]
             changed_after = [
                 sid for sid in board_state_after
                 if sid not in board_state_before
                 or board_state_after[sid] != board_state_before[sid]
             ]
         else:
             raise InvalidMove('Move affected too many sids.')
     # Get the move end sid
     e_sid = changed_after[0]
     piece_moved = board_state_after[
         e_sid] if promoted_piece is None else promoted_piece
     # Find the move start sid
     s_sid = None
     for sid in changed_before:
         if board_state_before[sid] == piece_moved:
             s_sid = sid
     if s_sid is None:
         raise InvalidMove('Could not find move start.')
     move = f"{s_sid}{e_sid}{promoted_piece if promoted_piece is not None else ''}"
     # Verify that the move is valid
     chess_engine.set_position(previous_moves)
     if not chess_engine.is_move_correct(move):
         raise InvalidMove(f"Move {move} is invalid.")
     return move
Exemple #26
0
LOG_DIR = str(src.parent.absolute().joinpath('runtime').absolute().joinpath(
    'logs').absolute())

from src.tracking.Board import Board, KeyPosition
from src.tracking.Marker import Marker
from src.mechanical.Camera import Camera
from src.mechanical.Gantry import Gantry
from src.misc.Exceptions import InvalidMove, InconsistentBoardState, NoMoveFound
from src.misc.Helpers import *
from src.calibration.Calibration import calculate_fid_correction_coefficients
from src.misc.Log import log
from src.audio.Audio import play_audio_ids, AUDIO_IDS
"""
Initialize global objects/variables using the config file.
"""
log.info('Initializing components.')
# Read the config file
f = open(str(src.parent.joinpath('config.json').absolute()))
config = json.load(f)
f.close()
# Extract global variables from the config file
fcc_map = config['fid-correction-coefficients']
key_positions = [
    KeyPosition(position=kp['gantry-position'],
                sid_centers=kp['sid-centers'],
                sid_fid_mapping=kp['square-calibration-fid-mapping'],
                x_range=kp['x-range'],
                y_range=kp['y-range']) for kp in config['key-positions']
]
extension_values = config['z-axis-extension']
min_extension = extension_values['min']