Rubik's cube Robot solver

OK, new Forum bugs are slowly desappearing, time to post again :slight_smile:

This is the (probably) final design


>> Video <<

Time to start testing with a Rubik's cube

First test with a real Rubik's cube

>> Video <<

Here is the demo code

#define VERSION     "\n\nGripper demo V1.2- @kas2014\n"

// V1.2  use VarSpeedServo library
// V1.1  2 servo's
// V1.0  1 servo

#include <VarSpeedServo.h> 

#define    rot_pin              10                  // Green
#define    pinch_pin             9                  // Yellow

#define    CLOSE                85                  // servo's limits
#define    OPEN                132
#define    CW                    0
#define    MID                  87
#define    CCW                 171

#define    SLOW                 30
#define    FAST                 80

VarSpeedServo pinch_servo;                          // create servo objects
VarSpeedServo rot_servo;

void setup()  {
  pinch_servo.attach(pinch_pin);  
  rot_servo.attach(rot_pin, 580, 2570);
  gripOpen(FAST);
  armCenter(FAST);
  delay(500);
}

void loop() {
  for(int i=0; i<3; i++)  {
    gripClose(SLOW);
    armRight(SLOW);
    gripOpen(SLOW);
    armCenter(SLOW);
  }
  delay(500);

  for(int i=0; i<3; i++)  {
    gripClose(FAST);
    armRight(FAST);
    gripOpen(FAST);
    armCenter(FAST);
  }
  delay(500);

  for(int i=0; i<2; i++)  {
    gripClose(SLOW);
    armLeft(SLOW);
    gripOpen(SLOW);
    armCenter(SLOW);
  }
  while(true);
} 

void armRight(int speed)    { rot_servo.write(CW, speed, true); }
void armLeft(int speed)     { rot_servo.write(CCW, speed, true); }
void armCenter(int speed)   { rot_servo.write(MID, speed, true); }
void gripOpen(int speed)    { pinch_servo.write(OPEN, speed, true); }
void gripClose(int speed)   { pinch_servo.write(CLOSE, speed, true); }

For this project I chose the "VarSpeedServo" library which has some definite advantages
compared to the standard Servo library:

  • adjustable servo's speed (see video)
  • optional code pause until move is complete

Next stop: build a second gripper and assemble the final robot

** EDIT: see post #111 **

Nice looking setup. I'm assuming that the code actually makes the arm do what you claim. But, how is that going to be sufficient to solve a rubik's cube? How is the robot/Arduino going to know what moves to make to solve the cube? How is it going to know where to start? How is it going to know when to stop?

Hi Paul,

Nice looking setup

Thanks

how is that going to be sufficient to solve a rubik's cube?
How is the robot/Arduino going to know what moves to make to solve the cube?

Please refer to post#1, the solving itself will be performed using Cube Explorer
Solution will be passed to Arduino using Singmaster notation
(look here for a visual approach)

Arduino will convert notation into real world moves

The Rubik's cube Robot solver, together with my assistant :wink:

The hardware side is finished, let's produce some code to make it alive

together with my assistant

Your assistant does not look impressed. You need another groupie.

Nicely laid out and good fit.

Your assistant does not look impressed

She is just a bit jealous of this time consuming project :wink:

Nicely laid out and good fit

Thanks zoomkat

