mirror of
https://github.com/github/codeql.git
synced 2026-05-03 04:39:29 +02:00
QL code and tests for C#/C++/JavaScript.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
// semmle-extractor-options: --platform
|
||||
// semmle-extractor-options: node
|
||||
@@ -0,0 +1 @@
|
||||
| | 2 |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from Folder d
|
||||
select d.getRelativePath(), count(File f | f = d.getAFile() and f.getExtension() = "js")
|
||||
@@ -0,0 +1 @@
|
||||
| tst.js:31:1:33:1 | functio ... ++i);\\n} | This function uses i like a local variable. |
|
||||
@@ -0,0 +1,6 @@
|
||||
import javascript
|
||||
|
||||
from Function f, GlobalVariable gv
|
||||
where gv.getAnAccess().getEnclosingFunction() = f and
|
||||
not f.getStartBB().isLiveAtEntry(gv, _)
|
||||
select f, "This function uses " + gv + " like a local variable."
|
||||
@@ -0,0 +1,4 @@
|
||||
| tst.js:21:12:21:12 | x | Dead store of local variable. |
|
||||
| tst.js:31:12:31:12 | x | Dead store of local variable. |
|
||||
| tst.js:31:15:31:15 | y | Dead store of local variable. |
|
||||
| tst.js:31:18:31:18 | x | Dead store of local variable. |
|
||||
@@ -0,0 +1,6 @@
|
||||
import javascript
|
||||
|
||||
from VarDef def, LocalVariable v
|
||||
where v = def.getAVariable() and
|
||||
not exists (VarUse use | def = use.getADef())
|
||||
select def, "Dead store of local variable."
|
||||
@@ -0,0 +1,8 @@
|
||||
import javascript
|
||||
|
||||
from SimpleParameter res, DataFlow::Node resNode, MethodCallExpr send
|
||||
where res.getName() = "res" and
|
||||
resNode = DataFlow::parameterNode(res) and
|
||||
resNode.getASuccessor+() = DataFlow::valueNode(send.getReceiver()) and
|
||||
send.getMethodName() = "send"
|
||||
select send
|
||||
@@ -0,0 +1,7 @@
|
||||
import javascript
|
||||
|
||||
from StrictEqualityTest eq, DataFlow::AnalyzedNode nd, NullLiteral null
|
||||
where eq.hasOperands(nd.asExpr(), null) and
|
||||
not nd.getAValue().isIndefinite(_) and
|
||||
not nd.getAValue() instanceof AbstractNull
|
||||
select eq, "Spurious null check."
|
||||
@@ -0,0 +1 @@
|
||||
| tst.js:9:1:9:3 | h() | Unable to find a callee for this call site. |
|
||||
@@ -0,0 +1,6 @@
|
||||
import javascript
|
||||
|
||||
from CallSite cs
|
||||
where not cs.isIncomplete() and
|
||||
not exists(cs.getACallee())
|
||||
select cs, "Unable to find a callee for this call site."
|
||||
@@ -0,0 +1,12 @@
|
||||
import javascript
|
||||
|
||||
class TrackedStringLiteral extends DataFlow::TrackedNode {
|
||||
TrackedStringLiteral() {
|
||||
this.asExpr() instanceof ConstantString
|
||||
}
|
||||
}
|
||||
|
||||
from TrackedStringLiteral source, DataFlow::Node sink, SsaExplicitDefinition def
|
||||
where source.flowsTo(sink) and sink = DataFlow::ssaDefinitionNode(def) and
|
||||
def.getSourceVariable().getName().toLowerCase() = "password"
|
||||
select sink
|
||||
@@ -0,0 +1,28 @@
|
||||
import javascript
|
||||
|
||||
class PasswordTracker extends DataFlow::Configuration {
|
||||
PasswordTracker() {
|
||||
// unique identifier for this configuration
|
||||
this = "PasswordTracker"
|
||||
}
|
||||
|
||||
override predicate isSource(DataFlow::Node nd) {
|
||||
nd.asExpr() instanceof StringLiteral
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node nd) {
|
||||
passwordVarAssign(_, nd)
|
||||
}
|
||||
|
||||
predicate passwordVarAssign(Variable v, DataFlow::Node nd) {
|
||||
exists (SsaExplicitDefinition def |
|
||||
nd = DataFlow::ssaDefinitionNode(def) and
|
||||
def.getSourceVariable() = v and
|
||||
v.getName().toLowerCase() = "password"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from PasswordTracker pt, DataFlow::Node source, DataFlow::Node sink, Variable v
|
||||
where pt.hasFlow(source, sink) and pt.passwordVarAssign(v, sink)
|
||||
select sink, "Password variable " + v + " is assigned a constant string."
|
||||
@@ -0,0 +1 @@
|
||||
| m.js:1:1:3:0 | <toplevel> | 0 |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from NodeModule m
|
||||
select m, count(m.getAnImportedModule())
|
||||
@@ -0,0 +1,7 @@
|
||||
import javascript
|
||||
|
||||
from NPMPackage pkg, PackageDependencies deps, string name
|
||||
where deps = pkg.getPackageJSON().getDependencies() and
|
||||
deps.getADependency(name, _) and
|
||||
not exists (Require req | req.getTopLevel() = pkg.getAModule() | name = req.getImportedPath().getValue())
|
||||
select deps, "Unused dependency '" + name + "'."
|
||||
@@ -0,0 +1 @@
|
||||
| tst.js:25:3:25:3 | , | Omitted array elements are bad style. |
|
||||
@@ -0,0 +1,12 @@
|
||||
import javascript
|
||||
|
||||
class CommaToken extends PunctuatorToken {
|
||||
CommaToken() {
|
||||
getValue() = ","
|
||||
}
|
||||
}
|
||||
|
||||
from CommaToken comma
|
||||
where comma.getNextToken() instanceof CommaToken
|
||||
select comma, "Omitted array elements are bad style."
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import javascript
|
||||
|
||||
from SQL::SqlString ss
|
||||
where ss instanceof AddExpr
|
||||
select ss, "Use templating instead of string concatenation."
|
||||
@@ -0,0 +1 @@
|
||||
| tst.js:19:4:19:9 | @param | @param tag is missing name. |
|
||||
@@ -0,0 +1,6 @@
|
||||
import javascript
|
||||
|
||||
from JSDocTag t
|
||||
where t.getTitle() = "param" and
|
||||
not exists(t.getName())
|
||||
select t, "@param tag is missing name."
|
||||
@@ -0,0 +1 @@
|
||||
| tst.js:27:1:27:4 | <!-- | Do not use HTML comments. |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from HtmlLineComment c
|
||||
select c, "Do not use HTML comments."
|
||||
@@ -0,0 +1 @@
|
||||
| tst.js:29:1:29:5 | 1 + 2 | This expression should be bracketed to clarify precedence rules. |
|
||||
@@ -0,0 +1,5 @@
|
||||
import javascript
|
||||
|
||||
from ShiftExpr shift, AddExpr add
|
||||
where add = shift.getAnOperand()
|
||||
select add, "This expression should be bracketed to clarify precedence rules."
|
||||
@@ -0,0 +1,5 @@
|
||||
import javascript
|
||||
|
||||
from FunctionExpr fe
|
||||
where fe.getBody() instanceof Expr
|
||||
select fe, "Use arrow expressions instead of expression closures."
|
||||
@@ -0,0 +1 @@
|
||||
| tst.js:31:1:33:1 | functio ... ++i);\\n} | This function has two parameters that bind the same variable. |
|
||||
@@ -0,0 +1,8 @@
|
||||
import javascript
|
||||
|
||||
from Function fun, Parameter p, Parameter q, int i, int j
|
||||
where p = fun.getParameter(i) and
|
||||
q = fun.getParameter(j) and
|
||||
i < j and
|
||||
p.getAVariable() = q.getAVariable()
|
||||
select fun, "This function has two parameters that bind the same variable."
|
||||
@@ -0,0 +1 @@
|
||||
| tst.js:35:1:35:9 | var j, j; | Variable j is declared both $@ and $@. | tst.js:35:5:35:5 | j | here | tst.js:35:8:35:8 | j | here |
|
||||
@@ -0,0 +1,10 @@
|
||||
import javascript
|
||||
|
||||
from DeclStmt ds, VariableDeclarator d1, VariableDeclarator d2, Variable v, int i, int j
|
||||
where d1 = ds.getDecl(i) and
|
||||
d2 = ds.getDecl(j) and
|
||||
i < j and
|
||||
v = d1.getBindingPattern().getAVariable() and
|
||||
v = d2.getBindingPattern().getAVariable() and
|
||||
not ds.getTopLevel().isMinified()
|
||||
select ds, "Variable " + v.getName() + " is declared both $@ and $@.", d1, "here", d2, "here"
|
||||
@@ -0,0 +1 @@
|
||||
| tst.js:1:2:1:24 | { x: 23 ... x: 56 } | Property x is defined both $@ and $@. | tst.js:1:4:1:8 | x: 23 | here | tst.js:1:18:1:22 | x: 56 | here |
|
||||
@@ -0,0 +1,9 @@
|
||||
import javascript
|
||||
|
||||
from ObjectExpr oe, Property p1, Property p2, int i, int j
|
||||
where p1 = oe.getProperty(i) and
|
||||
p2 = oe.getProperty(j) and
|
||||
i < j and
|
||||
p1.getName() = p2.getName() and
|
||||
not oe.getTopLevel().isMinified()
|
||||
select oe, "Property " + p1.getName() + " is defined both $@ and $@.", p1, "here", p2, "here"
|
||||
@@ -0,0 +1,2 @@
|
||||
| tst.js:3:1:3:15 | function f() {} | tst.js:6:5:6:19 | function f() {} |
|
||||
| tst.js:6:5:6:19 | function f() {} | tst.js:3:1:3:15 | function f() {} |
|
||||
@@ -0,0 +1,7 @@
|
||||
import javascript
|
||||
|
||||
from FunctionDeclStmt f, FunctionDeclStmt g
|
||||
where f != g and f.getVariable() = g.getVariable() and
|
||||
not f.getTopLevel().isMinified() and
|
||||
not g.getTopLevel().isMinified()
|
||||
select f, g
|
||||
@@ -0,0 +1,35 @@
|
||||
({ x: 23, y: 42, x: 56 })
|
||||
|
||||
function f() {}
|
||||
function g() {}
|
||||
{
|
||||
function f() {}
|
||||
}
|
||||
|
||||
h();
|
||||
|
||||
goog.require('foo');
|
||||
goog.require('foo');
|
||||
|
||||
/*global imm, mut:true*/
|
||||
imm = 23;
|
||||
mut = 42;
|
||||
|
||||
/**
|
||||
* @param
|
||||
*/
|
||||
function k(x) {}
|
||||
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
|
||||
[1,,3]
|
||||
|
||||
<!--
|
||||
|
||||
1 + 2 << 3
|
||||
|
||||
function l(x, y, x) {
|
||||
for (i=0;i<10;++i);
|
||||
}
|
||||
|
||||
var j, j;
|
||||
1
javascript/ql/test/tutorials/README
Normal file
1
javascript/ql/test/tutorials/README
Normal file
@@ -0,0 +1 @@
|
||||
Example queries from the tutorials; if these have to be fixed, fix the tutorials as well.
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Some commonly used HTTP verbs.
|
||||
*/
|
||||
|
||||
string httpVerb() {
|
||||
result = "get" or result = "put" or
|
||||
result = "post" or result = "delete"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
| robonode/src/app.js:43:13:43:27 | res.status(500) | Response 500 is not specified by $@. | robonode/src/assets/raml/api.raml:9:5:14:2 | description: \| | description: \| |
|
||||
| robonode/src/app.js:78:17:78:31 | res.status(422) | Response 422 is not specified by $@. | robonode/src/assets/raml/api.raml:30:9:54:4 | description: \| | description: \| |
|
||||
| robonode/src/app.js:91:21:91:35 | res.status(500) | Response 500 is not specified by $@. | robonode/src/assets/raml/api.raml:30:9:54:4 | description: \| | description: \| |
|
||||
| robonode/src/app.js:115:13:115:27 | res.status(422) | Response 422 is not specified by $@. | robonode/src/assets/raml/api.raml:30:9:54:4 | description: \| | description: \| |
|
||||
| robonode/src/app.js:122:9:122:23 | res.status(500) | Response 500 is not specified by $@. | robonode/src/assets/raml/api.raml:30:9:54:4 | description: \| | description: \| |
|
||||
| robonode/src/app.js:139:13:139:27 | res.status(500) | Response 500 is not specified by $@. | robonode/src/assets/raml/api.raml:56:9:72:14 | description: \| | description: \| |
|
||||
| robonode/src/app.js:144:13:144:27 | res.status(202) | Response 202 is not specified by $@. | robonode/src/assets/raml/api.raml:56:9:72:14 | description: \| | description: \| |
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @name Missing status code specification
|
||||
* @description Using undocumented status codes in REST API methods may confuse client applications.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import Osprey
|
||||
import RAML
|
||||
|
||||
RAMLMethod getSpecification(OspreyMethod om) {
|
||||
exists (RAMLResource rr, File f, string rPath |
|
||||
rr.getLocation().getFile() = f and
|
||||
f = om.getDefinition().getAPI().getSpecFile() and
|
||||
rPath = om.getResourcePath() and
|
||||
rr.getPath() = rPath.regexpReplaceAll("/:([^/]+)/", "/{$1}/") and
|
||||
result = rr.getMethod(om.getVerb())
|
||||
)
|
||||
}
|
||||
|
||||
from MethodResponseSetStatus mrss, RAMLMethod rm
|
||||
where rm = getSpecification(mrss.getMethod()) and
|
||||
not exists(rm.getResponse(mrss.getStatusCode()))
|
||||
select mrss, "Response " + mrss.getStatusCode() + " is not specified by $@.", rm, rm.toString()
|
||||
@@ -0,0 +1,123 @@
|
||||
/* Model of Osprey API implementations. */
|
||||
|
||||
import javascript
|
||||
import HTTP
|
||||
|
||||
/** An import of the Osprey module. */
|
||||
class OspreyImport extends Require {
|
||||
OspreyImport() {
|
||||
getImportedPath().getValue() = "osprey"
|
||||
}
|
||||
}
|
||||
|
||||
/** A variable that holds the Osprey module. */
|
||||
class Osprey extends Variable {
|
||||
Osprey() {
|
||||
getAnAssignedExpr() instanceof OspreyImport
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to `osprey.create`. */
|
||||
class OspreyCreateAPICall extends MethodCallExpr {
|
||||
OspreyCreateAPICall() {
|
||||
getReceiver().(VarAccess).getVariable() instanceof Osprey and
|
||||
getMethodName() = "create"
|
||||
}
|
||||
|
||||
/** Get the specification file for the API definition. */
|
||||
File getSpecFile() {
|
||||
exists (ObjectExpr oe, PathExpr p |
|
||||
oe = getArgument(2) and
|
||||
p = oe.getPropertyByName("ramlFile").getInit() and
|
||||
result = p.resolve()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A variable in which an Osprey API object is stored. */
|
||||
class OspreyAPI extends Variable {
|
||||
OspreyAPI() {
|
||||
getAnAssignedExpr() instanceof OspreyCreateAPICall
|
||||
}
|
||||
|
||||
File getSpecFile() {
|
||||
result = getAnAssignedExpr().(OspreyCreateAPICall).getSpecFile()
|
||||
}
|
||||
}
|
||||
|
||||
/** An Osprey REST method definition. */
|
||||
class OspreyMethodDefinition extends MethodCallExpr {
|
||||
OspreyMethodDefinition() {
|
||||
exists (OspreyAPI api | getReceiver() = api.getAnAccess()) and
|
||||
getMethodName() = httpVerb()
|
||||
}
|
||||
|
||||
/** Get the API to which this method belongs. */
|
||||
OspreyAPI getAPI() {
|
||||
getReceiver() = result.getAnAccess()
|
||||
}
|
||||
|
||||
/** Get the verb which this method implements. */
|
||||
string getVerb() {
|
||||
result = getMethodName()
|
||||
}
|
||||
|
||||
/** Get the resource path to which this method belongs. */
|
||||
string getResourcePath() {
|
||||
result = getArgument(0).(ConstantString).getStringValue()
|
||||
}
|
||||
}
|
||||
|
||||
/** A callback function bound to a REST method. */
|
||||
class OspreyMethod extends FunctionExpr {
|
||||
OspreyMethod() {
|
||||
exists (OspreyMethodDefinition omd | this = omd.getArgument(1))
|
||||
}
|
||||
|
||||
OspreyMethodDefinition getDefinition() {
|
||||
this = result.getArgument(1)
|
||||
}
|
||||
|
||||
string getVerb() {
|
||||
result = getDefinition().getVerb()
|
||||
}
|
||||
|
||||
string getResourcePath() {
|
||||
result = getDefinition().getResourcePath()
|
||||
}
|
||||
}
|
||||
|
||||
/** A variable that is bound to a response object. */
|
||||
class MethodResponse extends Variable {
|
||||
MethodResponse() {
|
||||
exists (OspreyMethod m, SimpleParameter res |
|
||||
res = m.getParameter(1) and
|
||||
this = res.getVariable()
|
||||
)
|
||||
}
|
||||
|
||||
OspreyMethod getMethod() {
|
||||
this = result.getParameter(1).(SimpleParameter).getVariable()
|
||||
}
|
||||
}
|
||||
|
||||
/** A call that sets the status on a response object. */
|
||||
class MethodResponseSetStatus extends MethodCallExpr {
|
||||
MethodResponseSetStatus() {
|
||||
exists (MethodResponse mr |
|
||||
getReceiver() = mr.getAnAccess() and
|
||||
getMethodName() = "status"
|
||||
)
|
||||
}
|
||||
|
||||
OspreyMethod getMethod() {
|
||||
exists (MethodResponse mr |
|
||||
getReceiver() = mr.getAnAccess() and
|
||||
result = mr.getMethod()
|
||||
)
|
||||
}
|
||||
|
||||
int getStatusCode() {
|
||||
result = getArgument(0).getIntValue()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/* Model of RAML specifications. */
|
||||
|
||||
import javascript
|
||||
import HTTP
|
||||
|
||||
/** A RAML specification. */
|
||||
class RAMLSpec extends YAMLDocument, YAMLMapping {
|
||||
RAMLSpec() {
|
||||
getLocation().getFile().getExtension() = "raml"
|
||||
}
|
||||
}
|
||||
|
||||
/** A RAML resource specification. */
|
||||
class RAMLResource extends YAMLMapping {
|
||||
RAMLResource() {
|
||||
getDocument() instanceof RAMLSpec and
|
||||
exists (YAMLMapping m, string name |
|
||||
this = m.lookup(name) and
|
||||
name.matches("/%")
|
||||
)
|
||||
}
|
||||
/** Get the path of this resource relative to the API root. */
|
||||
string getPath() {
|
||||
exists (RAMLSpec spec |
|
||||
this = spec.lookup(result)
|
||||
) or
|
||||
exists (RAMLResource that, string p |
|
||||
this = that.lookup(p) and
|
||||
result = that.getPath() + p
|
||||
)
|
||||
}
|
||||
/** Get the method for this resource with the given verb. */
|
||||
RAMLMethod getMethod(string verb) {
|
||||
verb = httpVerb() and
|
||||
result = lookup(verb)
|
||||
}
|
||||
}
|
||||
|
||||
/** A RAML method specification. */
|
||||
class RAMLMethod extends YAMLValue {
|
||||
RAMLMethod() {
|
||||
getDocument() instanceof RAMLSpec and
|
||||
exists (YAMLMapping obj |
|
||||
this = obj.lookup(httpVerb())
|
||||
)
|
||||
}
|
||||
/** Get the response specification for the given status code. */
|
||||
YAMLValue getResponse(int code) {
|
||||
exists (YAMLMapping obj, string s |
|
||||
obj = this.(YAMLMapping).lookup("responses") and
|
||||
result = obj.lookup(s) and
|
||||
code = s.toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
| robonode/src/assets/raml/api.raml:2:1:72:14 | title: ... in Mini |
|
||||
@@ -0,0 +1,11 @@
|
||||
import javascript
|
||||
|
||||
/** A RAML specification. */
|
||||
class RAMLSpec extends YAMLDocument, YAMLMapping {
|
||||
RAMLSpec() {
|
||||
getLocation().getFile().getExtension() = "raml"
|
||||
}
|
||||
}
|
||||
|
||||
from RAMLSpec s
|
||||
select s
|
||||
@@ -0,0 +1,6 @@
|
||||
WARNING: Unused method getMethod (query2.ql:33,14-23)
|
||||
WARNING: Unused method getResponse (query2.ql:47,13-24)
|
||||
| robonode/src/assets/raml/api.raml:8:3:72:14 | get: | /robots |
|
||||
| robonode/src/assets/raml/api.raml:15:5:72:14 | uriParameters: | /robots/{robotId} |
|
||||
| robonode/src/assets/raml/api.raml:24:7:54:4 | get: | /robots/{robotId}/commands |
|
||||
| robonode/src/assets/raml/api.raml:55:7:72:14 | get: | /robots/{robotId}/state |
|
||||
@@ -0,0 +1,56 @@
|
||||
import javascript
|
||||
string httpVerb() {
|
||||
result = "get" or result = "put" or
|
||||
result = "post" or result = "delete"
|
||||
}
|
||||
/** A RAML specification. */
|
||||
class RAMLSpec extends YAMLDocument, YAMLMapping {
|
||||
RAMLSpec() {
|
||||
getLocation().getFile().getExtension() = "raml"
|
||||
}
|
||||
}
|
||||
|
||||
/** A RAML resource specification. */
|
||||
class RAMLResource extends YAMLMapping {
|
||||
RAMLResource() {
|
||||
getDocument() instanceof RAMLSpec and
|
||||
exists (YAMLMapping m, string name |
|
||||
this = m.lookup(name) and
|
||||
name.matches("/%")
|
||||
)
|
||||
}
|
||||
/** Get the path of this resource relative to the API root. */
|
||||
string getPath() {
|
||||
exists (RAMLSpec spec |
|
||||
this = spec.lookup(result)
|
||||
) or
|
||||
exists (RAMLResource that, string p |
|
||||
this = that.lookup(p) and
|
||||
result = that.getPath() + p
|
||||
)
|
||||
}
|
||||
/** Get the method for this resource with the given verb. */
|
||||
RAMLMethod getMethod(string verb) {
|
||||
verb = httpVerb() and
|
||||
result = lookup(verb)
|
||||
}
|
||||
}
|
||||
/** A RAML method specification. */
|
||||
class RAMLMethod extends YAMLValue {
|
||||
RAMLMethod() {
|
||||
getDocument() instanceof RAMLSpec and
|
||||
exists (YAMLMapping obj |
|
||||
this = obj.lookup(httpVerb())
|
||||
)
|
||||
}
|
||||
/** Get the response specification for the given status code. */
|
||||
YAMLValue getResponse(int code) {
|
||||
exists (YAMLMapping obj, string s |
|
||||
obj = this.(YAMLMapping).lookup("responses") and
|
||||
result = obj.lookup(s) and
|
||||
code = s.toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
from RAMLResource rr
|
||||
select rr, rr.getPath()
|
||||
@@ -0,0 +1,2 @@
|
||||
WARNING: Unused method getMethod (query3.ql:32,14-23)
|
||||
| 1 |
|
||||
@@ -0,0 +1,45 @@
|
||||
import javascript
|
||||
string httpVerb() {
|
||||
result = "get" or result = "put" or
|
||||
result = "post" or result = "delete"
|
||||
}
|
||||
/** A RAML specification. */
|
||||
class RAMLSpec extends YAMLDocument, YAMLMapping {
|
||||
RAMLSpec() {
|
||||
getLocation().getFile().getExtension() = "raml"
|
||||
}
|
||||
}
|
||||
/** A RAML resource specification. */
|
||||
class RAMLResource extends YAMLMapping {
|
||||
RAMLResource() {
|
||||
getDocument() instanceof RAMLSpec and
|
||||
exists (YAMLMapping m, string name |
|
||||
this = m.lookup(name) and
|
||||
name.matches("/%")
|
||||
)
|
||||
}
|
||||
/** Get the path of this resource relative to the API root. */
|
||||
string getPath() {
|
||||
exists (RAMLSpec spec |
|
||||
this = spec.lookup(result)
|
||||
) or
|
||||
exists (RAMLResource that, string p |
|
||||
this = that.lookup(p) and
|
||||
result = that.getPath() + p
|
||||
)
|
||||
}
|
||||
/** Get the method for this resource with the given verb. */
|
||||
RAMLMethod getMethod(string verb) {
|
||||
verb = httpVerb() and
|
||||
result = lookup(verb)
|
||||
}
|
||||
}
|
||||
class RAMLMethod extends YAMLValue {
|
||||
RAMLMethod() {
|
||||
getDocument() instanceof RAMLSpec and
|
||||
exists (YAMLMapping obj |
|
||||
this = obj.lookup(httpVerb())
|
||||
)
|
||||
}
|
||||
}
|
||||
select 1
|
||||
@@ -0,0 +1,6 @@
|
||||
WARNING: Unused method getMethod (query4.ql:35,14-23)
|
||||
WARNING: Unused method getResponse (query4.ql:50,13-24)
|
||||
| robonode/src/assets/raml/api.raml:9:5:14:2 | description: \| |
|
||||
| robonode/src/assets/raml/api.raml:25:9:29:6 | description: \| |
|
||||
| robonode/src/assets/raml/api.raml:30:9:54:4 | description: \| |
|
||||
| robonode/src/assets/raml/api.raml:56:9:72:14 | description: \| |
|
||||
@@ -0,0 +1,60 @@
|
||||
import javascript
|
||||
|
||||
string httpVerb() {
|
||||
result = "get" or result = "put" or
|
||||
result = "post" or result = "delete"
|
||||
}
|
||||
|
||||
/** A RAML specification. */
|
||||
class RAMLSpec extends YAMLDocument, YAMLMapping {
|
||||
RAMLSpec() {
|
||||
getLocation().getFile().getExtension() = "raml"
|
||||
}
|
||||
}
|
||||
|
||||
/** A RAML resource specification. */
|
||||
class RAMLResource extends YAMLMapping {
|
||||
RAMLResource() {
|
||||
getDocument() instanceof RAMLSpec and
|
||||
exists (YAMLMapping m, string name |
|
||||
this = m.lookup(name) and
|
||||
name.matches("/%")
|
||||
)
|
||||
}
|
||||
/** Get the path of this resource relative to the API root. */
|
||||
string getPath() {
|
||||
exists (RAMLSpec spec |
|
||||
this = spec.lookup(result)
|
||||
) or
|
||||
exists (RAMLResource that, string p |
|
||||
this = that.lookup(p) and
|
||||
result = that.getPath() + p
|
||||
)
|
||||
}
|
||||
/** Get the method for this resource with the given verb. */
|
||||
RAMLMethod getMethod(string verb) {
|
||||
verb = httpVerb() and
|
||||
result = lookup(verb)
|
||||
}
|
||||
}
|
||||
|
||||
/** A RAML method specification. */
|
||||
class RAMLMethod extends YAMLValue {
|
||||
RAMLMethod() {
|
||||
getDocument() instanceof RAMLSpec and
|
||||
exists (YAMLMapping obj |
|
||||
this = obj.lookup(httpVerb())
|
||||
)
|
||||
}
|
||||
/** Get the response specification for the given status code. */
|
||||
YAMLValue getResponse(int code) {
|
||||
exists (YAMLMapping obj, string s |
|
||||
obj = this.(YAMLMapping).lookup("responses") and
|
||||
result = obj.lookup(s) and
|
||||
code = s.toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from RAMLMethod m
|
||||
select m
|
||||
@@ -0,0 +1,43 @@
|
||||
var path = require('path');
|
||||
|
||||
module.exports = function(grunt) {
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
jshint: {
|
||||
all: ['src/**/*.js']
|
||||
},
|
||||
|
||||
express: {
|
||||
options: {
|
||||
port: process.env.PORT || 3000,
|
||||
script: 'src/app.js'
|
||||
},
|
||||
development: {
|
||||
options: {
|
||||
node_env: 'development'
|
||||
}
|
||||
},
|
||||
test: {
|
||||
options: {
|
||||
node_env: 'test',
|
||||
port: 3001
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
express: {
|
||||
files: ['src/**/*.js', 'src/assets/raml/**/*.*'],
|
||||
tasks: ['jshint', 'express:development'],
|
||||
options: {
|
||||
spawn: false,
|
||||
atBegin: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
require('load-grunt-tasks')(grunt);
|
||||
|
||||
grunt.registerTask('default', ['watch']);
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
Copyright 2014 (c) MuleSoft, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "robonode",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"express": "3.4.4",
|
||||
"osprey": "0.1.1",
|
||||
"cors": ">=2.4.1",
|
||||
"bluetooth-serial-port": ">=1.1",
|
||||
"xml2js": ">=0.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "~0.4.2",
|
||||
"grunt-contrib-watch": "~0.5.3",
|
||||
"grunt-contrib-copy": "~0.4.1",
|
||||
"grunt-contrib-clean": "~0.5.0",
|
||||
"grunt-mocha-test": "~0.8.1",
|
||||
"mocha": "1.15.1",
|
||||
"should": "2.1.1",
|
||||
"grunt-express-server": "~0.4.13",
|
||||
"load-grunt-tasks": "~0.2.1",
|
||||
"supertest": "~0.8.2",
|
||||
"grunt-contrib-jshint": "~0.8.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// See http://support.robotis.com/en/product/dynamixel2/communication/crc.htm
|
||||
|
||||
var crcTable =
|
||||
[
|
||||
0x0000, 0x8005, 0x800F, 0x000A, 0x801B, 0x001E, 0x0014, 0x8011,
|
||||
0x8033, 0x0036, 0x003C, 0x8039, 0x0028, 0x802D, 0x8027, 0x0022,
|
||||
0x8063, 0x0066, 0x006C, 0x8069, 0x0078, 0x807D, 0x8077, 0x0072,
|
||||
0x0050, 0x8055, 0x805F, 0x005A, 0x804B, 0x004E, 0x0044, 0x8041,
|
||||
0x80C3, 0x00C6, 0x00CC, 0x80C9, 0x00D8, 0x80DD, 0x80D7, 0x00D2,
|
||||
0x00F0, 0x80F5, 0x80FF, 0x00FA, 0x80EB, 0x00EE, 0x00E4, 0x80E1,
|
||||
0x00A0, 0x80A5, 0x80AF, 0x00AA, 0x80BB, 0x00BE, 0x00B4, 0x80B1,
|
||||
0x8093, 0x0096, 0x009C, 0x8099, 0x0088, 0x808D, 0x8087, 0x0082,
|
||||
0x8183, 0x0186, 0x018C, 0x8189, 0x0198, 0x819D, 0x8197, 0x0192,
|
||||
0x01B0, 0x81B5, 0x81BF, 0x01BA, 0x81AB, 0x01AE, 0x01A4, 0x81A1,
|
||||
0x01E0, 0x81E5, 0x81EF, 0x01EA, 0x81FB, 0x01FE, 0x01F4, 0x81F1,
|
||||
0x81D3, 0x01D6, 0x01DC, 0x81D9, 0x01C8, 0x81CD, 0x81C7, 0x01C2,
|
||||
0x0140, 0x8145, 0x814F, 0x014A, 0x815B, 0x015E, 0x0154, 0x8151,
|
||||
0x8173, 0x0176, 0x017C, 0x8179, 0x0168, 0x816D, 0x8167, 0x0162,
|
||||
0x8123, 0x0126, 0x012C, 0x8129, 0x0138, 0x813D, 0x8137, 0x0132,
|
||||
0x0110, 0x8115, 0x811F, 0x011A, 0x810B, 0x010E, 0x0104, 0x8101,
|
||||
0x8303, 0x0306, 0x030C, 0x8309, 0x0318, 0x831D, 0x8317, 0x0312,
|
||||
0x0330, 0x8335, 0x833F, 0x033A, 0x832B, 0x032E, 0x0324, 0x8321,
|
||||
0x0360, 0x8365, 0x836F, 0x036A, 0x837B, 0x037E, 0x0374, 0x8371,
|
||||
0x8353, 0x0356, 0x035C, 0x8359, 0x0348, 0x834D, 0x8347, 0x0342,
|
||||
0x03C0, 0x83C5, 0x83CF, 0x03CA, 0x83DB, 0x03DE, 0x03D4, 0x83D1,
|
||||
0x83F3, 0x03F6, 0x03FC, 0x83F9, 0x03E8, 0x83ED, 0x83E7, 0x03E2,
|
||||
0x83A3, 0x03A6, 0x03AC, 0x83A9, 0x03B8, 0x83BD, 0x83B7, 0x03B2,
|
||||
0x0390, 0x8395, 0x839F, 0x039A, 0x838B, 0x038E, 0x0384, 0x8381,
|
||||
0x0280, 0x8285, 0x828F, 0x028A, 0x829B, 0x029E, 0x0294, 0x8291,
|
||||
0x82B3, 0x02B6, 0x02BC, 0x82B9, 0x02A8, 0x82AD, 0x82A7, 0x02A2,
|
||||
0x82E3, 0x02E6, 0x02EC, 0x82E9, 0x02F8, 0x82FD, 0x82F7, 0x02F2,
|
||||
0x02D0, 0x82D5, 0x82DF, 0x02DA, 0x82CB, 0x02CE, 0x02C4, 0x82C1,
|
||||
0x8243, 0x0246, 0x024C, 0x8249, 0x0258, 0x825D, 0x8257, 0x0252,
|
||||
0x0270, 0x8275, 0x827F, 0x027A, 0x826B, 0x026E, 0x0264, 0x8261,
|
||||
0x0220, 0x8225, 0x822F, 0x022A, 0x823B, 0x023E, 0x0234, 0x8231,
|
||||
0x8213, 0x0216, 0x021C, 0x8219, 0x0208, 0x820D, 0x8207, 0x0202
|
||||
];
|
||||
|
||||
// Given a data string like 'fffffd00c807000342000300', calculates the CRC16
|
||||
// and appends it to the end, low byte first
|
||||
module.exports = function addCRC(dataString)
|
||||
{
|
||||
var buffer = new Buffer(dataString, 'hex');
|
||||
var crcAccum = 0;
|
||||
for (var j=0; j<buffer.length; j++)
|
||||
{
|
||||
var i = ( (crcAccum >> 8) ^ buffer.readUInt8(j) ) & 0xFF;
|
||||
crcAccum = (crcAccum << 8) ^ crcTable[i];
|
||||
}
|
||||
var crc16 = (crcAccum + 0x10000).toString(16).substr(-4); // e.g. '7fda'
|
||||
crcHigh = crc16.substr(0, 2);
|
||||
crcLow = crc16.substr(2, 2);
|
||||
return dataString + crcLow + crcHigh;
|
||||
};
|
||||
@@ -0,0 +1,154 @@
|
||||
var express = require('express');
|
||||
var cors = require('cors');
|
||||
var path = require('path');
|
||||
var osprey = require('osprey');
|
||||
|
||||
var robot = require('./robot');
|
||||
var populateMotionData = require('./populateMotionData');
|
||||
|
||||
var app = module.exports = express();
|
||||
|
||||
app.use(express.bodyParser());
|
||||
app.use(express.methodOverride());
|
||||
app.use(express.compress());
|
||||
app.use(express.logger('dev'));
|
||||
|
||||
// Enable CORS, including preflighting
|
||||
app.use(cors());
|
||||
app.options('*', cors());
|
||||
|
||||
app.set('port', process.env.PORT || 3000);
|
||||
|
||||
function normalizeCommandName(name)
|
||||
{
|
||||
return name.replace(/\s+/g, '').toLowerCase();
|
||||
}
|
||||
|
||||
var motionData = {};
|
||||
populateMotionData(motionData, normalizeCommandName);
|
||||
|
||||
api = osprey.create('/api', app,
|
||||
{
|
||||
ramlFile: path.join(__dirname, '/assets/raml/api.raml'),
|
||||
logLevel: 'debug' // logLevel: off->No logs | info->Show Osprey modules initializations | debug->Show all
|
||||
});
|
||||
|
||||
// GET information about the commands
|
||||
api.get('/robots', function (req, res)
|
||||
{
|
||||
robot.findRobots(function onFound(err, robotDevices)
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
res.status(500).send({ error: err });
|
||||
}
|
||||
else
|
||||
{
|
||||
res.send(robotDevices);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// GET information about the commands
|
||||
// TODO: One day we'll need to ask the robot for its motion file
|
||||
// instead of assuming all robots use the assumedMotionFile.mtnx bundled here.
|
||||
api.get('/robots/:robotId/commands', function (req, res)
|
||||
{
|
||||
res.send(motionData);
|
||||
});
|
||||
|
||||
// POST a command for the robot to execute
|
||||
api.post('/robots/:robotId/commands', function (req, res)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var robotId = req.params.robotId;
|
||||
var commandNumber = req.param('number');
|
||||
var commandName = req.param('name');
|
||||
var normalizedCommandName = commandName ? normalizeCommandName(commandName) : null;
|
||||
var async = req.param('async') || false;
|
||||
|
||||
|
||||
if (commandName && !commandNumber)
|
||||
{
|
||||
commandNumber = motionData.flowsByName[normalizedCommandName];
|
||||
if (!commandNumber)
|
||||
{
|
||||
res.status(422).send({ error: 'Unrecognized command name' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (commandNumber in motionData.flowsByNumber)
|
||||
{
|
||||
var command = motionData.flowsByNumber[commandNumber];
|
||||
console.info('Executing command: ' + command.name + ' for ' + command.time + 'ms');
|
||||
robot.connect(robotId, function onConnect(err, btSerial)
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
res.status(500).send({ error: err });
|
||||
}
|
||||
else
|
||||
{
|
||||
robot.sendCommand(btSerial, Number(commandNumber));
|
||||
if (async) // respond immediately, with the execution time
|
||||
{
|
||||
console.log('Command sent, not waiting for execution to finish')
|
||||
res.status(202).send({ executionTimeInMs: command.time });
|
||||
}
|
||||
else // respond only after the execution time has passed
|
||||
{
|
||||
setTimeout(function ()
|
||||
{
|
||||
console.log('Command sent, finished waiting for execution');
|
||||
res.status(204).send('');
|
||||
}, command.time);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
res.status(422).send({ error: 'Unrecognized command number' });
|
||||
}
|
||||
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.error(e);
|
||||
res.status(500).send('');
|
||||
}
|
||||
});
|
||||
|
||||
// GET the state of the robot by reading the control table at a certain address
|
||||
// DOES NOT WORK RIGHT YET
|
||||
// Only asks the robot to read the data, but the data itself is only logged and not returned
|
||||
api.get('/robots/:robotId/state', function (req, res)
|
||||
{
|
||||
var robotId = req.params.robotId;
|
||||
var address = Number(req.param('address'));
|
||||
var bytesToRead = Number(req.param('bytesToRead'));
|
||||
console.info('Reading state: ' + [ address, bytesToRead ]);
|
||||
robot.connect(robotId, function onConnect(err, btSerial)
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
res.status(500).send({ error: err });
|
||||
}
|
||||
else
|
||||
{
|
||||
robot.readState(btSerial, address, bytesToRead);
|
||||
res.status(202).send('');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!module.parent)
|
||||
{
|
||||
var port = app.get('port');
|
||||
app.listen(port);
|
||||
console.log('listening on port ' + port);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#%RAML 0.8
|
||||
title: RESTful API for Robotis Darwin Mini
|
||||
version: v1
|
||||
mediaType: application/json
|
||||
baseUri: http://localhost:3000/api # optionally, change to http://<subdomain>.ngrok.com/api
|
||||
|
||||
/robots:
|
||||
get:
|
||||
description: |
|
||||
Retrieve information about any robots paired with your computer.
|
||||
Useful to see what's your robotId.
|
||||
responses:
|
||||
200:
|
||||
/{robotId}:
|
||||
uriParameters:
|
||||
robotId:
|
||||
description: |
|
||||
The last 4 characters of the bluetooth address of your Robotis device.
|
||||
Use `GET /robots` to see all paired robots so you know the valid options
|
||||
for `robotId`. Or, you can see its address on a Mac if you hold down 'option'
|
||||
while viewing the menu bar's bluetooth dropdown.
|
||||
e.g. if the address is 'b8-63-bc-00-12-16' your robotId is 1216
|
||||
/commands:
|
||||
get:
|
||||
description: |
|
||||
Returns information about the commands in the motion file currently loaded into the robot.
|
||||
responses:
|
||||
200:
|
||||
post:
|
||||
description: |
|
||||
Execute a command, and return when the robot has finished.
|
||||
You can submit either the number of the command or its name,
|
||||
based on the motion file currently loaded into the robot.
|
||||
If you submit both, only the number is used.
|
||||
By default, the API call returns only after the robot
|
||||
(is supposed to have) finished executing the command.
|
||||
If you specify `async` of `true`, the API call returns immediately,
|
||||
responding with the number of ms you should wait for the robot to finish.
|
||||
body:
|
||||
example: |
|
||||
{ "number": 3, "name": "Sit", "async": false }
|
||||
responses:
|
||||
202:
|
||||
description: |
|
||||
The command is now executing, and should finish within the number of ms
|
||||
returned in the response. This is the response when `async` was true.
|
||||
body:
|
||||
example: |
|
||||
{ "executionTimeInMs": 800 }
|
||||
204:
|
||||
description: |
|
||||
The command finished executing. This is the default behavior, when `async`
|
||||
was not specified or was `false`.
|
||||
/state:
|
||||
get:
|
||||
description: |
|
||||
DOES NOT WORK PROPERLY YET.
|
||||
It only logs the data it sees to the console.
|
||||
See http://support.robotis.com/en/product/dynamixel_pro/dynamixelpro/control_table.htm
|
||||
queryParameters:
|
||||
address:
|
||||
description: The address in the control table.
|
||||
type: number
|
||||
required: true
|
||||
example: 610
|
||||
bytesToRead:
|
||||
description: How many bytes to read starting from that address.
|
||||
type: number
|
||||
required: true
|
||||
example: 1
|
||||
responses:
|
||||
200:
|
||||
@@ -0,0 +1,39 @@
|
||||
var robot = require('./robot');
|
||||
var fs = require('fs');
|
||||
var parseString = require('xml2js').parseString;
|
||||
|
||||
// robotId should be the last 4 characters of the bluetooth address of your Robotis device
|
||||
// You can see its address on a Mac if you hold down 'option' while viewing the menu bar's bluetooth dropdown
|
||||
|
||||
// Example: node src/command.js 1216 19
|
||||
var args = process.argv.slice(2);
|
||||
var robotId = args[0]; // e.g. if the address is 'b8-63-bc-00-12-16' your robotId is 1216
|
||||
var command = args[1] || '1'; // 1 should be set to mean: return to initial position
|
||||
|
||||
parseString(fs.readFileSync(__dirname + '/assets/assumedMotionFile.mtnx', { encoding: 'utf8' }), function (err, result)
|
||||
{
|
||||
var flows = result.Root.BucketRoot[0].Bucket[0].callFlows[0].callFlow.map(function (obj) { return obj.$; });
|
||||
var flowsByNumber = {};
|
||||
flows.forEach(function (flow)
|
||||
{
|
||||
flowsByNumber[flow.callIndex] = flow.flow;
|
||||
});
|
||||
|
||||
// console.log(JSON.stringify(flowsByNumber, null, 2));
|
||||
|
||||
if (command in flowsByNumber)
|
||||
{
|
||||
console.log('>> ' + flowsByNumber[command]);
|
||||
robot.connect(robotId, function onConnect(err)
|
||||
{
|
||||
robot.sendCommand(Number(command));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log('Unrecognized command: ' + command);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
var fs = require('fs');
|
||||
var parseString = require('xml2js').parseString;
|
||||
|
||||
var pageData = {};
|
||||
var flowData = {};
|
||||
|
||||
var MILLIS_PER_FRAME = 8; // This is a guess based on reading docs such as http://support.robotis.com/en/software/roboplus/roboplus_motion/motionedit/stepedit/roboplus_motion_pausetime.htm
|
||||
var UNIT_PADDING_TIME = 100; // Another guess of how long to wait before motion is truly done
|
||||
var RECOVERY_TIME = 300; // Another guess of how long to wait before motion is truly done
|
||||
function elapsedTime(flow)
|
||||
{
|
||||
var millis = 0;
|
||||
if (flow.$.return == '-1') // This flow has a finite time and returns
|
||||
{
|
||||
var units = flow.units[0].unit;
|
||||
units.forEach(function (unit)
|
||||
{
|
||||
var pageName = unit.$.main;
|
||||
var steps = pageData[pageName].steps[0].step;
|
||||
var lastFrame = Number(steps[steps.length - 1].$.frame);
|
||||
millis += (lastFrame * MILLIS_PER_FRAME + UNIT_PADDING_TIME) * unit.$.exitSpeed * unit.$.loop;
|
||||
millis += RECOVERY_TIME;
|
||||
});
|
||||
}
|
||||
return millis;
|
||||
}
|
||||
|
||||
module.exports = function populateMotionData(motionData, normalizeCommandName)
|
||||
{
|
||||
motionData.flowsByNumber = {};
|
||||
motionData.flowsByName = {};
|
||||
parseString(fs.readFileSync(__dirname + '/assets/assumedMotionFile.mtnx', { encoding: 'utf8' }), function (err, parsed)
|
||||
{
|
||||
motionData.raw = parsed;
|
||||
|
||||
// Create a reference map of pages, to use in calculating the elapsed time for each command
|
||||
parsed.Root.PageRoot[0].Page.forEach(function (page)
|
||||
{
|
||||
pageData[page.$.name] = page;
|
||||
});
|
||||
|
||||
// Create a reference map of flows
|
||||
parsed.Root.FlowRoot[0].Flow.forEach(function (flow)
|
||||
{
|
||||
flowData[flow.$.name] = flow;
|
||||
});
|
||||
|
||||
var callFlows = parsed.Root.BucketRoot[0].Bucket[0].callFlows[0].callFlow;
|
||||
callFlows.forEach(function (callFlow)
|
||||
{
|
||||
var flowNumber = callFlow.$.callIndex;
|
||||
var flowName = callFlow.$.flow;
|
||||
var flow = flowData[flowName];
|
||||
motionData.flowsByNumber[flowNumber] =
|
||||
{
|
||||
name: flowName,
|
||||
time: elapsedTime(flow)
|
||||
};
|
||||
motionData.flowsByName[normalizeCommandName(flowName)] = flowNumber;
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
var addCRC = require('./addCRC');
|
||||
|
||||
var PREFIX = 'fffffd00'; // Defined by http://support.robotis.com/en/techsupport_eng.htm#product/dynamixel_pro/communication.htm
|
||||
var DYNAMIXEL_ID = 'c8'; // Not sure why, but I'm told this is 200
|
||||
var COMMAND_PACKET_LENGTH = '0700';
|
||||
var STATE_PACKET_LENGTH = '0700';
|
||||
var INSTRUCTION_READ = '02';
|
||||
var INSTRUCTION_WRITE = '03';
|
||||
var ADDRESS_EXECUTE = '4200'; // 66 decimal expressed in hex, low byte first; "66 is the address value that states the motion will be executed"
|
||||
|
||||
var ROBOT_NAME_REGEXP = /Robotis/i;
|
||||
|
||||
exports.findRobots = findRobots = function findRobots(onFound)
|
||||
{
|
||||
var btSerial = new (require('bluetooth-serial-port')).BluetoothSerialPort();
|
||||
btSerial.listPairedDevices(function (pairedDevices)
|
||||
{
|
||||
var robotDevices = pairedDevices.filter(function (device)
|
||||
{
|
||||
return (device.name.search(ROBOT_NAME_REGEXP) != -1);
|
||||
});
|
||||
|
||||
robotDevices.forEach(function (device)
|
||||
{
|
||||
device.robotId = device.address.substr(-5).replace(/\-/g, '');
|
||||
});
|
||||
|
||||
onFound(null, robotDevices);
|
||||
});
|
||||
};
|
||||
|
||||
// Setup a little registry of btSerial ports, one per robot
|
||||
var robotPorts = {};
|
||||
function getPort(robotId)
|
||||
{
|
||||
if (!(robotId in robotPorts))
|
||||
{
|
||||
robotPorts[robotId] = new (require('bluetooth-serial-port')).BluetoothSerialPort();
|
||||
}
|
||||
return robotPorts[robotId];
|
||||
}
|
||||
|
||||
// Find the Robotis device among the paired devices, and connect to it
|
||||
// robotId should be the last 4 characters of the bluetooth address of your Robotis device
|
||||
// You can see its address on a Mac if you hold down 'option' while viewing the menu bar's bluetooth dropdown
|
||||
// e.g. if the address is 'b8-63-bc-00-12-16' your robotId is 1216
|
||||
exports.connect = function connect(robotId, onConnect)
|
||||
{
|
||||
findRobots(function onFound(err, robots)
|
||||
{
|
||||
var candidates = robots.filter(function (r)
|
||||
{
|
||||
return (r.robotId == robotId);
|
||||
});
|
||||
|
||||
if (candidates.length === 0)
|
||||
{
|
||||
onConnect('No robots found!');
|
||||
}
|
||||
else
|
||||
{
|
||||
if (candidates.length > 1) console.info('Found ' + candidates.length + ' robots; using the first');
|
||||
robot = candidates[0];
|
||||
channel = robot.services[0].channel;
|
||||
var btSerial = getPort(robotId);
|
||||
connectOne(btSerial, robot.address, channel, onConnect);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Actually connect to the device, on its address and channel
|
||||
function connectOne(btSerial, address, channel, onConnect)
|
||||
{
|
||||
if (btSerial.isOpen())
|
||||
{
|
||||
console.info('already connected');
|
||||
onConnect(null, btSerial);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.info('connecting...');
|
||||
|
||||
btSerial.connect(address, channel, function()
|
||||
{
|
||||
console.info('connected');
|
||||
onConnect(null, btSerial);
|
||||
|
||||
// Log any returning data:
|
||||
btSerial.on('data', function (buffer)
|
||||
{
|
||||
console.info('<< ' + buffer.toString('hex'));
|
||||
});
|
||||
|
||||
}, function ()
|
||||
{
|
||||
onConnect('cannot connect', btSerial);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function toHexLowHigh(number)
|
||||
{
|
||||
var hex = (number + 0x10000).toString(16).substr(-4); // e.g. '0262'
|
||||
hexHigh = hex.substr(0, 2);
|
||||
hexLow = hex.substr(2, 2);
|
||||
return hexLow + hexHigh; // e.g. '6202'
|
||||
}
|
||||
|
||||
// write a command to the bluetooth port
|
||||
// command should be a number from 1-255
|
||||
exports.sendCommand = function sendCommand(btSerial, command, onSent)
|
||||
{
|
||||
var hexCommand = toHexLowHigh(command);
|
||||
|
||||
var packet = PREFIX + DYNAMIXEL_ID + COMMAND_PACKET_LENGTH + INSTRUCTION_WRITE + ADDRESS_EXECUTE + hexCommand;
|
||||
packet = addCRC(packet);
|
||||
console.info('Sending packet: ' + packet);
|
||||
|
||||
btSerial.write(new Buffer(packet, 'hex'), function (err, bytesWritten)
|
||||
{
|
||||
console.info('sent');
|
||||
if (err) console.error(err);
|
||||
if (onSent) onSent(err, btSerial);
|
||||
});
|
||||
};
|
||||
|
||||
// read a state from the bluetooth port
|
||||
// address and lengthInBytes should be numbers
|
||||
exports.readState = function readState(btSerial, address, lengthInBytes, onSent)
|
||||
{
|
||||
var addr16 = toHexLowHigh(address);
|
||||
var len16 = toHexLowHigh(lengthInBytes);
|
||||
|
||||
var packet = PREFIX + DYNAMIXEL_ID + STATE_PACKET_LENGTH + INSTRUCTION_READ + addr16 + len16;
|
||||
packet = addCRC(packet);
|
||||
console.info('Sending packet: ' + packet);
|
||||
|
||||
btSerial.write(new Buffer(packet, 'hex'), function (err, bytesWritten)
|
||||
{
|
||||
console.info('sent');
|
||||
if (err) console.error(err);
|
||||
if (onSent) onSent(err, btSerial);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
| tst.js:3:3:3:14 | "use strict" | This expression has no effect. |
|
||||
| tst.js:5:3:5:18 | point.bias == -1 | This expression has no effect. |
|
||||
@@ -0,0 +1,6 @@
|
||||
import javascript
|
||||
|
||||
from Expr e
|
||||
where e.isPure() and
|
||||
e.getParent() instanceof ExprStmt
|
||||
select e, "This expression has no effect."
|
||||
@@ -0,0 +1 @@
|
||||
| tst.js:5:3:5:18 | point.bias == -1 | This expression has no effect. |
|
||||
@@ -0,0 +1,7 @@
|
||||
import javascript
|
||||
|
||||
from Expr e
|
||||
where e.isPure() and
|
||||
e.getParent() instanceof ExprStmt and
|
||||
not e.getParent() instanceof Directive // new check
|
||||
select e, "This expression has no effect."
|
||||
@@ -0,0 +1,6 @@
|
||||
!function () {
|
||||
|
||||
"use strict"; // jshint ;_;
|
||||
|
||||
point.bias == -1;
|
||||
}();
|
||||
Reference in New Issue
Block a user