Week 9
Assignment
- Control circuit over wireless network
- Send or get data from the internet
Project
For my final project, I am doing a Rubik's cube solver so I decided to control the solver via ESP8266.
The idea was to create API which would be able to find a solution to scrambled Rubik's cube and this solution would be requested by the ESP. But ESP does not have that many pins, so I decided to send via serial the solution to Arduino and control the robot from there.
Sequence diagram
Wiring
Source codes
Arduino
#include <AccelStepper.h>
#define MOTOR_INTERFACE_TYPE 1
#define ENABLE 12
const int stepsPerRevolution = 200;
const int motorSpeed = 16000;
const int stepperCount = 6;
const int potPin = A6;
AccelStepper steppers[stepperCount] = {
AccelStepper(MOTOR_INTERFACE_TYPE, 2, 3),
AccelStepper(MOTOR_INTERFACE_TYPE, 4, 5),
AccelStepper(MOTOR_INTERFACE_TYPE, 6, 7),
AccelStepper(MOTOR_INTERFACE_TYPE, 8, 9),
AccelStepper(MOTOR_INTERFACE_TYPE, A0, A1),
AccelStepper(MOTOR_INTERFACE_TYPE, A2, A3)
};
void setup() {
pinMode(ENABLE, OUTPUT);
digitalWrite(ENABLE, HIGH);
Serial.begin(115200);
for (int i = 0; i < stepperCount; i++) {
steppers[i].setMaxSpeed(motorSpeed);
steppers[i].setAcceleration(motorSpeed);
}
delay(1000);
digitalWrite(ENABLE, LOW);
}
int getDelayFromPot() {
int potValue = analogRead(potPin);
return map(potValue, 0, 1023, 10, 200);
}
void moveAndWait(AccelStepper &stepper, int steps) {
stepper.move(steps);
while (stepper.distanceToGo() != 0) {
stepper.run();
}
delay(getDelayFromPot());
}
int processNextMove(int index, String data) {
AccelStepper motor;
switch (data.charAt(index)) {
case 'F':
motor = steppers[0];
break;
case 'R':
motor = steppers[1];
break;
case 'B':
motor = steppers[2];
break;
case 'U':
motor = steppers[3];
break;
case 'D':
motor = steppers[4];
break;
case 'L':
motor = steppers[5];
break;
default:
return data.length();
}
int dirMultiplier = -1;
int moveMultiplier = 1;
int nextIndex = index + 3;
switch(data.charAt(index + 1)) {
case '\'':
dirMultiplier = 1;
break;
case '2':
moveMultiplier = 2;
break;
case ' ':
nextIndex--;
break;
}
for (int i = 0; i < moveMultiplier; i++) {
moveAndWait(motor, stepsPerRevolution/4*dirMultiplier);
}
return nextIndex;
}
void solveCube(String data) {
int index = 0;
while (index < data.length()) {
index = processNextMove(index, data);
}
}
void loop() {
if (Serial.available() > 0) {
String receivedData = Serial.readStringUntil('\n');
solveCube(receivedData);
}
delay(100);
}
ESP8266
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPClient.h>
const char* ssid = "ssid";
const char* password = "pswd";
// solver API
const char* flask_host = "192.168.0.109";
const int flask_port = 8080;
ESP8266WebServer server(8081);
void handleSolve() {
String cube = server.uri();
cube.replace("/solve/", "");
if (cube.length() != 54) {
server.send(400, "text/plain", "Cube string must be 54 characters.");
return;
}
WiFiClient client;
HTTPClient http;
String url = "http://" + String(flask_host) + ":" + String(flask_port) + "/solve/" + cube;
http.begin(client, url);
int httpCode = http.GET();
if (httpCode > 0) {
String payload = http.getString();
Serial.println(payload);
server.send(200, "application/json", payload);
} else {
server.send(500, "text/plain", "Failed to contact solver");
}
http.end();
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
server.enableCORS(true);
server.on("/solve/", HTTP_GET, handleSolve);
server.onNotFound([]() {
if (server.uri().startsWith("/solve/")) {
handleSolve();
} else {
server.send(404, "text/plain", "Not found");
}
});
server.begin();
}
void loop() {
server.handleClient();
}
Rubik's cube solver
from enum import Enum
import twophase.solver as sv
'''URFDLB'''
face_indices = {
'U': [0, 1, 2, 3, 5, 6, 7, 8],
'R': [9, 10, 11, 12, 14, 15, 16, 17],
'F': [18, 19, 20, 21, 23, 24, 25, 26],
'D': [27, 28, 29, 30, 32, 33, 34, 35],
'L': [36, 37, 38, 39, 41, 42, 43, 44],
'B': [45, 46, 47, 48, 50, 51, 52, 53]
}
class TryEnum(Enum):
FIRST_TRY = 1
SECOND_TRY = 2
FAIL = 3
def rotate_face_around_center(cube, face, clockwise=True):
cube = list(cube)
idx = face_indices[face]
rotate_map = [0, 1, 2, 3, 4, 5, 6, 7]
if clockwise:
rotated_order = [5, 3, 0, 6, 1, 7, 4, 2]
else:
rotated_order = [2, 4, 7, 1, 6, 0, 3, 5]
original = [cube[idx[i]] for i in rotate_map]
for i, j in enumerate(rotated_order):
cube[idx[rotate_map[i]]] = original[j]
return cube
def rotate_faces_sideways(cube):
cube = list(cube)
sides = ['L', 'F', 'R', 'B']
saved = [ [cube[i] for i in face_indices[face]] for face in sides ]
for i, face in enumerate(sides):
prev_face = sides[i - 1]
for idx, val in zip(face_indices[face], saved[sides.index(prev_face)]):
cube[idx] = val
cube = rotate_face_around_center(cube, 'U', clockwise=False)
cube = rotate_face_around_center(cube, 'D', clockwise=True)
return ''.join(cube)
def format_result(result):
sp = result.split('(')[0]
return sp.replace('1', '').replace('3', '\'')
def rotate_turns_back(result):
cp = list(result)
for i in range(len(cp)):
if cp[i] == 'B':
cp[i] = 'R'
elif cp[i] == 'R':
cp[i] = 'F'
elif cp[i] == 'F':
cp[i] = 'L'
elif cp[i] == 'L':
cp[i] = 'B'
return ''.join(cp)
def solve(cube: str, max_length: int, timeout: int):
res = sv.solve(cube, max_length, timeout)
if 'Error' not in res:
return TryEnum.FIRST_TRY, format_result(res)
cube = rotate_faces_sideways(cube)
res = sv.solve(cube, max_length, timeout)
if 'Error' not in res:
res = rotate_turns_back(res)
return TryEnum.SECOND_TRY, format_result(res)
return TryEnum.FAIL, res
if __name__ == '__main__':
'''URFDLB'''
cubestring = 'LRDFUDUFRURRLRRFBRRDBLFDBLULFLRDUFBDBUFLLBLFDFUUBBUBDD'
tries, solution = solve(cubestring, 20, 1)
print()
print(tries.name, solution)
Flask API
from flask import Flask, request, jsonify
from flask_cors import CORS
from rubiks_solver import solve
app = Flask(__name__)
CORS(app)
@app.route("/solve/<cube>")
def solve_cube(cube):
max_length = int(request.args.get("max_length", 20))
timeout = int(request.args.get("timeout", 1))
if len(cube) != 54:
return jsonify({"error": "Cube string must be exactly 54 characters."}), 400
result_type, solution = solve(cube, max_length, timeout)
print(solution)
return solution
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
Result