First cube moves according to Singmaster notation (see post #8)
< D, B, B', D' >
This sequence will finally bring back the cube to its initial position


>> Video <<

Those 4 moves are hard coded in the Arduino sketch

I will now prepare a sketch with all possible moves:
F (Front), B (Back), U (Up), D (Down), L (Left), R (Right), plus the " ' " and " 2 " variants

For debugging purpose, moves will be entered through Arduino's serial monitor

EDIT: see post #242 (code)

Hi Looking for something to build. I am a relative newbie to Arduino and have made a finished a robot car using a uno board and Robot Shield. I found this quite easy and want to make the rubixcube solver. How hard was this build on a scale of 1-10? Looks good. Need one as I can complete a rubixcude in a couple of days.

Hi bjim525

You just passed from "Lurker" status to "Active Contributor"
Congratulation !! :wink:

How hard was this build on a scale of 1-10?

I would say... 6

The project is challenging both on the Hardware and Software sides
To create the grips, you need a CNC, a Laser cutter or a 3D printer
Do you have access to this type of equipment ??

Assembly is an easy task:


>> Video <<

Should you need additional info, let me know

I am in the progress of saving up the money for a 3d printer. Any ideas which one. The Arduino one looks good. Costs a lot though!!! :money_mouth_face: :money_mouth_face: :money_mouth_face:

Not too familiar with 3D printers :roll_eyes:

Rubik's robot parts are CNC'd using a cheap chinese 30X40 router

I will now prepare a sketch with all possible moves:
F (Front), B (Back), U (Up), D (Down), L (Left), R (Right), plus the " ' " and " 2 " variants

For debugging purpose, moves will be entered through Arduino's serial monitor

Here it is:

#define VERSION       "Cube Mover V1.2  @kas2014\n"

// V1.2: refactored using Cube class
// V1.1: replaced Servo with VarSpeedServo library 
// V1.0: initial release

#include <VarSpeedServo.h> 
#include "cube.h"

// ---------- user adjustments -------------------
#define    DOWN_CLOSE          91 //92
#define    DOWN_OPEN          132
#define    DOWN_CW              6
#define    DOWN_MID            89
#define    DOWN_CCW           172

#define    BACK_CLOSE          84 //85
#define    BACK_OPEN          129
#define    BACK_CW              2
#define    BACK_MID            87
#define    BACK_CCW           171

#define    LOW_SPEED           50 //50
#define    HI_SPEED           100 //80
// -----------------------------------------------

#define    downPinchPin         9
#define    downRotPin          10
#define    backPinchPin         5
#define    backRotPin           6
#define    bipPin              11             // buzzer
#define    myRX                 2
#define    myTX                 3

#define    STX               0x02             // serial data frame delimiters
#define    ETX               0x03

Cube myCube(downPinchPin, downRotPin, backPinchPin, backRotPin);

char cmd[128];                                 // bytes received buffer

void setup() {
  Serial.begin(57600);
  Serial.println(VERSION);
  pinMode(bipPin, OUTPUT);     

  myCube.begin(HI_SPEED);                 // set HIGH servo's speed 
  myCube.downSetLimits(DOWN_CLOSE, DOWN_OPEN, DOWN_CW,DOWN_MID, DOWN_CCW); // set limits for pinch and rotation servo's
  myCube.backSetLimits(BACK_CLOSE, BACK_OPEN, BACK_CW, BACK_MID, BACK_CCW);
  myCube.seize();
  bip(20, 2);                             // bip
}

void loop() {
  if(getSerialData())       parseData(); 
}

// ---------------------------

boolean getSerialData()  {
  if(Serial.available())  {                           // data received from smartphone
    delay(2);
    cmd[0] =  Serial.read();  
    if(cmd[0] == STX)  {
      int i=1;      
      while(Serial.available())  {
        delay(1);
        cmd[i] = Serial.read();
//      Serial.print(cmd[i]);
        if(cmd[i]>'u' || i>124)    {  bip(20, 5);    return false; }    // Communication error  XXX reinitialiser à zero <<<
        if((cmd[i]==ETX))               return true;     // 
        i++;
      }
    }
  }
 return false; 
}

boolean getSerialMonitor()  {  // Serial Monitor fsetting: Newline
  if(Serial.available())  {
    for(int i=0; i<124; i++)    cmd[i] = 0;
    int n = Serial.readBytesUntil('\n', cmd, 124);
//   Serial.print(cmd[0]); Serial.print(" ");
   cmd[n+1] = ETX;
   return true;
  }
 return false; 
}

void parseData()    { // parseData(cmd)
  int i = 0;
  String progress = "";
  while (cmd[i] != ETX) {
//  Serial.print(cmd[i]); mySerial.print(" ");
    switch(cmd[i])  {

      // Move commands  ------------------------------------------------------------
      case 'R':                                                    //  'R' moves
        switch(cmd[i+1])  {
          case '2':
            Serial.print("R2 ");
            myCube.R2();
            break;
          case 39:
            Serial.print("R' ");
            myCube.Rp();
            break;
          default:
            Serial.print("R ");
            myCube.R();
            break;
        }
        break;
      case 'L':                                                    //  'L' moves
        switch(cmd[i+1])  {
          case '2':
            Serial.print("L2 ");
            myCube.L2();
            break;
          case 39:
            Serial.print("L' ");
            myCube.Lp();
            break;
          default:
            Serial.print("L ");
            myCube.L();
            break;
        }
        break;
      case 'U':                                                    //  'U' moves
        switch(cmd[i+1])  {
          case '2':
            Serial.print("U2 ");
            myCube.U2();
            break;
          case 39:
            Serial.print("U' ");
            myCube.Up();
            break;
          default:
            Serial.print("U ");
            myCube.U();
            break;
        }
        break;
      case 'D':       ** snip (9000 caracters limitation) **
      case 'F': 
      case 'B':
       }
        break;

      // Scan commands  -----------------------------------------------------------
      case 'f':                                             // Scan Front side
        myCube.scanFront();
        Serial.println("OKf");
        break;
      case 'r':                                            // Scan Right side
        myCube.scanRight();
        Serial.println("OKr");
        break;
      case 'b':                                            // Scan Back side
        myCube.scanBack();
        Serial.println("OKb");
        break;
      case 'l':                                            // Scan Right side
        myCube.scanLeft();
        Serial.println("OKl");
        break;
      case 'u':                                            // Scan Up side
        myCube.scanUp();
        Serial.println("OKu");
        break;
      case 'd':                                            // Scan Down side
        myCube.scanDown();
        Serial.println("OKd");
        break;
      case 'g':                                           // back to Front side
        myCube.scanFront2();
        Serial.println("OKg");
        break;

      // Other commands  --------------------------------------------------------------
      case 'T':                                          // release gripper pressure
        myCube.seize();
        bip(40, 2);
        Serial.print("seize");
        break;
      case 'S':                                         // change move speed
        switch(cmd[i+1])  {
          case '2':
            myCube.setSpeed(HI_SPEED);
            Serial.print("High Speed");
            break;
          case '1':
            myCube.setSpeed(LOW_SPEED);
            Serial.print("Low Speed");
            break;
        }
        break;
      case 'V':                                         // bips
        switch(cmd[i+1])  {
          case '4':
            bip(80, 4);
            Serial.print("bip (4)");
            break;
          case '2':
            bip(80, 2);
            Serial.print("bip (2)");
            break;
          default:
            bip(80, 1);
            Serial.print("bip ");
            break;
        }
        break;

      default:
          break;
      }
      i++;
  }
  Serial.println();
  bip(20, 2);
}

