Rubik's Cube Solver Robot

Rubik's Cube Solver Robot © CERN-OHL

A fascinating way to solve the Rubik´s cube by a robot. Using mainly Arduino, Python and OpenCV.

  • 807 views
  • 0 comments
  • 4 respects

Components and supplies

Necessary tools and machines

About this project

The drivers to develop my project were:

  • Rubik´s cube is a "best seller" category toy, more than 350 millions of units
  • Time to solve by humans has been decreasing from 22 seconds in 1982 to 3.47 second in 2018
  • Brilliant mathematicians as Kociemba, Rokiki and Fridrich has been fascinated with the toy - at adult age!!!
  • Thera are people solving cubes blindfolded - 48 cubes in less than one hour
  • Opencv and Python are subject I wanted to know, so I choose this challenge
  • I need a pandemic project to maintain soul balance and my mind working in new knowledge as a mechanism to use the spare time

Conceptualization:

I took the Matt2uy design and optimize the image recognition and algorithm to solve by implementing the Herbert kociemba algorithm.

Process of development

It was an instinctive and observation process of the mechanics -assay and error - 4 models I built until found the more accurate and with more repeatibility featuring.

The idea was to built knowledge based on existent knowledge, so Instructables, youtube, github were very important knowledge's sources.

Results

Implementing some changes I got to save about 20-24 minutes solving the cube - Average time is 6 to 8 minutes to solve.

If you are looking for speed solving, please see my other project, this is more about strategies to solve a problem, learning and patient.

This video summarize, as integrated view, what I did:

Rubik´s cube solver Robot - 6-8 mins to solve

Process:

Cube images are captured by a python program module, then color extraction for each sticker is proceesed by other program module and finally the cube is solved by Kociemba algorithm, the solution is sent to Arduino trough a serial port. Once the solution is received the arduino code start to processing the movements, after 6-8 minutes the process finish and the robot show all the faces of the cube solved.

Next challenge

Based on this project I moved forward and got from 6 minutes to 30 seconds, 18 seconds time to solve. In other video I explain how can we reach 18 seconds. Please watch, below, my comparison video:

30 seconds vs.6-8 minutes

What for?

This robot is designed to teach:

1. Patient, resilience, frustration management

2. Repeatability and Accuracy, applied to robots

3. Rubik's cube notation, solving algorithms

4. Maths: matrixes theory, statistical, group theory,

5. Programming: Arduino-Python

6. Image processing with openCV

Code

Arduino Code Arduino
Arduino code-Receive solution and perform movements of the robot.Pyhton kociemba module was used to establish the solution.OpencV to process and extract images of the stickers
Instructables -Matt2Yu
Version 0- 15-mayo-2020

*/


#include <Servo.h>
//#include <Solver.h>
 
// servo objects
Servo rotate_servo;
Servo push_servo;
 
int move_speed = 8 ; 
int buffer_time = 115; // time between moves
int rotate_pos = 90;
int push_pos = 180;
int hold_progress = 3;
int offset_degrees = 10;
bool slow_push = false;
String kociemba_sol = "";
 
//////// cube move variables:
bool sim_only = false;
 
// test function:
bool test_ongoing = true;
 
                    

 
////////////////////// Serial Communication (receive arrays from .py script)
 
 
void accept_string()
{
        
        char ready_signal = 'ready';
        char received_signal = 'received';
 
        for (int piece_num = 0; piece_num <5; piece_num++)
        {       
                // send ready signal
                Serial.println(ready_signal);
                delay(100);
        }
        // receive string
        while(kociemba_sol == "")
        {
                char character;
                while(Serial.available())
                {
                    character = Serial.read();
                        kociemba_sol.concat(character);
                }
        }
        delay(10);
        Serial.print("String Aceptado: ");
       Serial.print(kociemba_sol);
 
        // send color confirmed signal
        Serial.println("arduino dice:");
        Serial.println(received_signal);
        Serial.println(kociemba_sol);
        delay(10);
}
 

