An attempt to port the Student Robotics API to Javascript to allow easier testing of algorithms by providing a 3D arena which robots can move around in that doesn't involve card stuck to table legs. It also allows testing to be performed before anything has been physically constructed.
The latest (possibly unstable) version of SRJS can be downloaded as a zip.
Alternatively, older versions of the code, which should be less prone to problems, have been tagged and can be downloaded from there. The higher the tag number, the more recent the code.
SRJS will run in the latest versions of Chrome and Firefox. Switching tabs while it is running will cause strange things to happen. Vision code will not work correctly unless your graphics card supports WebGL.
Some of the examples use the console to display output, so you'll need to open it to see anything happening.
The examples can be run in the browser here.
See examples/boilerplate.html for an annotated layout which can be used as the basis of code you write.
When writing code for the robot within the main()
and initialise()
functions, this
and robot
refers to the Robot being controlled. In some cases, such as within a forEach loop, it's necessary to pass one of the two as a parameter if this
is to reference the correct object.
Used to create a property with a given name which will keep its value between calls to robot.main()
, while allowing multiple robots to use the same variable names.
robot.createProperty( 'hitAWall', false ); // create the property
var shouldReverse = robot.hitAWall; // access the value and store it in another variable
robot.hitAWall = true; // change the value after initialisation
Motor Control (Python Docs)
Setting the speed of the motors is similar to in Python.
robot.motors[0]
is the left wheelrobot.motors[1]
is the right wheel
R.motors[0].target = 100
R.motors[1].target = 100
robot.motors[0].target = 100;
robot.motors[1].target = 100;
IO (Python Docs)
There are some notable differences between the Python API and SRJS.
In Python, io[IO_BOARD_NUMBER]
has two properties: input
and output
, which are arrays of input or output devices respectively.
In SRJS, io
is not an array, so [IO_BOARD_NUMBER]
is not required. There are also no input
or output
arrays. These are replaced with bumpSensor
and rangeFinder
arrays, which contain bump sensors and range finders in various positions around the robot starting in the front-left corner and working round the robot clockwise. The default number of each are stored in SRJS.bumpSensorsPerRobot
and SRJS.rangeFindersPerRobot
, although can be customised on a per-robot basis. Bump Sensors have a digital output and Range Finders have an analogue output.
# to read JointIO board 0's digital pin 0...
pin0 = R.io[0].input[0].d
# to read JointIO board 0's analogue pin 2...
pin2 = R.io[0].input[2].a
// check to see whether the front-left bump sensor is pressed
var pin0 = robot.io.bumpSensor[0].d;
// read the value from the front range finder
var pin2 = robot.io.rangeFinder[0].a;
Vision (Python Docs)
You must explicitly enable vision in SRJS to use it by adding the following to your code: SRJS.robotVision = true;
. Enabling vision will cause the framerate to drop.
In SRJS, markers are attached to the centre of objects. This means that if there is a 50x50x50cm robot with a marker on it, the position of the marker is judged as being at the centre of the robot rather than on one of its sides. It also means that markers to not have a vertices[]
property. In addition, buckets only have a single type of marker attached, SRJS.MARKER_BUCKET
, rather than different markers for the ends and sides of the bucket.
To compare the type of marker that is visible, the type name needs to be prefixed by SRJS
, so MARKER_ROBOT
in Python becomes SRJS.MARKER_ROBOT
in SRJS.
robot.see()
in SRJS accepts two parameters. The first is the width
of the image, the second the height
. This differs from the Python API which accepts a single res
parameter. These dimensions are optional and will default to an 800x600
image.
markers = R.see( res=(1280,1024) )
for marker in markers:
if marker.dist < 50:
print "A marker is close to the robot!"
if marker.info.type == MARKER_ROBOT:
print "I spy a robot"
var markers = robot.see( 1280, 1024 );
markers.forEach(function(marker){
if( marker.dist < 50 ){
console.log( 'A marker is close to the robot!' );
}
if( marker.info.type === SRJS.MARKER_ROBOT ){
console.log( 'I spy a robot' );
}
});
It is possible to set functions running in the background, independent of other code. This code will still run in the background even when the robot is waiting for a call to wait_for()
to return true.
robot.invokeRepeating()
takes three parameters: robot.invokeRepeating( callback, initialDelay, repeatRate )
If only two parameters are passed, so repeatRate
is missed out, it will default to the value given to initialDelay
.
import time, thread
def cheese():
while True:
time.sleep(1)
print "I'm a Robot"
thread.start_new_thread(cheese,())
// Within the initialise() function for the robot
var cheese = function(){
console.log("I'm a Robot");
};
robot.invokeRepeating( cheese, 1000 );
// OR:
robot.invokeRepeating(function(){
console.log("I'm a Robot");
}, 0, 1000 );
wait_for (Python Docs)
With the Python API, it's possible to stop the execution of the code to wait for something to occur. This cannot be done in SRJS, so queries have to be written differently.
time.sleep(3)
# do other things here
robot.wait_for( 3, function(){
// do other things here
});
robot.wait_for()
is a function which stops the robot's main()
loop code until the query passed as the first parameter is true. The second parameter is a function which will be called when the query becomes true. In SRJS, robot.wait_for()
can also be referenced as robot.waitFor()
or robot.Yield()
.
The syntax to yield for non-sleep events is noticeably different to Python.
# Wait for digital input 3 on JointIO board 0 to become digital '1'
wait_for( R.io[0].input[0].query.d == 1 )
# Wait for the reading of analogue input 3 on JointIO board 0 to exceed 1V
wait_for( R.io[0].input[1].query.a > 1 )
print "done!"
robot.wait_for( new SRJS.Query( ['robot.io.bumpSensor[0].d', 'eq', true] ), function(){
robot.wait_for( new SRJS.Query( ['robot.io.rangeFinder[0].a', 'gt', 1] ), function(){
console.log( 'done!' );
});
});
Queries are created by passing new SRJS.Query()
as the first parameter for robot.wait_for()
. The query is then passed an array with three parameters:
- A string containing the name of the variable to watch
- A string to define the type of comparison to perform. Can be one of the following:
eq
- wait for the values to be equal (===
)gt
- wait for the variable to become greater than the given value (>
)lt
- wait for the variable to become less than the given value (<
)ne
- wait for the variable to become not equal to the given value (!==
)gte
- wait for the variable to become greater than or equal to the given value (>=
)lte
- wait for the variable to become less than or equal to the given value (<=
)
- A value to perform the comparison against
It is possible to combine a number of events in a single query and wait for one or all of them to be true before the query as a whole returns true.
# OR:
wait_for( R.io[0].input[0].query.a < 2, R.io[0].input[0].query.a > 3 )
# ALTERNATE OR:
wait_for( Or( R.io[0].input[3].query.a < 2, R.io[0].input[3].query.a > 3 ) )
# AND:
wait_for( And( R.io[0].input[3].query.d == 1, R.io[0].input[2].query.d == 0 ) )
print "done!"
// OR:
robot.wait_for( new SRJS.Query( ['robot.io.rangeFinder[0].a', 'lt', 2],
['robot.io.rangeFinder[0].a', 'gt', 3] ), function(){
// ALTERNATE OR:
robot.wait_for( new SRJS.Query( 'or',
['robot.io.rangeFinder[3].a', 'lt', 2],
['robot.io.rangeFinder[3].a', 'gt', 3] ), function(){
// AND:
robot.wait_for( new SRJS.Query( 'and',
['robot.io.bumpSensor[2].d', 'eq', true],
['robot.io.bumpSensor[3].d', 'eq', false] ), function(){
console.log( 'done!' );
});
});
});
The first parameter is a string, either and
or or
to specify how the query should work. This is then followed by a number of arrays containing the comparisons. If there are multiple comparisons being made, but no string to specify how the query should operate, it will default to or
.
When an or
query returns, it will pass an array containing the status of the tracked items as a parameter to the callback. Any tracked items that do not return true when the query returns will be represented as null
. If a timeout has completed, it will be represented by true
.
robot.motors[0].target = 100;
robot.motors[1].target = 100;
robot.wait_for( new SRJS.Query( 'or',
['robot.io.rangeFinder[0].a', 'gt', 1],
3,
['robot.io.bumpSensor[0].d', 'eq', true]), function( status ){
console.log(status);
// Output (values may differ slightly): [null, true, null]
// Alternative Output (values may differ slightly): [1.0040160642570304, null, null]
});
Variable watchers and timeouts cannot be combined within a single query in the Python API for the 2012 SR competition.
To use both a timeout and a comparison within a single query, pass the number of seconds to wait as a parameter.
yield query.io[0].input[0].a > 1, query.timeout(3)
# do things here
robot.wait_for( new SRJS.Query( 'or',
['robot.io.rangeFinder[0].a', 'gt', 1],
3 ), function(){
// do things here
});
Power (Python Docs)
Not available in SRJS.
Servos (Python Docs)
Not available in SRJS.
Blob-based Vision (Python Docs)
This section describes the blob-based vision system (2011 competition and before). The marker-based vision system (2012 competition) works differently and is described above
You must explicitly enable vision in SRJS to use it by adding the following to your code: SRJS.robotVision = true;
. Enabling vision will cause the framerate to drop significantly.
ev = yield vision
if ev.was(vision):
for blob in ev.vision.blobs:
if blob.colour == RED:
print "Found red blob at " + str(blob.x) + ", " + str(blob.y)
var vision = robot.vision;
vision.blobs.forEach(function( blob ){
if( blob.colour === SRJS.RED ){
console.log( 'Found red blob at', blob.x, ',', blob.y );
}
}, robot);