Working With Robots

This guide should help you interact with Cylon's Robot class.

Here's the example Robot we'll be using:

Cylon.robot({
  connections: {
    arduino: { adaptor: 'firmata', port: '/dev/ttyACM0' }
  },

  devices: {
    led: { driver: 'led', pin: 13 },
    button: { driver: 'button', pin: 2 }
  },

  work: function(my) {
    my.button.on('push', function() {
      my.led.toggle();
    });
  }
});

my vs. this

A robot's work block is run in the context of the robot. To make code with closures and other nesting work better, we also pass it as an argument to the work block. So these two works blocks are equivalent in behaviour:

  work: function() {
    this.button.on("push", function() {
      this.led.toggle();
    }.bind(this));
  }

  // is equal to

  work: function(my) {
    my.button.on("push", function() {
      my.led.toggle();
    });
  }

Programmers preferring more idiomatic JavaScript might prefer to use this, but to simplify things in our examples, we will use the my-argument style.

Connections

When initializing a robot, you pass a connections object:

Cylon.robot({
  connections: {
    loopback: { adaptor: "loopback" },
    arduino: { adaptor: "firmata", port: "/dev/ttyACM0" }
  }
});

Inside a work block, the Robot's connections can be accessed through the connections attribute, which contains a mapping of names to connections.

Additionally, if they're not overridden by a Device's name, Connections are directly aliased as properties on the Robot for convenient access.

Cylon.robot({
  connections: {
    loopback: { adaptor: "loopback" },
    arduino: { adaptor: "firmata", port: "/dev/ttyACM0" }
  },

  devices: {
    loopback: { driver: 'ping', connection: 'loopback' }
  }

  work: function(my) {
    my.connections;
    // {
    //   loopback: [Connection],
    //   arduino:  [Connection]
    // }

    my.arduino;  // [Connection]
    my.loopback; // [Device]
  }
});

Devices

Devices, for the most part, behave similarly to Connections. When initializing a robot, you pass a devices object:

Cylon.robot({
  devices: {
    led: {
      driver: "led",
      pin: "13"
    }
  }
});

// vs

Cylon.robot({
  devices: {
    led: { driver: 'led', pin: 13 },
    button: { driver: 'button', pin: 2 }
  }
});

Inside a work block, the Robot's devices can be accessed through the devices attribute, which contains a mapping of names to devices.

Devices are also aliased directly as properties on the robot. This aliasing overrides connection aliases if they happen to have the same name.

Cylon.robot({
  connections: {
    loopback: { adaptor: 'loopback' },
    arduino: { adaptor: 'firmata', port: '/dev/ttyACM0' }
  },

  devices: {
    led: { driver: 'led', pin: 13, connection: 'arduino' },
    button: { driver: 'button', pin: 2, connection: 'arduino' },
    loopback: { driver: 'ping', connection: 'loopback' }
  },

  work: function(my) {
    my.devices;
    // {
    //   led: [Device],
    //   button: [Device],
    //   loopback: [Device],
    // }

    my.led;      // [Device]
    my.button;   // [Device]

    my.loopback; // [Device]
    my.arduino;  // [Connection]
  }
});

Commands

Robots in Cylon can have commands that can be called from the API.

There's three main ways to define commands:

Automatically - by default, any non-standard property on a Robot that is a function will be made accessible to the API. For example:

Cylon.robot({
  name: "testbot",

  connections: {
    "loopback": { adaptor: "loopback" }
  },

  devices: {
    "ping": { driver: "ping" }
  },

  work: function() {},

  doAThing: function() {
    console.log("I did a thing!");
  }
});

// GET /api/robots/testbot/commands
// => ['doAThing']

As an Object - providing a commands object in the Robot options:

Cylon.robot({
  name: "testbot",

  connections: {
    "loopback": { adaptor: "loopback" }
  },

  devices: {
    "ping": { driver: "ping" }
  },

  work: function() {},

  commands: {
    do_a_thing: function() { this.doAThing.call(this); }
  }

  doAThing: function() {
    console.log("I did a thing!");
  }
});

// GET /api/robots/testbot/commands
// => ['do_a_thing']

As a Function - this option allows for easier scoping, as you can directly reference Robot properties. The function must return an Object or an error will be thrown.

Cylon.robot({
  name: "testbot",

  connections: {
    "loopback": { adaptor: "loopback" }
  },

  devices: {
    "ping": { driver: "ping" }
  },

  work: function() {},

  commands: function() {
    var commands = {};

    commands.do_a_thing = this.doAThing

    return commands;
  }

  doAThing: function() {
    console.log("I did a thing!");
  }
});

// GET /api/robots/testbot/commands
// => ['do_a_thing']

Utilities

Cylon provides a few helpful utility functions that can be used in your Robot's work block.

Number#seconds is a new prototype method on Number, allowing for easier setting of intervals/timeouts. For example:

(10).seconds(); // 10000
(60).seconds(); // 60000

setInterval(randomFunction, (5).seconds()); // will repeat every 5s

The Number being used needs a wrapper set of parentheses, due to how JS the JS parser works. Alternatively, 10..seconds() will have the same effect.

Number#second functions the same, but is meant to make calling it on 1 look more natural. e.g. (1).seconds(); vs. (1).second();

Number#fromScale converts a number from a provided scale to a (0..1) scale. If the number exceeds the provided scale, either 0 or 1 will be returned. For example:

(100).fromScale(0, 200); // 0.5
(100).fromScale(0, 50);  // 1
(-100).fromScale(0, 50); // 0

Number#toScale converts a number from a (0..1) scale to the provided scale. If the number exceeds the provided scale, either the min or max end of the scale will be returned. For example:

(0.5).toScale(0, 200); // 100
(10).toScale(0, 50);   // 50
(-1).toScale(0, 50);   // 0

every is a wrapper around setInterval, with the aim of making the semantics easier for new developers. It's intended to be used with the Number#seconds helper method. Examples:

every((5).seconds(), function() {
  console.log("Hello world!");
});

// is equivalent to:

setInterval(function() {
  console.log("Hello world!");
}, 5000)

after is a wrapper around setTimeout, with similar semantics to every. Examples:

after((5).seconds(), function() {
  console.log("Hello world!");
});

// is equivalent to:

setTimeout(function() {
  console.log("Hello world!");
}, 5000)

constantly is a wrapper around setInterval, with a specified interval of 0 ms. Examples:

constantly(function() {
  console.log("Hello world!");
});

// is equivalent to:

setInterval(function() {
  console.log("Hello world!");

, 0)