///////////////////// Physical Movement Functions ///////////////////////////
 
////////// Servo movement function: ///////////
int move_servo(int start, int finish, int servo_pin)
{
        int pos;
        if (start - finish < 0)
        {
                for(pos = start; pos <= finish; pos += 1)
                {
                        if (servo_pin == 6)
                        {
                                push_servo.write(pos);
                                delay(move_speed);
                        }
                        else if (servo_pin == 9)
                        {
                                rotate_servo.write(pos);
                                delay(move_speed);
                        }
                }
        }
        else
        {
                for(pos = start; pos >= finish; pos -= 1)
                {
                        if (servo_pin == 6)
                        {
                                push_servo.write(pos);
                                delay(move_speed);
                        }
                        else if (servo_pin == 9)
                        {
                                rotate_servo.write(pos);
                                delay(move_speed);
                        }
                }
        }
        // use a swich case next time
        if (servo_pin == 9)
        {
                rotate_pos = pos;
        }
        if (servo_pin == 6)
        {
                push_pos = pos;
        }
        delay(buffer_time);
}
///////// Cube movement functions: ////////////

void push_cube(int num_of_pushes = 1)
{
        if (num_of_pushes == 1)
                {
                        if (slow_push == false)
                        {
                                move_servo(push_pos, 98, 6);
                                delay(buffer_time);
                                release_cube();
                                delay(buffer_time);
                        }
                        else // on rotate one
                        {
                                move_servo(push_pos, 98, 6);
                                delay(buffer_time+200);
                                release_cube();
                                delay(buffer_time);
                        }
                }
        else
        {
                while (num_of_pushes != 0)
                {
                        if (slow_push == false)
                        {
                                move_servo(push_pos, 98, 6);
                                delay(buffer_time+50);
                                move_servo(push_pos, 160,6);
                                delay(buffer_time);
                                num_of_pushes--;
                        }
                        else // on rotate one
                        {
                                move_servo(push_pos, 98, 6);
                                delay(buffer_time+200);
                                move_servo(push_pos, 160, 6);
                                delay(buffer_time);
                                num_of_pushes--;
                        }      
                }
        release_cube();
        }
}
void hold_cube()
{
        move_servo(push_pos, 140, 6);
        hold_progress = 1;
}
void release_cube()
{
        move_servo(push_pos, 180, 6);
        hold_progress = 3;
}
void rotate_one()
{
        slow_push = true;
        int rotate_finish = 0;
        if (hold_progress == 1) // hold progress 1 = hold
        {
                // from rotate_two
                if (rotate_pos < 140)
                {
                        // initial turn
                        move_servo(rotate_pos, rotate_finish-9, 9);
                        move_servo(rotate_pos, rotate_finish+10, 9);
                        // release and turn some more
                        release_cube();
                        move_servo(rotate_pos, 101, 9);
                        hold_cube();
                        move_servo(rotate_pos, 64, 9);
                        move_servo(rotate_pos, 92, 9); // prevent pulling
                        release_cube();
                        move_servo(rotate_pos, rotate_finish, 9);
                }
 
                // from rotate_three
                else if (rotate_pos > 140)
                {
                        // initial turn
                        move_servo(rotate_pos, rotate_finish-3, 9);
                        move_servo(rotate_pos, rotate_finish+3, 9);
                        // release and turn some more
                        release_cube();
                        move_servo(rotate_pos, 115, 9);
                        hold_cube();
                        move_servo(rotate_pos, 64, 9);
                        move_servo(rotate_pos, 93, 9); // prevent pulling
                        release_cube();
                        move_servo(rotate_pos, rotate_finish, 9);
                }
 
 
 
 
                hold_progress = 2;
        }
        else if (hold_progress == 2) // hold progress 2 = release, but offset still there
        {
                hold_progress = 3;
                move_servo(rotate_pos, rotate_finish, 9);
        }
        else if (hold_progress == 3) // hold progress 3 = release, offsets reconciled
        {
                // do nothing
                move_servo(rotate_pos, rotate_finish, 9);
        }
}
void rotate_two()
{
        slow_push = false;
        int rotate_finish = 90;
        if (hold_progress == 1) // hold progress 1 = hold
        {
                // rotate from rotate_one
                if (rotate_pos < 50)
                {
                        // initial turn
                        move_servo(rotate_pos, rotate_finish+10, 9);
                        move_servo(rotate_pos, rotate_finish-5, 9);
 
                        // release and turn some more
                       
                        release_cube();
                        move_servo(rotate_pos, 0, 9);
                        hold_cube();
                        move_servo(rotate_pos, 8, 9);
                        move_servo(rotate_pos, 5, 9); // prevent pulling
                        release_cube();
                       
                        move_servo(rotate_pos, rotate_finish, 9);
                }
                // rotate from rotate_three
                else if (rotate_pos > 150)
                {
                        move_servo(rotate_pos, rotate_finish-12, 9);
                        move_servo(rotate_pos, rotate_finish+4, 9);
 
 
                        // release and turn some more
                        release_cube();
                        move_servo(rotate_pos, 180, 9);
                        hold_cube();
                        move_servo(rotate_pos, 170, 9);
                        move_servo(rotate_pos, 178, 9); // prevent pulling
                        release_cube();
                        move_servo(rotate_pos, rotate_finish, 9);
                }
                hold_progress = 2;
        }
        else if (hold_progress == 2) // hold progress 2 = release, but offset still there
        {
                hold_progress = 3;
                move_servo(rotate_pos, rotate_finish, 9);
        }
        else if (hold_progress == 3) // hold progress 3 = release, offsets reconciled
        {
                // do nothing
                move_servo(rotate_pos, rotate_finish, 9);
        }
}
void rotate_three()
{
        slow_push = false;
        int rotate_finish = 180;
        if (hold_progress == 1) // hold progress 1 = hold
        {
                // from rotate_two
                if (rotate_pos > 40)
                {
                        move_servo(rotate_pos, rotate_finish+4, 9);
                        move_servo(rotate_pos, rotate_finish-5, 9); // prevent pulling
 
                        // fix: cube not fully turned
                        release_cube();
                        move_servo(rotate_pos, 80, 9);
                        hold_cube();
                        move_servo(rotate_pos, 104, 9);
                        move_servo(rotate_pos, 90, 9); // prevent pulling
                        release_cube();
                        move_servo(rotate_pos, rotate_finish, 9);
                }
 
                // from rotate_one
                if (rotate_pos < 40)
                {
                        move_servo(rotate_pos, rotate_finish+5, 9);
                        move_servo(rotate_pos, rotate_finish-7, 9); // prevent pulling
 
                        // fix: cube not fully turned
                        release_cube();
                        move_servo(rotate_pos, 70, 9);
                        hold_cube();
                        move_servo(rotate_pos, 85, 9);
                        move_servo(rotate_pos,105, 9); // prevent pulling
                        release_cube();
                        move_servo(rotate_pos, rotate_finish, 9);
                }
 
                hold_progress = 2;
        }
        else if (hold_progress == 2) // hold progress 2 = release, but offset still there
        {
                hold_progress = 3;
                move_servo(rotate_pos, rotate_finish, 9);
        }
        else if (hold_progress == 3) // hold progress 3 = release, offsets reconciled
        {
                // do nothing
                move_servo(rotate_pos, rotate_finish, 9);
        }
}
 
