adding untracked files
This commit is contained in:
parent
374d67df52
commit
8678b2e4ec
171
arduino/multistepper/multistepper.ino
Normal file
171
arduino/multistepper/multistepper.ino
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
// Include the AccelStepper library:
|
||||
#include <AccelStepper.h>
|
||||
|
||||
// Define stepper motor connections and motor interface type. Motor interface type must be set to 1 when using a driver:
|
||||
|
||||
// Set stepper 1 pins
|
||||
#define m1LimitNegPin 2
|
||||
#define m1LimitPosPin 3
|
||||
#define m1DirPin 4
|
||||
#define m1StepPin 5
|
||||
#define m1PowerPin 6
|
||||
|
||||
// Set stepper 2 pins
|
||||
#define m2LimitNegPin 9
|
||||
#define m2LimitPosPin 10
|
||||
#define m2DirPin 11
|
||||
#define m2StepPin 12
|
||||
#define m2PowerPin 13
|
||||
|
||||
#define motorInterfaceType 1
|
||||
|
||||
// Create a new instance of the AccelStepper class:
|
||||
AccelStepper m1Stepper = AccelStepper(motorInterfaceType, m1StepPin, m1DirPin);
|
||||
AccelStepper m2Stepper = AccelStepper(motorInterfaceType, m2StepPin, m2DirPin);
|
||||
|
||||
unsigned long previousMillis = 0;
|
||||
unsigned long currentMillis = 0;
|
||||
|
||||
void setup() {
|
||||
|
||||
pinMode(m1PowerPin, OUTPUT);
|
||||
pinMode(m1LimitNegPin, INPUT);
|
||||
pinMode(m1LimitPosPin, INPUT);
|
||||
|
||||
pinMode(m2PowerPin, OUTPUT);
|
||||
pinMode(m2LimitNegPin, INPUT);
|
||||
pinMode(m2LimitPosPin, INPUT);
|
||||
|
||||
Serial.begin(115200);
|
||||
|
||||
// Set the maximum speed in steps per second:
|
||||
m1Stepper.setMaxSpeed(200);
|
||||
m1Stepper.setAcceleration(100);
|
||||
m1Stepper.setCurrentPosition(0);
|
||||
|
||||
m2Stepper.setMaxSpeed(200);
|
||||
m2Stepper.setAcceleration(100);
|
||||
m2Stepper.setCurrentPosition(0);
|
||||
}
|
||||
|
||||
|
||||
int integerValue=0;
|
||||
bool negativeNumber = false; // track if number is negative
|
||||
char incomingByte;
|
||||
|
||||
void loop() {
|
||||
|
||||
currentMillis = millis();
|
||||
|
||||
int m1EorNeg = digitalRead(m1LimitNegPin);
|
||||
int m1EorPos = digitalRead(m1LimitPosPin);
|
||||
|
||||
int m2EorNeg = digitalRead(m2LimitNegPin);
|
||||
int m2EorPos = digitalRead(m2LimitPosPin);
|
||||
|
||||
if (currentMillis - previousMillis >= 1000 == true ) {
|
||||
Serial.println("------Stepper 1------");
|
||||
Serial.print("m1EorPos:");
|
||||
Serial.println(m1EorNeg);
|
||||
Serial.print("m1EorNeg: ");
|
||||
Serial.println(m1EorPos);
|
||||
Serial.print("m1CurPos: ");
|
||||
Serial.println(m1Stepper.currentPosition() * -1);
|
||||
Serial.print("m1TarPos: ");
|
||||
Serial.println(m1Stepper.targetPosition() * -1);
|
||||
Serial.println("");
|
||||
|
||||
Serial.println("------Stepper 2------");
|
||||
Serial.print("m2EorPos: ");
|
||||
Serial.println(m2EorNeg);
|
||||
Serial.print("m2EorNeg: ");
|
||||
Serial.println(m2EorPos);
|
||||
Serial.print("m2CurPos: ");
|
||||
Serial.println(m2Stepper.currentPosition() * -1);
|
||||
Serial.print("m2TarPos: ");
|
||||
Serial.println(m2Stepper.targetPosition() * -1);
|
||||
Serial.println("");
|
||||
|
||||
previousMillis = currentMillis;
|
||||
}
|
||||
|
||||
// limit switch logic for stepper 1
|
||||
if ((m1EorNeg < m1EorPos) && (m1Stepper.targetPosition() > m1Stepper.currentPosition())) {
|
||||
m1Stepper.setSpeed(0);
|
||||
m1Stepper.moveTo(m1Stepper.currentPosition());
|
||||
digitalWrite(m1PowerPin, HIGH);
|
||||
} else if ((m1EorNeg > m1EorPos) && (m1Stepper.targetPosition() < m1Stepper.currentPosition())) {
|
||||
m1Stepper.setSpeed(0);
|
||||
m1Stepper.moveTo(m1Stepper.currentPosition());
|
||||
digitalWrite(m1PowerPin, HIGH);
|
||||
} else if (m1Stepper.targetPosition() == m1Stepper.currentPosition()) {
|
||||
digitalWrite(m1PowerPin, HIGH);
|
||||
} else {
|
||||
digitalWrite(m1PowerPin, LOW);
|
||||
m1Stepper.run();
|
||||
}
|
||||
|
||||
// limit switch logic for stepper 2
|
||||
if ((m2EorNeg < m2EorPos) && (m2Stepper.targetPosition() > m2Stepper.currentPosition())) {
|
||||
m2Stepper.setSpeed(0);
|
||||
m2Stepper.moveTo(m2Stepper.currentPosition());
|
||||
digitalWrite(m2PowerPin, HIGH);
|
||||
} else if ((m2EorNeg > m2EorPos) && (m2Stepper.targetPosition() < m2Stepper.currentPosition())) {
|
||||
m2Stepper.setSpeed(0);
|
||||
m2Stepper.moveTo(m1Stepper.currentPosition());
|
||||
digitalWrite(m2PowerPin, HIGH);
|
||||
} else if (m2Stepper.targetPosition() == m2Stepper.currentPosition()) {
|
||||
digitalWrite(m2PowerPin, HIGH);
|
||||
} else {
|
||||
digitalWrite(m2PowerPin, LOW);
|
||||
m2Stepper.run();
|
||||
}
|
||||
|
||||
if (Serial.available() > 0) { // something came across serial
|
||||
integerValue = 0; // throw away previous integerValue
|
||||
negativeNumber = false; // reset for negative
|
||||
|
||||
while(1) { // force into a loop until 'n' is received
|
||||
incomingByte = Serial.read();
|
||||
if (incomingByte == ' ') break; // exit the while(1), we're done receiving
|
||||
if (incomingByte == -1) continue; // if no characters are in the buffer read() returns -1
|
||||
if (incomingByte == '-') {
|
||||
negativeNumber = true;
|
||||
continue;
|
||||
}
|
||||
integerValue *= 10; // shift left 1 decimal place
|
||||
integerValue = ((incomingByte - 48) + integerValue); // convert ASCII to integer, add, and shift left 1 decimal place
|
||||
}
|
||||
|
||||
if (negativeNumber)
|
||||
integerValue = -integerValue;
|
||||
|
||||
integerValue = -integerValue; // this makes up for the fact that things are backwards
|
||||
m1Stepper.moveTo(integerValue);
|
||||
|
||||
|
||||
integerValue = 0; // throw away previous integerValue
|
||||
negativeNumber = false; // reset for negative
|
||||
|
||||
while(1) { // force into a loop until 'n' is received
|
||||
incomingByte = Serial.read();
|
||||
if (incomingByte == '\n') break; // exit the while(1), we're done receiving
|
||||
if (incomingByte == -1) continue; // if no characters are in the buffer read() returns -1
|
||||
if (incomingByte == '-') {
|
||||
negativeNumber = true;
|
||||
continue;
|
||||
}
|
||||
integerValue *= 10; // shift left 1 decimal place
|
||||
integerValue = ((incomingByte - 48) + integerValue); // convert ASCII to integer, add, and shift left 1 decimal place
|
||||
}
|
||||
|
||||
if (negativeNumber)
|
||||
integerValue = -integerValue;
|
||||
|
||||
integerValue = -integerValue; // this makes up for the fact that things are backwards
|
||||
m2Stepper.moveTo(integerValue);
|
||||
|
||||
}
|
||||
|
||||
//delay(100);
|
||||
}
|
||||
BIN
latex/documentation/a_history_of_the_domino_problem_score.pdf
Normal file
BIN
latex/documentation/a_history_of_the_domino_problem_score.pdf
Normal file
Binary file not shown.
BIN
latex/documentation/selects/discos.png
Normal file
BIN
latex/documentation/selects/discos.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 MiB |
BIN
latex/documentation/selects/jaendel.jpg
Normal file
BIN
latex/documentation/selects/jaendel.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 MiB |
BIN
latex/documentation/selects/jaendel.xcf
Normal file
BIN
latex/documentation/selects/jaendel.xcf
Normal file
Binary file not shown.
BIN
latex/documentation/selects/maquina.png
Normal file
BIN
latex/documentation/selects/maquina.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 MiB |
BIN
latex/documentation/selects/maquinalit.jpg
Normal file
BIN
latex/documentation/selects/maquinalit.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 MiB |
BIN
latex/documentation/selects/maquinalit.png
Normal file
BIN
latex/documentation/selects/maquinalit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 MiB |
BIN
latex/documentation/selects/maquinalit.xcf
Normal file
BIN
latex/documentation/selects/maquinalit.xcf
Normal file
Binary file not shown.
BIN
latex/documentation/selects/oraclesannotated.jpg
Normal file
BIN
latex/documentation/selects/oraclesannotated.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 MiB |
BIN
latex/documentation/selects/oraclesannotated.xcf
Normal file
BIN
latex/documentation/selects/oraclesannotated.xcf
Normal file
Binary file not shown.
99
python/vernier_tracker.py
Normal file
99
python/vernier_tracker.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#This is a proof of concept for motion tracking of the vernier in very early stages
|
||||
# TODO: stabilize the tracker and connect the plumbing via OSC to the SuperCollider app
|
||||
# and get the stream to feed to the Open Stage Control GUI for calibration
|
||||
|
||||
import cv2
|
||||
import sys
|
||||
|
||||
# Read video (eventually will be the live capture from the camera)
|
||||
video = cv2.VideoCapture("/home/mwinter/Sketches/a_history_of_the_domino_problem/recs/a_history_of_the_domino_problem_final_documentation_hq.mp4")
|
||||
|
||||
# Exit if video not opened.
|
||||
if not video.isOpened():
|
||||
print("Could not open video")
|
||||
sys.exit()
|
||||
|
||||
# Read first frame.
|
||||
video.set(cv2.CAP_PROP_POS_FRAMES, 5000)
|
||||
ok, frame = video.read()
|
||||
if not ok:
|
||||
print('Cannot read video file')
|
||||
sys.exit()
|
||||
|
||||
# Define an initial bounding box
|
||||
#bbox = (287, 23, 86, 320)
|
||||
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
|
||||
#frame = cv2.GaussianBlur(frame,(5,5),cv2.BORDER_DEFAULT)
|
||||
r1 = cv2.selectROI('Tracking', frame)
|
||||
r2 = cv2.selectROI('Tracking', frame)
|
||||
#r = (606, 448, 35, 177);
|
||||
#cv2.destroyWindow('select')
|
||||
#print(r)
|
||||
crop1 = frame[int(r1[1]):int(r1[1]+r1[3]), int(r1[0]):int(r1[0]+r1[2])]
|
||||
crop2 = frame[int(r2[1]):int(r2[1]+r2[3]), int(r2[0]):int(r2[0]+r2[2])]
|
||||
|
||||
|
||||
|
||||
while True:
|
||||
# Read a new frame
|
||||
ok, frame = video.read()
|
||||
if not ok:
|
||||
break
|
||||
|
||||
crop1 = frame[int(r1[1]):int(r1[1]+r1[3]), int(r1[0]):int(r1[0]+r1[2])]
|
||||
crop1 = cv2.cvtColor(crop1, cv2.COLOR_RGB2GRAY)
|
||||
crop1 = cv2.GaussianBlur(crop1,(5,5),cv2.BORDER_DEFAULT)
|
||||
|
||||
crop2 = frame[int(r2[1]):int(r2[1]+r2[3]), int(r2[0]):int(r2[0]+r2[2])]
|
||||
crop2 = cv2.cvtColor(crop2, cv2.COLOR_RGB2GRAY)
|
||||
crop2 = cv2.GaussianBlur(crop2,(5,5),cv2.BORDER_DEFAULT)
|
||||
|
||||
ret1, thresh1 = cv2.threshold(crop1, 230, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
|
||||
cnts1 = cv2.findContours(thresh1.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
cnts1 = cnts1[1]
|
||||
|
||||
ret2, thresh2 = cv2.threshold(crop2, 230, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
|
||||
cnts2 = cv2.findContours(thresh2.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
cnts2 = cnts2[1]
|
||||
|
||||
center = None
|
||||
|
||||
for c in cnts1[0:2]:
|
||||
# calculate moments for each contour
|
||||
M = cv2.moments(c)
|
||||
# calculate x,y coordinate of center
|
||||
if M["m00"] != 0:
|
||||
cX = int(M["m10"] / M["m00"])
|
||||
cY = int(M["m01"] / M["m00"])
|
||||
#else:
|
||||
# cX, cY = 0, 0
|
||||
#print(cY)
|
||||
cv2.circle(frame, (int(r1[0]) + cX, int(r1[1]) + cY), 5, (255, 255, 255), -1)
|
||||
|
||||
# only proceed if at least one contour was found
|
||||
if len(cnts2) > 0:
|
||||
# find the largest contour in the mask, then use
|
||||
# it to compute the minimum enclosing circle and
|
||||
# centroid
|
||||
c = max(cnts2, key=cv2.contourArea)
|
||||
((x, y), radius) = cv2.minEnclosingCircle(c)
|
||||
M = cv2.moments(c)
|
||||
center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
|
||||
|
||||
# only proceed if the radius meets a minimum size
|
||||
if radius > 5:
|
||||
# draw the circle and centroid on the frame,
|
||||
# then update the list of tracked points
|
||||
cv2.circle(frame, (int(x), int(y)), int(radius), (0, 255, 255), 2)
|
||||
cv2.circle(frame, center, 5, (0, 0, 255), -1)
|
||||
|
||||
# Display result
|
||||
cv2.imshow("Tracking", frame)
|
||||
#cv2.imshow("Crop", crop)
|
||||
|
||||
# Exit if ESC pressed
|
||||
k = cv2.waitKey(1) & 0xff
|
||||
if k == 27 :
|
||||
cv2.destroyWindow('Tracking')
|
||||
break
|
||||
457
supercollider/installation_control.scd
Normal file
457
supercollider/installation_control.scd
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
// main controller for the installation
|
||||
// TODO: playback of the recordings, automation, switch from open-loop to closed-loop with the openCV tracker
|
||||
(
|
||||
var imageDist, micronsPerStep, automation, imgPositions, curPos, tarPos,
|
||||
netAddress, serialPort, serialListener,
|
||||
moveTo, jogControl, jogHorizontal, jogVertical,
|
||||
imgSelect, imgCalibrate, automate, lastSelect;
|
||||
|
||||
// init global vars
|
||||
imageDist = 300; // in microns
|
||||
micronsPerStep = 0.0977;
|
||||
automation = false;
|
||||
imgPositions = 9.collect({nil});
|
||||
curPos = Point.new(0, 0);
|
||||
tarPos = Point.new(0, 0);
|
||||
netAddress = NetAddr.new("127.0.0.1", 7777);
|
||||
~serialPort = SerialPort("/dev/ttyACM0", baudrate: 115200, crtscts: true);
|
||||
|
||||
// recieve motor feedback
|
||||
~serialListener = Routine({
|
||||
var byte, str, res, valArray,
|
||||
stepper, limitSwitchNeg, limitSwitchPos, safeMode, limitPos;
|
||||
|
||||
safeMode = false;
|
||||
|
||||
loop{
|
||||
byte = ~serialPort.read;
|
||||
if(byte==13, {
|
||||
if(str[1].asString == "[", {
|
||||
valArray = str.asString.interpret.postln;
|
||||
curPos = Point.new(valArray[0], valArray[1]);
|
||||
limitSwitchNeg = valArray[2];
|
||||
limitSwitchPos = valArray[3];
|
||||
if(safeMode && (limitSwitchNeg == limitSwitchPos), {
|
||||
safeMode = false;
|
||||
fork {
|
||||
netAddress.sendMsg("/STATE/SET", "{message: \"all clear\"}");
|
||||
2.wait;
|
||||
netAddress.sendMsg("/STATE/SET", "{message: \"\"}");
|
||||
}
|
||||
});
|
||||
if(automation, {
|
||||
var centerPos = nil, dist = 0;
|
||||
if(lastSelect != 0, {
|
||||
centerPos = imgPositions[lastSelect].deepCopy;
|
||||
dist = 300;
|
||||
}, {
|
||||
centerPos = imgPositions[4].deepCopy;
|
||||
dist = imageDist / micronsPerStep;
|
||||
});
|
||||
if((curPos.x - tarPos.x).abs < 100, {tarPos.x = centerPos.x + dist.rand2});
|
||||
if((curPos.y - tarPos.y).abs < 100, {tarPos.y = centerPos.y + dist.rand2});
|
||||
moveTo.value(tarPos);
|
||||
});
|
||||
}, {
|
||||
if(str[1..3].asString == "!!!", {
|
||||
netAddress.sendMsg("/STATE/SET", "{message: \"!!! limit switch still on after 1000 steps, this should NEVER happen\"}");
|
||||
}, {
|
||||
automation = false;
|
||||
safeMode = true;
|
||||
netAddress.sendMsg("/STATE/SET", "{message: \"!! limit hit, move the other direction\"}");
|
||||
});
|
||||
});
|
||||
str = "";
|
||||
}, {str = str++byte.asAscii});
|
||||
};
|
||||
}).play(AppClock);
|
||||
|
||||
// send new coordinates to the arduino / motors
|
||||
moveTo = {arg point;
|
||||
~serialPort.putAll(point.x.asInteger.asString ++ " " ++ point.y.asInteger.asString);
|
||||
~serialPort.put(10);
|
||||
};
|
||||
|
||||
jogControl = {arg axis;
|
||||
var jog, count = 0, jogRate= 0, jogDirection = 1;
|
||||
jog = Task({
|
||||
loop{
|
||||
count = (count + 0.01).clip(0, 1);
|
||||
jogRate = pow(count, 2) * 500;
|
||||
if(axis == '/jog_horizontal', {
|
||||
tarPos.x = curPos.x + (jogRate * jogDirection);
|
||||
}, {
|
||||
tarPos.y = curPos.y + (jogRate * jogDirection);
|
||||
});
|
||||
moveTo.value(tarPos);
|
||||
0.1.wait
|
||||
};
|
||||
});
|
||||
OSCFunc({arg msg;
|
||||
//tarPos.x = curPos.x + (1000 * msg[1]);
|
||||
//moveTo.value(tarPos);
|
||||
if(msg[1] == 0, {count = 0; jogRate = 0; jog.pause()}, {jogDirection = msg[1]; jog.play(AppClock)});
|
||||
automation = false;
|
||||
netAddress.sendMsg("/STATE/SET", "{automate: 0}");
|
||||
}, axis, netAddress)
|
||||
};
|
||||
|
||||
jogHorizontal = jogControl.value('/jog_horizontal');
|
||||
jogVertical = jogControl.value('/jog_vertical');
|
||||
|
||||
imgSelect = {
|
||||
//var lastSelect = nil;
|
||||
OSCFunc({arg msg;
|
||||
var imgIndex;
|
||||
if(msg[1] > 0, {
|
||||
imgIndex = msg[1] - 1;
|
||||
if(imgPositions[imgIndex] != nil, {tarPos = imgPositions[imgIndex].deepCopy; moveTo.value(tarPos)});
|
||||
9.do({arg i; if(imgIndex != i, {
|
||||
netAddress.sendMsg("/STATE/SET", "{img_" ++ (i + 1).asString ++ "_select: " ++ (i + 1).neg ++ "}")})});
|
||||
automation = false;
|
||||
netAddress.sendMsg("/STATE/SET", "{automate: 0}");
|
||||
lastSelect = imgIndex;
|
||||
}, {
|
||||
lastSelect = 0;
|
||||
/*
|
||||
imgIndex = msg[1].neg - 1;
|
||||
if(imgIndex == lastSelect, {
|
||||
if(imgPositions[imgIndex] != nil, {tarPos = imgPositions[imgIndex].deepCopy; moveTo.value(tarPos)});
|
||||
netAddress.sendMsg("/STATE/SET", "{img_" ++ (imgIndex + 1).asInteger.asString ++ "_select: " ++ (imgIndex + 1) ++ "}")});
|
||||
*/
|
||||
});
|
||||
}, '/img_select', netAddress)
|
||||
}.value;
|
||||
|
||||
imgCalibrate = {
|
||||
var calibrateHold, imgIndex, setPos;
|
||||
calibrateHold = Routine({
|
||||
20.do({0.1.wait});
|
||||
imgPositions[imgIndex] = setPos.deepCopy;
|
||||
netAddress.sendMsg("/STATE/SET", "{message: \"image calibrated\"}");
|
||||
});
|
||||
|
||||
OSCFunc({ arg msg;
|
||||
imgIndex = msg[1] - 1;
|
||||
if(imgIndex >= 0, {
|
||||
setPos = curPos.deepCopy;
|
||||
calibrateHold.play(AppClock);
|
||||
}, {
|
||||
calibrateHold.stop; calibrateHold.reset; netAddress.sendMsg("/STATE/SET", "{message: \"\"}");
|
||||
});
|
||||
}, '/img_calibrate', netAddress);
|
||||
}.value;
|
||||
|
||||
automate = OSCFunc({arg msg;
|
||||
if(msg[1] == 1, {
|
||||
automation = true;
|
||||
}, {
|
||||
automation = false;
|
||||
tarPos = curPos.deepCopy;
|
||||
moveTo.value(tarPos);
|
||||
});
|
||||
9.do({arg i; netAddress.sendMsg("/STATE/SET", "{img_" ++ (i + 1).asString ++ "_select: " ++ (i + 1).neg ++ "}")});
|
||||
}, '/automate', netAddress);
|
||||
)
|
||||
~serialPort.close
|
||||
~serialPort = SerialPort.new("/dev/ttyACM0", baudrate: 115200, crtscts: true);
|
||||
~serialListener.reset
|
||||
~serialListener.play(AppClock);
|
||||
|
||||
(
|
||||
// TODO:
|
||||
// set position to 0
|
||||
// limit switch warnings
|
||||
// More clean up and testing
|
||||
var imageDist, rotation, micronsPerStep, curPos, tarPos, automate, imagePositions,
|
||||
serialPort, serialListener, moveTo,
|
||||
window, xOffset, yOffset,
|
||||
userView, imageButtonRects,
|
||||
dirKeyBlockTasks, jogTasks, jogRates,
|
||||
moveButtons, curPosFields, tarPosFields,
|
||||
calibrationSteps, wizardButtons, wizMoveBlock, curWizardStep, curWizardText;
|
||||
|
||||
// init global vars
|
||||
imageDist = 25; // in microns
|
||||
rotation = 0; // in degrees
|
||||
micronsPerStep = 0.0977;
|
||||
curPos = Point.new(0, 0);
|
||||
tarPos = Point.new(0, 0);
|
||||
automate = false;
|
||||
imagePositions = 3.collect({arg r; 3.collect({arg c; Point(imageDist * (c - 1), imageDist * (r - 1))})}).reverse.flat;
|
||||
|
||||
// connect to arduino
|
||||
serialPort = SerialPort(
|
||||
"/dev/ttyACM0", //edit to match the port (SerialPort.listDevice)
|
||||
baudrate: 115200, //check that baudrate is the same as in arduino sketch
|
||||
crtscts: true);
|
||||
|
||||
// recieve motor feedback
|
||||
serialListener = Routine({
|
||||
var byte, str, res, valArray,
|
||||
stepper, limitSwitchPos, limitSwitchNeg, safeMode, limitPos;
|
||||
loop{
|
||||
byte = serialPort.read;
|
||||
if(byte==13, {
|
||||
if(str[1].asString == "[", {
|
||||
|
||||
valArray = str.asString.interpret;
|
||||
stepper = valArray[0];
|
||||
if(stepper == 1, {curPos.x = valArray[1]}, {curPos.y = valArray[1]});
|
||||
//tarPos = valArray[2];
|
||||
limitSwitchPos = valArray[3];
|
||||
limitSwitchNeg = valArray[4];
|
||||
safeMode = valArray[5];
|
||||
limitPos = valArray[6];
|
||||
|
||||
// update all the curPos fields
|
||||
if(stepper == 2, {
|
||||
//curPos = curPos.rotate(rotation.neg * (pi / 180.0)) * micronsPerStep;
|
||||
curPos = curPos * micronsPerStep;
|
||||
curPosFields[0].string = (curPos.x).round(0.1).asString;
|
||||
curPosFields[1].string = (curPos.y).round(0.1).asString;
|
||||
curPosFields[2].string = (curPos.rho).round(0.1).asString;
|
||||
curPosFields[3].string = (if(curPos.theta >= 0, {0}, {360}) + (curPos.theta * (180 / pi))).round(0.1).asString;
|
||||
userView.refresh;
|
||||
|
||||
// automate mode: select new point before the motor comes to a stop
|
||||
if(automate, {
|
||||
if((curPos.x - tarPos.x).abs < 5.0, {tarPos.x = imageDist.rand2.round(0.1)});
|
||||
if((curPos.y - tarPos.y).abs < 5.0, {tarPos.y = imageDist.rand2.round(0.1)});
|
||||
moveTo.value(tarPos);
|
||||
});
|
||||
});
|
||||
}, {
|
||||
(str).postln;
|
||||
});
|
||||
str = "";
|
||||
}, {str = str++byte.asAscii});
|
||||
};
|
||||
});
|
||||
|
||||
// send new coordinates to the arduino / motors
|
||||
moveTo = {arg point;
|
||||
var rotatedPoint, xMove, yMove;
|
||||
tarPosFields[0].string = tarPos.x.round(0.1).asString;
|
||||
tarPosFields[1].string = tarPos.y.round(0.1).asString;
|
||||
tarPosFields[2].string = tarPos.rho.round(0.1).asString;
|
||||
tarPosFields[3].string = (if(tarPos.theta >= 0, {0}, {360}) + (tarPos.theta * (180 / pi))).round(0.1).asString;
|
||||
//rotatedPoint = point.rotate(rotation * (pi / 180.0));
|
||||
rotatedPoint = point;
|
||||
xMove = (rotatedPoint.x / micronsPerStep).round(1).asInteger;
|
||||
yMove = (rotatedPoint.y / micronsPerStep).round(1).asInteger;
|
||||
serialPort.putAll(xMove.asString ++ " " ++ yMove.asString);
|
||||
serialPort.put(10);
|
||||
};
|
||||
|
||||
|
||||
// generate the gui
|
||||
window = Window.new("", Rect(400, 400, 480, 650)).front;
|
||||
|
||||
xOffset = 240;
|
||||
yOffset = 220;
|
||||
|
||||
|
||||
// drawing and window key commands
|
||||
userView = UserView(window, Rect(0, 0, 800, 600));
|
||||
imageButtonRects = (({arg r; ({arg c; Rect.aboutPoint(Point(xOffset + (120 * (r - 1)), yOffset + (120 * (c - 1))), 5, 5)}) ! 3}) ! 3).flat;
|
||||
|
||||
userView.drawFunc = ({
|
||||
imageButtonRects.do({ arg rect, i;
|
||||
Pen.addOval(rect);
|
||||
Pen.color = Color.blue;
|
||||
Pen.draw;
|
||||
});
|
||||
|
||||
Pen.addOval(Rect.aboutPoint(Point(xOffset + (curPos.x * (120 / imageDist)), yOffset + (curPos.y.neg * (120 / imageDist))), 5, 5));
|
||||
Pen.color = Color.black;
|
||||
Pen.draw;
|
||||
|
||||
Pen.line(Point(xOffset, yOffset + 150), Point(xOffset, yOffset + 250));
|
||||
Pen.stroke;
|
||||
});
|
||||
|
||||
userView.keyDownAction = ({arg view, char, mod, unicode, keycode, key;
|
||||
switch(key,
|
||||
16r1000012, {moveButtons[0].focus; dirKeyBlockTasks[0].stop; jogTasks[0].pause; jogTasks[0].play(AppClock)},
|
||||
16r1000013, {moveButtons[1].focus; dirKeyBlockTasks[1].stop; jogTasks[1].pause; jogTasks[1].play(AppClock)},
|
||||
16r1000014, {moveButtons[2].focus; dirKeyBlockTasks[2].stop; jogTasks[2].pause; jogTasks[2].play(AppClock)},
|
||||
16r1000015, {moveButtons[3].focus; dirKeyBlockTasks[3].stop; jogTasks[3].pause; jogTasks[3].play(AppClock)})
|
||||
});
|
||||
|
||||
|
||||
// create all the jog buttons and logic
|
||||
dirKeyBlockTasks = [];
|
||||
jogTasks = [];
|
||||
jogRates = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||
moveButtons = ([[-1, 0], [0, -1], [1, 0], [0, 1], [-1, 0], [0, -1], [1, 0], [0, 1]].collect({arg m, i;
|
||||
var icons = ["◄", "▲", "►", "▼", "↻", "+", "↺", "-"], button;
|
||||
|
||||
// speeds up the jog based on how long the button was pressed
|
||||
jogTasks = jogTasks.add(
|
||||
Task({
|
||||
dirKeyBlockTasks[i].stop;
|
||||
loop{
|
||||
jogRates[i] = (jogRates[i] + 0.1).clip(0, 10);
|
||||
if(i < 4, {
|
||||
// cartesian horizontal movement
|
||||
if(m[0].abs == 1, {tarPos.x = tarPos.x + (jogRates[i] * m[0])});
|
||||
// cartesian vertical movement
|
||||
if(m[1].abs == 1, {tarPos.y = tarPos.y + (jogRates[i] * m[1].neg);});
|
||||
}, {// polar change theta (rotate)
|
||||
if(m[0].abs == 1, {tarPos.theta = ((tarPos.theta * (180 / pi)) + (jogRates[i] * m[0])) * (pi / 180.0)});
|
||||
// polar change magnitude
|
||||
if(m[1].abs == 1, {tarPos.rho = tarPos.rho + (jogRates[i] * m[1].neg)});
|
||||
});
|
||||
moveTo.value(tarPos);
|
||||
0.2.wait
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// hack to acount for a key held down
|
||||
dirKeyBlockTasks = dirKeyBlockTasks.add(Task({0.1.wait; jogRates[i] = 0;jogTasks[i].stop}));
|
||||
|
||||
// create buttons
|
||||
button = Button(window, Rect(xOffset - 12.5 + (25 * m[0]) + if(i < 4, {-175}, {175}), yOffset + 187.5 + (25 * m[1]), 25, 25))
|
||||
.states_([[icons[i]]])
|
||||
.mouseDownAction_({jogRates[i] = 0; jogTasks[i].play(AppClock)})
|
||||
.action_({jogTasks[i].stop(AppClock)})
|
||||
.enabled_(false)
|
||||
.keyDownAction_({arg butt, char, mod, unicode, keycode, key;
|
||||
switch(key,
|
||||
16r1000012, {moveButtons[0].focus; dirKeyBlockTasks[0].stop; jogTasks[0].pause; jogTasks[0].play(AppClock); true},
|
||||
16r1000013, {moveButtons[1].focus; dirKeyBlockTasks[1].stop; jogTasks[1].pause; jogTasks[1].play(AppClock); true},
|
||||
16r1000014, {moveButtons[2].focus; dirKeyBlockTasks[2].stop; jogTasks[2].pause; jogTasks[2].play(AppClock); true},
|
||||
16r1000015, {moveButtons[3].focus; dirKeyBlockTasks[3].stop; jogTasks[3].pause; jogTasks[3].play(AppClock); true},
|
||||
{false})})
|
||||
.keyUpAction_({arg butt, char, mod, unicode, keycode, key;
|
||||
switch(key,
|
||||
16r1000012, {dirKeyBlockTasks[0].start(AppClock); true},
|
||||
16r1000013, {dirKeyBlockTasks[1].start(AppClock); true},
|
||||
16r1000014, {dirKeyBlockTasks[2].start(AppClock); true},
|
||||
16r1000015, {dirKeyBlockTasks[3].start(AppClock); true},
|
||||
{false})})
|
||||
}));
|
||||
|
||||
|
||||
// position text fields
|
||||
StaticText(window, Rect(xOffset - 82, yOffset + 150, 300, 20)).string_("cartesian");
|
||||
StaticText(window, Rect(xOffset + 39, yOffset + 150, 300, 20)).string_("polar");
|
||||
curPosFields = [];
|
||||
tarPosFields = ["x", "y", "ρ", "θ"].collect({arg v, i;
|
||||
StaticText(window, Rect(xOffset + 22.5 + (55 * (i - 2)), yOffset + 170, 50, 20)).string_(v);
|
||||
curPosFields = curPosFields.add(StaticText(window, Rect(xOffset + 5 + (55 * (i - 2)), yOffset + 220, 50, 20)).string_("0.0"));
|
||||
TextField(window, Rect(xOffset + 2.5 + (55 * (i - 2)), yOffset + 190, 50, 20))
|
||||
.string_("0.0")
|
||||
.enabled_(false)
|
||||
.action_({arg field;
|
||||
if(i < 2, {
|
||||
tarPos.x = tarPosFields[0].string.asFloat;
|
||||
tarPos.y = tarPosFields[1].string.asFloat;
|
||||
tarPosFields[2].string = tarPos.rho.round(0.1).asString;
|
||||
tarPosFields[3].string = (if(tarPos.theta >= 0, {0}, {360}) + (tarPos.theta * (180 / pi))).round(0.1).asString;
|
||||
}, {
|
||||
tarPos.rho = tarPosFields[2].string.asFloat;
|
||||
tarPos.theta = tarPosFields[3].string.asFloat * (pi / 180);
|
||||
tarPosFields[0].string = tarPos.x.round(0.1).asString;
|
||||
tarPosFields[1].string = tarPos.y.round(0.1).asString;
|
||||
});
|
||||
moveTo.value(tarPos)})
|
||||
});
|
||||
|
||||
|
||||
// calibration wizard
|
||||
calibrationSteps = [
|
||||
"1) find center image",
|
||||
"2) find northwest image \ntry first by using only the ↻ ↺ buttons to change θ",
|
||||
"3) compute all other points \nthis will erase previously saved points unless skipped",
|
||||
"4) find north image",
|
||||
"5) find northeast image",
|
||||
"6) find east image",
|
||||
"7) find southeast image",
|
||||
"8) find south image",
|
||||
"9) find southwest image",
|
||||
"10) find west image"
|
||||
];
|
||||
|
||||
// disables everything till the point is reached between each step in the wizard
|
||||
wizMoveBlock = Task({
|
||||
while({curPos.dist(tarPos) > 1}, {
|
||||
moveButtons.do({arg button; button.enabled = false});
|
||||
wizardButtons.do({arg button; button.enabled = false});
|
||||
tarPosFields.do({arg field; field.enabled = false});
|
||||
0.1.wait;
|
||||
});
|
||||
wizardButtons.do({arg button; button.enabled = true});
|
||||
wizardButtons[2].focus;
|
||||
moveButtons.do({arg button; button.enabled = true});
|
||||
tarPosFields.do({arg field; field.enabled = true});
|
||||
});
|
||||
|
||||
// automate / calibrate button
|
||||
Button.new(window, Rect.aboutPoint(Point(xOffset, yOffset + 270), 75, 12.5))
|
||||
.states_([["calibrate"], ["automate"]])
|
||||
.action_({arg button;
|
||||
if(button.value == 0, {
|
||||
automate = true;
|
||||
curWizardText.string = "";
|
||||
wizardButtons.do({arg button; button.visible = false});
|
||||
}, {
|
||||
automate = false;
|
||||
curWizardText.string = calibrationSteps[0];
|
||||
tarPos = imagePositions[4].deepCopy;
|
||||
moveTo.value(tarPos);
|
||||
wizMoveBlock.start(AppClock);
|
||||
curWizardStep = 0;
|
||||
wizardButtons.do({arg button; button.visible = true});
|
||||
});
|
||||
moveButtons.do({arg button; button.enabled = automate.not});
|
||||
tarPosFields.do({arg field; field.enabled = automate.not});
|
||||
});
|
||||
|
||||
// wizard button logic
|
||||
curWizardStep = 0;
|
||||
curWizardText = StaticText(window, Rect.aboutPoint(Point(xOffset, yOffset + 310), 200, 20)).string_("").align_(\center);
|
||||
wizardButtons = ["back", "skip", "next"].collect({arg t, i;
|
||||
var pointSeq, button;
|
||||
pointSeq = [4, 0, 0, 1, 2, 5, 8, 7, 6, 3, 4];
|
||||
button = Button(window, Rect.aboutPoint(Point(xOffset - 60 + (60 * i), yOffset + 350), 25, 12.5))
|
||||
.states_([[t]])
|
||||
.action_({arg button;
|
||||
|
||||
// code to automate populate all the points based on relation between two of the points
|
||||
if((curWizardStep == 2) && (i == 2), {
|
||||
if(imagePositions[0].rho == imageDist, {
|
||||
|
||||
}, {
|
||||
|
||||
});
|
||||
rotation = imagePositions[0].theta - (0.75 * pi);
|
||||
imagePositions[1].theta = (0.5 * pi) + rotation;
|
||||
imagePositions[2].theta = (0.25 * pi) + rotation;
|
||||
imagePositions[3].theta = pi + rotation;
|
||||
imagePositions[5].theta = rotation;
|
||||
imagePositions[6].theta = (1.25 * pi) + rotation;
|
||||
imagePositions[7].theta = (1.5 * pi) + rotation;
|
||||
imagePositions[8].theta = (1.75 * pi) + rotation;
|
||||
});
|
||||
|
||||
if((curWizardStep == 0) && (i == 2), {serialPort.putAll("c")});
|
||||
if(i == 2, {imagePositions[pointSeq[curWizardStep]] = if(curWizardStep == 0, {Point(0, 0)}, {curPos.deepCopy})});
|
||||
curWizardStep = (curWizardStep + if(i == 0, {-1}, {1})) % 10;
|
||||
tarPos = imagePositions[pointSeq[curWizardStep]].deepCopy;
|
||||
moveTo.value(tarPos);
|
||||
wizMoveBlock.start(AppClock);
|
||||
//wizardButtons.do({arg button; button.enabled = true});
|
||||
//moveButtons.do({arg button; button.enabled = true});
|
||||
//tarPosFields.do({arg field; field.enabled = true});
|
||||
curWizardText.string = calibrationSteps[curWizardStep];
|
||||
//wizardButtons[1].visible = if(curWizardStep == 2, {true}, {false});
|
||||
})
|
||||
.visible_(false)
|
||||
});
|
||||
|
||||
serialListener.play(AppClock);
|
||||
)
|
||||
|
||||
Loading…
Reference in a new issue