Rubik's cube Robot solver

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 :-\

:slight_smile: Thanks! I have read the Cube explorer manual!

Your documentation is very informative. Can u give more insights onto how I can build the gripper using ply board. Do you have any drawings of all the parts needed to build the gripper. I do not know how to use Solid works or any such CAD softwares. ( Electronics student :frowning: ).

Thanx in advance!

Hey Kas, amazing design man! I would love to get started also and maybe you can upload the paperscan of the wood grippers so i can print it and also make the grippers.
Looking forward to your reply!

Hi guys,

Too many relatives at home for the moment :stuck_out_tongue:
Will be back in a few days

I do not know how to use Solid works or any such CAD softwares. ( Electronics student :frowning: )

This is a nice opportunity to get your hands dirty :wink:
You will definitely have to use these softs in your future jobs

Vectric Vcarve Pro cutting simulation:

The assembly Video (reply #15) has been created using Autodesk Inventor

For dd5665, arduino5 and others, I attach to this post the gripper parts in pdf format
Not sure, you may have to login/register to see the file

Let me know if you need more info

Servo_gripper7(2).pdf (10.7 KB)

Reviewed it, looks really great! What a nice job. When i get my servo's i will test it out. (Should take some more days/weeks)

Meanwhile i will test with cardboard :stuck_out_tongue:

Timing is of the essence! TO THE BATCAVE!

@arduino5

When i get my servo's i will test it out

Make sure that the rotation (wrist) servo can perform at least a full 180° move
Some servo's just can't ::slight_smile:

For this project I used 5mm (.2") plywood and 3mm (1/8") screws
How will you cut the parts ??

Hey kas!

Thanx for the layout! Its amazing! I havn't worked with servos before although I know the principles and theory related to it, I am really confused how you interfaced 4 servos (2 for each gripper) to the arduino. Which motor driver are you using? Is it a custom made driver or ready-made one? I am really confused!!!

I havn't worked with servos before although I know the principles and theory related to it

Great 8) 8) 8)

Which motor driver are you using? Is it a custom made driver or ready-made one? I am really confused!!!

:astonished: :astonished: :astonished:
dd5665, I suggest you get really familiar with servo motors before tackling this project
No need for motor drivers, just one digital output (PWM) per Servo

Look for the sweep.ino sketch in the Aduino IDE Example
You may also look here and here

Happy New Year to you and all the Community

:slight_smile:

Thanx kas!! I interfaced 2 servos today. It was a silly question indeed! :stuck_out_tongue:

I went to fabricate the grippers on plyboard today. Since I stay in a pretty backward area :frowning: ... there is only one place which has a CNC router. They told me that they can only fabricate the pieces on metal sheet or Acrilic sheet. Plus, they are charging Rs . 15000 per gripper for metal sheet and Rs. 4000 per gripper for acrillic sheet!!!!

Both of these options is way beyond my budget.

With no options to cut these parts without a CNC router...I purchased some cardboard and tried cutting the pieces manuallly by hand...
I'll let you know if it works. :-((

I purchased some cardboard and tried cutting the pieces manuallly by hand...

I guess it won't be an easy task ::slight_smile:
Let us know