///////////////////// Cube Move Notation ///////////////////////////
// They print, simulate and call the physical functions
 
void right_inverted()
{
        Serial.println("R', ");
 
        if (sim_only == false)
        {
                rotate_three();
                push_cube();
                hold_cube();
                rotate_two();
                release_cube();
                rotate_one();
                push_cube();
                rotate_two();
                push_cube(3);
        }
 
}
void right()
{
        Serial.println("R, ");
 
        if (sim_only == false)
        {
                rotate_three();
                push_cube();
                rotate_two();
                hold_cube();
                rotate_three();
                release_cube();
                rotate_one();
                push_cube();
                rotate_two();
                push_cube();
        }
 }
void left_inverted()
{
        Serial.println("L', ");
 
        if (sim_only == false)
        {
                rotate_one();
                push_cube();
                rotate_two();
                hold_cube();
                rotate_one();
                release_cube();
                rotate_three();
                push_cube();
                rotate_two();
                push_cube();
        }
 
}
void left()
{
        Serial.println("L, ");
 
        if(sim_only == false)
        {
                rotate_one();
                push_cube();
                hold_cube();
                rotate_two();
                release_cube();
                rotate_three();
                push_cube();
                rotate_two();
                push_cube(3);
        }
 
}
void down_inverted()
{
        Serial.println("D', ");
 
        if (sim_only == false)
        {
                hold_cube();
                rotate_one();
                release_cube();
                rotate_two();
                push_cube();
                rotate_one();
                push_cube();
                rotate_two();
                push_cube(3);
        }
 
}
void down()
{
        Serial.println("D, ");
 
        if (sim_only == false)
        {
                hold_cube();
                rotate_three();
                release_cube();
                rotate_two();
                push_cube();
                rotate_three();
                push_cube();
                rotate_two();
                push_cube(3);
        }
 
}
void up_inverted()
{
        Serial.println("U', ");
 
        if (sim_only == false)
        {
                push_cube(2);
                hold_cube();
                rotate_one();
                release_cube();
                rotate_two();
                push_cube();
                rotate_one();
                push_cube();
                rotate_two();
                push_cube();
        }
 
}
void up()
{
        Serial.println("U, ");
 
        if (sim_only == false)
        {
                push_cube(2);
                hold_cube();
                rotate_three();
                release_cube();
                rotate_two();
                push_cube();
                rotate_three();
                push_cube();
                rotate_two();
                push_cube();
        }
 
}
void front_inverted()
{
        Serial.println("F', ");
 
        if (sim_only == false)
        {
        push_cube(3);
        hold_cube();
        rotate_one();
        release_cube();
        rotate_two();
        push_cube();
        rotate_one();
        push_cube();
        rotate_two();
        }
 
}
void front()
{
        Serial.println("F, ");
 
        if (sim_only == false)
        {
                push_cube(3);
                hold_cube();
                rotate_three();
                release_cube();
                rotate_two();
                push_cube();
                rotate_three();
                push_cube();
                rotate_two();
        }
}
void back_inverted()
{
        Serial.println("B', ");
        if (sim_only == false)
        {
                push_cube();
                hold_cube();
                rotate_one(); // ccw
                release_cube();
                rotate_two();
                push_cube(3);
                rotate_three(); //cw
                push_cube();
                rotate_two();
        }
 
}
void back()
{
        Serial.println("B, ");
 
        if (sim_only == false)
        {
                push_cube();
                hold_cube();
                rotate_three();
                release_cube();
                rotate_two();
                push_cube(3);
                rotate_one();
                push_cube();
                rotate_two();
        }
 
}    

// insert top layer edges

// miscellaneous algorithms
void warm_up() // do it six times to get back to the original position
{
        Serial.println();
        Serial.print("Warmup: ");
        Serial.print("R', D', R, D");
        //r'
        rotate_one();
        push_cube();
        hold_cube();//
        rotate_two();
        release_cube();
        rotate_three();
        push_cube();
        rotate_two();
        push_cube(3);
 
        //d'
 
        hold_cube();//
        rotate_three();
        release_cube();
 
        //r start here
 
        rotate_two();
        push_cube();
        rotate_three();
        hold_cube();
        rotate_two();
        release_cube();
 
        // d
 
        rotate_three();
        push_cube();
        hold_cube();
        rotate_two();
        release_cube();
        push_cube();
        rotate_one();
        push_cube();
        rotate_two();
        push_cube(3);
}
void superflip() // all edges are opposite (checkered pattern)
{
        Serial.println();
        Serial.println("Superflip: ");
        up();
        up();
        down();
        down();
 
        left();
        left();
        right();
        right();
 
 
        front();
        front();
        back();
        back();
}
 
                                                                                                                                                                                // test it
void scramble() // random 25 moves
{
        Serial.println();
        Serial.println("Scramble: ");
        int move;
        for(int j = 0; j < 25; j++)
        {
                move = random(1, 12);
                //Serial.println(move);
                switch(move)
                {
                        case 1:
                                right();
                                break;
                        case 2:
                                right_inverted();
                                break;
                        case 3:
                                left();
                                break;
                        case 4:
                                left_inverted();
                                break;
                        case 5:
                                up();
                                break;
                        case 6:
                                up_inverted();
                                break;
                        case 7:
                                down();
                                break;
                        case 8:
                                down_inverted();
                                break;
                        case 9:
                                front();
                                break;
                        case 10:
                                front_inverted();
                                break;
                        case 11:
                                back();
                                break;
                        case 12:
                                back_inverted();
                                break;
                }
        }
}
 
