├── .gitignore ├── ArmControl.py ├── ChessLogic.py ├── Interface.py ├── LICENSE ├── README.md ├── VisionModule.py ├── audio ├── capture.mp3 ├── check.mp3 ├── checkmate.mp3 ├── excuse.mp3 ├── good_luck.mp3 ├── goodbye.mp3 ├── invalid_move.mp3 ├── k_castling.mp3 ├── passant.mp3 ├── please.mp3 ├── promotion.mp3 ├── q_castling.mp3 └── reset.mp3 ├── games └── stockfishX64.exe ├── interface_images ├── bclock.png ├── cb_pattern.jpg ├── game_start.png ├── lss_arm_cb.png ├── lss_arm_setup.jpg ├── robot_icon.ico └── wclock.png ├── lss.py ├── lss_const.py ├── params.txt ├── pieces_images ├── ChessPiecesArray.png ├── bishopb.png ├── bishopw.png ├── blank.png ├── game.pgn ├── kingb.ico ├── kingb.png ├── kingw.png ├── knightb.png ├── knightw.png ├── nbishopb.png ├── nbishopw.png ├── nkingb.png ├── nkingw.png ├── nknightb.png ├── nknightw.png ├── npawnb.png ├── npawnw.png ├── nqueenb.png ├── nqueenw.png ├── nrookb.png ├── nrookw.png ├── pawnb.png ├── pawnw.png ├── queenb.png ├── queenw.png ├── rookb.png └── rookw.png └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | env/ 2 | __pycache__/ 3 | params.txt -------------------------------------------------------------------------------- /ArmControl.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from math import atan2, sqrt, cos, sin, acos, pi, copysign 3 | import time 4 | import os 5 | from gtts import gTTS 6 | import VisionModule as vm 7 | import lss 8 | import lss_const as lssc 9 | import platform 10 | import Interface as inter 11 | 12 | # Change to the correct COM port for your setup 13 | if platform.system() == 'Windows': 14 | port = "COM3" 15 | elif platform.system() == 'Linux': 16 | port = "/dev/ttyUSB0" 17 | 18 | lss.initBus(port,lssc.LSS_DefaultBaud) 19 | base = lss.LSS(1) 20 | shoulder = lss.LSS(2) 21 | elbow = lss.LSS(3) 22 | wrist = lss.LSS(4) 23 | gripper = lss.LSS(5) 24 | allMotors = lss.LSS(254) 25 | 26 | allMotors.setAngularHoldingStiffness(0) 27 | allMotors.setMaxSpeed(100) 28 | base.setMaxSpeed(60) 29 | shoulder.setMotionControlEnabled(0) 30 | elbow.setMotionControlEnabled(0) 31 | 32 | CST_ANGLE_MIN = -90 33 | CST_ANGLE_MAX = 90 34 | 35 | def checkConstraints(value, min, max): 36 | if (value < min): 37 | value = min 38 | if (value > max): 39 | value = max 40 | return (value) 41 | 42 | # Desired positions in x, y, z, gripper aperture 43 | def LSS_IK(targetXYZG): 44 | 45 | d1 = 4.13 # Bottom to shoulder 46 | d2 = 5.61 # Shoulder to elbow 47 | d3 = 6.39 # Elbow to wrist 48 | d4 = 4.52 # Wrist to end of gripper 49 | 50 | x0 = targetXYZG[0] 51 | y0 = targetXYZG[1] 52 | z0 = targetXYZG[2] 53 | g0 = targetXYZG[3] 54 | 55 | # Base angle (degrees) 56 | q1 = atan2(y0,x0) * 180/pi 57 | 58 | # Radius from the axis of rotation of the base in xy plane 59 | xyr = sqrt(x0**2 + y0**2) 60 | 61 | # Pitch angle 62 | q0 = 80 63 | 64 | # Gripper components in xz plane 65 | lx = d4 * cos(q0 * pi/180) 66 | lz = d4 * sin(q0 * pi/180) 67 | 68 | # Wrist coordinates in xz plane 69 | x1 = xyr - lx 70 | z1 = z0 + lz - d1 71 | 72 | # Distance between the shoulder axis and the wrist axis 73 | h = sqrt(x1**2 + z1**2) 74 | 75 | a1 = atan2(z1,x1) 76 | a2 = acos((d2**2 - d3**2 + h**2)/(2 * d2 * h)) 77 | 78 | # Shoulder angle (degrees) 79 | q2 = (a1 + a2) * 180/pi 80 | 81 | # Elbow angle (degrees) 82 | a3 = acos((d2**2 + d3**2 - h**2)/(2 * d2 * d3)) 83 | q3 = 180 - a3 * 180/pi 84 | 85 | # Wrist angle (degrees) (add 5 deg because of the wrist-gripper offset) 86 | q4 = q0 - (q3 - q2) + 5 87 | 88 | # Add 15 deg because of the shoulder-elbow axis offset 89 | q2 = q2 + 15 90 | 91 | # Return values Base, Shoulder, Elbow, Wrist, Gripper 92 | angles_BSEWG = [ q1, 90-q2, q3-90, q4, g0] 93 | 94 | # Check constraints 95 | for i in range(0,5): 96 | angles_BSEWG[i] = checkConstraints(angles_BSEWG[i], CST_ANGLE_MIN, CST_ANGLE_MAX) 97 | 98 | return(np.dot(10,angles_BSEWG).astype(int)) 99 | 100 | def LSSA_moveMotors(angles_BSEWG): 101 | 102 | # If the servos detect a current higher or equal than the value (mA) before reaching the requested positions they will halt and hold 103 | wrist.moveCH(angles_BSEWG[3], 1000) 104 | shoulder.moveCH(angles_BSEWG[1], 1600) 105 | elbow.moveCH(angles_BSEWG[2], 1600) 106 | base.moveCH(angles_BSEWG[0], 1000) 107 | gripper.moveCH(angles_BSEWG[4], 500) 108 | 109 | arrived = False 110 | issue = 0 111 | i = 0 112 | 113 | # Check if they reached the requested position 114 | while arrived == False and issue == 0: 115 | bStat = base.getStatus() 116 | sStat = shoulder.getStatus() 117 | eStat = elbow.getStatus() 118 | wStat = wrist.getStatus() 119 | gStat = gripper.getStatus() 120 | # If a status is None print message if it continues to be None return issue 1 121 | if (bStat is None or sStat is None or eStat is None or wStat is None or gStat is None): 122 | print("- Unknown status") 123 | i = i + 1 124 | if (i >= 5): 125 | print("- Issue detected") 126 | issue = 1 127 | # If the statuses aren't None check their values 128 | else: 129 | # If a servo is Outside limits, Stuck, Blocked or in Safe Mode before it reaches the requested position reset the servos and return issue 1 130 | if (int(bStat)>6 or int(sStat)>6 or int(eStat)>6 or int(wStat)>6 or int(gStat)>6): 131 | print("- Issue detected") 132 | issue = 1 133 | # If all the servos are holding positions check if they have arrived 134 | elif (int(bStat)==6 and int(sStat)==6 and int(eStat)==6 and int(wStat)==6 and int(gStat)==6): 135 | bPos = base.getPosition() 136 | sPos = shoulder.getPosition() 137 | ePos = elbow.getPosition() 138 | wPos = wrist.getPosition() 139 | # If any position is None 140 | if (bPos is None or sPos is None or ePos is None or wPos is None): 141 | print("- Unknown position") 142 | # If they are holding in a different position than the requested one return issue 2 143 | elif (abs(int(bPos)-angles_BSEWG[0])>20 or abs(int(sPos)-angles_BSEWG[1])>50 or abs(int(ePos)-angles_BSEWG[2])>50 or abs(int(wPos)-angles_BSEWG[3])>20): 144 | sStat = shoulder.getStatus() 145 | eStat = elbow.getStatus() 146 | # Re-check shoulder and elbow status and positions 147 | if (int(sStat)==6 and int(eStat)==6): 148 | sPos = shoulder.getPosition() 149 | ePos = elbow.getPosition() 150 | if (sPos is None or ePos is None): 151 | print("- Unknown position") 152 | elif (abs(int(sPos)-angles_BSEWG[1])>40 or abs(int(ePos)-angles_BSEWG[2])>40): 153 | print("- Obstacle detected") 154 | issue = 2 155 | else: 156 | print("- Arrived\n") 157 | arrived = True 158 | 159 | return(arrived, issue) 160 | 161 | def CBtoXY(targetCBsq, params, color): 162 | 163 | wletterWeight = [-4, -3, -2, -1, 1, 2, 3, 4] 164 | bletterWeight = [4, 3, 2, 1, -1, -2, -3, -4] 165 | bnumberWeight = [8, 7, 6, 5, 4, 3, 2, 1] 166 | 167 | if targetCBsq[0] == 'k': 168 | x = 6 * params["sqSize"] 169 | y = 6 * params["sqSize"] 170 | else: 171 | if color: # White -> Robot color = Black 172 | sqletter = bletterWeight[ord(targetCBsq[0]) - 97] 173 | sqNumber = bnumberWeight[int(targetCBsq[1]) - 1] 174 | else: # Black 175 | sqletter = wletterWeight[ord(targetCBsq[0]) - 97] 176 | sqNumber = int(targetCBsq[1]) 177 | 178 | x = params["baseradius"] + params["cbFrame"] + params["sqSize"] * sqNumber - params["sqSize"]*1/2 179 | y = params["sqSize"] * sqletter - copysign(params["sqSize"]*1/2,sqletter) 180 | 181 | return(x,y) 182 | 183 | def executeMove(move, params, color, homography, cap, selectedCam): 184 | 185 | allMotors.setColorLED(lssc.LSS_LED_Cyan) 186 | z = params["cbHeight"] + params["pieceHeight"] 187 | angles_rest = (0,-1155,450,1050,0) 188 | gClose = -2 189 | gOpen = -9.5 190 | goDown = 0.6*params["pieceHeight"] 191 | gripState = gOpen 192 | x, y = 0, 0 193 | 194 | for i in range(0,len(move),2): 195 | 196 | # Calculate position and FPC 197 | x0, y0 = x, y 198 | x, y = CBtoXY((move[i],move[i+1]), params, color) 199 | distance = sqrt(((x0-x)**2)+((y0-y)**2)) 200 | fpc = int(distance) + 15 201 | shoulder.setFilterPositionCount(fpc) 202 | elbow.setFilterPositionCount(fpc) 203 | 204 | # Go up 205 | angles_BSEWG1 = LSS_IK([x, y, z + 1, gripState]) 206 | print("1) MOVE UP") 207 | arrived1,issue1 = LSSA_moveMotors(angles_BSEWG1) 208 | askPermision(angles_BSEWG1, arrived1, issue1, homography, cap, selectedCam) 209 | 210 | # Go down 211 | shoulder.setFilterPositionCount(15) 212 | elbow.setFilterPositionCount(15) 213 | angles_BSEWG2 = LSS_IK([x, y, z - 1 - goDown, gripState]) 214 | print("2) GO DOWN") 215 | arrived2,issue2 = LSSA_moveMotors(angles_BSEWG2) 216 | askPermision(angles_BSEWG2, arrived2, issue2, homography, cap, selectedCam) 217 | 218 | if (i/2)%2: # Uneven move (go lower to grab the piece) 219 | gripState = gOpen 220 | goDown = 0.6*params["pieceHeight"] 221 | else: # Even move (go a little higher to drop the piece) 222 | gripState = gClose 223 | goDown = 0.5*params["pieceHeight"] 224 | 225 | # Close / Open the gripper 226 | gripper.moveCH(int(gripState*10), 500) 227 | time.sleep(1) 228 | print("3) CLOSE/OPEN the gripper\n") 229 | 230 | # Go up 231 | angles_BSEWG3 = LSS_IK([x, y, z + 1, gripState]) 232 | print("4) GO UP") 233 | arrived3,issue3 = LSSA_moveMotors(angles_BSEWG3) 234 | askPermision(angles_BSEWG3, arrived3, issue3, homography, cap, selectedCam) 235 | 236 | # Go back to resting position and go limp 237 | distance = sqrt(((x)**2)+((y)**2)) 238 | fpc = int(distance) + 15 239 | shoulder.setFilterPositionCount(fpc) 240 | elbow.setFilterPositionCount(fpc) 241 | print("5) REST") 242 | moveState,_ = LSSA_moveMotors(angles_rest) 243 | allMotors.limp() 244 | allMotors.setColorLED(lssc.LSS_LED_Black) 245 | 246 | return(moveState) 247 | 248 | def askPermision(angles_BSEWG, arrived, issue, homography, cap, selectedCam): 249 | 250 | angles_rest = (0,-1155,450,1050,0) 251 | sec = 0 252 | 253 | while arrived == False: # If the servos couldn't reach the requested position 254 | if issue == 1: 255 | inter.speak("reset") # If a servo exceeded a limit 256 | allMotors.setColorLED(lssc.LSS_LED_Red) # Set LEDs to color Red 257 | wrist.move(1050) # Send arm to rest position 258 | shoulder.move(-1155) 259 | elbow.move(450) 260 | base.move(0) 261 | time.sleep(2) 262 | allMotors.limp() # Make the arm go limp 263 | allMotors.reset() # Reset the servos 264 | time.sleep(2) 265 | allMotors.confirm() 266 | allMotors.setMaxSpeed(100) # Reconfigure parameters 267 | base.setMaxSpeed(60) 268 | shoulder.setMotionControlEnabled(0) 269 | elbow.setMotionControlEnabled(0) 270 | shoulder.setFilterPositionCount(15) 271 | elbow.setFilterPositionCount(15) 272 | elif issue == 2: 273 | if sec == 0: 274 | inter.speak("excuse") # Play audio asking for permission 275 | else: 276 | inter.speak("please") 277 | print("Please move over") 278 | allMotors.setColorLED(lssc.LSS_LED_Magenta) # Change LEDs to Magenta 279 | LSSA_moveMotors(angles_rest) # Go to resting position 280 | allMotors.limp() 281 | sec = 0 282 | else: 283 | print("OK") 284 | 285 | while arrived == False and sec < 3: # If the servos couldn't reach the requested position and we haven't waited 3 sec 286 | if vm.safetoMove(homography, cap, selectedCam) != 0 or sec == 2: # Check if it is safe to move (vision code) or if we have waited 3 sec 287 | print("- Retrying") 288 | allMotors.setColorLED(lssc.LSS_LED_Cyan) # If it is true we change LEDs back to cyan 289 | arrived,_ = LSSA_moveMotors(angles_BSEWG) # And try moving the motors again 290 | else: 291 | time.sleep(1) # Wait one second 292 | sec = sec + 1 293 | 294 | def winLED(allMotors): 295 | for i in range (0, 4): 296 | for j in range (1, 8): # Loop through each of the 8 LED color (black = 0, red = 1, ..., white = 7) 297 | allMotors.setColorLED(j) # Set the color (session) of the LSS 298 | time.sleep(0.3) # Wait 0.3 seconds per color change 299 | allMotors.setColorLED(lssc.LSS_LED_Black) -------------------------------------------------------------------------------- /ChessLogic.py: -------------------------------------------------------------------------------- 1 | 2 | ''' 3 | 4 | chess.SQUARE_NAMES.index("h8") 5 | print(chess.piece_name(board.piece_type_at(chess.A7))) 6 | 7 | chess.piece_name(board.piece_type_at(chess.SQUARE_NAMES.index("e3"))) 8 | 9 | chess.square_file(chess.SQUARE_NAMES.index("e2")) #me da realmente la columna, el resiltado para este ejemplo es 4 10 | 11 | chess.square_rank(chess.SQUARE_NAMES.index("e2")) #Da como resultado la fila, para este ejemplo es 1 12 | 13 | board.piece_type_at(chess.SQUARE_NAMES.index("e3")) == None 14 | 15 | ''' 16 | 17 | #CONSTRUIREMOS LAS FUNCIONES BASICAS PARA EL CASO DE JUGAR CON BLANCAS 18 | 19 | #Modificar sistema de juego para en lugar de colocar la jugada, cologar un vector de casillas 20 | 21 | import chess 22 | import chess.engine 23 | 24 | def showCheck(board): 25 | if board.is_check(): 26 | print("CHECK!!") 27 | 28 | def sequenceGenerator(uciMove, board): 29 | ''' 30 | INPUT: 31 | uciMove -> Move in UCI format to analyze it. 32 | board -> Game state, this is a chess.Board() object. 33 | OUTPUT: 34 | result -> Is empty if a valid move is not detected, if it is detected then it is a dictionary data structure with two values: 35 | "seq": String with a sequence of chess coordinates to guide th robot movement. 36 | "type": String with type of movement, Castling-Promotion-Move-Capture-Passant. 37 | ''' 38 | result = {} 39 | move = chess.Move.from_uci(uciMove) 40 | first = uciMove[:2] 41 | second = uciMove[2:4] 42 | last = uciMove[-1] 43 | graveyard = "k0" 44 | 45 | if last < '1' or last > '8': # Promotion Evaluation: Check if last char is a letter. 46 | if board.is_capture(move): 47 | result["seq"] = second + graveyard + first + second 48 | else: 49 | result["seq"] = first + second 50 | result["type"] = "Promotion" 51 | elif board.turn and uciMove == "e1g1" and chess.piece_name(board.piece_type_at(chess.SQUARE_NAMES.index("e1"))) == "king": #White King side castling 52 | result["seq"] = first + second + "h1f1" 53 | result["type"] = "White King Side Castling" 54 | elif board.turn and uciMove == "e1c1" and chess.piece_name(board.piece_type_at(chess.SQUARE_NAMES.index("e1"))) == "king": #White Queen side castling 55 | result["seq"] = first + second + "a1d1" 56 | result["type"] = "White Queen Side Castling" 57 | elif not board.turn and uciMove == "e8g8" and chess.piece_name(board.piece_type_at(chess.SQUARE_NAMES.index("e8"))) == "king": #Black King side castling 58 | result["seq"] = first + second + "h8f8" 59 | result["type"] = "Black King Side Castling" 60 | elif not board.turn and uciMove == "e8c8" and chess.piece_name(board.piece_type_at(chess.SQUARE_NAMES.index("e8"))) == "king": #Black Queen side castling 61 | result["seq"] = first + second + "a8d8" 62 | result["type"] = "White Queen Side Castling" 63 | elif board.is_en_passant(move): # En Passant 64 | if board.turn: 65 | squareFile = chess.square_file(chess.SQUARE_NAMES.index(first)) 66 | squareRank = chess.square_rank(chess.SQUARE_NAMES.index(first)) 67 | result["seq"] = chess.SQUARE_NAMES[chess.square(squareFile-1,squareRank)] + graveyard + first + second 68 | result["type"] = "Passant" 69 | else: 70 | squareFile = chess.square_file(chess.SQUARE_NAMES.index(first)) 71 | squareRank = chess.square_rank(chess.SQUARE_NAMES.index(first)) 72 | result["seq"] = chess.SQUARE_NAMES[chess.square(squareFile+1,squareRank)] + graveyard + first + second 73 | result["type"] = "Passant" 74 | elif board.is_capture(move): # Capture move 75 | result["seq"] = second + graveyard + first + second 76 | result["type"] = "Capture" 77 | else: # Normal move 78 | result["seq"] = uciMove 79 | result["type"] = "Move" 80 | 81 | return result 82 | 83 | def moveAnalysis (squares, board): 84 | ''' 85 | INPUT: 86 | squares -> Squares detected by the computer vision algorithm. 87 | board -> State of game, this is a chess.Board() object. 88 | OUTPUT: 89 | result -> Is empty if a valid move is not detected, or on the contrary, a dictionary data structure with two values: 90 | "move": String with the valid movement in UCI format, for example "e2e4". 91 | "type": String with type of movement, Castling-Promotion-Move-Capture-Passant. 92 | 93 | NOTE: In case of promotion we must concatenate the new piece to UCI command, 94 | before pushing the move to the board state, we ask the user which is the promoted piece. 95 | ''' 96 | 97 | result = {} # Move type, move coordinates 98 | kindOfMove = "" 99 | uciMove = "" 100 | 101 | elements = len(squares) 102 | if elements < 5 and elements > 1: # Lenght verification 103 | legalMoves = board.legal_moves 104 | 105 | if elements == 4: # Clastling evaluation 106 | if board.turn: # White Turn 107 | if ("e1" in squares) and ("h1" in squares): # Queen side castling evaluation 108 | if chess.Move.from_uci("e1g1") in legalMoves: 109 | result["move"] = "e1g1" 110 | result["type"] = "Castling" 111 | if ("e1" in squares) and ("a1" in squares): # Queen side castling evaluation 112 | if chess.Move.from_uci("e1c1") in legalMoves: 113 | result["move"] = "e1c1" 114 | result["type"] = "Castling" 115 | else: # Black Turn 116 | if ("e8" in squares) and ("h8" in squares): # Queen side castling evaluation 117 | if chess.Move.from_uci("e8g8") in legalMoves: 118 | result["move"] = "e8g8" 119 | result["type"] = "Castling" 120 | if ("e8" in squares) and ("a8" in squares): # Queen side castling evaluation 121 | if chess.Move.from_uci("e8c8") in legalMoves: 122 | result["move"] = "e8c8" 123 | result["type"] = "Castling" 124 | 125 | if not result: 126 | moveVector = [squares[0]+squares[1],squares[1]+squares[0]] # Instantiate the list with the two most probable movements 127 | if elements > 2: 128 | moveVector.append(squares[0]+squares[2]) 129 | moveVector.append(squares[2]+squares[0]) 130 | moveVector.append(squares[1]+squares[2]) 131 | moveVector.append(squares[2]+squares[1]) 132 | for uciMove in moveVector: # Move Validation 133 | move = chess.Move.from_uci(uciMove) 134 | if move in legalMoves: 135 | result["move"] = uciMove 136 | if board.is_en_passant(move): 137 | result["type"] = "Passant" 138 | elif board.is_capture(move): 139 | result["type"] = "Capture" 140 | else: 141 | result["type"] = "Move" 142 | return result 143 | elif chess.Move.from_uci(uciMove+"q") in legalMoves: 144 | result["move"] = uciMove 145 | result["type"] = "Promotion" 146 | return result 147 | 148 | return result -------------------------------------------------------------------------------- /Interface.py: -------------------------------------------------------------------------------- 1 | import FreeSimpleGUI as sg 2 | import ChessLogic as cl 3 | import time 4 | import os 5 | import copy 6 | import threading 7 | import time 8 | import cv2 9 | import sys 10 | import json 11 | import VisionModule as vm 12 | import platform 13 | import ArmControl as ac 14 | import lss_const as lssc 15 | import pygame 16 | import pathlib 17 | 18 | try: 19 | from picamera.array import PiRGBArray 20 | from picamera import PiCamera 21 | except: 22 | pass 23 | 24 | ''' 25 | styles: 26 | 27 | BlueMono 28 | GreenTan 29 | LightGreen 30 | Black 31 | Dark 32 | ''' 33 | 34 | # GLOBAL VARIABLES 35 | 36 | CHESS_PATH = 'pieces_images' # path to the chess pieces 37 | 38 | BLANK = 0 # piece names 39 | PAWNW = 1 40 | KNIGHTW = 2 41 | BISHOPW = 3 42 | ROOKW = 4 43 | QUEENW = 5 44 | KINGW = 6 45 | PAWNB = 7 46 | KNIGHTB = 8 47 | BISHOPB = 9 48 | ROOKB = 10 49 | QUEENB = 11 50 | KINGB = 12 51 | 52 | blank = os.path.join(CHESS_PATH, 'blank.png') 53 | bishopB = os.path.join(CHESS_PATH, 'nbishopb.png') 54 | bishopW = os.path.join(CHESS_PATH, 'nbishopw.png') 55 | pawnB = os.path.join(CHESS_PATH, 'npawnb.png') 56 | pawnW = os.path.join(CHESS_PATH, 'npawnw.png') 57 | knightB = os.path.join(CHESS_PATH, 'nknightb.png') 58 | knightW = os.path.join(CHESS_PATH, 'nknightw.png') 59 | rookB = os.path.join(CHESS_PATH, 'nrookb.png') 60 | rookW = os.path.join(CHESS_PATH, 'nrookw.png') 61 | queenB = os.path.join(CHESS_PATH, 'nqueenb.png') 62 | queenW = os.path.join(CHESS_PATH, 'nqueenw.png') 63 | kingB = os.path.join(CHESS_PATH, 'nkingb.png') 64 | kingW = os.path.join(CHESS_PATH, 'nkingw.png') 65 | 66 | images = {BISHOPB: bishopB, BISHOPW: bishopW, PAWNB: pawnB, PAWNW: pawnW, KNIGHTB: knightB, KNIGHTW: knightW, 67 | ROOKB: rookB, ROOKW: rookW, KINGB: kingB, KINGW: kingW, QUEENB: queenB, QUEENW: queenW, BLANK: blank} 68 | 69 | wclock = os.getcwd() + '/interface_images/wclock.png' 70 | bclock = os.getcwd() + '/interface_images/bclock.png' 71 | FENCODE = "" 72 | 73 | colorTurn = True 74 | graveyard = 'k0' 75 | playerColor = True 76 | playing = False 77 | blackSquareColor = '#B58863' 78 | whiteSquareColor = '#F0D9B5' 79 | Debug = False 80 | sequence = [] 81 | state = "stby" 82 | newGameState = "config" 83 | gameTime = 60.00 84 | whiteSide = 0 85 | route = os.getcwd() + '/' 86 | homography = [] 87 | prevIMG = [] 88 | chessRoute = "" 89 | detected = True 90 | selectedCam = 0 91 | skillLevel = 10 # Chess engine difficulty level 92 | cap = cv2.VideoCapture() 93 | rotMat = vm.np.zeros((2,2)) 94 | physicalParams = {"baseradius": 0.00, 95 | "cbFrame": 0.00, 96 | "sqSize": 0.00, 97 | "cbHeight": 0.00, 98 | "pieceHeight": 0.00} 99 | 100 | def systemConfig(): 101 | global chessRoute 102 | 103 | if platform.system() == 'Windows': 104 | chessRoute = "games/stockfishX64.exe" 105 | elif platform.system() == 'Linux': 106 | chessRoute = "/usr/games/stockfish" 107 | 108 | # GAME FUNCTIONS 109 | 110 | def pcTurn(board,engine): 111 | global sequence 112 | global state 113 | global homography 114 | global cap 115 | global selectedCam 116 | 117 | command = "" 118 | pcMove = engine.play(board, cl.chess.engine.Limit(time=1)) 119 | sequence = cl.sequenceGenerator(pcMove.move.uci(), board) 120 | 121 | window.FindElement(key = "gameMessage").Update(sequence["type"]) 122 | if sequence["type"] == "White Queen Side Castling" or sequence["type"] == "Black Queen Side Castling": 123 | command = "q_castling" 124 | elif sequence["type"] == "White King Side Castling" or sequence["type"] == "Black King Side Castling": 125 | command = "k_castling" 126 | elif sequence["type"] == "Capture": 127 | command = "capture" 128 | elif sequence["type"] == "Passant": 129 | command = "passant" 130 | elif sequence["type"] == "Promotion": 131 | command = "promotion" 132 | if command: 133 | speakThread = threading.Thread(target=speak, args=[command], daemon=True) 134 | speakThread.start() 135 | 136 | command = "" 137 | ac.executeMove(sequence["seq"], physicalParams, playerColor, homography, cap, selectedCam) 138 | board.push(pcMove.move) 139 | updateBoard(sequence, board) 140 | if board.is_checkmate(): 141 | window.FindElement(key = "robotMessage").Update("CHECKMATE!") 142 | command = "checkmate" 143 | elif board.is_check(): 144 | window.FindElement(key = "robotMessage").Update("CHECK!") 145 | command = "check" 146 | if command: 147 | speakThread = threading.Thread(target=speak, args=[command], daemon=True) 148 | speakThread.start() 149 | state = "robotMove" 150 | if board.is_game_over(): 151 | playing = False 152 | state = "showGameResult" 153 | 154 | def startEngine(): 155 | global engine 156 | global state 157 | global homography 158 | 159 | engine = cl.chess.engine.SimpleEngine.popen_uci(chessRoute) 160 | engine.configure({"Skill Level": skillLevel}) 161 | if playerColor == colorTurn: 162 | state = "playerTurn" 163 | else: 164 | state = "pcTurn" 165 | 166 | def playerTurn(board,squares): 167 | result = cl.moveAnalysis(squares, board) 168 | piece = "" 169 | if result: 170 | if result["type"] == "Promotion": 171 | while not piece: 172 | piece = coronationWindow() 173 | result["move"] += piece 174 | 175 | sequence = cl.sequenceGenerator(result["move"], board) 176 | window.FindElement(key = "gameMessage").Update(sequence["type"]) 177 | board.push_uci(result["move"]) 178 | updateBoard(sequence,board) 179 | return True 180 | else: 181 | return False 182 | 183 | def startGame(): 184 | global gameTime 185 | window.FindElement("newGame").Update(disabled=True) 186 | window.FindElement("quit").Update(disabled=False) 187 | window.FindElement(key = "wcount").Update(time.strftime("%H:%M:%S", time.gmtime(gameTime))) 188 | window.FindElement(key = "bcount").Update(time.strftime("%H:%M:%S", time.gmtime(gameTime))) 189 | window.FindElement(key = "clockButton").Update(image_filename=wclock) 190 | window.FindElement(key = "robotMessage").Update("Good Luck!") 191 | window.FindElement(key = "gameMessage").Update("--") 192 | 193 | def quitGame(): 194 | window.FindElement("newGame").Update(disabled=False) 195 | window.FindElement("quit").Update(disabled=True) 196 | engine.quit() 197 | 198 | # INTERFACE FUNCTIONS 199 | 200 | def renderSquare(image, key, location): 201 | if (location[0] + location[1]) % 2: 202 | color = blackSquareColor 203 | else: 204 | color = whiteSquareColor 205 | return sg.Button('', image_filename=image, size=(1, 1), 206 | border_width=0, button_color=('white', color), 207 | pad=(0, 0), key=key) 208 | 209 | def redrawBoard(board): 210 | columns = 'abcdefgh' 211 | global playerColor 212 | if playerColor: 213 | sq = 63 214 | for i in range(8): 215 | window.FindElement(key = str(8-i)+"r").Update(" "+str(8-i)) 216 | window.FindElement(key = str(8-i)+"l").Update(str(8-i)+" ") 217 | for j in range(8): 218 | window.FindElement(key = columns[j]+"t").Update(columns[j]) 219 | window.FindElement(key = columns[j]+"b").Update(columns[j]) 220 | color = blackSquareColor if (i + 7-j) % 2 else whiteSquareColor 221 | pieceNum = board.piece_type_at(sq) 222 | if pieceNum: 223 | if not board.color_at(sq): 224 | pieceNum += 6 225 | else: 226 | pieceNum = 0 227 | piece_image = images[pieceNum] 228 | elem = window.FindElement(key=(i, 7-j)) 229 | elem.Update(button_color=('white', color), 230 | image_filename=piece_image, ) 231 | sq -= 1 232 | else: 233 | sq = 0 234 | for i in range(8): 235 | window.FindElement(key = str(8-i)+"r").Update(" "+str(i+1)) 236 | window.FindElement(key = str(8-i)+"l").Update(str(i+1)+" ") 237 | for j in range(8): 238 | window.FindElement(key = columns[j]+"t").Update(columns[7-j]) 239 | window.FindElement(key = columns[j]+"b").Update(columns[7-j]) 240 | color = blackSquareColor if (i + 7-j) % 2 else whiteSquareColor 241 | pieceNum = board.piece_type_at(sq) 242 | if pieceNum: 243 | if not board.color_at(sq): 244 | pieceNum += 6 245 | else: 246 | pieceNum = 0 247 | piece_image = images[pieceNum] 248 | elem = window.FindElement(key=(i, 7-j)) 249 | elem.Update(button_color=('white', color), 250 | image_filename=piece_image, ) 251 | sq += 1 252 | 253 | def updateBoard(move, board): 254 | global playerColor 255 | global images 256 | for cont in range(0,len(move["seq"]),4): 257 | squareCleared = move["seq"][cont:cont+2] 258 | squareOcupied = move["seq"][cont+2:cont+4] 259 | scNum = cl.chess.SQUARE_NAMES.index(squareCleared) 260 | 261 | y = cl.chess.square_file(scNum) 262 | x = 7 - cl.chess.square_rank(scNum) 263 | color = blackSquareColor if (x + y) % 2 else whiteSquareColor 264 | if playerColor: 265 | elem = window.FindElement(key=(x, y)) 266 | else: 267 | elem = window.FindElement(key=(7-x, 7-y)) 268 | elem.Update(button_color=('white', color), 269 | image_filename=blank, ) 270 | 271 | if squareOcupied != graveyard: 272 | soNum = cl.chess.SQUARE_NAMES.index(squareOcupied) 273 | pieceNum = board.piece_type_at(soNum) 274 | if not board.color_at(soNum): 275 | pieceNum += 6 276 | y = cl.chess.square_file(soNum) 277 | x = 7 - cl.chess.square_rank(soNum) 278 | color = blackSquareColor if (x + y) % 2 else whiteSquareColor 279 | if playerColor: 280 | elem = window.FindElement(key=(x, y)) 281 | else: 282 | elem = window.FindElement(key=(7-x, 7-y)) 283 | elem.Update(button_color=('white', color), 284 | image_filename=images[pieceNum], ) 285 | 286 | def sideConfig(): # gameState: sideConfig 287 | global newGameState 288 | global state 289 | global whiteSide 290 | global prevIMG 291 | global rotMat 292 | i = 0 293 | 294 | img = vm.drawQuadrants(prevIMG) 295 | imgbytes = cv2.imencode('.png', img)[1].tobytes() 296 | 297 | windowName = "Calibration" 298 | initGame = [[sg.Text('Please select the "white" pieces side', justification='center', pad = (25,(5,15)), font='Any 15')], 299 | [sg.Image(data=imgbytes, key='boardImg')], 300 | [sg.Radio('1-2', group_id='grp', default = True,font='Any 14'), sg.Radio('2-3', group_id='grp',font='Any 14'),sg.Radio('4-3', group_id='grp',font='Any 14'), sg.Radio('1-4', group_id='grp',font='Any 14')], 301 | [sg.Text('_'*30)], 302 | [sg.Button("Back"), sg.Submit("Play")]] 303 | newGameWindow = sg.Window(windowName, default_button_element_size=(12,1), auto_size_buttons=False, location = (100,50), icon='interface_images/robot_icon.ico').Layout(initGame) 304 | 305 | while True: 306 | button,value = newGameWindow.Read(timeout=100) 307 | 308 | if button == "Play": 309 | newGameState = "initGame" 310 | while value[i] == False and i<4: 311 | i+=1 312 | whiteSide = i 313 | if whiteSide == 0: 314 | theta = 90 315 | elif whiteSide == 1: 316 | theta = 180 317 | elif whiteSide == 2: 318 | theta = -90 319 | elif whiteSide == 3: 320 | theta = 0 321 | 322 | rotMat = vm.findRotation(theta) 323 | prevIMG = vm.applyRotation(prevIMG,rotMat) 324 | break 325 | if button == "Back": 326 | newGameState = "ocupiedBoard" 327 | break 328 | if button in (None, 'Exit'): # MAIN WINDOW 329 | state = "stby" 330 | newGameState = "config" 331 | break 332 | 333 | newGameWindow.close() 334 | 335 | def ocupiedBoard(): # gameState: ocupiedBoard 336 | global newGameState 337 | global state 338 | global selectedCam 339 | global homography 340 | global prevIMG 341 | 342 | windowName = "Calibration" 343 | initGame = [[sg.Text('Place the chess pieces and press Next', justification='center', pad = (25,(5,15)), font='Any 15')], 344 | [sg.Image(filename='', key='boardVideo')], 345 | [sg.Text('_'*30)], 346 | [sg.Button("Back"), sg.Submit("Next")]] 347 | newGameWindow = sg.Window(windowName, default_button_element_size=(12,1), auto_size_buttons=False, location = (100,50), icon='interface_images/robot_icon.ico').Layout(initGame) 348 | 349 | while True: 350 | button,value = newGameWindow.Read(timeout = 10) 351 | 352 | if detected: 353 | frame = takePIC() 354 | prevIMG = vm.applyHomography(frame,homography) 355 | imgbytes = cv2.imencode('.png', prevIMG)[1].tobytes() 356 | newGameWindow.FindElement('boardVideo').Update(data=imgbytes) 357 | 358 | if button == "Next": 359 | newGameState = "sideConfig" 360 | break 361 | if button == "Back": 362 | newGameState = "calibration" 363 | break 364 | if button in (None, 'Exit'): # MAIN WINDOW 365 | state = "stby" 366 | newGameState = "config" 367 | break 368 | 369 | newGameWindow.close() 370 | 371 | def calibration(): # gameState: calibration 372 | global newGameState 373 | global state 374 | global selectedCam 375 | global homography 376 | global detected 377 | 378 | cbPattern = cv2.imread(route+'interface_images/cb_pattern.jpg', cv2.IMREAD_GRAYSCALE) 379 | 380 | windowName = "Camera calibration" 381 | initGame = [[sg.Text('Please adjust your camera and remove any chess piece', justification='center', pad = (25,(5,15)), font='Any 15', key = "calibrationBoard")], 382 | [sg.Image(filename='', key='boardVideo')], 383 | [sg.Text('_'*30)], 384 | [sg.Button("Back"), sg.Submit("Next")]] 385 | newGameWindow = sg.Window(windowName, default_button_element_size=(12,1), auto_size_buttons=False, location = (100,50), icon='interface_images/robot_icon.ico').Layout(initGame) 386 | 387 | while True: 388 | button,value = newGameWindow.Read(timeout = 10) 389 | 390 | if detected: 391 | frame = takePIC() 392 | imgbytes = cv2.imencode('.png', frame)[1].tobytes() 393 | newGameWindow.FindElement('boardVideo').Update(data=imgbytes) 394 | homography = [] 395 | retIMG, homography = vm.findTransformation(frame,cbPattern) 396 | if retIMG: 397 | newGameWindow.FindElement('calibrationBoard').Update("Camera calibration successful. Please press Next") 398 | else: 399 | newGameWindow.FindElement('calibrationBoard').Update("Please adjust your camera and remove any chess piece") 400 | 401 | if button == "Next" and retIMG: 402 | newGameState = "ocupiedBoard" 403 | break 404 | if button == "Back": 405 | if not selectedCam: 406 | cap.close() 407 | newGameState = "config" 408 | break 409 | if button in (None, 'Exit'): # MAIN WINDOW 410 | state = "stby" 411 | newGameState = "config" 412 | break 413 | 414 | newGameWindow.close() 415 | 416 | def newGameWindow (): # gameState: config 417 | global playerColor 418 | global gameTime 419 | global newGameState 420 | global state 421 | global detected 422 | global cap 423 | global selectedCam 424 | global skillLevel 425 | 426 | windowName = "Configuration" 427 | frame_layout = [[sg.Radio('RPi Cam', group_id='grp', default = True, key = "rpicam"), sg.VerticalSeparator(pad=None), sg.Radio('USB0', group_id='grp', key = "usb0"), sg.Radio('USB1', group_id='grp', key = "usb1")]] 428 | 429 | initGame = [[sg.Text('Game Parameters', justification='center', pad = (25,(5,15)), font='Any 15')], 430 | [sg.CBox('Play as White', key='userWhite', default = playerColor)], 431 | [sg.Spin([sz for sz in range(1, 300)], initial_value=10, font='Any 11',key='timeInput'),sg.Text('Game time (min)', pad=(0,0))], 432 | [sg.Combo([sz for sz in range(1, 11)], default_value=10, key="enginelevel"),sg.Text('Engine skill level', pad=(0,0))], 433 | [sg.Frame('Camera Selection', frame_layout, pad=(0, 10), title_color='white')], 434 | [sg.Text('_'*30)], 435 | [sg.Button("Exit"), sg.Submit("Next")]] 436 | windowNewGame = sg.Window(windowName, default_button_element_size=(12,1), auto_size_buttons=False, icon='interface_images/robot_icon.ico').Layout(initGame) 437 | while True: 438 | button,value = windowNewGame.Read() 439 | if button == "Next": 440 | if value["rpicam"] == True: 441 | selectedCam = 0 442 | elif value["usb0"] == True: 443 | selectedCam = 1 444 | elif value["usb1"] == True: 445 | selectedCam = 2 446 | cap = initCam(selectedCam) 447 | if detected: 448 | newGameState = "calibration" 449 | playerColor = value["userWhite"] 450 | skillLevel = value["enginelevel"]*2 451 | gameTime = float(value["timeInput"]*60) 452 | break 453 | if button in (None, 'Exit'): # MAIN WINDOW 454 | state = "stby" 455 | break 456 | windowNewGame.close() 457 | 458 | def coronationWindow (): # gameState: config 459 | global playerColor 460 | pieceSelected = "" 461 | rook = rookB 462 | knight = knightB 463 | bishop = bishopB 464 | queen = queenB 465 | if playerColor: 466 | rook = rookW 467 | knight = knightW 468 | bishop = bishopW 469 | queen = queenW 470 | 471 | windowName = "Promotion" 472 | pieceSelection = [[sg.Text('Select the piece for promotion', justification='center', pad = (25,(5,15)), font='Any 15')], 473 | [sg.Button('', image_filename=rook, size=(1, 1), 474 | border_width=0, button_color=('white', "brown"), 475 | pad=((40,0), 0), key="rook"),sg.Button('', image_filename=knight, size=(1, 1), 476 | border_width=0, button_color=('white', "brown"), 477 | pad=(0, 0), key="knight"),sg.Button('', image_filename=bishop, size=(1, 1), 478 | border_width=0, button_color=('white', "brown"), 479 | pad=(0, 0), key="bishop"),sg.Button('', image_filename=queen, size=(1, 1), 480 | border_width=0, button_color=('white', "brown"), 481 | pad=(0, 0), key="queen")]] 482 | windowNewGame = sg.Window(windowName, default_button_element_size=(12,1), auto_size_buttons=False, icon='interface_images/robot_icon.ico').Layout(pieceSelection) 483 | while True: 484 | button,value = windowNewGame.Read() 485 | if button == "rook": 486 | pieceSelected = "r" 487 | break 488 | if button == "knight": 489 | pieceSelected = "k" 490 | break 491 | if button == "bishop": 492 | pieceSelected = "b" 493 | break 494 | if button == "queen": 495 | pieceSelected = "q" 496 | break 497 | if button in (None, 'Exit'): # MAIN WINDOW 498 | break 499 | windowNewGame.close() 500 | return pieceSelected 501 | 502 | def takePIC(): 503 | global selectedCam 504 | global cap 505 | if selectedCam: 506 | for i in range(5): # Clear images stored in buffer 507 | cap.grab() 508 | _ , frame = cap.read() # USB Cam 509 | else: 510 | cap.capture(rawCapture, format="bgr") # RPi Cam 511 | frame = rawCapture.array 512 | rawCapture.truncate(0) # Clear the stream in preparation for the next image 513 | 514 | return frame 515 | 516 | def quitGameWindow (): 517 | global playing 518 | global window 519 | global cap 520 | windowName = "Quit Game" 521 | quitGame = [[sg.Text('Are you sure?',justification='center', size=(30, 1), font='Any 13')], [sg.Submit("Yes", size=(15, 1)),sg.Submit("No", size=(15, 1))]] 522 | if not selectedCam: 523 | cap.close() 524 | if playing: 525 | while True: 526 | windowNewGame = sg.Window(windowName, default_button_element_size=(12,1), auto_size_buttons=False, icon='interface_images/robot_icon.ico').Layout(quitGame) 527 | button,value = windowNewGame.Read() 528 | if button == "Yes": 529 | playing = False 530 | break 531 | if button in (None, 'Exit', "No"): # MAIN WINDOW 532 | break 533 | windowNewGame.close() 534 | 535 | def mainBoardLayout(): 536 | # ------ Menu Definition ------ # 537 | menu_def = [['&Configuration',["&Dimensions","E&xit"]], 538 | ['&Help', 'About'], ] 539 | 540 | # ------ Layout ------ # 541 | # sg.SetOptions(margins=(0,0)) 542 | sg.ChangeLookAndFeel('Dark') 543 | # Main board display layout 544 | board_layout = [[sg.T(' '*12)] + [sg.T('{}'.format(a), pad=((0,47),0), font='Any 13', key = a+'t') for a in 'abcdefgh']] 545 | # Loop though board and create buttons with images 546 | for i in range(8): 547 | numberRow = 8-i 548 | row = [sg.T(str(numberRow)+' ', font='Any 13', key = str(numberRow)+"l")] 549 | for j in range(8): 550 | row.append(renderSquare(blank, key=(i,j), location=(i,j))) 551 | row.append(sg.T(' '+str(numberRow), font='Any 13', key = str(numberRow)+"r")) 552 | board_layout.append(row) 553 | # Add labels across bottom of board 554 | board_layout.append([sg.T(' '*12)] + [sg.T('{}'.format(a), pad=((0,47),0), font='Any 13', key = a+'b') for a in 'abcdefgh']) 555 | 556 | frame_layout_game = [ 557 | [sg.Button('---', size=(14, 2), border_width=0, font=('courier', 16), button_color=('black', "white"), pad=(4, 4), key="gameMessage")], 558 | ] 559 | frame_layout_robot = [ 560 | [sg.Button('---', size=(14, 2), border_width=0, font=('courier', 16), button_color=('black', "white"), pad=(4, 4), key="robotMessage")], 561 | ] 562 | board_controls = [[sg.RButton('New Game', key='newGame', size=(15, 2), pad=(0,(0,7)), font=('courier', 16))], 563 | [sg.RButton('Quit', key='quit', size=(15, 2), pad=(0, 0), font=('courier', 16), disabled = True)], 564 | [sg.Frame('GAME', frame_layout_game, pad=(0, 10), font='Any 12', title_color='white', key = "frameMessageGame")], 565 | [sg.Frame('ROBOT', frame_layout_robot, pad=(0, (0,10)), font='Any 12', title_color='white', key = "frameMessageRobot")], 566 | [sg.Button('White Time', size=(7, 2), border_width=0, font=('courier', 16), button_color=('black', whiteSquareColor), pad=(0, 0), key="wt"),sg.Button('Black Time', font=('courier', 16), size=(7, 2), border_width=0, button_color=('black', blackSquareColor), pad=((7,0), 0), key="bt")], 567 | [sg.T("00:00:00",size=(9, 2), font=('courier', 13),key="wcount",pad = ((4,0),0)),sg.T("00:00:00",size=(9, 2), pad = (0,0), font=('courier', 13),key="bcount")], 568 | [sg.Button('', image_filename=wclock, key='clockButton', pad = ((25,0),0))]] 569 | 570 | layout = [[sg.Menu(menu_def, tearoff=False, key="manubar")], 571 | [sg.Column(board_layout),sg.VerticalSeparator(pad=None),sg.Column(board_controls)]] 572 | 573 | return layout 574 | 575 | def initCam(selectedCam): 576 | global detected 577 | global rawCapture 578 | 579 | button, value = window.Read(timeout=10) 580 | if selectedCam: # USB Cam 581 | cap = cv2.VideoCapture(selectedCam - 1) 582 | cap.set(cv2.CAP_PROP_FRAME_WIDTH,640) 583 | cap.set(cv2.CAP_PROP_FRAME_HEIGHT,480) 584 | if not cap.isOpened(): 585 | detected = False 586 | sg.popup_error('USB Video device not found') 587 | else: # RPi Cam 588 | cap = PiCamera() 589 | if not cap: 590 | detected = False 591 | sg.popup_error('RPi camera module not found') 592 | else: 593 | cap.resolution = (640, 480) 594 | rawCapture = PiRGBArray(cap, size=(640, 480)) 595 | return cap 596 | 597 | def loadParams(): 598 | global physicalParams 599 | 600 | if os.path.isfile('params.txt'): 601 | json_file = open('params.txt') 602 | physicalParams = json.load(json_file) 603 | print(json_file) 604 | else: 605 | outfile = open('params.txt', 'w') 606 | json.dump(physicalParams, outfile) 607 | 608 | def phisicalConfig (): 609 | global physicalParams 610 | 611 | windowName = "Chessboard parameters" 612 | robotParamLayout= [[sg.Text('Insert the physical dimensions in inches',justification='center', font='Any 14', pad=(10,10))], 613 | [sg.Spin([sz/100 for sz in range(1, 1000)], initial_value=physicalParams["baseradius"], font='Any 11'),sg.Text('Base Radius', pad=(0,0))], 614 | [sg.Spin([sz/100 for sz in range(1, 1000)], initial_value=physicalParams["cbFrame"], font='Any 11'),sg.Text('Chess Board Frame', pad=(0,0))], 615 | [sg.Spin([sz/100 for sz in range(1, 1000)], initial_value=physicalParams["sqSize"], font='Any 11'),sg.Text('Square Size', pad=(0,0))], 616 | [sg.Spin([sz/100 for sz in range(1, 1000)], initial_value=physicalParams["cbHeight"], font='Any 11'),sg.Text('Chess Board Height', pad=(0,0))], 617 | [sg.Spin([sz/100 for sz in range(1, 1000)], initial_value=physicalParams["pieceHeight"], font='Any 11'),sg.Text('Tallest Piece Height', pad=(0,0))], 618 | [sg.Text('_'*37)], 619 | [sg.Submit("Save", size=(15, 1)),sg.Submit("Close", size=(15, 1))]] 620 | 621 | while True: 622 | robotParamWindow = sg.Window(windowName, default_button_element_size=(12,1), auto_size_buttons=False, icon='interface_images/robot_icon.ico').Layout(robotParamLayout) 623 | button,value = robotParamWindow.Read() 624 | if button == "Save": 625 | physicalParams = {"baseradius": float(value[0]), 626 | "cbFrame": float(value[1]), 627 | "sqSize": float(value[2]), 628 | "cbHeight": float(value[3]), 629 | "pieceHeight": float(value[4])} 630 | outfile = open('params.txt', 'w') 631 | json.dump(physicalParams, outfile) 632 | break 633 | if button in (None, 'Close'): # MAIN WINDOW 634 | break 635 | 636 | robotParamWindow.close() 637 | 638 | layout = mainBoardLayout() 639 | window = sg.Window('ChessRobot', default_button_element_size=(12,1), auto_size_buttons=False, icon='interface_images/robot_icon.ico').Layout(layout) 640 | 641 | def speak(command): 642 | pygame.mixer.init() 643 | filePath = str(pathlib.Path().absolute())+"/audio/" 644 | pygame.mixer.music.load(filePath+command+".mp3") 645 | pygame.mixer.music.play() 646 | 647 | def main(): 648 | global playerColor 649 | global state 650 | global playing 651 | global sequence 652 | global newGameState 653 | global detected 654 | global physicalParams 655 | global moveState 656 | global prevIMG 657 | global rotMat 658 | global homography 659 | global colorTurn 660 | 661 | systemConfig() 662 | loadParams() 663 | interfaceMessage = "" 664 | board = cl.chess.Board() 665 | squares = [] 666 | whiteTime = 0 667 | blackTime = 0 668 | refTime = time.time() 669 | board = cl.chess.Board() 670 | 671 | while True : 672 | button, value = window.Read(timeout=100) 673 | 674 | if button in (None, 'Exit') or value["manubar"]=="Exit": # MAIN WINDOW 675 | angles_rest = (0,-1150,450,1100,0) 676 | _ = ac.LSSA_moveMotors(angles_rest) 677 | ac.allMotors.limp() 678 | ac.allMotors.setColorLED(lssc.LSS_LED_Black) 679 | break 680 | 681 | if value["manubar"]=="Dimensions": 682 | if playing: 683 | sg.popup("Please, first quit the game") 684 | else: 685 | phisicalConfig() 686 | 687 | if button =="newGame": 688 | if physicalParams["baseradius"] and physicalParams["cbFrame"] and physicalParams["sqSize"] and physicalParams["cbHeight"] and physicalParams["pieceHeight"]: 689 | state = "startMenu" 690 | else: 691 | sg.popup_error('Please configure the chess board dimensions in the Configuration option of menu bar') 692 | 693 | if button =="quit": 694 | ac.allMotors.setColorLED(lssc.LSS_LED_Black) 695 | quitGameWindow() 696 | if not playing: 697 | state = "showGameResult" 698 | 699 | # PC messages 700 | if playing: 701 | if whiteTime <= 0: 702 | playing = False 703 | state = "showGameResult" 704 | window.FindElement(key = "gameMessage").Update("Time Out\n"+"Black Wins") 705 | elif blackTime <= 0: 706 | playing = False 707 | state = "showGameResult" 708 | window.FindElement(key = "gameMessage").Update("Time Out\n"+ "White Wins") 709 | 710 | if state == "stby": # stby 711 | pass 712 | 713 | elif state == "startMenu": # Start Menu 714 | if newGameState == "config": 715 | newGameWindow() 716 | elif newGameState == "calibration": 717 | calibration() 718 | elif newGameState == "ocupiedBoard": 719 | ocupiedBoard() 720 | elif newGameState == "sideConfig": 721 | sideConfig() 722 | elif newGameState == "initGame": 723 | playing = True 724 | newGameState = "config" 725 | board = cl.chess.Board() 726 | if FENCODE: 727 | board = cl.chess.Board(FENCODE) 728 | colorTurn = board.turn 729 | startGame() 730 | whiteTime = gameTime 731 | blackTime = gameTime 732 | refTime = time.time() 733 | startEngineThread = threading.Thread(target=startEngine, daemon=True) 734 | startEngineThread.start() 735 | speak("good_luck") 736 | state = "stby" 737 | redrawBoard(board) 738 | 739 | elif state == "playerTurn": # Player Turn 740 | if button == "clockButton": 741 | currentIMG = takePIC() 742 | curIMG = vm.applyHomography(currentIMG,homography) 743 | curIMG = vm.applyRotation(curIMG,rotMat) 744 | squares = vm.findMoves(prevIMG, curIMG) 745 | if playerTurn(board, squares): 746 | state = "pcTurn" 747 | if board.is_game_over(): 748 | playing = False 749 | state = "showGameResult" 750 | else: 751 | window.FindElement(key = "gameMessage").Update("Invalid move!") 752 | speak("invalid_move") 753 | state = "playerTurn" 754 | 755 | elif state == "pcTurn": # PC turn 756 | 757 | if board.turn: 758 | window.FindElement(key = "clockButton").Update(image_filename=wclock) 759 | else: 760 | window.FindElement(key = "clockButton").Update(image_filename=bclock) 761 | 762 | pcTurnThread = threading.Thread(target=pcTurn, args=(board,engine,), daemon=True) 763 | pcTurnThread.start() 764 | state = "stby" # Wait for the PC move, thread changes the state 765 | 766 | elif state == "robotMove": # Robotic arm turn 767 | previousIMG = takePIC() 768 | prevIMG = vm.applyHomography(previousIMG,homography) 769 | prevIMG = vm.applyRotation(prevIMG,rotMat) 770 | state = "playerTurn" 771 | window.FindElement(key = "robotMessage").Update("---") 772 | if board.turn: 773 | window.FindElement(key = "clockButton").Update(image_filename=wclock) 774 | else: 775 | window.FindElement(key = "clockButton").Update(image_filename=bclock) 776 | 777 | elif state == "showGameResult": 778 | gameResult = board.result() 779 | if gameResult == "1-0": 780 | window.FindElement(key = "gameMessage").Update("Game Over" + "\nWhite Wins") 781 | if not playerColor: # If the player color is black -> robot color is white 782 | ac.winLED(ac.allMotors) 783 | else: 784 | speak("goodbye") 785 | elif gameResult == "0-1": 786 | window.FindElement(key = "gameMessage").Update("Game Over" + "\nBlack Wins") 787 | if playerColor: # If the player color is white -> robot color is black 788 | ac.winLED(ac.allMotors) 789 | else: 790 | speak("goodbye") 791 | elif gameResult == "1/2-1/2": 792 | window.FindElement(key = "gameMessage").Update("Game Over" + "\nDraw") 793 | else: 794 | window.FindElement(key = "gameMessage").Update("Game Over") 795 | window.FindElement(key = "robotMessage").Update("Goodbye") 796 | quitGame() 797 | state = "stby" 798 | 799 | if playing: 800 | dt = time.time() - refTime 801 | if board.turn: 802 | whiteTime = whiteTime - dt 803 | if whiteTime < 0: 804 | whiteTime = 0 805 | refTime = time.time() 806 | window.FindElement(key = "wcount").Update(time.strftime("%H:%M:%S", time.gmtime(whiteTime))) 807 | else: 808 | blackTime = blackTime - dt 809 | if blackTime < 0: 810 | blackTime = 0 811 | refTime = time.time() 812 | window.FindElement(key = "bcount").Update(time.strftime("%H:%M:%S", time.gmtime(blackTime))) 813 | 814 | window.close() 815 | 816 | 817 | if __name__ == "__main__": 818 | main() 819 | 820 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # LSS Chess Robot 6 | 7 | The LSS Chess Robot is a robotic arm that can play chess against you. This is a free and open source project that integrates aspects of robotics, computer vision, and of course chess. 8 | 9 | ## Table of Contents 10 | 11 | - [Requirements](#requirements) 12 | - [Modules List](#modules-list) 13 | - [Getting Started](#getting-started) 14 | - [Install](#install) 15 | - [Run](#run) 16 | - [Authors](#authors) 17 | - [License](#license) 18 | - [Resources](#resources) 19 | 20 | ## Requirements 21 | 22 | ### Hardware 23 | 24 | - Lynxmotion Smart Servo (LSS) 4 DoF Robotic Arm 25 | - Raspberry Pi or Windows PC 26 | - Display 27 | - Keyboard 28 | - Mouse 29 | - Camera (USB or Raspberry Pi Camera Module) 30 | - Lighting 31 | - Chess board and pieces 32 | - Speaker or Headphones (Optional) 33 | 34 | ### Software 35 | 36 | - [OpenCV](https://opencv.org/) 37 | - [Stockfish](https://stockfishchess.org/) 38 | - [Python Chess](https://python-chess.readthedocs.io/en/latest/) 39 | - [FreeSimpleGUI](https://github.com/spyoungtech/FreeSimpleGUI) 40 | 41 | ## Modules List 42 | 43 | - [Vision Module](https://github.com/Robotics-Technology/Chess-Robot/VisionModule.py) 44 | - [Chess Logic](https://github.com/Robotics-Technology/Chess-Robot/ChessLogic.py) 45 | - [Arm Control](https://github.com/Robotics-Technology/Chess-Robot/ArmControl.py) 46 | - [Interface](https://github.com/Robotics-Technology/Chess-Robot/Interface.py) 47 | 48 | ## Getting Started 49 | 50 | To start using this project, proceed to the standard *clone* procedure: 51 | 52 | ```bash 53 | cd 54 | git clone https://github.com/Robotics-Technology/Chess-Robot.git 55 | ``` 56 | 57 | ## Install 58 | 59 | - On Windows PC 60 | 61 | 1. Create a virtual environment (recommended): 62 | 63 | ``` 64 | python -m venv env 65 | cd env/Scripts 66 | activate 67 | ``` 68 | 69 | 2. Install requirements 70 | 71 | ``` 72 | pip install -r requirements.txt 73 | ``` 74 | - On Raspberry Pi 75 | 76 | 1. Create a virtual environment (recommended): 77 | 78 | ``` 79 | python3 -m venv env 80 | source env/bin/activate 81 | ``` 82 | 83 | 2. Install requirements 84 | 85 | ``` 86 | pip3 install -r requirements.txt 87 | ``` 88 | 89 | 3. If you are using the Raspberry Camera Module 90 | 91 | ``` 92 | pip3 install "picamera[array]" 93 | ``` 94 | Note: To avoid incompatibilities use the versions established in the "requirements", for this it is recommended to use a virtual environment. 95 | 96 | If you have issues with OpenCV on the Raspberry Pi try the following commands: 97 | 98 | ``` 99 | sudo apt-get install libatlas-base-dev 100 | sudo apt-get install libjasper-dev 101 | sudo apt-get install libqtgui4 102 | sudo apt-get install libqt4-test 103 | sudo apt-get install libhdf5-dev 104 | sudo apt-get install libhdf5-serial-dev 105 | sudo apt-get install python3-pyqt5 106 | sudo apt-get install stockfish 107 | sudo pip3 install opencv-python==4.5.5.64 108 | sudo pip3 install opencv-contrib-python 109 | ``` 110 | 111 | ## Run 112 | 113 | ``` 114 | cd 115 | python Interface.py 116 | ``` 117 | 118 |

