import ast import kociemba import requests import packages.move as move class machine: # trying to make this class support direct control mode first (i.e. from an external computer) # And then implement a second thing which runs off of the moveList class's 'generate_list_request' to send # all the commands to the webserver at once. url = None overshoot_counters = [0, 0, 0, 0] last_used_counters = [0, 0, 0, 0, 0, 0, 0, 0] tick_length = 0.05 servoAngles = [0 for _ in range(9)] # the actual angle of the servos # initialing both of these position lists with values representing 'unknown' rotatorPositions = [-1, -1, -1, -1] # retractorPositions = ['n', 'n', 'n', 'n'] overshoot_angles_cube = { # how much to 'overshoot' when performing rotations. # There's overrides for all 4 positions and their 2 respective directions # obviously, the two endpoints only have a single direction # todo implement some simple stuff for the two levels of wobble - i.e on the cube itself, # and within the machine as well } overshoot_angles_machine = { # how much to overshoot again when performing rotations not # involving interfacing with the cube. # These only have one value because it's mainly a visual thing, and it makes calibrating easier # they also apply to the retractor servos as well } rotator_servo_angles = {} retraction_servo_angles = {} overshootWait = [0, 0.3, 0.4, 0.5] # how long to wait after each length of move before turning the servo back lastUsedWait = 1 def __init__(self, _url): self.url = _url self.read_calibration_file() def read_calibration_file(self): with open('packages/calibration.txt') as calibration_file: calibration_vars = ast.literal_eval(calibration_file.read()) self.overshoot_angles_machine = calibration_vars['overshoot_angles_machine'] self.overshoot_angles_cube = calibration_vars['overshoot_angles_cube'] self.rotator_servo_angles = calibration_vars['rotator_servo_angles'] self.retraction_servo_angles = calibration_vars['retraction_servo_angles'] def apply_machine_overshoots(self, index, angle): if self.servoAngles[index] > angle: angle -= self.overshoot_angles_machine[index] elif self.servoAngles[index] < angle: angle += self.overshoot_angles_machine[index] return angle def apply_cube_overshoots(self, index, position): last_position = self.rotatorPositions[index] if last_position > position: new_angle = (self.rotator_servo_angles[index][position] + self.overshoot_angles_cube[index][position][1]) elif last_position < position: new_angle = (self.rotator_servo_angles[index][position] - self.overshoot_angles_cube[index][position][0]) else: new_angle = last_position return new_angle def tick(self): # handles timings of stuff like overshoot moves and turning off the servos if they haven't been used for a while for i, counter in enumerate(self.overshoot_counters): if counter > 0: self.overshoot_counters[i] -= self.tick_length if self.overshoot_counters[i] <= 0: # set the servo's angle to it's non-overshot angle once the counter has finished. self.set_angle(i + 4, self.rotator_servo_angles[i][self.rotatorPositions[i]]) for i, counter in enumerate(self.last_used_counters): if counter < 1: self.last_used_counters[i] += self.tick_length if self.last_used_counters[i] >= self.lastUsedWait: # set the servo's angle to it's non-overshot angle once the counter has finished. self.stop(i) self.read_calibration_file() def set_angle(self, index, angle): # the index sent to this class is the true index of the servo = # i.e on the physical servo controller self.last_used_counters[index] = 0 requests.post(self.url + 'receiveDirect', data={ "component": "servo", "index": index, "value": self.apply_machine_overshoots(index, angle), }) self.servoAngles[index] = angle def stop(self, index): requests.post(self.url + 'receiveDirect', data={ "component": "servo", "index": index, "value": -180, }) def set_leds(self, index, colour): requests.post(self.url + 'receiveDirect', data={ "component": "led", "index": index, "value": colour, }) def take_photo(self): requests.post(self.url + 'receiveDirect', data={ "component": "camera", }) def get_colours(self): response = requests.get(self.url + 'get_data', data={ "request": "image" }) return ast.literal_eval(response.content.decode("utf+8")) def goto(self, index, position): new_angle = self.apply_cube_overshoots(index, position) time_offset = 0 if self.rotatorPositions[index] != -1: # don't set the time offset if we don't know the last position time_offset = self.overshootWait[abs(self.rotatorPositions[index] - position)] if time_offset != 0: # don't bother with overshoot angles if the time offset is zero # set the position self.set_angle(index + 4, new_angle) self.overshoot_counters[index] = time_offset else: self.set_angle(index + 4, self.rotator_servo_angles[index][position]) self.rotatorPositions[index] = position def move(self, index, move_by): move_to = (self.rotatorPositions[index] + move_by) % 4 self.goto(index, move_to) def retract(self, index): self.retractorPositions[index] = 'r' self.set_angle(index, self.retraction_servo_angles['retract'][index]) def extend(self, index): self.retractorPositions[index] = 'e' self.set_angle(index, self.retraction_servo_angles['extend'][index]) def full_extend(self, index): self.retractorPositions[index] = 'f' self.set_angle(index, self.retraction_servo_angles['fullExtend'][index]) def solve_cube(self, cube_string): solution = kociemba.solve(cube_string) face_servo_dict = { 'B': 0, 'R': 1, 'F': 2, 'L': 3, 'U': 4, 'D': 5, } solution = solution.split() move_list = move.MoveList(self, []) for i in solution: servo = face_servo_dict[i[0]] if servo > 3: face_servo_dict = self.add_rotate_move(move_list, face_servo_dict) servo = face_servo_dict[i[0]] if i[-1] == "'": move_list.add_move('spin', servo, (self.rotatorPositions[servo] + 1) % 4) self.rotatorPositions[servo] = (self.rotatorPositions[servo] + 1) % 4 elif i[-1] == "2": move_list.add_move('spin', servo, (self.rotatorPositions[servo] + 2) % 4) self.rotatorPositions[servo] = (self.rotatorPositions[servo] + 2) % 4 else: move_list.add_move('spin', servo, (self.rotatorPositions[servo] - 1) % 4) self.rotatorPositions[servo] = (self.rotatorPositions[servo] - 1) % 4 move_list.add_move('delay',_delay=1) print('e') move_list.send_list_request() def add_rotate_move(self, move_list, face_servo_dict): # check if clockwise 13 rotation is valid if self.rotatorPositions[1] != 0 and self.rotatorPositions[3] != 3: move_list.add_move('extension', 0, 'r') move_list.add_move('extension', 2, 'r') move_list.add_move('delay', _delay=.5) move_list.add_move('spin', 1, self.rotatorPositions[1] - 1) move_list.add_move('spin', 3, self.rotatorPositions[3] + 1) move_list.add_move('spin', 0, 1) move_list.add_move('spin', 2, 1) move_list.add_move('delay', _delay=.5) move_list.add_move('extension', 0, 'e') move_list.add_move('extension', 2, 'e') self.rotatorPositions[1] -= 1 self.rotatorPositions[3] += 1 self.rotatorPositions[0] = 1 self.rotatorPositions[2] = 1 # # else try an anticlockwise 13 rotation # elif self.rotatorPositions[1] != 3 and self.rotatorPositions[3] != 0: # # # else try a clockwise 02 rotation # elif self.rotatorPositions[0] != 0 and self.rotatorPositions[2] != 3: # # else try an anticlockwise 02 rotation # elif self.rotatorPositions[0] != 3 and self.rotatorPositions[2] != 0: # # else retract 13 and set them to a position where they can perform a clockwise rotation else: move_list.add_move('extension', 1, 'r') move_list.add_move('extension', 3, 'r') move_list.add_move('delay', _delay=.5) move_list.add_move('spin', 1, 2) move_list.add_move('spin', 3, 0) move_list.add_move('delay', _delay=.5) move_list.add_move('extension', 1, 'e') move_list.add_move('extension', 3, 'e') move_list.add_move('delay', _delay=.5) move_list.add_move('extension', 0, 'r') move_list.add_move('extension', 2, 'r') move_list.add_move('delay', _delay=.5) move_list.add_move('spin', 1, 1) move_list.add_move('spin', 3, 1) move_list.add_move('spin', 0, 1) move_list.add_move('spin', 2, 1) move_list.add_move('delay', _delay=.5) move_list.add_move('extension', 0, 'e') move_list.add_move('extension', 2, 'e') self.rotatorPositions[1] = 1 self.rotatorPositions[3] = 1 self.rotatorPositions[0] = 1 self.rotatorPositions[2] = 1 k = list(face_servo_dict.keys()) k = [ k[5], k[1], k[4], k[3], k[0], k[2], ] v = list(face_servo_dict.values()) face_servo_dict = {k[i]: v[i] for i in range(6)} return face_servo_dict def execute_move_list(self, move_list): raw_move_list = [] time = 0 # move lists use an absolute time to execute moves instead of waiting between moves for move in move_list: # convert each move in move_list to a request match move.moveType: case 'spin': new_angle = self.apply_cube_overshoots(move.index, move.position) time_offset = 0 if self.rotatorPositions[move.index] != -1: # don't set the time offset if we don't know the last position time_offset = self.overshootWait[abs(self.rotatorPositions[move.index] - move.position)] # if time_offset != 0: # don't bother with overshoot angles if the time offset is zero # raw_move_list.append({ # 'component': 'servo', # 'index': move.index + 4, # 'value': new_angle # 'value': self.apply_machine_overshoots(move.index + 4, new_angle), # 'time': time, # }) raw_move_list.append({ 'component': 'servo', 'index': move.index + 4, 'value': self.apply_machine_overshoots(move.index + 4, self.rotator_servo_angles[move.index][move.position]), 'time': time# + time_offset }) print(new_angle) print(time_offset) print(self.rotator_servo_angles[move.index][move.position]) # update internal stored positions self.servoAngles[move.index + 4] = self.rotator_servo_angles[move.index][move.position] self.rotatorPositions[move.index] = move.position print(self.rotatorPositions) case 'extension': # for converting between some of the random variable names # todo obsolete this converter_dict = {'r': 'retract', 'e': 'extend', 'f': 'fullExtend'} value = self.retraction_servo_angles[converter_dict[move.position]][move.index] raw_move_list.append({ 'component': 'servo', 'index': move.index, 'value': value, 'time': time }) # update internal stored positions self.retractorPositions[move.index] = move.position case 'delay': time += move.delay case 'stop': raw_move_list.append({ 'component': 'servo', 'index': move.index, 'value': -180, 'time': time }) raw_move_list.append({ 'component': 'servo', 'index': move.index + 4, 'value': -180, 'time': time }) case 'led': raw_move_list.append({ 'component': 'led', 'index': move.index, 'value': move.colour, 'time': time }) case 'photo': raw_move_list.append({ 'component': 'camera', 'time': time }) # add in a stop message to the end of the list for i in range(9): raw_move_list.append({ 'component': 'servo', 'index': i, 'value': -180, 'time': time + 1 }) requests.post(self.url + 'receiveList', json=raw_move_list) class Counter: servo = None angle = None delay = None def __init__(self, n_servo, n_angle, n_delay): self.servo = n_servo self.angle = n_angle self.delay = n_delay