// test all possible rotation combinations (for mechanical testing)
void rotate_one_to_two()
{
        Serial.println("rotate_one to rotate_two");
        rotate_one();
        hold_cube();
        rotate_two();
        release_cube();
        push_cube();
}
 
void rotate_two_to_one()
{
        Serial.println("rotate_two to rotate_one");
        rotate_two();
        hold_cube();
        rotate_one();
        release_cube();
        push_cube();
}
 
void rotate_two_to_three()
{
        Serial.println("rotate_two to rotate_three");
        rotate_two();
        hold_cube();
        rotate_three();
        release_cube();
        push_cube();
}
 
void rotate_three_to_two()
{
        Serial.println("rotate_three to rotate_two");
        rotate_three();
        hold_cube();
        rotate_two();
        release_cube();
        push_cube();
}
 
// double turns:
void rotate_three_to_one()
{
        Serial.println("rotate_three to rotate_two");
        rotate_three();
        hold_cube();
        rotate_one();
        release_cube();
        push_cube();
}
 
void rotate_one_to_three()
{
        Serial.println("rotate_one to rotate_two");
        rotate_one();
        hold_cube();
        rotate_three();
        release_cube();
        push_cube();
}
void rotation_test()
{
        Serial.println("Rotation Test:");
 
        rotate_one_to_two();
        rotate_two_to_one();
        rotate_two_to_three();
        rotate_three_to_two();
        rotate_three_to_one();
        rotate_one_to_three();
 
        rotate_one();
        push_cube();
        rotate_two();
        push_cube();
        rotate_three();
        push_cube();
       
}


 

void run_kociemba(){


// Length (with one extra character for the null terminator)
int str_len = kociemba_sol.length() + 1; 


Serial.println("arduino dice: Caracteres:");
Serial.println((str_len-1));

for(int i = 0; i <= (str_len-1); i++){     //recorre

//Serial.print(i);


if ((kociemba_sol.charAt(i)) =='R'){


if ((kociemba_sol.charAt(i+1)) == '2') {
      right();
      right();}


if ((kociemba_sol.charAt(i+1)) == '\'') {
      right_inverted();}

if (((kociemba_sol.charAt(i+1))!= '2') and((kociemba_sol.charAt(i+1)) != '\'')) {
      right();}

}

else { //NO HACER NADA
      
}

if ((kociemba_sol.charAt(i)) =='L'){


if ((kociemba_sol.charAt(i+1)) == '2') {
      left();
      left();}


if ((kociemba_sol.charAt(i+1)) == '\'') {
      left_inverted();}

if (((kociemba_sol.charAt(i+1))!= '2') and((kociemba_sol.charAt(i+1)) != '\'')) {
      left();}

}

else { //NO HACER NADA
      
}

if ((kociemba_sol.charAt(i)) =='U'){


if ((kociemba_sol.charAt(i+1)) == '2') {
      up();
      up();}


if ((kociemba_sol.charAt(i+1)) == '\'') {
      up_inverted();}

if (((kociemba_sol.charAt(i+1))!= '2') and((kociemba_sol.charAt(i+1)) != '\'')) {
      up();}

}

else { //NO HACER NADA
      
}



if ((kociemba_sol.charAt(i)) =='D'){


if ((kociemba_sol.charAt(i+1)) == '2') {
      down();
      down();}


if ((kociemba_sol.charAt(i+1)) == '\'') {
      down_inverted();}

if (((kociemba_sol.charAt(i+1))!= '2') and((kociemba_sol.charAt(i+1)) != '\'')) {
      down();}

}

else { //NO HACER NADA
      
}





if ((kociemba_sol.charAt(i)) =='F'){


if ((kociemba_sol.charAt(i+1)) == '2') {
      front();
      front();}


if ((kociemba_sol.charAt(i+1)) == '\'') {
      front_inverted();}

if (((kociemba_sol.charAt(i+1))!= '2') and((kociemba_sol.charAt(i+1)) != '\'')) {
      front();}

}

else { //NO HACER NADA
      
}




if ((kociemba_sol.charAt(i)) =='B'){


if ((kociemba_sol.charAt(i+1)) == '2') {
      back();
      back();}


if ((kociemba_sol.charAt(i+1)) == '\'') {
      back_inverted();}

if (((kociemba_sol.charAt(i+1))!= '2') and((kociemba_sol.charAt(i+1)) != '\'')) {
      back();}

}

else { //NO HACER NADA
      
}

}
   
                
        //warm_up();
        //scramble();
}
 
void show_off_cube()
{
        rotate_one();
        rotate_three();
        push_cube(2);
        rotate_one();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////// PROGRAM START ///////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
 
void setup()
{
        rotate_servo.attach(9);  // attaches the servo on pin 9 to the servo object
        push_servo.attach(6);  // attaches the servo on pin 6 to the servo object
        push_servo.write(push_pos);
        rotate_servo.write(rotate_pos);
        delay(1000);
        Serial.begin(9600);
        while (! Serial); // Wait untilSerial is ready
       
}
 
/////////////// Se recibe por puerto serial la solucion  //////////////////

void loop()
{      
//recibir la solucion de kociemba por puerto serial


accept_string();


 //calentando 5 segundos

Serial.println("Calentando: volvera a la posicion inicial");


push_cube();
push_cube();
push_cube();
push_cube();

delay(2000);


Serial.println("Arduino dice:Inicia a correr la solucion:");


 run_kociemba(); //corre el string recibido
        
Serial.println("Arduino dice: Finaliza tiempo solucion");

//mostrar el cubo resuelto

show_off_cube();


Serial.println("Arduino dice:Finalizado enviar nueva solucion de Kociemba");
  
        
        while(true){}

  
 
}

Schematics

Schematics
Circuit diagram- Source: Matt2yu-Instructables
F8efd64hwii20r9 yf3nslcfkl

Comments

Similar projects you might like

Arduino robot PoliArdo - Maze solver

by MakerRobotics

  • 17,442 views
  • 2 comments
  • 59 respects

The Making of a Conscious Robot

Project in progress by ihacklab

  • 10,607 views
  • 6 comments
  • 23 respects

MeArm Robot Arm - Your Robot - V1.0

Project tutorial by Benjamin Gray

  • 33,248 views
  • 4 comments
  • 47 respects

Amazing 6WD Off-Road Robot | Arduino RC Robot

Project tutorial by Jithin Sanal

  • 13,561 views
  • 1 comment
  • 67 respects

Robot Rover - iPhone controlled using Blynk Joystick

Project tutorial by jim Myracle

  • 8,325 views
  • 0 comments
  • 20 respects

The Robot Who Didn't Know He is Alive!

Project in progress by ihacklab

  • 5,610 views
  • 3 comments
  • 24 respects
Add projectSign up / Login