119 | 120 |

121 |

Chess Robot Interface

122 |
123 | Chess Robot Gameplay 124 |
125 |

Gameplay Video

126 | 127 | ## Authors 128 | 129 | - [Geraldine Barreto](http://github.com/geraldinebc) 130 | - [Eduardo Nunes](https://github.com/EduardoFNA) 131 | 132 | ## License 133 | 134 | LSS Chess Robot is available under the GNU General Public License v3.0 135 | 136 | ## Contributing 137 | 138 | Anyone is very welcome to contribute. Below is a list of identified improvements. 139 | 140 | ## To do 141 | 142 | - Change the gripper aperture acording to the chess piece type 143 | - Allow re-taking pictures in the case of board obstruction 144 | 145 | ## Limitations 146 | 147 | - The chessboard is limited by the reach of the arm and the squares need to be big enough so the arm gripper doesn’t stumble upon adjacent pieces. 148 | - This project was specifically made to be used with LSS smart servo motors using the LSS serial communication protocol so if you want to use it for a different robotic arm you will need to change how the servos are controlled and change the inverse kinematics parameters to match your specific arm. 149 | 150 | ## Resources 151 | 152 | Every module of the project is explained in the tutorial series available [here](https://www.robotshop.com/community/robots/show/chess-playing-robot/). 153 | 154 | Read more about the LSS Robotic Arm in the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/servo-erector-set-robots-kits/ses-v2-robots/ses-v2-arms/lss-4dof-arm/). 155 | 156 | Purchase the LSS arm on [RobotShop](https://www.robotshop.com/en/lynxmotion-smart-servos-articulated-arm.html). 157 | 158 | Official Lynxmotion Smart Servo (LSS) libraries for Python available [here](https://github.com/Lynxmotion/LSS_Library_Python). 159 | 160 | If you want more details about the LSS protocol, go [here](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/). 161 | 162 | Have any questions? Ask them on the Robotshop [Community](https://www.robotshop.com/community/). 163 | -------------------------------------------------------------------------------- /VisionModule.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | import string 4 | import time 5 | import os 6 | 7 | try: 8 | from picamera.array import PiRGBArray 9 | from picamera import PiCamera 10 | except: 11 | pass 12 | 13 | def findTransformation(img,cbPattern): 14 | 15 | patternSize = (7,7) 16 | imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) 17 | 18 | # Find chessboard corners 19 | retCB, cornersCB = cv2.findChessboardCorners(cbPattern, patternSize, cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_FAST_CHECK + cv2.CALIB_CB_NORMALIZE_IMAGE) 20 | retIMG, cornersIMG = cv2.findChessboardCorners(imgGray, patternSize, cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_FAST_CHECK + cv2.CALIB_CB_NORMALIZE_IMAGE) 21 | 22 | if retIMG == 0: 23 | H = 0 24 | else: 25 | H, _ = cv2.findHomography(cornersIMG, cornersCB) # Find the transformation matrix 26 | 27 | return(retIMG, H) 28 | 29 | def applyRotation(img,R): 30 | if R.any() != 0: 31 | img = cv2.warpAffine(img, R, img.shape[1::-1], flags=cv2.INTER_LINEAR) 32 | 33 | return(img) 34 | 35 | def applyHomography(img,H): 36 | 37 | imgNEW = cv2.warpPerspective(img, H, (400, 400)) 38 | 39 | return(imgNEW) 40 | 41 | def drawQuadrants(img): 42 | 43 | # Draw quadrants and numbers on image 44 | imgquad = img.copy() 45 | cv2.line(imgquad, (200, 0), (200, 400), (0,255,0), 3) 46 | cv2.line(imgquad, (0, 200), (400, 200), (0,255,0), 3) 47 | imgquad = cv2.putText(imgquad, '1', (80, 120) , cv2.FONT_HERSHEY_SIMPLEX, 2, (0,255,0), 3, cv2.LINE_AA) 48 | imgquad = cv2.putText(imgquad, '2', (280, 120) , cv2.FONT_HERSHEY_SIMPLEX, 2, (0,255,0), 3, cv2.LINE_AA) 49 | imgquad = cv2.putText(imgquad, '3', (280, 320) , cv2.FONT_HERSHEY_SIMPLEX, 2, (0,255,0), 3, cv2.LINE_AA) 50 | imgquad = cv2.putText(imgquad, '4', (80, 320) , cv2.FONT_HERSHEY_SIMPLEX, 2, (0,255,0), 3, cv2.LINE_AA) 51 | 52 | return(imgquad) 53 | 54 | def findRotation(theta): 55 | 56 | if theta != 0: 57 | rotMAT = cv2.getRotationMatrix2D(tuple(np.array((400,400)[1::-1])/2), theta, 1.0) 58 | else: 59 | rotMAT = np.zeros((2,2)) 60 | 61 | return(rotMAT) 62 | 63 | def findMoves(img1, img2): 64 | 65 | size = 50 66 | img1SQ = img2SQ = [] 67 | largest = [0, 0, 0, 0] 68 | coordinates = [0, 0, 0, 0] 69 | for y in range(0,8*size,size): 70 | for x in range(0,8*size,size): 71 | img1SQ = img1[x:x+size, y:y+size] 72 | img2SQ = img2[x:x+size, y:y+size] 73 | dist = cv2.norm(img2SQ, img1SQ) 74 | for z in range(0,4): 75 | if dist >= largest[z]: 76 | largest.insert(z,dist) 77 | # Save in algebraic chess notation 78 | coordinates.insert(z,(string.ascii_lowercase[int(x/size)]+str(int(y/size+1)))) 79 | largest.pop() 80 | coordinates.pop() 81 | break 82 | 83 | # Make threshold with a percentage of the change in color of the biggest two 84 | thresh = (largest[0]+largest[1])/2*(0.5) 85 | for t in range(3,1,-1): 86 | if largest[t] < thresh: 87 | coordinates.pop() 88 | 89 | return(coordinates) 90 | 91 | def safetoMove(H, cap, selectedCam): 92 | 93 | cbPattern = cv2.imread(os.getcwd() + '/' +'interface_images/cb_pattern.jpg', cv2.IMREAD_GRAYSCALE) 94 | 95 | if selectedCam: 96 | for i in range(5): # Clear images stored in buffer 97 | cap.grab() 98 | _ , img = cap.read() # USB Cam 99 | else: 100 | rawCapture = PiRGBArray(cap, size=(640, 480)) 101 | cap.capture(rawCapture, format="bgr") # RPi Cam 102 | img = rawCapture.array 103 | rawCapture.truncate(0) # Clear the stream in preparation for the next image 104 | 105 | img = cv2.warpPerspective(img, H, (cbPattern.shape[1], cbPattern.shape[0])) 106 | 107 | # Kmeans algorithm (Map the image to only two colors) 108 | K = 2 109 | criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0) 110 | Z = np.float32(img.reshape((-1,3))) 111 | ret,label,center=cv2.kmeans(Z,K,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS) 112 | center = np.uint8(center) 113 | res = center[label.flatten()].reshape((img.shape)) 114 | imgGRAY = cv2.cvtColor(res,cv2.COLOR_BGR2GRAY) 115 | 116 | # Try to find chessboard corners (if there's an obstacle it won't be able to do so) 117 | retIMG, cornersIMG = cv2.findChessboardCorners(imgGRAY, (7,7), cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_FAST_CHECK + cv2.CALIB_CB_NORMALIZE_IMAGE) 118 | 119 | return(retIMG) -------------------------------------------------------------------------------- /audio/capture.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/audio/capture.mp3 -------------------------------------------------------------------------------- /audio/check.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/audio/check.mp3 -------------------------------------------------------------------------------- /audio/checkmate.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/audio/checkmate.mp3 -------------------------------------------------------------------------------- /audio/excuse.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/audio/excuse.mp3 -------------------------------------------------------------------------------- /audio/good_luck.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/audio/good_luck.mp3 -------------------------------------------------------------------------------- /audio/goodbye.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/audio/goodbye.mp3 -------------------------------------------------------------------------------- /audio/invalid_move.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/audio/invalid_move.mp3 -------------------------------------------------------------------------------- /audio/k_castling.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/audio/k_castling.mp3 -------------------------------------------------------------------------------- /audio/passant.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/audio/passant.mp3 -------------------------------------------------------------------------------- /audio/please.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/audio/please.mp3 -------------------------------------------------------------------------------- /audio/promotion.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/audio/promotion.mp3 -------------------------------------------------------------------------------- /audio/q_castling.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/audio/q_castling.mp3 -------------------------------------------------------------------------------- /audio/reset.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/audio/reset.mp3 -------------------------------------------------------------------------------- /games/stockfishX64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/games/stockfishX64.exe -------------------------------------------------------------------------------- /interface_images/bclock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/interface_images/bclock.png -------------------------------------------------------------------------------- /interface_images/cb_pattern.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/interface_images/cb_pattern.jpg -------------------------------------------------------------------------------- /interface_images/game_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/interface_images/game_start.png -------------------------------------------------------------------------------- /interface_images/lss_arm_cb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/interface_images/lss_arm_cb.png -------------------------------------------------------------------------------- /interface_images/lss_arm_setup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/interface_images/lss_arm_setup.jpg -------------------------------------------------------------------------------- /interface_images/robot_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/interface_images/robot_icon.ico -------------------------------------------------------------------------------- /interface_images/wclock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/interface_images/wclock.png -------------------------------------------------------------------------------- /lss.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Author: Sebastien Parent-Charette (support@robotshop.com) 3 | # Version: 1.0.0 4 | # Licence: LGPL-3.0 (GNU Lesser General Public License version 3) 5 | # 6 | # Desscription: A library that makes using the LSS simple. 7 | # Offers support for most Python platforms. 8 | # Uses the Python serial library (pySerial). 9 | ############################################################################### 10 | 11 | ### Import required liraries 12 | import re 13 | import serial 14 | from math import sqrt, atan, acos, fabs 15 | 16 | ### Import constants 17 | import lss_const as lssc 18 | 19 | ### Class functions 20 | def initBus(portName, portBaud): 21 | LSS.bus = serial.Serial(portName, portBaud) 22 | LSS.bus.timeout = 0.1 23 | 24 | def closeBus(): 25 | if LSS.bus is not None: 26 | LSS.bus.close() 27 | del LSS.bus 28 | 29 | # Write with optional parameter and modifiers 30 | def genericWrite(id, cmd, param = None, mod = None, value = None, mod2 = None, val2 = None): 31 | if LSS.bus is None: 32 | return False 33 | if mod is None: 34 | if param is None: 35 | LSS.bus.write((lssc.LSS_CommandStart + str(id) + cmd + lssc.LSS_CommandEnd).encode()) 36 | else: 37 | LSS.bus.write((lssc.LSS_CommandStart + str(id) + cmd + str(param) + lssc.LSS_CommandEnd).encode()) 38 | else: 39 | if mod2 is None: 40 | LSS.bus.write((lssc.LSS_CommandStart + str(id) + cmd + str(param) + mod + str(value) + lssc.LSS_CommandEnd).encode()) 41 | else: 42 | LSS.bus.write((lssc.LSS_CommandStart + str(id) + cmd + str(param) + mod + str(value) + mod2 + str(val2) + lssc.LSS_CommandEnd).encode()) 43 | return True 44 | 45 | # Read an integer result 46 | def genericRead_Blocking_int(id, cmd): 47 | if LSS.bus is None: 48 | return None 49 | try: 50 | # Get start of packet and discard header and everything before 51 | c = LSS.bus.read() 52 | while (c.decode("utf-8") != lssc.LSS_CommandReplyStart): 53 | c = LSS.bus.read() 54 | if(c.decode("utf-8") == ""): 55 | break 56 | # Get packet 57 | data = LSS.bus.read_until(lssc.LSS_CommandEnd) 58 | # Parse packet 59 | matches = re.match("(\d{1,3})([A-Z]{1,4})(-?\d{1,18})", data.decode("utf-8"), re.I) 60 | # Check if matches are found 61 | if(matches is None): 62 | return(None) 63 | if((matches.group(1) is None) or (matches.group(2) is None) or (matches.group(3) is None)): 64 | return(None) 65 | # Get values from match 66 | readID = matches.group(1) 67 | readIdent = matches.group(2) 68 | readValue = matches.group(3) 69 | # Check id 70 | if(readID != str(id)): 71 | return(None) 72 | # Check identifier 73 | if(readIdent != cmd): 74 | return(None) 75 | except: 76 | return(None) 77 | # return value 78 | return(readValue) 79 | 80 | # Read a string result 81 | #@classmethod 82 | def genericRead_Blocking_str(id, cmd, numChars): 83 | if LSS.bus is None: 84 | return None 85 | if LSS.bus is None: 86 | return None 87 | try: 88 | # Get start of packet and discard header and everything before 89 | c = LSS.bus.read() 90 | while (c.decode("utf-8") != lssc.LSS_CommandReplyStart): 91 | c = LSS.bus.read() 92 | if(c.decode("utf-8") == ""): 93 | break 94 | # Get packet 95 | data = LSS.bus.read_until(lssc.LSS_CommandEnd) 96 | data = (data[:-1]) 97 | # Parse packet 98 | matches = re.match("(\d{1,3})([A-Z]{1,4})(.{" + str(numChars) + "})", data.decode("utf-8"), re.I) 99 | # Check if matches are found 100 | if(matches is None): 101 | return(None) 102 | if((matches.group(1) is None) or (matches.group(2) is None) or (matches.group(3) is None)): 103 | return(None) 104 | # Get values from match 105 | readID = matches.group(1) 106 | readIdent = matches.group(2) 107 | readValue = matches.group(3) 108 | # Check id 109 | if(readID != str(id)): 110 | return(None) 111 | # Check identifier 112 | if(readIdent != cmd): 113 | return(None) 114 | except: 115 | return(None) 116 | # return value 117 | return(readValue) 118 | 119 | class LSS: 120 | # Class attribute 121 | bus = None 122 | 123 | ### Constructor 124 | def __init__(self, id = 0): 125 | self.servoID = id 126 | 127 | ### Attributes 128 | servoID = 0 129 | 130 | ### Functions 131 | #> Actions 132 | def reset(self): 133 | return (genericWrite(self.servoID, lssc.LSS_ActionReset)) 134 | 135 | def confirm(self): 136 | return (genericWrite(self.servoID, lssc.LSS_ActionConfirm)) 137 | 138 | def limp(self): 139 | return (genericWrite(self.servoID, lssc.LSS_ActionLimp)) 140 | 141 | def hold(self): 142 | return (genericWrite(self.servoID, lssc.LSS_ActionHold)) 143 | 144 | def move(self, pos): 145 | return (genericWrite(self.servoID, lssc.LSS_ActionMove, pos)) 146 | 147 | def moveRelative(self, delta): 148 | return (genericWrite(self.servoID, lssc.LSS_ActionMoveRelative, delta)) 149 | 150 | def wheel(self, speed): 151 | return (genericWrite(self.servoID, lssc.LSS_ActionWheel, speed)) 152 | 153 | def wheelRPM(self, rpm): 154 | return (genericWrite(self.servoID, lssc.LSS_ActionWheelRPM, rpm)) 155 | 156 | def moveT(self, pos, value): 157 | return (genericWrite(self.servoID, lssc.LSS_ActionMove, pos, lssc.LSS_ActionParameterTime, value)) 158 | 159 | def moveCH(self, pos, value): 160 | return (genericWrite(self.servoID, lssc.LSS_ActionMove, pos, lssc.LSS_ModifierCurrentHaltHold, value)) 161 | 162 | def moveCHT(self, pos, value, tval): 163 | return (genericWrite(self.servoID, lssc.LSS_ActionMove, pos, lssc.LSS_ModifierCurrentHaltHold, value, lssc.LSS_ActionParameterTime, tval)) 164 | 165 | def moveCL(self, pos, value): 166 | return (genericWrite(self.servoID, lssc.LSS_ActionMove, pos, lssc.LSS_ModifierCurrentLimp, value)) 167 | 168 | #> Queries 169 | #def getID(self): 170 | #def getBaud(self): 171 | 172 | def getStatus(self): 173 | genericWrite(self.servoID, lssc.LSS_QueryStatus) 174 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryStatus)) 175 | 176 | def getOriginOffset(self, queryType = lssc.LSS_QuerySession): 177 | genericWrite(self.servoID, lssc.LSS_QueryOriginOffset, queryType) 178 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryOriginOffset)) 179 | 180 | def getAngularRange(self, queryType = lssc.LSS_QuerySession): 181 | genericWrite(self.servoID, lssc.LSS_QueryAngularRange, queryType) 182 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryAngularRange)) 183 | 184 | def getPositionPulse(self): 185 | genericWrite(self.servoID, lssc.LSS_QueryPositionPulse) 186 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryPositionPulse)) 187 | 188 | def getPosition(self): 189 | genericWrite(self.servoID, lssc.LSS_QueryPosition) 190 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryPosition)) 191 | 192 | def getSpeed(self): 193 | genericWrite(self.servoID, lssc.LSS_QuerySpeed) 194 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QuerySpeed)) 195 | 196 | def getSpeedRPM(self): 197 | genericWrite(self.servoID, lssc.LSS_QuerySpeedRPM) 198 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QuerySpeedRPM)) 199 | 200 | def getSpeedPulse(self): 201 | genericWrite(self.servoID, lssc.LSS_QuerySpeedPulse) 202 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QuerySpeedPulse)) 203 | 204 | def getMaxSpeed(self, queryType = lssc.LSS_QuerySession): 205 | genericWrite(self.servoID, lssc.LSS_QueryMaxSpeed, queryType) 206 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryMaxSpeed)) 207 | 208 | def getMaxSpeedRPM(self, queryType = lssc.LSS_QuerySession): 209 | genericWrite(self.servoID, lssc.LSS_QueryMaxSpeedRPM, queryType) 210 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryMaxSpeedRPM)) 211 | 212 | def getColorLED(self, queryType = lssc.LSS_QuerySession): 213 | genericWrite(self.servoID, lssc.LSS_QueryColorLED, queryType) 214 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryColorLED)) 215 | 216 | def getGyre(self, queryType = lssc.LSS_QuerySession): 217 | genericWrite(self.servoID, lssc.LSS_QueryGyre, queryType) 218 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryGyre)) 219 | 220 | # returns 0 if "DIS" 221 | def getFirstPosition(self): 222 | genericWrite(self.servoID, lssc.LSS_QueryFirstPosition) 223 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryFirstPosition)) 224 | 225 | # returns true/false based on if QFD returns "DIS" (= False) 226 | def getIsFirstPositionEnabled(self): 227 | genericWrite(self.servoID, lssc.LSS_QueryFirstPosition) 228 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryFirstPosition) is not None) 229 | 230 | def getModel(self): 231 | genericWrite(self.servoID, lssc.LSS_QueryModelString) 232 | return (genericRead_Blocking_str(self.servoID, lssc.LSS_QueryModelString, 7)) 233 | 234 | def getSerialNumber(self): 235 | genericWrite(self.servoID, lssc.LSS_QuerySerialNumber) 236 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QuerySerialNumber)) 237 | 238 | def getFirmwareVersion(self): 239 | genericWrite(self.servoID, lssc.LSS_QueryFirmwareVersion) 240 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryFirmwareVersion)) 241 | 242 | def getVoltage(self): 243 | genericWrite(self.servoID, lssc.LSS_QueryVoltage) 244 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryVoltage)) 245 | 246 | def getTemperature(self): 247 | genericWrite(self.servoID, lssc.LSS_QueryTemperature) 248 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryTemperature)) 249 | 250 | def getCurrent(self): 251 | genericWrite(self.servoID, lssc.LSS_QueryCurrent) 252 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryCurrent)) 253 | 254 | #> Queries (advanced) 255 | def getAngularStiffness(self, queryType = lssc.LSS_QuerySession): 256 | genericWrite(self.servoID, lssc.LSS_QueryAngularStiffness, queryType) 257 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryAngularStiffness)) 258 | 259 | def getAngularHoldingStiffness(self, queryType = lssc.LSS_QuerySession): 260 | genericWrite(self.servoID, lssc.LSS_QueryAngularHoldingStiffness, queryType) 261 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryAngularHoldingStiffness)) 262 | 263 | def getAngularAcceleration(self, queryType = lssc.LSS_QuerySession): 264 | genericWrite(self.servoID, lssc.LSS_QueryAngularAcceleration, queryType) 265 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryAngularAcceleration)) 266 | 267 | def getAngularDeceleration(self, queryType = lssc.LSS_QuerySession): 268 | genericWrite(self.servoID, lssc.LSS_QueryAngularDeceleration, queryType) 269 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryAngularDeceleration)) 270 | 271 | def getIsMotionControlEnabled(self): 272 | genericWrite(self.servoID, lssc.LSS_QueryEnableMotionControl) 273 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryEnableMotionControl)) 274 | 275 | def getBlinkingLED(self): 276 | genericWrite(self.servoID, lssc.LSS_QueryBlinkingLED) 277 | return (genericRead_Blocking_int(self.servoID, lssc.LSS_QueryBlinkingLED)) 278 | 279 | #> Configs 280 | def setOriginOffset(self, pos, setType = lssc.LSS_SetSession): 281 | if setType == lssc.LSS_SetSession: 282 | return (genericWrite(self.servoID, lssc.LSS_ActionOriginOffset, pos)) 283 | elif setType == lssc.LSS_SetConfig: 284 | return (genericWrite(self.servoID, lssc.LSS_ConfigOriginOffset, pos)) 285 | 286 | def setAngularRange(self, delta, setType = lssc.LSS_SetSession): 287 | if setType == lssc.LSS_SetSession: 288 | return (genericWrite(self.servoID, lssc.LSS_ActionAngularRange, delta)) 289 | elif setType == lssc.LSS_SetConfig: 290 | return (genericWrite(self.servoID, lssc.LSS_ConfigAngularRange, delta)) 291 | 292 | def setMaxSpeed(self, speed, setType = lssc.LSS_SetSession): 293 | if setType == lssc.LSS_SetSession: 294 | return (genericWrite(self.servoID, lssc.LSS_ActionMaxSpeed, speed)) 295 | elif setType == lssc.LSS_SetConfig: 296 | return (genericWrite(self.servoID, lssc.LSS_ConfigMaxSpeed, speed)) 297 | 298 | def setMaxSpeedRPM(self, rpm, setType = lssc.LSS_SetSession): 299 | if setType == lssc.LSS_SetSession: 300 | return (genericWrite(self.servoID, lssc.LSS_ActionMaxSpeedRPM, rpm)) 301 | elif setType == lssc.LSS_SetConfig: 302 | return (genericWrite(self.servoID, lssc.LSS_ConfigMaxSpeedRPM, rpm)) 303 | 304 | def setColorLED(self, color, setType = lssc.LSS_SetSession): 305 | if setType == lssc.LSS_SetSession: 306 | return (genericWrite(self.servoID, lssc.LSS_ActionColorLED, color)) 307 | elif setType == lssc.LSS_SetConfig: 308 | return (genericWrite(self.servoID, lssc.LSS_ConfigColorLED, color)) 309 | 310 | def setGyre(self, gyre, setType = lssc.LSS_SetSession): 311 | if setType == lssc.LSS_SetSession: 312 | return (genericWrite(self.servoID, lssc.LSS_ActionGyreDirection, gyre)) 313 | elif setType == lssc.LSS_SetConfig: 314 | return (genericWrite(self.servoID, lssc.LSS_ConfigGyreDirection, gyre)) 315 | 316 | def setFirstPosition(self, pos): 317 | return (genericWrite(self.servoID, lssc.LSS_ConfigFirstPosition, pos)) 318 | 319 | def clearFirstPosition(self): 320 | return (genericWrite(self.servoID, lssc.LSS_ConfigFirstPosition)) 321 | 322 | def setMode(self, mode): 323 | return (genericWrite(self.servoID, lssc.LSS_ConfigMode, mode)) 324 | 325 | #> Configs (advanced) 326 | def setAngularStiffness(self, value, setType = lssc.LSS_SetSession): 327 | if setType == lssc.LSS_SetSession: 328 | return (genericWrite(self.servoID, lssc.LSS_ActionAngularStiffness, value)) 329 | elif setType == lssc.LSS_SetConfig: 330 | return (genericWrite(self.servoID, lssc.LSS_ConfigAngularStiffness, value)) 331 | 332 | def setAngularHoldingStiffness(self, value, setType = lssc.LSS_SetSession): 333 | if setType == lssc.LSS_SetSession: 334 | return (genericWrite(self.servoID, lssc.LSS_ActionAngularHoldingStiffness, value)) 335 | elif setType == lssc.LSS_SetConfig: 336 | return (genericWrite(self.servoID, lssc.LSS_ConfigAngularHoldingStiffness, value)) 337 | 338 | def setAngularAcceleration(self, value, setType = lssc.LSS_SetSession): 339 | if setType == lssc.LSS_SetSession: 340 | return (genericWrite(self.servoID, lssc.LSS_ActionAngularAcceleration, value)) 341 | elif setType == lssc.LSS_SetConfig: 342 | return (genericWrite(self.servoID, lssc.LSS_ConfigAngularAcceleration, value)) 343 | 344 | def setAngularDeceleration(self, value, setType = lssc.LSS_SetSession): 345 | if setType == lssc.LSS_SetSession: 346 | return (genericWrite(self.servoID, lssc.LSS_ActionAngularDeceleration, value)) 347 | elif setType == lssc.LSS_SetConfig: 348 | return (genericWrite(self.servoID, lssc.LSS_ConfigAngularDeceleration, value)) 349 | 350 | def setMotionControlEnabled(self, value): 351 | return (genericWrite(self.servoID, lssc.LSS_ActionEnableMotionControl, value)) 352 | 353 | def setBlinkingLED(self, state): 354 | return (genericWrite(self.servoID, lssc.LSS_ConfigBlinkingLED, state)) 355 | 356 | def setFilterPositionCount(self, value): 357 | return (genericWrite(self.servoID, lssc.LSS_FilterPositionCount, value)) 358 | 359 | ### EOF ###################################################################### -------------------------------------------------------------------------------- /lss_const.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Author: Sebastien Parent-Charette (support@robotshop.com) 3 | # Version: 1.0.0 4 | # Licence: LGPL-3.0 (GNU Lesser General Public License version 3) 5 | # 6 | # Desscription: A library that makes using the LSS simple. 7 | # Offers support for most Python platforms. 8 | # Uses the Python serial library (pySerial). 9 | ############################################################################### 10 | 11 | ### List of constants 12 | 13 | # Bus communication 14 | LSS_DefaultBaud = 115200 15 | LSS_MaxTotalCommandLength = (30 + 1) # ex: #999XXXX-2147483648\r Adding 1 for end string char (\0) 16 | # # ex: #999XX000000000000000000\r 17 | LSS_Timeout = 100 # in ms 18 | LSS_CommandStart = "#" 19 | LSS_CommandReplyStart = "*" 20 | LSS_CommandEnd = "\r" 21 | LSS_FirstPositionDisabled = "DIS" 22 | 23 | # LSS constants 24 | LSS_ID_Default = 0 25 | LSS_ID_Min = 0 26 | LSS_ID_Max = 250 27 | LSS_Mode255ID = 255 28 | LSS_BroadcastID = 254 29 | 30 | # Read/write status 31 | LSS_CommStatus_Idle = 0 32 | LSS_CommStatus_ReadSuccess = 1 33 | LSS_CommStatus_ReadTimeout = 2 34 | LSS_CommStatus_ReadWrongID = 3 35 | LSS_CommStatus_ReadWrongIdentifier = 4 36 | LSS_CommStatus_ReadWrongFormat = 5 37 | LSS_CommStatus_ReadNoBus = 6 38 | LSS_CommStatus_ReadUnknown = 7 39 | LSS_CommStatus_WriteSuccess = 8 40 | LSS_CommStatus_WriteNoBus = 9 41 | LSS_CommStatus_WriteUnknown = 10 42 | 43 | # LSS status 44 | LSS_StatusUnknown = 0 45 | LSS_StatusLimp = 1 46 | LSS_StatusFreeMoving = 2 47 | LSS_StatusAccelerating = 3 48 | LSS_StatusTravelling = 4 49 | LSS_StatusDecelerating = 5 50 | LSS_StatusHolding = 6 51 | LSS_StatusOutsideLimits = 7 52 | LSS_StatusStuck = 8 #cannot move at current speed setting 53 | LSS_StatusBlocked = 9 #same as stuck but reached maximum duty and still can't move 54 | LSS_StatusSafeMode = 10 55 | LSS_StatusLast = 11 56 | 57 | # LSS models 58 | LSS_ModelHighTorque = 0 59 | LSS_ModelStandard = 1 60 | LSS_ModelHighSpeed = 2 61 | LSS_ModelUnknown = 3 62 | 63 | LSS_ModelHT1 = "LSS-HT1" 64 | LSS_ModelST1 = "LSS-ST1" 65 | LSS_ModelHS1 = "LSS-HS1" 66 | 67 | # Parameter for query 68 | LSS_QuerySession = 0 69 | LSS_QueryConfig = 1 70 | LSS_QueryInstantaneousSpeed = 2 71 | LSS_QueryTargetTravelSpeed = 3 72 | 73 | # Parameter for setter 74 | LSS_SetSession = 0 75 | LSS_SetConfig = 1 76 | 77 | # Parameter for Serial/RC mode change 78 | LSS_ModeSerial = 0 79 | LSS_ModePositionRC = 1 80 | LSS_ModeWheelRC = 2 81 | 82 | # Parameter for gyre direction 83 | LSS_GyreClockwise = 1 84 | LSS_GyreCounterClockwise = -1 85 | 86 | # LED colors 87 | LSS_LED_Black = 0 88 | LSS_LED_Red = 1 89 | LSS_LED_Green = 2 90 | LSS_LED_Blue = 3 91 | LSS_LED_Yellow = 4 92 | LSS_LED_Cyan = 5 93 | LSS_LED_Magenta = 6 94 | LSS_LED_White = 7 95 | 96 | # Commands - actions 97 | LSS_ActionReset = "RESET" 98 | LSS_ActionConfirm = "CONFIRM" 99 | LSS_ActionLimp = "L" 100 | LSS_ActionHold = "H" 101 | LSS_ActionParameterTime = "T" 102 | LSS_ActionParameterSpeed = "S" 103 | LSS_ActionMove = "D" 104 | LSS_ActionMoveRelative = "MD" 105 | LSS_ActionWheel = "WD" 106 | LSS_ActionWheelRPM = "WR" 107 | 108 | # Commands - actions settings 109 | LSS_ActionOriginOffset = "O" 110 | LSS_ActionAngularRange = "AR" 111 | LSS_ActionMaxSpeed = "SD" 112 | LSS_ActionMaxSpeedRPM = "SR" 113 | LSS_ActionColorLED = "LED" 114 | LSS_ActionGyreDirection = "G" 115 | 116 | # Commands - modifiers 117 | LSS_ModifierCurrentHaltHold = "CH" 118 | LSS_ModifierCurrentLimp = "CL" 119 | 120 | # Commands - actions advanced settings 121 | LSS_ActionAngularStiffness = "AS" 122 | LSS_ActionAngularHoldingStiffness = "AH" 123 | LSS_ActionAngularAcceleration = "AA" 124 | LSS_ActionAngularDeceleration = "AD" 125 | LSS_ActionEnableMotionControl = "EM" 126 | LSS_FilterPositionCount = "FPC" 127 | 128 | # Commands - queries 129 | LSS_QueryStatus = "Q" 130 | LSS_QueryOriginOffset = "QO" 131 | LSS_QueryAngularRange = "QAR" 132 | LSS_QueryPositionPulse = "QP" 133 | LSS_QueryPosition = "QD" 134 | LSS_QuerySpeed = "QWD" 135 | LSS_QuerySpeedRPM = "QWR" 136 | LSS_QuerySpeedPulse = "QS" 137 | LSS_QueryMaxSpeed = "QSD" 138 | LSS_QueryMaxSpeedRPM = "QSR" 139 | LSS_QueryColorLED = "QLED" 140 | LSS_QueryGyre = "QG" 141 | LSS_QueryID = "QID" 142 | LSS_QueryBaud = "QB" 143 | LSS_QueryFirstPosition = "QFD" 144 | LSS_QueryModelString = "QMS" 145 | LSS_QuerySerialNumber = "QN" 146 | LSS_QueryFirmwareVersion = "QF" 147 | LSS_QueryVoltage = "QV" 148 | LSS_QueryTemperature = "QT" 149 | LSS_QueryCurrent = "QC" 150 | 151 | # Commands - queries advanced 152 | LSS_QueryAngularStiffness = "QAS" 153 | LSS_QueryAngularHoldingStiffness = "QAH" 154 | LSS_QueryAngularAcceleration = "QAA" 155 | LSS_QueryAngularDeceleration = "QAD" 156 | LSS_QueryEnableMotionControl = "QEM" 157 | LSS_QueryBlinkingLED = "QLB" 158 | 159 | # Commands - configurations 160 | LSS_ConfigID = "CID" 161 | LSS_ConfigBaud = "CB" 162 | LSS_ConfigOriginOffset = "CO" 163 | LSS_ConfigAngularRange = "CAR" 164 | LSS_ConfigMaxSpeed = "CSD" 165 | LSS_ConfigMaxSpeedRPM = "CSR" 166 | LSS_ConfigColorLED = "CLED" 167 | LSS_ConfigGyreDirection = "CG" 168 | LSS_ConfigFirstPosition = "CFD" 169 | LSS_ConfigMode = "CRC" 170 | 171 | # Commands - configurations advanced 172 | LSS_ConfigAngularStiffness = "CAS" 173 | LSS_ConfigAngularHoldingStiffness = "CAH" 174 | LSS_ConfigAngularAcceleration = "CAA" 175 | LSS_ConfigAngularDeceleration = "CAD" 176 | LSS_ConfigBlinkingLED = "CLB" 177 | 178 | ### EOF ####################################################################### 179 | -------------------------------------------------------------------------------- /params.txt: -------------------------------------------------------------------------------- 1 | {"baseradius": 0.0, "cbFrame": 0.0, "sqSize": 0.0, "cbHeight": 0.0, "pieceHeight": 0.0} 2 | -------------------------------------------------------------------------------- /pieces_images/ChessPiecesArray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/ChessPiecesArray.png -------------------------------------------------------------------------------- /pieces_images/bishopb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/bishopb.png -------------------------------------------------------------------------------- /pieces_images/bishopw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/bishopw.png -------------------------------------------------------------------------------- /pieces_images/blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/blank.png -------------------------------------------------------------------------------- /pieces_images/game.pgn: -------------------------------------------------------------------------------- 1 | [Event "Wch U12"] 2 | [Site "Duisburg"] 3 | [Date "1992.??.??"] 4 | [Round "1"] 5 | [White "Malakhov, Vladimir"] 6 | [Black "Ab Rahman, M."] 7 | [Result "1-0"] 8 | [WhiteElo ""] 9 | [BlackElo ""] 10 | [ECO "A05"] 11 | 12 | 1.Nf3 Nf6 2.b3 g6 3.Bb2 Bg7 4.g3 d6 5.Bg2 O-O 6.O-O c6 7.d3 e5 8.c4 Ne8 9.Nbd2 f5 13 | 10.Qc2 Na6 11.c5 Nxc5 12.Nxe5 Qe7 13.d4 Na6 14.Qc4+ Kh8 15.Nef3 Be6 16.Qc3 f4 14 | 17.gxf4 Rxf4 18.Qe3 Rf8 19.Ng5 Nec7 20.Nc4 Rae8 21.Nxe6 Qxe6 22.Qxe6 Rxe6 15 | 23.e3 d5 24.Ne5 g5 25.Ba3 Rff6 26.Bh3 Re8 27.Bd7 Rd8 28.Be7 Rxd7 29.Bxf6 1-0 16 | 17 | 18 | [Event "Wch U12"] 19 | [Site "Duisburg"] 20 | [Date "1992.??.??"] 21 | [Round "2"] 22 | [White "Malakhov, Vladimir"] 23 | [Black "Berescu, Alin"] 24 | [Result "1-0"] 25 | [WhiteElo ""] 26 | [BlackElo ""] 27 | [ECO "D05"] 28 | 29 | 1.d4 Nf6 2.Nd2 d5 3.Ngf3 e6 4.e3 c5 5.c3 Nbd7 6.Bd3 Bd6 7.O-O O-O 8.Re1 b6 30 | 9.e4 dxe4 10.Nxe4 Be7 11.Ne5 Bb7 12.Ng5 g6 13.Qe2 Nxe5 14.dxe5 Nh5 15.Ne4 Qd5 31 | 16.f4 Rfd8 17.Bc2 Qc6 18.Be3 Rd7 19.Rad1 Rad8 20.Rxd7 Rxd7 21.Nd2 Ng7 22.Be4 Qc8 32 | 23.g4 Qd8 24.Bxb7 Rxb7 25.Ne4 Rd7 26.c4 h5 27.h3 h4 28.Kh2 Ne8 29.f5 Qc7 33 | 30.Bf4 Rd4 31.Qf2 Rxc4 32.f6 Qb7 33.Ng5 Bf8 34.b3 Rc3 35.Qd2 Rf3 36.Nxf3 Qxf3 34 | 37.Qe3 Qd5 38.Qe4 Qd7 39.Qf3 Nc7 40.Rd1 Nd5 41.Bg5 Qc7 42.Re1 b5 43.Qd1 c4 35 | 44.Qc1 Bb4 45.Bd2 Bxd2 46.Qxd2 Nxf6 47.bxc4 bxc4 48.Qd6 Qa5 49.Rf1 Nd5 50.Qd7 Qd2+ 36 | 51.Kh1 f5 52.exf6 1-0 37 | -------------------------------------------------------------------------------- /pieces_images/kingb.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/kingb.ico -------------------------------------------------------------------------------- /pieces_images/kingb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/kingb.png -------------------------------------------------------------------------------- /pieces_images/kingw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/kingw.png -------------------------------------------------------------------------------- /pieces_images/knightb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/knightb.png -------------------------------------------------------------------------------- /pieces_images/knightw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/knightw.png -------------------------------------------------------------------------------- /pieces_images/nbishopb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/nbishopb.png -------------------------------------------------------------------------------- /pieces_images/nbishopw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/nbishopw.png -------------------------------------------------------------------------------- /pieces_images/nkingb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/nkingb.png -------------------------------------------------------------------------------- /pieces_images/nkingw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/nkingw.png -------------------------------------------------------------------------------- /pieces_images/nknightb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/nknightb.png -------------------------------------------------------------------------------- /pieces_images/nknightw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/nknightw.png -------------------------------------------------------------------------------- /pieces_images/npawnb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/npawnb.png -------------------------------------------------------------------------------- /pieces_images/npawnw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/npawnw.png -------------------------------------------------------------------------------- /pieces_images/nqueenb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/nqueenb.png -------------------------------------------------------------------------------- /pieces_images/nqueenw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/nqueenw.png -------------------------------------------------------------------------------- /pieces_images/nrookb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/nrookb.png -------------------------------------------------------------------------------- /pieces_images/nrookw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/nrookw.png -------------------------------------------------------------------------------- /pieces_images/pawnb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/pawnb.png -------------------------------------------------------------------------------- /pieces_images/pawnw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/pawnw.png -------------------------------------------------------------------------------- /pieces_images/queenb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/queenb.png -------------------------------------------------------------------------------- /pieces_images/queenw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/queenw.png -------------------------------------------------------------------------------- /pieces_images/rookb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/rookb.png -------------------------------------------------------------------------------- /pieces_images/rookw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDGE-tronics/Chess-Robot/0d38471bd8d453376441b8f6c57b371bccf1fdaa/pieces_images/rookw.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | altgraph==0.17 2 | numpy==1.22.0 3 | PyInstaller==5.13.1 4 | pyserial==3.4 5 | python-chess==0.30.1 6 | gTTS==2.3.1 7 | pygame==2.1.3 8 | pefile==2024.8.26 9 | FreeSimpleGUI==5.2.0.post1 10 | opencv-python --------------------------------------------------------------------------------