void bip(int duration, int n)    {            // Bip piezo: duration in ms, n repeats
  for(int i=0; i<n; i++)  {  
     digitalWrite(bipPin, HIGH);        
     delay(duration);
     digitalWrite(bipPin, LOW);         
     delay(75);
  }
}

I also created a library to separate the "logic" of the program from the low-level details
EDIT: see post #43 for complete code including cube.h

According to Singmaster notation,
myCube.L generates a move
myCube.L2 for move
myCube.Lp (L prime) for a <L'> move

For demonstration purpose, the cube was first mixed according to < D B2 R2 U' F' L F2 R D2 R B2 R' >
In this video, I entered < R B2 R' D2 R' F2 L' F U R2 B2 D' > (inverse moves) in the IDE Serial Monitor.


>> Video <<

The cube is restored, as expected :wink:

I will now create a simple Python script (2 buttons) that will:

  • Launch Cube Explorer
  • move the cube, face by face, for color recognition by Cube Explorer
  • request and obtain the solution from Cube Explorer
  • transfer this solution to Arduino

Nice!

I have started to build a cube solver a couple of times but never finished any one. I will follow your project and maybe i will be inspired to build one too. After all you got me to build an balancing bot :slight_smile:

I feel now pretty confident to finish this project :wink:

Should I succeed, my long range plan is to remove the webcam and PC and use a single Android phone as both a camera and a computing device
This will then become a 100% autonomous machine

**It's python's time folks ** :slight_smile:
Python is a nice language when it's comes to produce small utilities and middlewares
For the moment, I just implemented the "Arduino side"

this program accepts a string and transmit it to Arduino, through the serial port
in same way as Arduino Serial Monitor in Post #18

This is Rubik kasBot V1.13 (May 2017: change software versioning )

## Rubik kasBot             @ kas 2014

## V1.13  changed windows position
## V1.12  removed delayB4scan
## V1.11  slow/fast cube moves
## V1.1   communication error management
## V1.0   initial release, serial communication with Arduino only

from tkinter import *
import serial, time
from serial import SerialException
import atexit
import subprocess

defaultPortNumber = '3'                     ##  default Serial port

root =Tk()
root.title('Rubik kasBot')
root.geometry('325x335+1000+100')    # window size + position

vSolve = StringVar()
comVar = IntVar()
speedVar = IntVar()
comSent = StringVar()
comIn = StringVar()
vErrror = StringVar()
vComPort = StringVar()
vComPort.set(defaultPortNumber)

comSent.set("")
comIn.set("")

def bSolve_CallB():
  arduino.write(b"x02" + str.encode(vSolve.get() + 'T') + b"x03") 

def speed_CallB():
  if speedVar.get():
    arduino.write(b"x02 S2 x03")        ## slow moves
  else:
    arduino.write(b"x02 S1 x03")        ## fast moves
      
def com_CallB():
  if comVar.get():
    root.update()
    global arduino
    try:
      arduino = serial.Serial('COM' + vComPort.get(), 57600, timeout=15)
      time.sleep(1.25)                    ## give time to settle
      sComPort.config(state = DISABLED)
      print("COM  <ON>")
      comSent.set("COM  <ON>")
      EnableButtons(True)
      speedVar.set(True)
    except SerialException:
      EnableButtons(False)
      comVar.set(False)
      displayComError('COM' + vComPort.get() +" not available")
  else:
    speedVar.set(False)
    EnableButtons(False)
    arduino.close()
    sComPort.config(state = NORMAL)
    print("COM  <OFF>")
    comSent.set("COM  <OFF>")
    comIn.set("")

## utilities ------------------------------------
def displayComError(message):
  vErrror.set(message)
  root.update()
  print(message)
  time.sleep(2)
  vErrror.set("")
   
def EnableButtons(flag):
  if flag == True:
    aspect = NORMAL
  else:
    aspect = DISABLED
  bSolve.config(state=aspect)
  eSolve.config(state=aspect)
  checkSpeed.config(state=aspect)
  
def cleanup():  ## Exit Cube Explorer and close COM port
  if comVar.get():
    arduino.close()

atexit.register(cleanup)

## UI elements --------------------------
eSolve = Entry(textvariable = vSolve, width = 60, fg="Blue", bd = 2, font=('arial', 6))
eSolve.place(x=20, y=135, height=20, width=285)

bSolve = Button(padx=59, pady=0, bd=3, text="Solve cube ", fg="black", font=('arial', 16), command = bSolve_CallB)
bSolve.place(x=20, y=160, height=55, width=285)

checkSpeed = Checkbutton(text = "High speed", variable = speedVar, command = speed_CallB)
checkSpeed.place(x=20, y=255)

tError = Label(textvariable = vErrror, fg = "red", bd = 3, font=('arial', 8))
tError.place(x=160, y=260)

checkCom = Checkbutton(text = "COM", variable = comVar, command = com_CallB)
checkCom.place(x=20, y=287)

tComOut = Label(textvariable = comSent, bd = 3, font=('arial', 8))
tComOut.place(x=140, y=278)

tComIn = Label(textvariable = comIn, bd = 3, font=('arial', 8))
tComIn.place(x=140, y=297)

sComPort = Spinbox(from_=1, to=9, width = 1, textvariable=vComPort)
sComPort.place(x=80, y=289)

checkCom.invoke()
root.mainloop()

To run or modify this code, please download Python 3.4.2
(available for Windows, Mac OS, Linux and others)

As for previous test, I shuffled the cube using < D B2 R2 U' F' L F2 R D2 R B2 R' > moves
and entered < R B2 R' D2 R' F2 L' F U R2 B2 D'> to solve it
Same result as above video, trust me on this one :wink:
Next steps:

  • install a Webcam on the robot, to allow color recognition by Cube Explorer
  • probably also install a LED lighting to overcome possible white balance variations

EDIT: getting started (April 2017) ---------------------------------------------------

install Python 3.xx
install tkinter (graphical UI)
install pyserial (Serial comm)

now... do your home work :wink: (this thread is not a Python tutorial)
... and run this basic "hello World" test program:

## Rubik (Hello, World)             @ kas 2017

from tkinter import *
import serial, time
from serial import SerialException
import atexit
import subprocess

root =Tk()
root.title('Hello, World')
root.geometry('300x300+200+200')          ## window size + position

root.mainloop()

See the window ?? you are all set

The final bot, with led's and webcam:

Now let's do the final Python coding to transmit color information to Cube Explorer, and obtain the magic formula to solve the cube :wink:

Hey! I am building the same project and wanted to know how u automated the scanning of the each 9 tiles of a face and the entire cube using this setup.

Hey! I am building the same project and wanted to know how u automated the scanning of the each 9 tiles of a face and the entire cube using this setup.

Hi dd5665,
As mentioned, I use Cube Explorer (CE)

Cube explorer has a built in webserver
The robot has to move the cube and expose each face to the webcam, while sending IP queries to CE
Cube Explorer will insure facelets color recognition and will finally compute the solution

This is an excerpt from CE manual:

You can control the process of scanning the cube with the webcam interface by sending some
strings to the webserver.
Position the Back face of the cube in front of the webcam with your robot (as you would do it manually)
and send the string " http://127.0.0.1:8081/?scanB" to the webserver to scan the back face of the cube.
Then you position the Left face and send the string "http://127.0.0.1:8081/?scanL".
Proceed in this way, using the strings "?scanF", "?scanR", "?scanU" and "?scanD".
The webserver will respond with a "Done!" in all cases.

When the 6 faces have been scanned, you send the string "http://127.0.0.1:8081/?transfer".
If all facelets are ok, the scanned cube is transferred to the Main Window and the solving maneuver is computed.
The webserver will respond with a "Done!".

Finally, with the string " http://127.0.0.1:8081/?getLast" the webserver will respond with the last
computed solving maneuver in the Main Window of Cube Explorer.
Process this string with your robot to solve the cube.

I am busy coding this feature, using Python :-\