diff --git a/software/config.js b/software/config.js
new file mode 100644
index 0000000..fc2a48c
--- /dev/null
+++ b/software/config.js
@@ -0,0 +1,55 @@
+var config = {}
+
+//Side of end effector
+//~~Do not touch~~
+config.e = 34.64101615137754; // Math.sqrt(3) * 10 * 2
+
+//Side of top triangle
+//~~Do not touch~~
+config.f = 110.85125168440814; // Math.sqrt(3) * 32 * 2
+
+//Length of parallelogram joint
+//~~Do not touch~~
+config.re = 153.5; // 145 + 8.5
+
+//Length of upper joint
+//~~Do not touch~~
+config.rf = 52.690131903421914; // Math.sqrt(52**2 + 8.5**2)
+
+//Input ranges for servos
+//~~Do not touch~~
+config.servo1 = {in_min: 0, in_max: 90};
+config.servo2 = {in_min: 0, in_max: 90};
+config.servo3 = {in_min: 0, in_max: 90};
+
+//Default output ranges for servos
+//CHANGE THESE
+config.servo1.out_min = 12;
+config.servo1.out_max = 93;
+config.servo2.out_min = 8;
+config.servo2.out_max = 90;
+config.servo3.out_min = 14;
+config.servo3.out_max = 96;
+
+//Dimensions of the base plate
+config.baseHeight = 95;
+config.baseWidth = 80;
+
+//Default Z-Level of the pen
+config.penHeight = -140;
+
+//Default drawing height of the pen
+config.drawHeight = -152.75;
+
+//Delay for commands in SVGReader
+//Note that some commands will take longer than this
+//Default value is 150
+config.delay = 200;
+
+//The default easing type to be used
+//When no easing is specified, this is the type that will be used
+//"none" means that if no easing is specified, do not ease
+//For a list of possible easing types, look in motion.js
+config.defaultEaseType = "linear";
+
+module.exports = config;
\ No newline at end of file
diff --git a/software/package.json b/software/package.json
index 0463414..d5b7665 100755
--- a/software/package.json
+++ b/software/package.json
@@ -16,7 +16,9 @@
"engines":{
"node":"0.x.x"
},
- "dependencies":{
- "johnny-five":"0.7.x"
+ "dependencies": {
+ "johnny-five": "0.8.x",
+ "svg-path-parser": "^1.0.1",
+ "xml2js": "^0.4.9"
}
}
diff --git a/software/recorder/index.html b/software/recorder/index.html
new file mode 100644
index 0000000..4602082
--- /dev/null
+++ b/software/recorder/index.html
@@ -0,0 +1,167 @@
+
+
+
+
+
+Tracing a line with d3.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/software/recorder/simplify.js b/software/recorder/simplify.js
new file mode 100644
index 0000000..fa86746
--- /dev/null
+++ b/software/recorder/simplify.js
@@ -0,0 +1,133 @@
+/*
+ (c) 2013, Vladimir Agafonkin
+ Simplify.js, a high-performance JS polyline simplification library
+ mourner.github.io/simplify-js
+*/
+
+(function () { 'use strict';
+
+// to suit your point format, run search/replace for '.x' and '.y';
+// for 3D version, see 3d branch (configurability would draw significant performance overhead)
+
+// square distance between 2 points
+function getSqDist(p1, p2) {
+
+ var dx = p1.x - p2.x,
+ dy = p1.y - p2.y;
+
+ return dx * dx + dy * dy;
+}
+
+// square distance from a point to a segment
+function getSqSegDist(p, p1, p2) {
+
+ var x = p1.x,
+ y = p1.y,
+ dx = p2.x - x,
+ dy = p2.y - y;
+
+ if (dx !== 0 || dy !== 0) {
+
+ var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);
+
+ if (t > 1) {
+ x = p2.x;
+ y = p2.y;
+
+ } else if (t > 0) {
+ x += dx * t;
+ y += dy * t;
+ }
+ }
+
+ dx = p.x - x;
+ dy = p.y - y;
+
+ return dx * dx + dy * dy;
+}
+// rest of the code doesn't care about point format
+
+// basic distance-based simplification
+function simplifyRadialDist(points, sqTolerance) {
+
+ var prevPoint = points[0],
+ newPoints = [prevPoint],
+ point;
+
+ for (var i = 1, len = points.length; i < len; i++) {
+ point = points[i];
+
+ if (getSqDist(point, prevPoint) > sqTolerance) {
+ newPoints.push(point);
+ prevPoint = point;
+ }
+ }
+
+ if (prevPoint !== point) newPoints.push(point);
+
+ return newPoints;
+}
+
+// simplification using optimized Douglas-Peucker algorithm with recursion elimination
+function simplifyDouglasPeucker(points, sqTolerance) {
+
+ var len = points.length,
+ MarkerArray = typeof Uint8Array !== 'undefined' ? Uint8Array : Array,
+ markers = new MarkerArray(len),
+ first = 0,
+ last = len - 1,
+ stack = [],
+ newPoints = [],
+ i, maxSqDist, sqDist, index;
+
+ markers[first] = markers[last] = 1;
+
+ while (last) {
+
+ maxSqDist = 0;
+
+ for (i = first + 1; i < last; i++) {
+ sqDist = getSqSegDist(points[i], points[first], points[last]);
+
+ if (sqDist > maxSqDist) {
+ index = i;
+ maxSqDist = sqDist;
+ }
+ }
+
+ if (maxSqDist > sqTolerance) {
+ markers[index] = 1;
+ stack.push(first, index, index, last);
+ }
+
+ last = stack.pop();
+ first = stack.pop();
+ }
+
+ for (i = 0; i < len; i++) {
+ if (markers[i]) newPoints.push(points[i]);
+ }
+
+ return newPoints;
+}
+
+// both algorithms combined for awesome performance
+function simplify(points, tolerance, highestQuality) {
+
+ if (points.length <= 1) return points;
+
+ var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
+
+ points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
+ points = simplifyDouglasPeucker(points, sqTolerance);
+
+ return points;
+}
+
+// export as AMD module / Node module / browser or worker variable
+if (typeof define === 'function' && define.amd) define(function() { return simplify; });
+else if (typeof module !== 'undefined') module.exports = simplify;
+else if (typeof self !== 'undefined') self.simplify = simplify;
+else window.simplify = simplify;
+
+})();
diff --git a/software/src/SVGReader.js b/software/src/SVGReader.js
new file mode 100644
index 0000000..52e2fdc
--- /dev/null
+++ b/software/src/SVGReader.js
@@ -0,0 +1,901 @@
+//Draws stuff from SVG files
+//Built with InkScape in mind, but should support:
+//http://svg-edit.googlecode.com/svn/branches/stable/editor/svg-editor.html
+//> To test:
+//> svgRead.drawSVG(filepath)
+
+var parse = require('svg-path-parser');
+var fs = require('fs');
+var draw = require("./draw");
+var parseString = require('xml2js').parseString;
+
+function SVGReader(args) {
+ this.baseWidth = 80;
+ this.baseHeight = 95;
+ this.drawHeight = -140;
+ this.delay = 150;
+ this.defaultEaseType = "linear";
+
+ if (args) {
+ var keys = Object.keys(args)
+ keys.forEach(function(key){
+ this[key] = args[key]
+ }, this)
+ }
+ objRef = this;
+ defaultEaseType = this.defaultEaseType;
+}
+
+//Draws from an SVG image specified by filepath
+//> Usage:
+//> drawSVG("C:/Projects/Tapsterbot/software/src/drawing.svg");
+//Note: filePath can be relative
+//connect is a special flag that indicates that each path should be drawn connected to each other
+//It is really only used for drawing in cursive and does not need to be specified otherwise
+SVGReader.prototype.drawSVG = function(filePath, connect) {
+ resetTimer();
+ var parsed;
+
+ if (connect)
+ connected = connect;
+ else
+ connected = null;
+
+ objRef = this;
+
+ //Create a JSON string out of the SVG image data
+ //parseString strips away the XML data
+ try {
+ parseString(fs.readFileSync(filePath, "utf8"), function(err, result) {
+ parsed = JSON.stringify(result, null, 1);
+ });
+ } catch (e) {
+ if (e.code === "ENOENT")
+ console.log("File not found.");
+ else
+ throw e;
+
+ return; //If the file is not found stop execution
+ }
+
+ //Parse the JSON string into an array
+ objArr = JSON.parse(parsed);
+
+ //Extract width and height data from the drawing
+ var svgDimensions = dimensionConversion(objArr.svg.$.width, objArr.svg.$.height);
+ width = svgDimensions.width;
+ height = svgDimensions.height;
+
+ //Check for translation and account for it
+ //Commented out because going to stop supporting transformations
+ /*
+ transformX = 0;
+ transformY = 0;
+
+ if (objArr.svg.g[0].$ && objArr.svg.g[0].$.transform) { //Done in multiple checks to avoid errors being thrown
+ var transString = objArr.svg.g[0].$.transform;
+ var subX = transString.indexOf("(");
+ transformX = parseInt(transString.substring(subX + 1));
+ var subY = transString.indexOf(",");
+ transformY = parseInt(transString.substring(subY + 1));
+ }
+ */
+
+ var phoneWidth = this.baseWidth;
+ var phoneHeight = this.baseHeight;
+ penHeight = this.drawHeight;
+
+ widthRatio = width / phoneWidth;
+ heightRatio = height / phoneHeight;
+
+ halfway = {x:width / 2, y:height / 2};
+ currentPoint = {x:halfway.x, y:halfway.y}; //Start at the center of the canvas, which corresponds to (0,0) on the Tapster
+
+ if (objArr.svg.g[0].g && objArr.svg.g[0].g[0].path) { //If there are multiple groups. Additional check to make sure that there is actually path data
+ for (var i = 0; i < objArr.svg.g[0].g.length; i++) {
+ pathArray = objArr.svg.g[0].g[i].path;
+ drawImage(pathArray);
+ }
+ }
+
+ else if (objArr.svg.g.length > 0) { //Depending on how the paths are grouped, it is possible that this value can be greater than zero
+ for (var i = 0; i < objArr.svg.g.length; i++) { //If this is the case, loop through the array to get the path data
+ pathArray = objArr.svg.g[i].path;
+ drawImage(pathArray);
+ }
+ }
+
+ else if (objArr.svg.g[0].path) { //If there is only one group
+ pathArray = objArr.svg.g[0].path;
+ drawImage(pathArray);
+ }
+
+ doSetTimeout(0, 48, -130, delay);
+ //setTimeout(function() { resetTimer() }, timer + 5);
+}
+
+drawImage = function(pathArray) {
+ var d = "";
+ firstMove = null; //After a group is done being drawn, firstMove is reset as there has not yet been a first move made in the next group
+ for (var i = 0; i < pathArray.length; i++) { //When drawing multiple lines, there are multiple paths
+ firstPoint = null;
+ d = pathArray[i].$.d;
+ var commands = parse(d);
+ objRef.interpretCommands(commands);
+ /* if (i < (pathArray.length - 1)) { //Smooth transition to the next path
+ doSetTimeout(mapX(currentPoint.x), mapY(currentPoint.y), penHeight + 10, 300); //Moves the pen up and over so no line is drawn between the two
+ doSetTimeout(mapX(parse(pathArray[i+1].$.d)[0].x), mapY(parse(pathArray[i+1].$.d)[0].y), penHeight + 10, 300);
+ doSetTimeout(mapX(parse(pathArray[i+1].$.d)[0].x), mapY(parse(pathArray[i+1].$.d)[0].y), penHeight, 300);
+ } */
+ }
+ doSetTimeout(mapX(currentPoint.x), mapY(currentPoint.y), penHeight + 10, delay);
+}
+
+//Move from one point to (x, y)
+move = function(x, y) {
+ var ptArray = [];
+ if (!connected) { //If the paths should not be connected, lift up the pen and move over so that a line is not drawn
+ doSetTimeout(mapX(currentPoint.x), mapY(currentPoint.y), penHeight + 10, delay, "none");
+ doSetTimeout(mapX(x), mapY(y), penHeight + 10, delay, "none");
+ doSetTimeout(mapX(x), mapY(y), penHeight, delay, "none");
+ //svg.drawSVG("C:/Projects/Tapsterbot/software/src/hello/helloChF.svg")
+ //ptArray.push({x:mapX(currentPoint.x), y:mapY(currentPoint.y), z:penHeight + 10});
+ //ptArray.push({x:mapX(x), y:mapY(y), z:penHeight + 10});
+ //ptArray.push({x:mapX(x), y:mapY(y), z:penHeight});
+ }
+ else if (connected && !firstMove) { //If the paths should be connected and a move has not been made, lift up the pen and move to the first point
+ doSetTimeout(mapX(currentPoint.x), mapY(currentPoint.y), penHeight + 10, delay, "none");
+ doSetTimeout(mapX(x), mapY(y), penHeight + 10, delay, "none");
+ doSetTimeout(mapX(x), mapY(y), penHeight, delay, "none");
+ //ptArray.push({x:mapX(currentPoint.x), y:mapY(currentPoint.y), z:penHeight + 10});
+ //ptArray.push({x:mapX(x), y:mapY(y), z:penHeight + 10});
+ //ptArray.push({x:mapX(x), y:mapY(y), z:penHeight});
+ }
+ else //If the paths should be connected and a move has been made, just draw a line between the two paths
+ doSetTimeout(mapX(x), mapY(y), penHeight, delay);
+ //ptArray.push({x:mapX(x), y:mapY(y), z:penHeight});
+
+ currentPoint = {x:x, y:y}; //Update the current point (done every time an SVG command is called)
+
+ if (!firstMove) //Keeps track of if a move has been made or not.
+ firstMove = true;
+
+ if (!firstPoint) //Keeps track of the first point, for use with the Z/z command
+ firstPoint = {x:currentPoint.x, y:currentPoint.y}; //Since the first command of a path is always to Move, this check only occurs here
+
+ //return ptArray;
+}
+
+//Move from one point to that that point + x, y
+relMove = function(x, y) {
+ x = currentPoint.x + x;
+ y = currentPoint.y + y;
+
+ //return move(x, y);
+ move(x, y);
+}
+
+//Draw a line from one point to (x, y)
+line = function(x, y) {
+ var ptArray = [];
+ doSetTimeout(mapX(x), mapY(y), penHeight, delay, "linear");
+ //ptArray.push({x:mapX(x), y:mapY(y), z:penHeight});
+ currentPoint = {x:x, y:y};
+
+}
+
+//Draw a line from one point to that point + x, y
+relLine = function(x, y) {
+ x = currentPoint.x + x;
+ y = currentPoint.y + y;
+
+ //return line(x, y);
+ line(x, y);
+}
+
+//Draws a cubic Bezier curve.
+//(x1,y1) is the first control point
+//(x2, y2) is the second
+//(x, y) is the end point
+cubicCurve = function(x1, y1, x2, y2, x, y) {
+
+ //Function for calculating the coordinates of points on the curve
+ //Calculates t+1 points
+ b = function(x1, y1, x2, y2, x, y, t) {
+ var ptArray = new Array();
+ for (var i = 0; i <= t; i++) {
+ var newI = i/t; //Converts i to a decimal, to satisfy 0 <= i <= 1
+ var ptX = (Math.pow((1-newI), 3) * currentPoint.x) + (3 * Math.pow((1-newI), 2) * newI * x1) //From https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves
+ + (3 * (1-newI) * Math.pow(newI, 2) * x2) + (Math.pow(newI, 3) * x);
+ var ptY = (Math.pow((1-newI), 3) * currentPoint.y) + (3 * Math.pow((1-newI), 2) * newI * y1)
+ + (3 * (1-newI) * Math.pow(newI, 2) * y2) + (Math.pow(newI, 3) * y);
+ var newPt = {x:ptX, y:ptY, z:penHeight};
+ ptArray.push(newPt); //Populates the array with points
+ }
+ currentPoint = {x:ptArray[t].x, y:ptArray[t].y};
+ return ptArray;
+ }
+
+ var curvePts = new Array();
+ curvePts = b(x1, y1, x2, y2, x, y, 5); //Arbitrarily-chosen value. It creates a smooth-looking curve without calculating too many points
+ for (var i = 0;i < curvePts.length; i++)
+ doSetTimeout(mapX(curvePts[i].x), mapY(curvePts[i].y), penHeight, delay*2 / curvePts.length, "none"); //The cubic curve command takes 2*delay ms to complete so that an accurate curve is created
+ //return b(x1, y1, x2, y2, x, y, 8);
+}
+
+//Draws a relative cubic Bezier curve
+relCubicCurve = function(x1, y1, x2, y2, x, y)
+{
+ var tempX = currentPoint.x;
+ var tempY = currentPoint.y;
+ //return cubicCurve(tempX + x1, tempY + y1, tempX + x2, tempY + y2, tempX + x, tempY + y);
+ cubicCurve(tempX + x1, tempY + y1, tempX + x2, tempY + y2, tempX + x, tempY + y);
+}
+
+//Draws a quadratic Bezier curve
+//(x1, y1) is the control point
+//(x, y) is the end point
+quadraticCurve = function(x1, y1, x, y) {
+
+ //Helper function for generating the points
+ q = function(x1, y1, x, y, t) {
+ var ptArray = new Array();
+ for (var i = 0; i <= t; i++) {
+ var newI = i/t; //Converts i to a decimal, to satisfy 0 <= i <= 1
+ var ptX = Math.pow((1-newI), 2)*currentPoint.x + (2 * (1-newI) * newI * x1) + (Math.pow(newI, 2) * x); //From https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Quadratic_B.C3.A9zier_curves
+ var ptY = Math.pow((1-newI), 2)*currentPoint.y + (2 * (1-newI) * newI * y1) + (Math.pow(newI, 2) * y);
+ var newPt = {x:ptX, y:ptY, z:penHeight};
+ ptArray.push(newPt);
+ }
+ currentPoint = {x:ptArray[t].x, y:ptArray[t].y};
+ return ptArray;
+ }
+
+ var curvePts = new Array();
+ curvePts = q(x1, y1, x, y, 5);
+ for (var i = 0; i < curvePts.length; i++)
+ doSetTimeout(mapX(curvePts[i].x), mapY(curvePts[i].y), penHeight, delay*2 / curvePts.length, "none");
+ //return q(x1, y1, x, y, 8);
+}
+
+//Draws a relative quadratic Bezier curve
+relQuadraticCurve = function(x1, y1, x, y) {
+ var tempX = currentPoint.x;
+ var tempY = currentPoint.y;
+ //return quadraticCurve(tempX + x1, tempY + y1, tempX + x, tempY + y);
+ quadraticCurve(tempX + x1, tempY + y1, tempX + x, tempY + y);
+}
+
+//Draws an elliptical arc
+//rx and ry are the radii
+//rotation is the angle (in degrees) between the rotated x-axis and the original x-axis
+//largeArc is a flag that determines if the arc is greater than or less than or equal to 180 degrees
+//sweep is a flag that determines if the arc is drawn in a positive direction or a negative direction
+//(x, y) is the final point of the arc
+//From: http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
+arc = function(rx, ry, rotation, largeArc, sweep, x, y) {
+
+ //Helper function for calculating the points
+ a = function(rx, ry, largeArc, sweep, x, y, t) {
+ var ptArray = new Array();
+
+ for (var i = 0; i <= t; i++) {
+ var newI = i / t;
+ var angle = startAngle + sweepAngle * newI;
+ var newPt = {x: cx + rx * cos(angle), y: cy + ry * sin(angle), z:penHeight};
+ ptArray.push(newPt);
+ }
+
+ currentPoint = {x: ptArray[t].x, y: ptArray[t].y};
+ return ptArray;
+ }
+
+ //Helper function for calculating the angle between two vectors
+ angleBetween = function(v1, v2) {
+ var p = v1.x*v2.x + v1.y*v2.y;
+ var n = Math.sqrt((Math.pow(v1.x, 2)+Math.pow(v1.y, 2)) * (Math.pow(v2.x, 2)+Math.pow(v2.y, 2)));
+ var sign = v1.x*v2.y - v1.y*v2.x < 0 ? -1 : 1;
+ var angle = sign*Math.acos(p/n) * 180 / Math.PI;
+
+ return angle;
+ }
+
+ tempX = currentPoint.x;
+ tempY = currentPoint.y;
+
+ var xPrime = (cos(rotation) * ((tempX - x) / 2)) + (sin(rotation) * ((tempY - y) / 2));
+ var yPrime = (-sin(rotation) * ((tempX - x) / 2)) + (cos(rotation) * ((tempY - y) / 2));
+
+ //Checks to ensure radii are as they should be
+ rx = Math.abs(rx); //Ensures they are non-zero and positive
+ ry = Math.abs(ry);
+
+ var lambda = (Math.pow(xPrime, 2) / Math.pow(rx, 2)) + (Math.pow(yPrime, 2) / Math.pow(ry, 2));
+
+ if (lambda > 1) { //Ensures they are large enough
+ rx = Math.sqrt(lambda) * rx;
+ ry = Math.sqrt(lambda) * ry;
+ }
+
+ var sign = 1;
+
+ if (largeArc == sweep) //If they are equal, cPrime is negative
+ sign = -1;
+
+ //For some reason this would occasionally result in NaN
+ //Implemented this check at the suggestion of:
+ //http://users.ecs.soton.ac.uk/rfp07r/interactive-svg-examples/arc.html
+ var cPrimeNumerator = ((Math.pow(rx, 2) * Math.pow(ry, 2)) - (Math.pow(rx, 2) * Math.pow(yPrime, 2)) - (Math.pow(ry, 2) * Math.pow(xPrime, 2)));
+ var cPrimeDenom = ((Math.pow(rx, 2) * Math.pow(yPrime, 2)) + (Math.pow(ry, 2) * Math.pow(xPrime, 2)));
+
+ if ((cPrimeNumerator / cPrimeDenom) < 1e-7)
+ cPrime = 0;
+ else
+ cPrime = Math.sqrt(cPrimeNumerator / cPrimeDenom);
+
+ //Calculates the transformed center
+ var cxPrime = sign * cPrime * ((rx * yPrime) / ry);
+ var cyPrime = sign * cPrime * (-(ry * xPrime) / rx);
+
+ //Calculates the original center
+ var cx = ((cos(rotation) * cxPrime) + (-sin(rotation) * cyPrime)) + ((tempX + x) / 2);
+ var cy = ((sin(rotation) * cxPrime) + (cos(rotation) * cyPrime)) + ((tempY + y) / 2);
+
+ //Calculates the start angle of the arc and the total change in the angle
+ var startVector = {x: (xPrime - cxPrime) / rx, y: (yPrime - cyPrime) / ry};
+ var startAngle = angleBetween({x:1, y:0}, startVector);
+ var endVector = {x: (-xPrime - cxPrime) / rx, y: (-yPrime - cyPrime) / ry};
+ var sweepAngle = angleBetween(startVector, endVector);
+
+ if (!sweep && sweepAngle > 0) {
+ sweepAngle -= 360;
+ }
+
+ else if (sweep && sweepAngle < 0) {
+ sweepAngle += 360;
+ }
+
+ sweepAngle %= 360;
+
+ var ptArray = a(rx, ry, largeArc, sweep, x, y, 5);
+
+ for (var i = 0; i < ptArray.length; i++) {
+ doSetTimeout(mapX(ptArray[i].x), mapY(ptArray[i].y), penHeight, delay*2 / ptArray.length, "none");
+ }
+
+ //return a(rx, ry, largeArc, sweep, x, y, 10);
+}
+
+//Function for drawing a relative elliptical arc
+relArc = function(rx, ry, rotation, largeArc, sweep, x, y) {
+ x = currentPoint.x + x;
+ y = currentPoint.y + y;
+
+ //return arc(rx, ry, rotation, largeArc, sweep, x, y);
+ arc(rx, ry, rotation, largeArc, sweep, x, y);
+}
+
+//A function for setting the penHeight from the command line
+setPenHeight = function(penHeight) {
+ penHeight = penHeight;
+}
+
+//Reflects the point (x,y) across the point (x1, y1)
+//For use with smooth curves
+reflect = function(x, y, x1, y1) {
+ var tempX = x;
+ var tempY = y;
+
+ tempX = x1 - (tempX - x1);
+ tempY = y1 - (tempY - y1);
+
+ var point = {x:tempX, y:tempY};
+
+ return point;
+}
+
+//Convert points pixel coordinates to Tapster coordinates
+//Done in two methods for ease of use
+mapX = function(x) {
+ var newX = x;
+ newX = (newX - halfway.x) / widthRatio; //The center of the canvas corresponds to (0, 0) on the Tapster
+ return newX;
+};
+
+mapY = function(y) {
+ var newY = y;
+ newY = (halfway.y - newY) / heightRatio;
+ return newY;
+}
+
+// A sine function for working with degrees, not radians
+sin = function(degree) {
+ return Math.sin(Math.PI * (degree/180));
+}
+
+// A cosine function for working with degrees, not radians
+cos = function(degree) {
+ return Math.cos(Math.PI * (degree/180));
+}
+
+//Function for converting Inkscape dimensions into Tapster-friendly pixels
+//Should switch to switch statements
+dimensionConversion = function(width, height) {
+ width = String(width);
+ if (width.search("mm") != -1) {
+ var dimension = {width: parseInt(width) * 3.779527559, height: parseInt(height) * 3.779527559}; //The px:mm ratio is ~ 3.8:1
+ }
+ else if (width.search("in") != -1) {
+ var dimension = {width: parseInt(width) * 96, height: parseInt(height) * 96}; //The px:in ratio is 96:1
+ }
+ else if (width.search("ft") != -1) {
+ var dimension = {width: parseInt(width) * 96 * 12, height: parseInt(height) * 96 * 12}; //The px:ft ratio is 96*12:1
+ }
+ else if (width.search("m") != -1) {
+ var dimension = {width: parseInt(width) * 3.779527559 * 1000, height: parseInt(height) * 3.779527559 * 1000}; //The px:m ratio is ~3.8*1000:1
+ }
+ else if (width.search("cm") != -1) {
+ var dimension = {width: parseInt(width) * 3.779527559 * 100, height: parseInt(height) * 3.779527559 * 100}; //The px:cm ratio is ~3.8*100:1
+ }
+ else if (width.search("pt") != -1) {
+ var dimension = {width: parseInt(width) * 1.3333, height: parseInt(height) * 1.3333}; //The px:pt ratio is ~1.3:1
+ }
+ else if (width.search("pc") != -1) {
+ var dimension = {width: parseInt(width) * 16, height: parseInt(height) * 16}; //The px:pc ratio is 16:1
+ }
+ else //No unit specified == pixels
+ var dimension = {width: width, height: height};
+
+ return dimension;
+}
+
+//Goes through the list of commands an generated by the SVG-Path-Parser and calls the corresponding functions
+//Should switch to switch statements
+SVGReader.prototype.interpretCommands = function(commands) {
+ delay = this.delay;
+ for (var i = 0; i < commands.length; i++) {
+ var cmdCode = commands[i].code;
+ switch (cmdCode) {
+ case 'M':
+ move(commands[i].x, commands[i].y);
+ break;
+
+ case 'm':
+ relMove(commands[i].x, commands[i].y);
+ break;
+
+ case 'L':
+ line(commands[i].x, commands[i].y);
+ break;
+
+ case 'l':
+ relLine(commands[i].x, commands[i].y);
+ break;
+
+ case 'V':
+ line(currentPoint.x, commands[i].y);
+ break;
+
+ case 'v':
+ relLine(currentPoint.x, commands[i].y);
+ break;
+
+ case 'H':
+ line(commands[i].x, currentPoint.y);
+ break;
+
+ case 'h':
+ relLine(commands[i].x, currentPoint.y);
+ break;
+
+ case 'C':
+ cubicCurve(commands[i].x1, commands[i].y1, commands[i].x2, commands[i].y2, commands[i].x, commands[i].y);
+ break;
+
+ case 'c':
+ relCubicCurve(commands[i].x1, commands[i].y1, commands[i].x2, commands[i].y2, commands[i].x, commands[i].y);
+ break;
+
+ //Smooth cubic curve
+ case 'S':
+ if (i > 1 && (commands[i-1].code == 's' || commands[i-1].code == 'c' || commands[i-1].code == 'C' || commands[i-1].code == 'S')) {
+ var reflected = reflect(commands[i].x, commands[i].y, commands[i-1].x, commands[i-1].y);
+ var ctrl = {x:reflected.x, y:reflected.y};
+ }
+ else
+ var ctrl = {x:currentPoint.x, y:currentPoint.y};
+
+ cubicCurve(ctrl.x, ctrl.y, commands[i].x2, commands[i].y2, commands[i].x, commands[i].y);
+ break;
+
+ //Smooth relative cubic curve
+ case 's':
+ if (i > 1 && (commands[i-1].code == 's' || commands[i-1].code == 'c' || commands[i-1].code == 'C' || commands[i-1].code == 'S')) {
+ var reflected = reflect(commands[i].x, commands[i].y, commands[i-1].x, commands[i-1].y);
+ var ctrl = {x:reflect(commands[i-1].x2).x, y:reflect(commands[i-1].y2).y};
+ }
+ else
+ var ctrl = {x:currentPoint.x, y:currentPoint.y};
+
+ relCubicCurve(ctrl.x, ctrl.y, commands[i].x2, commands[i].y2, commands[i].x, commands[i].y);
+ break;
+
+ case 'Q':
+ quadraticCurve(commands[i].x1, commands[i].y1, commands[i].x, commands[i].y);
+ break;
+
+ case 'q':
+ relQuadraticCurve(commands[i].x1, commands[i].y1, commands[i].x, commands[i].y);
+ break;
+
+ //Smooth quadratic curve
+ case 'T':
+ if (i > 1 && (commands[i-1].code == 't' || commands[i-1].code == 'q' || commands[i-1].code == 'Q' || commands[i-1].code == 'T')) {
+ var reflected = reflect(commands[i].x, commands[i].y, commands[i-1].x, commands[i-1].y);
+ var ctrl = {x:reflect(commands[i-1].x1).x, y:reflect(commands[i-1].y1).y};
+ }
+ else
+ var ctrl = {x:currentPoint.x, y:currentPoint.y};
+
+ quadraticCurve(ctrl.x, ctrl.y, commands[i].x, commands[i].y);
+ break;
+
+ //Smooth relative quadratic curve
+ case 't':
+ if (i > 1 && (commands[i-1].code == 't' || commands[i-1].code == 'q' || commands[i-1].code == 'Q' || commands[i-1].code == 'T')) {
+ var reflected = reflect(commands[i].x, commands[i].y, commands[i-1].x, commands[i-1].y);
+ var ctrl = {x:reflect(commands[i-1].x1).x, y:reflect(commands[i-1].y1).y};
+ }
+ else
+ var ctrl = {x:currentPoint.x, y:currentPoint.y};
+
+ relQuadraticCurve(ctrl.x, ctrl.y, commands[i].x, commands[i].y);
+ break;
+
+ case 'A':
+ arc(commands[i].rx, commands[i].ry, commands[i].xAxisRotation, commands[i].largeArc, commands[i].sweep, commands[i].x, commands[i].y);
+ break;
+
+ case 'a':
+ relArc(commands[i].rx, commands[i].ry, commands[i].xAxisRotation, commands[i].largeArc, commands[i].sweep, commands[i].x, commands[i].y);
+ break;
+
+ case 'Z':
+ line(firstPoint.x, firstPoint.y);
+ firstPoint = null;
+ break;
+
+ case 'z':
+ line(firstPoint.x, firstPoint.y);
+ firstPoint = null;
+ break;
+ }
+ }
+ }
+
+//Creates a working clock
+SVGReader.prototype.clock = function() {
+ var dimensions = dimensionConversion("80mm", "95mm"); //Since no dimensions are specified, assume the default
+ //To-do: Pull this from a config file
+ width = dimensions.width;
+ height = dimensions.height;
+
+ objRef = this;
+
+ resetTimer();
+ connected = false;
+
+ //Used to access the erase functions
+ var drawing = new draw.Draw({
+ baseWidth: objRef.baseWidth,
+ baseHeight: objRef.baseHeight,
+ drawHeight: objRef.drawHeight
+ });
+
+ var phoneWidth = this.baseWidth;
+ var phoneHeight = this.baseHeight;
+
+ widthRatio = width / phoneWidth;
+ heightRatio = height / phoneHeight;
+
+ penHeight = this.drawHeight;
+ halfway = {x:width / 2, y:height / 2};
+ currentPoint = {x:halfway.x, y:halfway.y}; //Start at the center of the canvas, which corresponds to (0,0) on the Tapster
+
+ //Currently hardcoded path data
+ var zero = "M 40.890286,241.44557 30.687127,245.57254 23.885021,257.95342 20.483968,278.58823 20.483968,290.96912 23.885021,311.60393 30.687127,323.98482 40.890286,328.11178 47.692392,328.11178 57.895551,323.98482 64.697657,311.60393 68.09871,290.96912 68.09871,278.58823 64.697657,257.95342 57.895551,245.57254 47.692392,241.44557 40.890286,241.44557";
+ var one = "M 50.289453,243.07635 50.289453,329.52761";
+ var two = "M 27.618803,262.08031 27.618803,257.95271 30.945521,249.69749 34.27224,245.56989 40.925676,241.44228 54.23255,241.44228 60.885986,245.56989 64.212704,249.69749 67.539423,257.95271 67.539423,266.20792 64.212704,274.46313 57.559268,286.84595 24.292085,328.12202 70.866141,328.12202";
+ var three = "M 33.809127,248.8729 C 45.779077,241.87747 46.887418,243.5556 53.186196,243.05913 61.663816,242.39092 65.286579,246.55715 64.812967,252.72615 64.140416,261.48642 55.207358,274.28536 40.185777,283.01419 L 52.623884,281.94331 59.295192,285.47169 62.630846,289.00007 65.9665,299.58523 65.9665,306.64193 62.630846,317.22713 55.959538,324.28383 45.952576,327.81223 35.945614,327.81223 25.938652,324.28383 22.602998,320.75553 19.267344,313.69873";
+ var four = "M 54.845427,241.51709 22.803997,300.19076 70.866142,300.19076 M 54.845427,241.51709 54.845427,329.5276";
+ var five = "M 61.700462,246.77512 26.654546,246.77512 23.149954,281.56734 26.654546,277.70154 37.168321,273.83574 47.682096,273.83574 58.19587,277.70154 65.205054,285.43315 68.709645,297.03055 68.709645,304.76216 65.205054,316.35956 58.19587,324.09117 47.682096,327.95697 37.168321,327.95697 26.654546,324.09117 23.149954,320.22537 19.645363,312.49376";
+ var six = "M 64.294901,253.7887 60.65789,245.53396 49.746856,241.40659 42.472833,241.40659 31.561799,245.53396 24.287776,257.91607 20.650765,278.55291 20.650765,299.18976 24.287776,315.69923 31.561799,323.95397 42.472833,328.08134 46.109844,328.08134 57.020878,323.95397 64.294901,315.69923 67.931912,303.31713 67.931912,299.18976 64.294901,286.80765 57.020878,278.55291 46.109844,274.42554 42.472833,274.42554 31.561799,278.55291 24.287776,286.80765 20.650765,299.18976";
+ var seven = "M 68.262659,241.73033 32.158284,328.90301 M 17.716535,241.73033 68.262659,241.73033";
+ var eight = "M 37.489233,241.44557 27.286074,245.57254 23.885021,253.82646 23.885021,262.08039 27.286074,270.33431 34.08818,274.46127 47.692392,278.58823 57.895551,282.7152 64.697657,290.96912 68.09871,299.22304 68.09871,311.60393 64.697657,319.85785 61.296604,323.98482 51.093445,328.11178 37.489233,328.11178 27.286074,323.98482 23.885021,319.85785 20.483968,311.60393 20.483968,299.22304 23.885021,290.96912 30.687127,282.7152 40.890286,278.58823 54.494498,274.46127 61.296604,270.33431 64.697657,262.08039 64.697657,253.82646 61.296604,245.57254 51.093445,241.44557 37.489233,241.44557";
+ var nine = "M 67.931912,270.29818 64.2949,282.68028 57.020878,290.93502 46.109844,295.06239 42.472833,295.06239 31.561799,290.93502 24.287776,282.68028 20.650765,270.29818 20.650765,266.17081 24.287776,253.7887 31.561799,245.53396 42.472833,241.40659 46.109844,241.40659 57.020878,245.53396 64.2949,253.7887 67.931912,270.29818 67.931912,290.93502 64.2949,311.57186 57.020878,323.95397 46.109844,328.08134 38.835821,328.08134 27.924787,323.95397 24.287776,315.69923";
+
+ pathData = [zero, one, two, three, four, five, six, seven, eight, nine];
+
+ var colon = "M 165.73227,300.21546 Z M 165.73227,253.34648 Z";
+ var arrayTime = new Array();
+
+ objRef = this;
+
+ //Simple function for converting units in millimeters to units in pixels
+ //Based on the fixed ratio between mm and px
+ toPixels = function(mm) {
+ return mm * 3.779527559;
+ }
+
+ //Draws the time
+ //Takes an array of path data and a callback function
+ drawTime = function(arrayOfPaths, callback) {
+ for (var i = 0; i < arrayOfPaths.length; i++) {
+ objRef.interpretCommands(arrayOfPaths[i]); //Interprets the path data and draws the number
+
+ firstPoint = null;
+
+ if (i == 1) //Inserts the colon between the second and third number
+ objRef.interpretCommands(parse(colon));
+ }
+ doSetTimeout(0, 0, -140, delay);
+ setTimeout(function() { endTime = new Date().getTime() }, timer + 1); //Gets the time after drawTime finishes executing
+ //Because of the way doSetTimeout works, timer + 1 occurs (is meant to occur) a millisecond after the doSetTimeout call
+ setTimeout(function() { difference = endTime - startTime }, timer + 3);
+ setTimeout(function() { callback() }, timer + 4);
+ }
+
+ //Gets the curent time and converts it into an array of four digits
+ //The first two digits are the hours, the last two are the minutes
+ getTheTime = function() {
+ var currentTime = new Date()
+ var hours = String(currentTime.getHours());
+ var minutes = String(currentTime.getMinutes());
+
+ //Converts from 24 hour time to 12 hour time
+ if (hours > 12)
+ hours -= 12;
+ else if (hours === 0)
+ hours = 12;
+
+ //Adds a placeholder zero to ensure that there are four digits in the time
+ //The zero is not actually drawn
+ if (hours < 10)
+ hours = "0" + hours;
+
+ if (minutes < 10)
+ minutes = "0" + minutes;
+
+ var timeArray = new Array();
+ for (var i = 0; i < hours.length; i++) {
+ timeArray.push(hours.charAt(i));
+ }
+
+ for (var i = 0; i < minutes.length; i++) {
+ timeArray.push(minutes.charAt(i));
+ }
+ arrayTime = timeArray; //Stores the array in another variable so it can be accessed elsewhere without calling the function again
+ return timeArray;
+ }
+
+ //Converts the digits in the timeArray into path data
+ convertToPath = function(timeArray) {
+ var pathArray = new Array();
+ var offset = toPixels(3); //The first digit is drawn three mm from the left side
+ var commandArray = new Array();
+ //Loops through the digits in timeArray
+ for (var i = 0; i < timeArray.length; i++) {
+ if (i == 0 && timeArray[i] == 0) { //If the first digit is 0 (if the hours < 10) AND the loop is on the first digit
+ offset += toPixels(5); //Changes the offset so that the three digits that will be drawn will be centered
+ commandArray.push(["Z"]); //The Z command, without a firstPoint value, will not draw anything, but can still be interpreted without errors
+ colon = "M 118.73227,307.21546 Z M 118.73227,253.34648 Z"; //Changes the path data of the colon so that it will still be in the correct location
+ }
+ else {
+ var data = pathData[timeArray[i]];
+ var commands = parse(data);
+
+ //Loops through the commands in the array and adds the offset to each x value
+ for (var x = 0; x < commands.length; x++) {
+ if (commands[x].code != 'Z' || commands[x].code != 'z') { //Every command but the close path commands have an x value
+ commands[x].x += offset;
+ if (commands[x].x1) //Some commands (curves and arcs) have an x1 value
+ commands[x].x1 += offset;
+ if (commands[x].x2) //Some commands (cubic curves) have an x2 value
+ commands[x].x2 += offset;
+ }
+ }
+
+ commandArray.push(commands);
+
+ if (timeArray[0] != 0)
+ colon = "M 165.73227,300.21546 Z M 165.73227,253.34648 Z"; //Ensures that the colon has the correct path data
+ //Without this check, once the hours changed from single to double digits, the colon would be in the incorrect place
+
+ offset += toPixels(18); //Adds an offset to each digit
+ //Each digit should be 15mm wide, with 3mm space between each digit
+ if (i == 1)
+ offset += toPixels(4); //Adds an extra 4mm of space to account for the colon
+ }
+ }
+
+ return commandArray;
+ }
+
+ //Draws a circle to indicate the amount of time left in the minute
+ timeCircle = function() {
+ resetTimer();
+
+ //Draws a circle, given an array of points and the amount of delay in between each point
+ circle = function(array, timeDelay) {
+ for (var i=0; i -2; degree--) {
+ var radians = (degree + 90) * Math.PI/180; //Add 90 to degree so that the circle starts at the top, not at the right
+ var x = centerX + radius * Math.cos(radians);
+ var y = centerY + radius * Math.sin(radians);
+ points.push({x:x,y:y});
+ }
+
+ setTimeout(function() { circle(points, calcDelay(calcTimeLeft(), points.length)) }, 0);
+ }
+
+ //Tells the time
+ tellTime = function() {
+ resetTimer();
+
+ startTime = new Date().getTime();
+
+ drawing.erase(function() {
+ drawTime(convertToPath(getTheTime()), function() {
+ timeCircle();
+ });
+ });
+
+ //drawTime(convertToPath([0, 2, 4, 2]), console.log);
+ //setTimeout(function() { drawTime(convertToPath(getTheTime())) }, 12500);
+ //setTimeout(function() { drawTime(convertToPath([0, 8, 5, 8]))}, 12500);
+ //setTimeout(function() { timeCircle() }, 12501);
+ }
+
+ tellTime();
+ clockTimer = setInterval(function() { tellTime() }, 60000); //Repeat every 60 seconds
+}
+
+//A function to stop the clock
+//The clock will finish erasing, drawing, and moving the arms in a circle, but it will not repeat
+SVGReader.prototype.clearClock = function() {
+ clearInterval(clockTimer);
+ console.log("Stopping clock.");
+}
+
+//Says 'hello' in multiple languages
+SVGReader.prototype.hello = function() {
+ resetTimer();
+ var fileArray = fs.readdirSync("./hello");
+ var fileNum;
+ objRef = this;
+
+ //Used to access the erase functions
+ var drawing = new draw.Draw({
+ baseWidth: objRef.baseWidth,
+ baseHeight: objRef.baseHeight,
+ drawHeight: objRef.drawHeight
+ });
+
+ //Picks a file at random from a folder of 'hello's
+ pickFile = function() {
+ do {
+ fileNum = Math.floor(Math.random() * fileArray.length);
+ }
+ while (fileNum == lastNum1 || fileNum == lastNum2); //Generates numbers until a number that has not been used in the past two calls is picked
+ //A language can only be used every third time
+
+ lastNum2 = lastNum1; //Adjusts the last two numbers used
+ lastNum1 = fileNum;
+
+ //Checks to see if the characters should be drawn connected or not
+ //Specified in file name
+ if (fileArray[fileNum].charAt(7) === 'T')
+ connect = true;
+ else
+ connect = false;
+
+ return "./hello/" + fileArray[fileNum];
+ }
+
+ //Says hello.
+ sayHello = function() {
+ resetTimer();
+ var fileChoice = pickFile();
+ drawing.erase(function() {
+ setTimeout(function() { objRef.drawSVG(fileChoice, connect) }, 10000);
+ });
+ }
+
+ sayHello();
+ helloTimer = setInterval(function() { sayHello() }, 60000); //Repeat every minute
+}
+
+//A function to cancel saying hello
+//The robot will finish writing/erasing if it has already started but after that it will stop
+SVGReader.prototype.sayGoodbye = function() {
+ clearInterval(helloTimer);
+ console.log("Goodbye!");
+}
+
+//Cycles through all the languages rather than picking one at random
+SVGReader.prototype.helloCycle = function() {
+ resetTimer();
+ var fileArray = fs.readdirSync("./hello");
+ objRef = this;
+ var fileNum = 0;
+
+ //Used to access the erase functions
+ var drawing = new draw.Draw({
+ baseWidth: objRef.baseWidth,
+ baseHeight: objRef.baseHeight,
+ drawHeight: objRef.drawHeight
+ });
+
+ //Function to actually write hello
+ sayHello = function() {
+ var fileChoice = "./hello/" + fileArray[fileNum];
+
+ if (fileChoice.charAt(15) === 'T')
+ connect = true;
+ else
+ connect = false;
+
+ //drawing.erase(function() {
+ // setTimeout(function() { objRef.drawSVG(fileChoice, connect) }, 10000);
+ //});
+
+ fileNum++;
+
+ if (fileNum >= fileArray.length) //Once the end of the list is reached, stop writing
+ clearInterval(helloTimer);
+ }
+
+ sayHello();
+ helloTimer = setInterval(function() { sayHello() }, 60000);
+
+}
+
+//Saves the coordinates of the first point drawn
+var firstPoint;
+
+var objArr;
+var pathArray;
+
+var widthRatio;
+var heightRatio;
+var halfway;
+var currentPoint;
+var penHeight;
+var delay, loaded, fontFile;
+var transformX, transformY, objRef, connected, startTime, endTime, difference, clockTimer, firstMove, lastNum1, lastNum2, connect, baseWidth, baseHeight, defaultEaseType;
+
+module.exports.SVGReader = SVGReader;
diff --git a/software/src/bot.js b/software/src/bot.js
index f313d6a..4c0cb8e 100755
--- a/software/src/bot.js
+++ b/software/src/bot.js
@@ -1,23 +1,115 @@
five = require("johnny-five");
-ik = require("./ik");
+kinematics = require("./kinematics");
+svgRead = require("./SVGReader");
+drawing = require("./draw");
+motion = require("./motion");
+
+//If a filepath is specified, load that config
+//Otherwise, resort to the default config
+//> Usage:
+//> node bot.js "C:\Projects\Tapsterbot\software\config.js"
+if (process.argv[2]) {
+ try {
+ var config = require(process.argv[2]);
+ console.log("Config found and loaded.");
+ } catch (e) {
+ console.log("Config not found. Loading default.");
+ var config = require("../config.js");
+ }
+}
+else {
+ console.log("Config not specified. Loading default.");
+ var config = require("../config.js");
+}
+
+//Alternate config loading code
+//If a Tapster version is specified, load that config
+//Otherwise resort to the default config
+//> Usage:
+//> node bot.js "Tapster-2-plus"
+
+/*if (process.argv[2]) {
+ try {
+ var config = require("../" + process.argv[2] + ".js");
+ console.log("Config found and loaded.");
+ } catch (e) {
+ console.log("Config not found. Loading default.");
+ var config = require("../config.js");
+ }
+}
+else {
+ console.log("Config not specified. Loading default.");
+ var config = require("../config.js");
+} */
+
+k = new kinematics.Kinematics({
+ e: config.e,
+ f: config.f,
+ re: config.re,
+ rf: config.rf
+});
+
+svg = new svgRead.SVGReader({
+ baseWidth: config.baseWidth,
+ baseHeight: config.baseHeight,
+ drawHeight: config.drawHeight,
+ delay: config.delay,
+ defaultEaseType: config.defaultEaseType
+});
+
+draw = new drawing.Draw({
+ baseWidth: config.baseWidth,
+ baseHeight: config.baseHeight,
+ drawHeight: config.drawHeight,
+ defaultEaseType: config.defaultEaseType
+});
+
board = new five.Board({
debug: false
});
+var steps = 5;
+var delay = config.delay / steps;
+var defaultEaseType = config.defaultEaseType;
+
+current = [0, 0, -140];
+timer = 0;
+
board.on("ready", function() {
// Setup
- servo1 = five.Servo({
- pin: 9,
- range: [0,90]
+ /*servo1 = five.Servo({
+ address: 0x40,
+ controller: "PCA9685",
+ pin: 0,
+ range: [35, 145] //Too high of a minimum input will cause issues with the forward kinematics
});
servo2 = five.Servo({
- pin: 10,
- range: [0,90]
+ address: 0x40,
+ controller: "PCA9685",
+ pin: 1,
+ range: [35, 145]
});
servo3 = five.Servo({
- pin: 11,
- range: [0, 90]
- });
+ address: 0x40,
+ controller: "PCA9685",
+ pin: 2,
+ range: [35, 145]
+ }); */
+
+ servo1 = five.Servo({
+ pin: 9,
+ range: [0, 100]
+ });
+
+ servo2 = five.Servo({
+ pin: 10,
+ range: [0, 100]
+ });
+
+ servo3 = five.Servo({
+ pin: 11,
+ range: [0, 100]
+ });
servo1.on("error", function() {
console.log(arguments);
@@ -42,10 +134,11 @@ board.on("ready", function() {
var max = 15;
var min = 5;
var range = max - min;
- servo1.to(min);
- servo2.to(min);
- servo3.to(min);
+ servo1.to(15);
+ servo2.to(15);
+ servo3.to(15);
+ /*
var dance = function() {
servo1.to(parseInt((Math.random() * range) + min, 10));
servo2.to(parseInt((Math.random() * range) + min, 10));
@@ -66,14 +159,13 @@ board.on("ready", function() {
}
board.repl.inject({
- dance: start_dance,
+ dance: start_dance,
chill: stop_dance
- });
+ }); */
});
-
Number.prototype.map = function ( in_min , in_max , out_min , out_max ) {
return ( this - in_min ) * ( out_max - out_min ) / ( in_max - in_min ) + out_min;
}
@@ -100,23 +192,57 @@ sin = function(degree) {
// A cosine function for working with degrees, not radians
cos = function(degree) {
- return Math.cos(Math.PI * (degree/180));
+ return Math.cos(Math.PI * (degree/180));
}
-
-// TODO: pull out map values to config file or some other solution.
-go = function(x, y, z) {
+moveServosTo = function(x, y, z) {
+ current = [x, y, z]
reflected = reflect(x,y);
rotated = rotate(reflected[0],reflected[1]);
-
- angles = ik.inverse(rotated[0], rotated[1], z);
- servo1.to((angles[1]).map( 0 , 90 , 8 , 90 ));
- servo2.to((angles[2]).map( 0 , 90 , 8 , 90 ));
- servo3.to((angles[3]).map( 0 , 90 , 8 , 90 ));
+
+ angles = k.inverse(rotated[0], rotated[1], z);
+
+ servo1.to((angles[1]).map(config.servo1.in_min, config.servo1.in_max, config.servo1.out_min, config.servo1.out_max));
+ servo2.to((angles[2]).map(config.servo2.in_min, config.servo2.in_max, config.servo2.out_min, config.servo2.out_max));
+ servo3.to((angles[3]).map(config.servo3.in_min, config.servo3.in_max, config.servo3.out_min, config.servo3.out_max));
console.log(angles);
}
+go = function(x, y, z, easeType) {
+ var pointB = [x, y, z];
+ if (easeType == "none") {
+ moveServosTo(pointB[0], pointB[1], pointB[2]);
+ return; //Ensures that it doesn't move twice
+ }
+
+ else if (!easeType)
+ easeType = defaultEaseType //If no easeType is specified, go with default (specified in config.js)
+
+ //motion.move(current, pointB, steps, easeType, delay);
+ var points = motion.getPoints(current, pointB, steps, easeType);
+
+ for (var i = 0; i < points.length; i++) {
+ setTimeout( function(point) { moveServosTo(point[0], point[1], point[2]) }, i * delay, points[i]);
+ }
+}
+
+//Returns the coordinates of the end effector, based on the angles
+//Using the map function messes up these values
+//Simply passing in the original angles will return the correct coordinates
position = function() {
- return ik.forward(servo1.last.degrees, servo2.last.degrees, servo3.last.degrees);
+ return k.forward(servo1.last.degrees, servo2.last.degrees, servo3.last.degrees);
}
+//A separate setTimeout method so that delays work properly
+doSetTimeout = function(x, y, z, timeDelay, easing) {
+ if (!easing)
+ easing = defaultEaseType;
+
+ setTimeout(function() { go(x, y, z, easing) }, timer);
+ timer = timer + timeDelay;
+};
+
+
+resetTimer = function() {
+ timer = 0;
+}
\ No newline at end of file
diff --git a/software/src/data.json b/software/src/data.json
new file mode 100644
index 0000000..d77a6af
--- /dev/null
+++ b/software/src/data.json
@@ -0,0 +1,240 @@
+[[
+ {
+ "x": 2.013888359069824,
+ "y": -0.9861116409301758
+ },
+ {
+ "x": 0.013888359069824219,
+ "y": -0.9861116409301758
+ },
+ {
+ "x": 0.013888359069824219,
+ "y": 4.013888359069824
+ },
+ {
+ "x": 9.013888359069824,
+ "y": 23.01388931274414
+ },
+ {
+ "x": 24.01388931274414,
+ "y": 36.01388931274414
+ },
+ {
+ "x": 35.01388931274414,
+ "y": 48.01388931274414
+ },
+ {
+ "x": 44.01388931274414,
+ "y": 65.01388549804688
+ },
+ {
+ "x": 47.01388931274414,
+ "y": 76.01388549804688
+ },
+ {
+ "x": 55.01388931274414,
+ "y": 93.01388549804688
+ },
+ {
+ "x": 67.01388549804688,
+ "y": 104.01388549804688
+ },
+ {
+ "x": 85.01388549804688,
+ "y": 117.01388549804688
+ },
+ {
+ "x": 106.01388549804688,
+ "y": 149.01388549804688
+ },
+ {
+ "x": 107.01388549804688,
+ "y": 154.01388549804688
+ },
+ {
+ "x": 114.01388549804688,
+ "y": 165.01388549804688
+ },
+ {
+ "x": 123.01388549804688,
+ "y": 173.01388549804688
+ },
+ {
+ "x": 132.01388549804688,
+ "y": 178.01388549804688
+ },
+ {
+ "x": 163.01388549804688,
+ "y": 214.01388549804688
+ },
+ {
+ "x": 187.01388549804688,
+ "y": 233.01388549804688
+ },
+ {
+ "x": 226.01388549804688,
+ "y": 276.0138854980469
+ },
+ {
+ "x": 252.01388549804688,
+ "y": 290.0138854980469
+ },
+ {
+ "x": 255.01388549804688,
+ "y": 294.0138854980469
+ },
+ {
+ "x": 260.0138854980469,
+ "y": 312.0138854980469
+ },
+ {
+ "x": 262.0138854980469,
+ "y": 313.0138854980469
+ },
+ {
+ "x": 266.0138854980469,
+ "y": 323.0138854980469
+ },
+ {
+ "x": 270.0138854980469,
+ "y": 326.0138854980469
+ },
+ {
+ "x": 279.0138854980469,
+ "y": 329.0138854980469
+ },
+ {
+ "x": 287.0138854980469,
+ "y": 337.0138854980469
+ }
+]
+, [
+ {
+ "x": 294.0138854980469,
+ "y": 6.013888359069824
+ },
+ {
+ "x": 255.01388549804688,
+ "y": 49.01388931274414
+ },
+ {
+ "x": 253.01388549804688,
+ "y": 53.01388931274414
+ },
+ {
+ "x": 245.01388549804688,
+ "y": 59.01388931274414
+ },
+ {
+ "x": 242.01388549804688,
+ "y": 68.01388549804688
+ },
+ {
+ "x": 239.01388549804688,
+ "y": 70.01388549804688
+ },
+ {
+ "x": 235.01388549804688,
+ "y": 82.01388549804688
+ },
+ {
+ "x": 225.01388549804688,
+ "y": 92.01388549804688
+ },
+ {
+ "x": 216.01388549804688,
+ "y": 97.01388549804688
+ },
+ {
+ "x": 209.01388549804688,
+ "y": 106.01388549804688
+ },
+ {
+ "x": 203.01388549804688,
+ "y": 123.01388549804688
+ },
+ {
+ "x": 195.01388549804688,
+ "y": 134.01388549804688
+ },
+ {
+ "x": 178.01388549804688,
+ "y": 145.01388549804688
+ },
+ {
+ "x": 169.01388549804688,
+ "y": 154.01388549804688
+ },
+ {
+ "x": 156.01388549804688,
+ "y": 176.01388549804688
+ },
+ {
+ "x": 153.01388549804688,
+ "y": 185.01388549804688
+ },
+ {
+ "x": 126.01388549804688,
+ "y": 229.01388549804688
+ },
+ {
+ "x": 115.01388549804688,
+ "y": 244.01388549804688
+ },
+ {
+ "x": 102.01388549804688,
+ "y": 258.0138854980469
+ },
+ {
+ "x": 88.01388549804688,
+ "y": 269.0138854980469
+ },
+ {
+ "x": 87.01388549804688,
+ "y": 272.0138854980469
+ },
+ {
+ "x": 68.01388549804688,
+ "y": 282.0138854980469
+ },
+ {
+ "x": 54.01388931274414,
+ "y": 287.0138854980469
+ },
+ {
+ "x": 50.01388931274414,
+ "y": 292.0138854980469
+ },
+ {
+ "x": 47.01388931274414,
+ "y": 305.0138854980469
+ },
+ {
+ "x": 43.01388931274414,
+ "y": 310.0138854980469
+ },
+ {
+ "x": 35.01388931274414,
+ "y": 315.0138854980469
+ },
+ {
+ "x": 30.01388931274414,
+ "y": 315.0138854980469
+ },
+ {
+ "x": 20.01388931274414,
+ "y": 320.0138854980469
+ },
+ {
+ "x": 20.01388931274414,
+ "y": 322.0138854980469
+ },
+ {
+ "x": 16.01388931274414,
+ "y": 326.0138854980469
+ },
+ {
+ "x": 13.013888359069824,
+ "y": 337.0138854980469
+ }
+]]
diff --git a/software/src/demo/angry-birds/angrybirds.js b/software/src/demo/angry-birds/angrybirds.js
index 697d9ac..86643ec 100644
--- a/software/src/demo/angry-birds/angrybirds.js
+++ b/software/src/demo/angry-birds/angrybirds.js
@@ -1,24 +1,217 @@
+var five = require("johnny-five")
+var kin = require("./../../kinematics.js");
+var config = require("./../../../config.js");
+
+drawHeight = config.drawHeight;
+
+board = new five.Board({
+ debug: false
+});
+
+k = new kin.Kinematics({
+ e: config.e,
+ f: config.f,
+ re: config.re,
+ rf: config.rf
+})
+
+board.on("ready", function() {
+ // Setup
+ /*servo1 = five.Servo({
+ address: 0x40,
+ controller: "PCA9685",
+ pin: 0,
+ range: [35, 145] //Too high of a minimum input will cause issues with the forward kinematics
+ });
+ servo2 = five.Servo({
+ address: 0x40,
+ controller: "PCA9685",
+ pin: 1,
+ range: [35, 145]
+ });
+ servo3 = five.Servo({
+ address: 0x40,
+ controller: "PCA9685",
+ pin: 2,
+ range: [35, 145]
+ }); */
+
+ servo1 = five.Servo({
+ pin: 9,
+ range: [0, 100]
+ });
+
+ servo2 = five.Servo({
+ pin: 10,
+ range: [0, 100]
+ });
+
+ servo3 = five.Servo({
+ pin: 11,
+ range: [0, 100]
+ });
+
+ servo1.on("error", function() {
+ console.log(arguments);
+ })
+ servo2.on("error", function() {
+ console.log(arguments);
+ })
+ servo3.on("error", function() {
+ console.log(arguments);
+ })
+
+ board.repl.inject({
+ servo1: servo1,
+ s1: servo1,
+ servo2: servo2,
+ s2: servo2,
+ servo3: servo3,
+ s3: servo3,
+ });
+
+ // Move to starting point
+ var max = 15;
+ var min = 5;
+ var range = max - min;
+ servo1.to(15);
+ servo2.to(15);
+ servo3.to(15);
+
+ /*
+ var dance = function() {
+ servo1.to(parseInt((Math.random() * range) + min, 10));
+ servo2.to(parseInt((Math.random() * range) + min, 10));
+ servo3.to(parseInt((Math.random() * range) + min, 10));
+ };
+
+ var dancer;
+
+ start_dance = function() {
+ if (!dancer) dancer = setInterval(dance, 250);
+ }
+
+ stop_dance = function() {
+ if (dancer) {
+ clearInterval(dancer);
+ dancer = null;
+ }
+ }
+
+ board.repl.inject({
+ dance: start_dance,
+ chill: stop_dance
+ }); */
+
+
+});
+
+rotate = function(x,y) {
+ var theta = -60 * Math.PI / 180;
+ x1 = x * Math.cos(theta) - y * Math.sin(theta);
+ y1 = y * Math.cos(theta) + x * Math.sin(theta);
+ return [x1,y1]
+}
+
+reflect = function(x,y) {
+ var theta = 0;
+ x1 = x;
+ y1 = x * Math.sin(2*theta) - y * Math.cos(2*theta);
+ return [x1,y1]
+}
+
+Number.prototype.map = function ( in_min , in_max , out_min , out_max ) {
+ return ( this - in_min ) * ( out_max - out_min ) / ( in_max - in_min ) + out_min;
+}
+
+moveServosTo = function(x, y, z) {
+ reflected = reflect(x,y);
+ rotated = rotate(reflected[0],reflected[1]);
+
+ angles = k.inverse(rotated[0], rotated[1], z);
+
+ servo1.to((angles[1]).map(config.servo1.in_min, config.servo1.in_max, config.servo1.out_min, config.servo1.out_max));
+ servo2.to((angles[2]).map(config.servo2.in_min, config.servo2.in_max, config.servo2.out_min, config.servo2.out_max));
+ servo3.to((angles[3]).map(config.servo3.in_min, config.servo3.in_max, config.servo3.out_min, config.servo3.out_max));
+ console.log(angles);
+}
+
move = function(x,y,z, when) {
- setTimeout(function(){ go(x,y,z);}, when);
+ setTimeout(function(){ moveServosTo(x,y,z) }, moveTimer);
+ moveTimer += when;
+}
+
+resetMoveTimer = function() {
+ moveTimer = 0;
+}
+
+var moveTimer = 0;
+
+playLevelOne = function(){
+ resetMoveTimer();
+ move(-20, -3, drawHeight + 10, 0);
+ move(-20, -3, drawHeight, 500);
+ move(-35,-6, drawHeight,300);
+ move(-35,-6, drawHeight + 10,400);
+ move(-30,0, drawHeight + 10,400);
+
+ move(42,-18, drawHeight + 10, 2800);
+ move(42,-18, drawHeight - 3, 500);
+ move(42, -18, drawHeight + 10, 500);
+
+ move(9,-13, drawHeight + 10, 3000);
+ move(9,-13, drawHeight - 2, 300);
+ move(9, -13, drawHeight + 10,300);
+ move(0,0, drawHeight + 10,300);
}
-play = function(){
- move(-10,-20,-140,0);
- move(-10,-20,-149,200);
- move(-25,-28,-148,500);
- move(-25,-28,-140,800);
- move(0,0,-120,1200);
+playLevelTwo = function() {
+ resetMoveTimer();
+ move(-20, -3, drawHeight + 10, 500);
+ move(-20, -3, drawHeight, 200);
+ move(-35, -2, drawHeight, 300);
+ move(-35, -2, drawHeight + 10, 300);
+ move(-20, -3, drawHeight + 10, 15000);
+ move(-20, -3, drawHeight - 1, 300);
+ move(-35, -8, drawHeight, 400);
+ move(-35, -8, drawHeight + 10, 300);
- move(34,-36,-140, 4000);
- move(34,-36,-150, 4500);
- move(34,-36,-140, 5000);
+ move(42, -18, drawHeight + 10, 6000);
+ move(42, -18, drawHeight - 3, 500);
+ move(42, -18, drawHeight + 10, 500);
- move(4,-32,-140,0+8000);
- move(4,-32,-149,300+8000);
- move(4,-32,-140,600+8000);
- move(0,0,-120,900+8000);
+ move(9, -13, drawHeight + 10, 3000);
+ move(9, -13, drawHeight - 2, 300);
+ move(9, -13, drawHeight + 10, 300);
+ move(0, 0, drawHeight + 10, 400);
}
+playLevelThree = function() {
+ resetMoveTimer();
+ move(-20, -3, drawHeight + 10, 1000);
+ move(-20, -3, drawHeight, 200);
+ move(-25, -10, drawHeight, 350);
+ move(-23, -6.5, drawHeight, 350);
+ move(-23, -6.5, drawHeight + 10, 350);
+
+ move(42, -18, drawHeight + 10, 8000);
+ move(42, -18, drawHeight - 3, 500);
+ move(42, -18, drawHeight + 10, 500);
+};
+
+goToLevelOne = function() {
+ resetMoveTimer();
+ move(-14, -12, drawHeight + 10, 250);
+ move(-14, -12, drawHeight - 2, 250);
+ move(-14, -12, drawHeight + 10, 250);
+ move(-48, 23, drawHeight + 10, 1000);
+ move(-46, 23, drawHeight - 2, 1000);
+ move(-48, 23, drawHeight + 10, 500);
+ move(45, -18, drawHeight + 10, 500);
+ move(41, -18, drawHeight - 4, 500);
+ move(45, -18, drawHeight + 10, 500);
+ move(0, 0, drawHeight + 10, 250);
+}
repeat = function(){
move(4,-32,-140,0);
@@ -27,10 +220,25 @@ repeat = function(){
move(0,0,-120,900);
}
+playLevels = function() {
+ resetMoveTimer();
+ var objRef = this;
+ setTimeout(objRef.playLevelOne, 0);
+ setTimeout(objRef.playLevelTwo, 15000);
+ setTimeout(objRef.playLevelThree, 47500);
+ setTimeout(goToLevelOne, 65000);
+}
+
play_forever = function(){
+ var objRef = this;
console.log("Now playing forever...")
- play();
- interval = setInterval(play,13000);
+ this.playLevels();
+ interval = setInterval(objRef.playLevels, 70000);
return interval;
+}
+
+stop_playing = function() {
+ clearInterval(interval);
+ console.log("No longer playing forever.");
}
\ No newline at end of file
diff --git a/software/src/demo/hi.js b/software/src/demo/hi.js
new file mode 100644
index 0000000..768f5bd
--- /dev/null
+++ b/software/src/demo/hi.js
@@ -0,0 +1,70 @@
+five = require("johnny-five");
+bot = require("bot");
+board = new five.Board({
+ debug: false
+});
+
+board.on("ready", function() {
+ // Setup
+ servo1 = five.Servo({
+ pin: 9,
+ range: [0,90]
+ });
+ servo2 = five.Servo({
+ pin: 10,
+ range: [0,90]
+ });
+ servo3 = five.Servo({
+ pin: 11,
+ range: [0, 90]
+ });
+
+ servo1.on("error", function() {
+ console.log(arguments);
+ })
+ servo2.on("error", function() {
+ console.log(arguments);
+ })
+ servo3.on("error", function() {
+ console.log(arguments);
+ })
+
+ board.repl.inject({
+ servo1: servo1,
+ s1: servo1,
+ servo2: servo2,
+ s2: servo2,
+ servo3: servo3,
+ s3: servo3,
+ });
+
+ bot.go(0, 0, -140);
+ });
+
+hi = function() {
+ setTimeout(function() { go(-20, 20, -150) }, 0);
+ setTimeout(function() { go(-20, -20, -150) }, 250);
+ setTimeout(function() { go(-20, 0, -150) }, 500);
+ setTimeout(function() { go(-10, 0, -150) }, 750)
+ setTimeout(function() { go(-10, 20, -150) }, 1000);
+ setTimeout(function() { go(-10, -20, -150) }, 1250);
+
+ setTimeout(function() { go(-10, -20, -140) }, 1500);
+ setTimeout(function() { go(-5, -20, -140) }, 1750)
+ setTimeout(function() { go(-5, -20, -150) }, 2000);
+ setTimeout(function() { go(-5, 0, -150) }, 2250);
+ setTimeout(function() { go(-5, 0, -140) }, 2500);
+ setTimeout(function() { go(-5, 10, -140) }, 2750);
+ setTimeout(function() { go(-5, 10, -150) }, 3000);
+ setTimeout(function() { go(-5, 15, -150) }, 3250);
+
+ setTimeout(function() { go(-5, 20, -140) }, 3500);
+ setTimeout(function() { go(5, 20, -140) }, 3750);
+ setTimeout(function() { go(5, 20, -150) }, 4000);
+ setTimeout(function() { go(5, -10, -150) }, 4250);
+ setTimeout(function() { go(5, -10, -140) }, 4500);
+ setTimeout(function() { go(5, -15, -140) }, 4750);
+ setTimeout(function() { go(5, -15, -150) }, 5000);
+ setTimeout(function() { go(5, -20, -150) }, 5250);
+ setTimeout(function() { go(5, 0, -140) }, 5500);
+ }
diff --git a/software/src/draw.js b/software/src/draw.js
new file mode 100644
index 0000000..066eb05
--- /dev/null
+++ b/software/src/draw.js
@@ -0,0 +1,345 @@
+//Draws lines and shapes.
+
+var fs = require('fs');
+var objRef, defaultEaseType;
+var calculated, spiralPts;
+
+function Draw(args) {
+ this.baseWidth = 80;
+ this.baseHeight = 95;
+ this.drawHeight = -140;
+ this.defaultEaseType = "linear";
+
+ if (args) {
+ var keys = Object.keys(args);
+ keys.forEach(function(key){
+ this[key] = args[key];
+ }, this)
+ }
+ penHeight = this.drawHeight;
+ defaultEaseType = this.defaultEaseType;
+ objRef = this;
+}
+
+//Maps point from canvas to the Tapster coordinate plane
+//The conversion is based on the canvas size and the size of the Tapster base
+//These can be changed as needed
+mapPoints = function(x, y) {
+ var newX = x;
+ var newY = y;
+
+ newX = (newX - halfway.x) / widthRatio;
+ newY = (halfway.y - newY) / heightRatio;
+ return {x:newX, y:newY};
+};
+
+//Adds delays while the Tapster is writing
+//The specific delay can be changed if the bot has to go slower or faster for a particular segment
+/*doSetTimeout = function(x, y, z, delay, easing) {
+ if (!easing)
+ easing = defaultEaseType;
+ setTimeout(function() { go(x, y, z, easing) }, timer);
+ currentPoint = {x: x, y: y, z: z};
+ timer = timer + delay;
+}; */
+
+//Initialized here so that they are accessible from the mapPoints function
+var baseHeight, baseWidth, canvasHeight, canvasWidth, heightRatio, widthRatio, halfway;
+
+var currentPoint = {x: 0, y: 0, z: -140};
+var penHeight = this.drawHeight;
+
+//Set the penHeight from the command line
+Draw.prototype.setPenHeight = function(height) {
+ penHeight = height;
+ console.log("The pen is now set at: " + height);
+}
+
+//Draws an image from a JSON file of coordinates
+//The SVGReader drawSVG method is preferred
+Draw.prototype.drawFromCoordinates = function() {
+
+ baseHeight = this.baseHeight;
+ baseWidth = this.baseWidth;
+ canvasHeight = baseHeight * 3.779527559; //Set ratio of ~1:3.8 mm:px
+ canvasWidth = baseWidth * 3.779527559;
+
+ //The ratio between the sizes of the canvas and robot.
+ //It will always be 3.779527559 because the canvas size is set
+ //according to that ratio
+ heightRatio = 3.779527559;
+ widthRatio = 3.779527559;
+
+ //The center of the canvas
+ halfway = {x:canvasWidth / 2, y:canvasHeight / 2};
+
+
+ var jFile = fs.readFileSync('.//data.json', 'utf8'); //Reads data from a JSON file of coordinates
+ var objArr = JSON.parse(jFile); //Creates an array out of the data
+
+ //Loops through the JSON array
+ for (var i = 0; i < objArr.length; i++) {
+ var x = 0;
+ if (objArr[i].length > 0) { //If there are multiple lines
+ var point = objArr[i][x];
+ var transMap = mapPoints(point.x, point.y);
+ doSetTimeout(transMap.x, transMap.y, penHeight + 20, 200); //Moves the arm vertically so that it does not draw a line between the last point of one line
+ //and the first point of another
+
+ for (x = 0; x < objArr[i].length; x++) {
+ point = objArr[i][x];
+ var mapped = mapPoints(point.x, point.y);
+ doSetTimeout(mapped.x, mapped.y, penHeight, 100);
+ }
+ transMap = mapPoints(point.x, point.y);
+ doSetTimeout(transMap.x, transMap.y, penHeight + 20, 200);
+ }
+
+ else { //Only one line to be drawn
+ point = objArr[i];
+ var mapped = mapPoints(point.x, point.y);
+ doSetTimeout(mapped.x, mapped.y, penHeight, 100);
+ }
+ }
+};
+
+//Draws a square in order to ensure that everything is working properly
+//Optional args:
+//sideLength: the length of each side (default: 20)
+//n: draws every nth point (default: 2)
+Draw.prototype.drawSquare = function(args) {
+
+ //Default values
+ this.sideLength = 20;
+ this.n = 2;
+
+ if (args) {
+ var keys = Object.keys(args)
+ keys.forEach(function(key){
+ this[key] = args[key]
+ }, this)
+ }
+
+ resetTimer(); //Reset the timer so that there isn't unnecessary delay when calling the function multiple times
+
+ var halfSide = this.sideLength / 2;
+ var points = this.sideLength / this.n;
+
+ doSetTimeout(-halfSide, halfSide, penHeight + 10, 0)
+ doSetTimeout(-halfSide, halfSide, penHeight, 500); //Top left corner
+
+ for (var i = 0; i < points; i++) { //To bottom left
+ doSetTimeout(-halfSide, halfSide - (this.n * i), penHeight, i * 5);
+ }
+
+ for (var i = 0; i < points; i++) { //To bottom right
+ doSetTimeout(-halfSide + (this.n * i), -halfSide, penHeight, i * 5);
+ }
+
+ for (var i = 0; i < points; i++) { //To top right
+ doSetTimeout(halfSide, -halfSide + (this.n * i), penHeight, i * 5);
+ }
+
+ for (var i = 0; i < points; i++) { //To top left
+ doSetTimeout(halfSide - (this.n * i), halfSide, penHeight, i * 5);
+ }
+
+ doSetTimeout(0, 0, -140, timer + 100);
+
+};
+
+//Draws a star to test that the Tapster bot is working properly
+Draw.prototype.drawStar = function() {
+ resetTimer();
+ doSetTimeout(-20, -20, penHeight, 1000);
+ doSetTimeout(0, 30, penHeight, 1000);
+ doSetTimeout(20, -20, penHeight, 1000);
+ doSetTimeout(-30, 10, penHeight, 1000);
+ doSetTimeout(30, 10, penHeight, 1000);
+ doSetTimeout(-20, -20, penHeight, 1000);
+
+ //-20, -20, 0, 30, 20, -20
+};
+
+
+Draw.prototype.drawTriangle = function(x, y, x1, y1, x2, y2) {
+ resetTimer();
+ doSetTimeout(x, y, penHeight, 1000);
+ doSetTimeout(x1, y1, penHeight, 1000);
+ doSetTimeout(x2, y2, penHeight, 1000);
+ doSetTimeout(x, y, penHeight, 1000);
+}
+
+//Draws a circle
+//Optional args:
+//centerX: the x coordinate of the center (default: 0)
+//centerY: the y coordinate of the center (default: 0)
+//radius: the radius of the center (default: 20)
+Draw.prototype.drawCircle = function(args) {
+ resetTimer();
+
+ this.centerX = 0;
+ this.centerY = 0;
+ this.radius = 20;
+
+ if (args) {
+ var keys = Object.keys(args)
+ keys.forEach(function(key){
+ this[key] = args[key]
+ }, this)
+ }
+
+ // an array to save your points
+ var points=[];
+
+ // populate array with points along a circle
+ //Goes slightly over so that the circle is actually completed
+ for (var degree=0; degree < 395; degree++) {
+ var radians = (degree + 90) * Math.PI/180;
+ var x = this.centerX + this.radius * Math.cos(radians);
+ var y = this.centerY + this.radius * Math.sin(radians);
+ points.push({x:x,y:y});
+ }
+
+ circle = function() {
+ doSetTimeout(0, 0, -120, 50, "none");
+ doSetTimeout(points[0].x, points[0].y, penHeight + 10, 150, "none");
+ doSetTimeout(points[0].x, points[0].y, penHeight, 150, "none");
+ for (var i=0; i
+
+
+
diff --git a/software/src/example/textTutorial.svg b/software/src/example/textTutorial.svg
new file mode 100644
index 0000000..d816475
--- /dev/null
+++ b/software/src/example/textTutorial.svg
@@ -0,0 +1,74 @@
+
+
+
+
diff --git a/software/src/example/tutorial.svg b/software/src/example/tutorial.svg
new file mode 100644
index 0000000..70ed47d
--- /dev/null
+++ b/software/src/example/tutorial.svg
@@ -0,0 +1,77 @@
+
+
+
+
diff --git a/software/src/hello/helloChF.svg b/software/src/hello/helloChF.svg
new file mode 100644
index 0000000..2fe5d96
--- /dev/null
+++ b/software/src/hello/helloChF.svg
@@ -0,0 +1,150 @@
+
+
+
+
diff --git a/software/src/hello/helloEnT.svg b/software/src/hello/helloEnT.svg
new file mode 100644
index 0000000..c5fa472
--- /dev/null
+++ b/software/src/hello/helloEnT.svg
@@ -0,0 +1,86 @@
+
+
+
+
diff --git a/software/src/hello/helloFrF.svg b/software/src/hello/helloFrF.svg
new file mode 100644
index 0000000..e6997af
--- /dev/null
+++ b/software/src/hello/helloFrF.svg
@@ -0,0 +1,101 @@
+
+
+
+
diff --git a/software/src/hello/helloItF.svg b/software/src/hello/helloItF.svg
new file mode 100644
index 0000000..b7dac35
--- /dev/null
+++ b/software/src/hello/helloItF.svg
@@ -0,0 +1,80 @@
+
+
+
+
diff --git a/software/src/hello/helloJpF.svg b/software/src/hello/helloJpF.svg
new file mode 100644
index 0000000..aff0c36
--- /dev/null
+++ b/software/src/hello/helloJpF.svg
@@ -0,0 +1,126 @@
+
+
+
+
diff --git a/software/src/hello/helloRuF.svg b/software/src/hello/helloRuF.svg
new file mode 100644
index 0000000..d3d732e
--- /dev/null
+++ b/software/src/hello/helloRuF.svg
@@ -0,0 +1,90 @@
+
+
+
+
diff --git a/software/src/hello/helloSpT.svg b/software/src/hello/helloSpT.svg
new file mode 100644
index 0000000..ae19b57
--- /dev/null
+++ b/software/src/hello/helloSpT.svg
@@ -0,0 +1,74 @@
+
+
+
+
diff --git a/software/src/ik.js b/software/src/ik.js
deleted file mode 100755
index e129746..0000000
--- a/software/src/ik.js
+++ /dev/null
@@ -1,134 +0,0 @@
-// Original code from
-// http://forums.trossenrobotics.com/tutorials/introduction-129/delta-robot-kinematics-3276/
-
-(function(exports) {
-
- // Specific geometry for Tapster:
- var e = 34.64101615137754; // Math.sqrt(3) * 10 * 2
- var f = 110.85125168440814; // Math.sqrt(3) * 32 * 2
- var re = 153.5; // 145 + 8.5
- var rf = 52.690131903421914; // Math.sqrt(52**2 + 8.5**2)
-
- exports.updateSize = function(parameters) {
- e = parameters[0];
- f = parameters[1];
- re = parameters[2];
- rf = parameters[3];
- exports.e = e;
- exports.f = f;
- exports.re = re;
- exports.rf = rf;
- };
-
- exports.getSize = function() {
- return new Array(e, f, re, rf);
- };
-
- // Trigonometric constants
- var s = 165 * 2;
- var sqrt3 = Math.sqrt(3.0);
- var pi = 3.141592653;
- var sin120 = sqrt3 / 2.0;
- var cos120 = -0.5;
- var tan60 = sqrt3;
- var sin30 = 0.5;
- var tan30 = 1.0 / sqrt3;
-
- // Forward kinematics: (theta1, theta2, theta3) -> (x0, y0, z0)
- // Returned {error code,theta1,theta2,theta3}
- exports.forward = function(theta1, theta2, theta3) {
- var x0 = 0.0;
- var y0 = 0.0;
- var z0 = 0.0;
-
- var t = (f - e) * tan30 / 2.0;
- var dtr = pi / 180.0;
-
- theta1 *= dtr;
- theta2 *= dtr;
- theta3 *= dtr;
-
- var y1 = -(t + rf * Math.cos(theta1));
- var z1 = -rf * Math.sin(theta1);
-
- var y2 = (t + rf * Math.cos(theta2)) * sin30;
- var x2 = y2 * tan60;
- var z2 = -rf * Math.sin(theta2);
-
- var y3 = (t + rf * Math.cos(theta3)) * sin30;
- var x3 = -y3 * tan60;
- var z3 = -rf * Math.sin(theta3);
-
- var dnm = (y2 - y1) * x3 - (y3 - y1) * x2;
-
- var w1 = y1 * y1 + z1 * z1;
- var w2 = x2 * x2 + y2 * y2 + z2 * z2;
- var w3 = x3 * x3 + y3 * y3 + z3 * z3;
-
- // x = (a1*z + b1)/dnm
- var a1 = (z2 - z1) * (y3 - y1) - (z3 - z1) * (y2 - y1);
- var b1 = -((w2 - w1) * (y3 - y1) - (w3 - w1) * (y2 - y1)) / 2.0;
-
- // y = (a2*z + b2)/dnm;
- var a2 = -(z2 - z1) * x3 + (z3 - z1) * x2;
- var b2 = ((w2 - w1) * x3 - (w3 - w1) * x2) / 2.0;
-
- // a*z^2 + b*z + c = 0
- var a = a1 * a1 + a2 * a2 + dnm * dnm;
- var b = 2.0 * (a1 * b1 + a2 * (b2 - y1 * dnm) - z1 * dnm * dnm);
- var c = (b2 - y1 * dnm) * (b2 - y1 * dnm) + b1 * b1 + dnm * dnm * (z1 * z1 - re * re);
-
- // discriminant
- var d = b * b - 4.0 * a * c;
- if (d < 0.0) {
- return new Array(1, 0, 0, 0); // non-existing povar. return error,x,y,z
- }
-
- z0 = -0.5 * (b + Math.sqrt(d)) / a;
- x0 = (a1 * z0 + b1) / dnm;
- y0 = (a2 * z0 + b2) / dnm;
-
- return new Array(0, x0, y0, z0);
- };
-
- // Inverse kinematics
- // Helper functions, calculates angle theta1 (for YZ-pane)
- var delta_calcAngleYZ = function(x0, y0, z0) {
- var y1 = -0.5 * 0.57735 * f; // f/2 * tg 30
- y0 -= 0.5 * 0.57735 * e; // shift center to edge
- // z = a + b*y
- var a = (x0 * x0 + y0 * y0 + z0 * z0 + rf * rf - re * re - y1 * y1) / (2.0 * z0);
- var b = (y1 - y0) / z0;
-
- // discriminant
- var d = -(a + b * y1) * (a + b * y1) + rf * (b * b * rf + rf);
- if (d < 0) {
- return new Array(1, 0); // non-existing povar. return error, theta
- }
-
- var yj = (y1 - a * b - Math.sqrt(d)) / (b * b + 1); // choosing outer povar
- var zj = a + b * yj;
- var theta = Math.atan(-zj / (y1 - yj)) * 180.0 / pi + ((yj > y1) ? 180.0 : 0.0);
-
- return new Array(0, theta); // return error, theta
- };
-
- exports.inverse = function(x0, y0, z0) {
- var theta1 = 0;
- var theta2 = 0;
- var theta3 = 0;
- var status = delta_calcAngleYZ(x0, y0, z0);
-
- if (status[0] === 0) {
- theta1 = status[1];
- status = delta_calcAngleYZ(x0 * cos120 + y0 * sin120, y0 * cos120 - x0 * sin120, z0, theta2);
- }
- if (status[0] === 0) {
- theta2 = status[1];
- status = delta_calcAngleYZ(x0 * cos120 - y0 * sin120, y0 * cos120 + x0 * sin120, z0, theta3);
- }
- theta3 = status[1];
-
- return new Array(status[0], theta1, theta2, theta3);
- };
-}(typeof exports === 'undefined' ? this.ik = {} : exports));
diff --git a/software/src/kinematics.js b/software/src/kinematics.js
new file mode 100644
index 0000000..400eeb1
--- /dev/null
+++ b/software/src/kinematics.js
@@ -0,0 +1,135 @@
+// Trigonometric constants
+var s = 165 * 2;
+var sqrt3 = Math.sqrt(3.0);
+var pi = 3.141592653;
+var sin120 = sqrt3 / 2.0;
+var cos120 = -0.5;
+var tan60 = sqrt3;
+var sin30 = 0.5;
+var tan30 = 1.0 / sqrt3;
+
+function Kinematics(args) {
+ //Side of end effector
+ this.e = 0;
+
+ //Side of top triangle
+ this.f = 0;
+
+ //Length of parallelogram joint
+ this.re = 0;
+
+ //Length of upper joint
+ this.rf = 0;
+
+ if (args) {
+ var keys = Object.keys(args)
+ keys.forEach(function(key){
+ this[key] = args[key]
+ }, this)
+ }
+}
+
+// Forward kinematics: (theta1, theta2, theta3) -> (x0, y0, z0)
+// Returned {error code,theta1,theta2,theta3}
+Kinematics.prototype.forward = function(theta1, theta2, theta3) {
+ var x0 = 0.0;
+ var y0 = 0.0;
+ var z0 = 0.0;
+
+ var t = (this.f - this.e) * tan30 / 2.0;
+ var dtr = pi / 180.0;
+
+ theta1 *= dtr;
+ theta2 *= dtr;
+ theta3 *= dtr;
+
+ var y1 = -(t + this.rf * Math.cos(theta1));
+ var z1 = -this.rf * Math.sin(theta1);
+
+ var y2 = (t + this.rf * Math.cos(theta2)) * sin30;
+ var x2 = y2 * tan60;
+ var z2 = -this.rf * Math.sin(theta2);
+
+ var y3 = (t + this.rf * Math.cos(theta3)) * sin30;
+ var x3 = -y3 * tan60;
+ var z3 = -this.rf * Math.sin(theta3);
+
+ var dnm = (y2 - y1) * x3 - (y3 - y1) * x2;
+
+ var w1 = y1 * y1 + z1 * z1;
+ var w2 = x2 * x2 + y2 * y2 + z2 * z2;
+ var w3 = x3 * x3 + y3 * y3 + z3 * z3;
+
+ // x = (a1*z + b1)/dnm
+ var a1 = (z2 - z1) * (y3 - y1) - (z3 - z1) * (y2 - y1);
+ var b1 = -((w2 - w1) * (y3 - y1) - (w3 - w1) * (y2 - y1)) / 2.0;
+
+ // y = (a2*z + b2)/dnm;
+ var a2 = -(z2 - z1) * x3 + (z3 - z1) * x2;
+ var b2 = ((w2 - w1) * x3 - (w3 - w1) * x2) / 2.0;
+
+ // a*z^2 + b*z + c = 0
+ var a = a1 * a1 + a2 * a2 + dnm * dnm;
+ var b = 2.0 * (a1 * b1 + a2 * (b2 - y1 * dnm) - z1 * dnm * dnm);
+ var c = (b2 - y1 * dnm) * (b2 - y1 * dnm) + b1 * b1 + dnm * dnm * (z1 * z1 - this.re * this.re);
+
+ // discriminant
+ var d = b * b - 4.0 * a * c;
+ if (d < 0.0) {
+ return new Array(1, 0, 0, 0); // non-existing povar. return error,x,y,z
+ }
+
+ z0 = -0.5 * (b + Math.sqrt(d)) / a;
+ x0 = (a1 * z0 + b1) / dnm;
+ y0 = (a2 * z0 + b2) / dnm;
+
+ return new Array(0, x0, y0, z0);
+};
+
+
+
+
+// Inverse kinematics
+
+// Helper functions, calculates angle theta1 (for YZ-pane)
+Kinematics.prototype.delta_calcAngleYZ = function(x0, y0, z0) {
+ var y1 = -0.5 * 0.57735 * this.f; // f/2 * tg 30
+ y0 -= 0.5 * 0.57735 * this.e; // shift center to edge
+ // z = a + b*y
+ var a = (x0 * x0 + y0 * y0 + z0 * z0 + this.rf * this.rf - this.re * this.re - y1 * y1) / (2.0 * z0);
+ var b = (y1 - y0) / z0;
+
+ // discriminant
+ var d = -(a + b * y1) * (a + b * y1) + this.rf * (b * b * this.rf + this.rf);
+ if (d < 0) {
+ return new Array(1, 0); // non-existing povar. return error, theta
+ }
+
+ var yj = (y1 - a * b - Math.sqrt(d)) / (b * b + 1); // choosing outer povar
+ var zj = a + b * yj;
+ var theta = Math.atan(-zj / (y1 - yj)) * 180.0 / pi + ((yj > y1) ? 180.0 : 0.0);
+
+ return new Array(0, theta); // return error, theta
+ };
+
+
+Kinematics.prototype.inverse = function(x0, y0, z0) {
+ var theta1 = 0;
+ var theta2 = 0;
+ var theta3 = 0;
+ var status = this.delta_calcAngleYZ(x0, y0, z0);
+
+ if (status[0] === 0) {
+ theta1 = status[1];
+ status = this.delta_calcAngleYZ(x0 * cos120 + y0 * sin120, y0 * cos120 - x0 * sin120, z0, theta2);
+ }
+ if (status[0] === 0) {
+ theta2 = status[1];
+ status = this.delta_calcAngleYZ(x0 * cos120 - y0 * sin120, y0 * cos120 + x0 * sin120, z0, theta3);
+ }
+ theta3 = status[1];
+
+ return new Array(status[0], theta1, theta2, theta3);
+};
+
+module.exports.Kinematics = Kinematics;
\ No newline at end of file
diff --git a/software/src/motion.js b/software/src/motion.js
new file mode 100644
index 0000000..1f62398
--- /dev/null
+++ b/software/src/motion.js
@@ -0,0 +1,90 @@
+/*
+
+// Usage:
+// motion.move(pointA, pointB, numberOfSteps, easingType, timeDeltaInMilliseconds)
+// Example
+$ node bot.js
+>> motion = require('./motion')
+>> motion.move([0,0,-140],[-20,20,-140], 20, 'easeInOutCubic', 500)
+>> motion.move([-20,60,-165],[-20,-60,-165], 30, 'easeInOutCubic', 20)
+>> motion.move([-20,-60,-165],[-20,60,-165], 30, 'easeInOutCubic', 20)
+
+
+Usage:
+> motion = require('./motion')
+> motion.directionVector([0,0,-100], [5,10,-150])
+[ 5, 10, -50 ]
+
+> var A = [1,6,3];
+> var B = [8,2,7];
+> motion.directionVector(A, B);
+[ 7, -4, 4 ]
+
+
+Reference:
+Line between two points in 3D space: http://mathcentral.uregina.ca/QQ/database/QQ.09.01/murray2.html
+Easing functions: https://gist.github.com/gre/1650294
+
+*/
+var EasingFunctions = {
+ linear: function (t) { return t },
+ easeInQuad: function (t) { return t*t },
+ easeOutQuad: function (t) { return t*(2-t) },
+ easeInOutQuad: function (t) { return t<.5 ? 2*t*t : -1+(4-2*t)*t },
+ easeInCubic: function (t) { return t*t*t },
+ easeOutCubic: function (t) { return (--t)*t*t+1 },
+ easeInOutCubic: function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 },
+ easeInQuart: function (t) { return t*t*t*t },
+ easeOutQuart: function (t) { return 1-(--t)*t*t*t },
+ easeInOutQuart: function (t) { return t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t },
+ easeInQuint: function (t) { return t*t*t*t*t },
+ easeOutQuint: function (t) { return 1+(--t)*t*t*t*t },
+ easeInOutQuint: function (t) { return t<.5 ? 16*t*t*t*t*t : 1+16*(--t)*t*t*t*t }
+};
+
+(function(exports) {
+ var directionVector = function(pointA, pointB) {
+ var vector = [ pointB[0] - pointA[0],
+ pointB[1] - pointA[1],
+ pointB[2] - pointA[2] ];
+ return vector;
+ }
+
+ // (x,y,z) = (1,6,3) + t(7,-4,4) = (1 + 7t, 6 - 4t, 3 + 4t).
+ var parametricEquation = function(pointA, pointB) {
+ var dv = directionVector(pointA, pointB);
+ var equation = function(t) {
+ return [ pointA[0] + dv[0]*t,
+ pointA[1] + dv[1]*t,
+ pointA[2] + dv[2]*t ];
+ }
+ return equation;
+ }
+
+ // get an array of points between (and including) two end points
+ // numberOfSteps and easingType are required
+ var getPoints = function(pointA, pointB, numberOfSteps, easingType) {
+ var points = [];
+ var point = parametricEquation(pointA, pointB);
+ var easingFunction = EasingFunctions[easingType];
+
+ for (var i = 0; i <= numberOfSteps; i++) {
+ t = easingFunction(i/numberOfSteps);
+ points.push(point(t))
+ }
+ return points;
+ }
+
+ var move = function(pointA, pointB, numberOfSteps, easingType, timeDelta) {
+ var points = getPoints(pointA, pointB, numberOfSteps, easingType);
+ for (var i=0; i
+
+
+