def rot90(self, id: str, sleep_time: float, half_step: bool, direction: str = "CW"): if not id in Cube.ids: raise ValueError(f"Unrecognized id '{id}'") log( l.DEBUG, f"Rotating {id} 90deg in direction {direction} with sleep time {sleep_time} half step is {half_step}" ) stepper = getattr(self, id) stepper.arm() if half_step: rot_n = 101 comp_n = 1 else: rot_n = 53 comp_n = 3 stepper.step(direction=direction, n=rot_n, sleep_time=sleep_time, half_step=half_step) # To compensate the shaft tolerance issues stepper.step(direction=self._opposite_direction(direction), n=comp_n, sleep_time=sleep_time, half_step=half_step) stepper.disarm()
def __init__(self, pin1: int, pin2: int): self.pin1 = pin1 self.pin2 = pin2 self.energized = 0 for pin in [pin1, pin2]: GPIO.setup(pin, GPIO.OUT) log(l.DEBUG, f"winding instantiated with pins [{pin1}, {pin2}]")
def move(self, move: str, sleep_time: float, half_step: bool): """ A move always starts with the id of the face to rotate. It can then be follow by either 2 which means move twice, ' which means move counter clockwise or nothing. One move is 90 degrees. """ log( l.DEBUG, f"Doing move '{move}' with sleep time {sleep_time} half step is {half_step}" ) if len(move) > 2: raise ValueError( f"Move must be described by maximum 2 characters, got {move}") elif len(move) == 0: raise ValueError(f"Got an empty string") elif len(move) == 1: self.rot90(move, sleep_time=sleep_time, half_step=half_step) elif len(move) == 2: if move[1] == "2": self.rot180(move[0], sleep_time=sleep_time, half_step=half_step) elif move[1] == "1": self.rot90(move[0], sleep_time=sleep_time, half_step=half_step) elif move[1] == "'" or move[1] == "3": self.rot90(move[0], direction="CCW", sleep_time=sleep_time, half_step=half_step) else: raise ValueError(f"Unrecognized modifier {move[1]}") else: raise Exception(f"Should not get here")
def cleanup(cube): log(l.INFO, "Cleaning up and exiting") for id in Cube.ids: face = getattr(cube, id) face.arm() face.store_state(Cube.ids[id]) face.state = 8 # De energize windings to preserve steppers GPIO.cleanup()
def step(self, half_step: bool, sleep_time: float, direction: str = "CW", n: int = 1): for i in range(n): next_state = self.get_next_state(half_step=half_step, direction=direction) log(l.DEBUG, f"state: {next_state}") self.state = next_state sleep(sleep_time)
def load_state(self, filename: str): if not Path("states", filename).exists(): raise FileNotFoundError(f"No state file created for {filename}") with open(str(Path("states", filename)), "r") as fp: state = fp.read() try: state = int(state) except ValueError: raise ValueError(f"read state '{state}' is not an integer") if state not in list(range(8)): log( l.WARNING, f"Read invalid state '{state}' from '{filename}', not loading") else: winding_states = self.inverted_state_dict[state] self.windingA.energize(winding_states[0]) self.windingB.energize(winding_states[1])
def jog_if_needed(cube: Cube, force=False, half_step: bool = False): jogged = False for id in Cube.ids: if force: jog(cube, half_step=half_step) jogged = True break elif not Path("states", Cube.ids[id]).exists(): jog(cube, half_step=half_step) jogged = True break else: with open(str(Path("states", Cube.ids[id]))) as fp: state = fp.read() if state == "-1": jog(cube, half_step=half_step) jogged = True break if not jogged: log(l.INFO, "Jogging not needed, all steppers calibrated") else: log(l.INFO, "Jogging sequence completed")
def main(): parser = ArgumentParser( description="Top level for MARCS Rubik's cube solver") parser.add_argument( "-t", "--delay-time", type=float, default=5e-3, help= "Sleep time between each step of the motors in seconds (default 1e-3)") parser.add_argument("-mdt", "--move-delay-time", type=float, default=5e-2, help="Sleep time between each move (default 5e-2") parser.add_argument("-ll", "--log-level", type=str, choices=["debug", "info", "warning"], default="info", help="Set log level") parser.add_argument("--test", default=False, action="store_true", help="Test sequence, jog then do 90 deg rotations") parser.add_argument( "-i", "--interactive", action="store_true", default=False, help="Go step by step while waiting for user input between each") parser.add_argument( "--no-jog", action="store_true", default=False, help="Skip initial jogging calibration of steppers, use with caution") parser.add_argument("-j", "--jog", action="store_true", default=False, help="Redo jogging sequence") parser.add_argument("--full-step", dest="half_step", action="store_false", default=True, help="Use full steps when moving (not recommended)") parser.add_argument("--max-speed", action="store_true", default=False, help="Use fastest settings") parser.add_argument("-c", "--cubestr", type=str, default="", help="Cube string to use for solving") args = parser.parse_args() log(l.INFO, "Starting MARCS main loop") log(l.DEBUG, f"Passed arguments: {sys.argv}") set_log_level(getattr(l, args.log_level.upper())) log(l.INFO, f"Logging level set to {args.log_level}") if args.log_level == "debug": log(l.WARNING, "WARNING: debug log level WILL slow down the solving") if args.max_speed: args.delay_time = 1e-3 args.move_delay_time = 6e-2 log(l.DEBUG, "Using max speed, get that CTRL+C ready") cube = Cube() atexit.register(cleanup, cube) log(l.INFO, f"All steppers instantiated, GPIO assigned and configured") try: if args.test: log(l.DEBUG, "Entering test sequence") jog(cube, half_step=args.half_step) while True: input() cube.move("U", sleep_time=args.delay_time, half_step=args.half_step) if not args.no_jog: log(l.INFO, "Starting jogging sequence") jog_if_needed(cube, force=args.jog, half_step=args.half_step) else: log(l.WARNING, "Jogging sequence skipped") if not args.cubestr: log(l.INFO, "Generating scrambling sequence...") cubelib.scramble() scramble_seq = cubelib.get_scramble() scramble_moves = scramble_seq.split(" ") log(l.DEBUG, f"Scrambling sequence is: {scramble_seq}") log(l.INFO, "Scrambling...") for move in scramble_moves: log(l.DEBUG, move) if args.interactive: input() cube.move(move, sleep_time=args.delay_time, half_step=args.half_step) sleep(args.move_delay_time) log(l.INFO, "Scrambling done") conversion_dict = { "W": "D", "G": "R", "R": "F", "O": "B", "Y": "U", "B": "L" } cubestr = "" c = cubelib.a c[2] = np.rot90(c[2], 3) c[3] = np.rot90(c[3]) c[5] = np.rot90(c[5], 2) for face in [0, 2, 1, 4, 3, 5]: for row in range(3): for col in range(3): cubestr += conversion_dict[c[face][row][col]] else: cubestr = args.cubestr log(l.INFO, "Generating solving sequence...") moves = solve(cubestr) solve_moves = moves.split(" ")[0:-1] log(l.INFO, f"Solving sequence is: {moves}") input("When ready to solve, press enter") start_time = time() log(l.INFO, "Solving...") for move in solve_moves: log(l.DEBUG, move) if args.interactive: input() cube.move(move, sleep_time=args.delay_time, half_step=args.half_step) sleep(args.move_delay_time) end_time = time() solve_time = end_time - start_time log( l.INFO, f"Solving done in {round(solve_time, 3)}s with {len(solve_moves)} moves, exiting" ) except KeyboardInterrupt: log(l.DEBUG, "Keyboard interrupt, exiting") exit(0)
def jog(cube: Cube, half_step: bool): log(l.INFO, "Entering jog routine") print( "Choose direction by inputing 'cw', 'ccw' or 'r' to reverse direction (default cw), step once by pressing enter and end by inputing 'ok'" ) direction = "cw" try: for id in Cube.ids: log(l.INFO, f"Jogging {Cube.ids[id]}({id})") face = getattr(cube, id) option = input("option: ") while option != "ok": option = input("option: ") if option == "": face.step(direction=direction.upper(), n=1, sleep_time=0, half_step=half_step) elif option in ["cw", "ccw"]: log(l.DEBUG, f"Switched to rotating {option}") direction = option elif option == "ok": if half_step: n = 1 else: n = 3 log( l.DEBUG, f"Got ok, reversing {n} steps for tolerance compensation" ) face.step(direction=( cube._opposite_direction(direction)).upper(), n=n, sleep_time=1e-2, half_step=half_step) pass elif option == "r": if direction == "cw": direction = "ccw" else: direction = "cw" log(l.DEBUG, f"Reversing direction, now rotating {direction}") else: log(l.WARNING, f"Don't know what to do with {option}, ignoring") face.store_state(Cube.ids[id]) face.disarm() log(l.INFO, f"Stored state of {Cube.ids[id]}") except KeyboardInterrupt: log(l.WARNING, "Exited before jogging was completed!") raise KeyboardInterrupt
def arm(self): if not self.cached_state == -1: self.state = self.cached_state else: log(l.DEBUG, "Can't arm before disarm, ignoring")
def state(self, state): states = self.inverted_state_dict.get(state) log(l.DEBUG, f"Setting windings to {states}") self.windingA.energize(states[0]) self.windingB.energize(states[1])
help="Do a half step of the motor") argParser.add_argument("--turn", default=False, dest="full_turn", action="store_true", help="Do one full turn") argParser.add_argument("-d", "--direction", type=str, default="CW", choices=["CW", "CCW"], help="spin CW or CCW") args = argParser.parse_args() set_log_level(l.DEBUG) log(l.INFO, "setting up GPIO") GPIO.setmode(GPIO.BCM) B1N1 = 24 B1N2 = 23 A1N1 = 25 A1N2 = 8 STBY = 7 GPIO.setup(STBY, GPIO.OUT) GPIO.output(STBY, GPIO.HIGH) # Standby needs to be high or motor is braked log(l.INFO, "motor armed") stepper = Stepper(A1N1, A1N2, B1N1, B1N2) wait_time = 1e-2 if args.spin: log(l.INFO, "starting, press CTRL+C to exit") while True: