Go Down

Topic: Rubik's cube Robot solver (Read 83465 times) previous topic - next topic

kas

#15
Nov 22, 2014, 09:42 pm Last Edit: Dec 01, 2014, 10:04 pm by kas
Hi bjim525

You just passed from "Lurker" status to "Active Contributor"
Congratulation !!  ;)

Quote
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



bjim525

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!!! :smiley-money:  :smiley-money: :smiley-money:

kas

#17
Nov 24, 2014, 08:04 am Last Edit: Nov 26, 2014, 08:29 pm by kas
Not too familiar with 3D printers  :smiley-roll:

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

kas

#18
Dec 07, 2014, 06:25 pm Last Edit: May 16, 2017, 09:15 am by kas
Quote
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:

Code: [Select]
#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  <L>  move
myCube.L2  for  <L2> 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  ;)

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

Patrik

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 :)
The balancing robot for dummies guide
http://www.x-firm.com/?page_id=145

kas

I feel now pretty confident to finish this project  ;)

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

kas

#21
Dec 11, 2014, 07:47 pm Last Edit: May 07, 2017, 09:57 am by kas
It's python's time folks     :)
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 )             
Code: [Select]
## 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   ;)
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  ;)      (this thread is not a Python tutorial)
       ... and run this basic "hello World" test program:

Code: [Select]
## 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

kas

#22
Dec 20, 2014, 02:06 pm Last Edit: Dec 29, 2014, 10:35 am by kas
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  ;)



dd5665

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.

 

kas

#24
Dec 21, 2014, 09:28 am Last Edit: Dec 21, 2014, 09:30 am by kas
Quote
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:
Quote
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  :-\





dd5665

#25
Dec 25, 2014, 08:42 am Last Edit: Dec 25, 2014, 09:31 am by dd5665
:-) 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 :-( ).

Thanx in advance!

arduino5

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!

kas

#27
Dec 28, 2014, 02:39 pm Last Edit: Dec 28, 2014, 05:16 pm by kas
Hi guys,

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

kas

Quote
I do not know how to use Solid works or any such CAD softwares. ( Electronics student :-( )
This is a nice opportunity to get your hands dirty :smiley-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





arduino5

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

Timing is of the essence! TO THE BATCAVE!

Go Up