Index ¦ Archives ¦ RSS

Cara ready for internal use!

After about a month of work, cara (Cap'n proto Alternative RPC API) is finally ready for internal use for Chain Reaction Mfg's main project.

The features required are finally working (and tested): 1. Send raw data and structs over the wire (like everybody else). 2. Send interfaces over the wire in both directions. 3. Support a reasonable io event loop.

The second one is the most important (and unique) feature for me. It means I can call a function on a server that returns another function (or rather, a set of functions) as well as some data.

Here's a hypothetical situation first:

You have a server that counts the seconds between calls to Start and Stop methods. In a normal RPC, you would have Start return a unique id (or, worse, send a 'unique' id to Start), and then call Stop with that id. This requires keeping track of the server and the id variables until you're done.

With cara in this situation, there is only a Start method initially, and calling it returns a new object with a Stop method. No extra variable to keep track of, just keep the object and call Stop when you're done. Even in this scenario, we've cut the variables to track in half.

And now a more realistic situation modeled after my needs:

I have a computer in charge of a physical motor. I would like to control it remotely, so I've exposed a Move method that returns a Stop method when called, as well as a Position method, etc. I also have a (potentially separate) computer in charge of sensors, and on a remote computer I want to first query all the sensors available, and then subscribe to their value updates.

Again with cara, the sensor server can return a list of objects in control of each sensor. Remotely, I can get information on them (name, value type, etc), and I can Query them and lastly Subscribe to them. To Subscribe, I can implement a streaming RPC, but that requires keeping a channel open for an arbitrarily long window. What if a sensor changes rarely? Should I keep an RPC call with an hour-long deadline or should I resort to polling?

Instead, I can Subscribe by sending my own sensor-handling function. Whenever it changes, the server calls my function, on the client, with the new value.

The resulting capnp schema:

interface Sensors {
  query @0 () -> (info :List(SensorInfo));
  struct SensorInfo {
    interface SensorController {
      query @0 () -> (value :Int32);  # Assuming it returns Int32 here, for simplicity.
      interface SensorSubscriber {
        update @0 (value :Int32) -> ();
      }
      interface SensorCanceller {
        cancel @0 () -> ();
      }
      subscribe @1 (subscriber :SensorSubscriber) -> (canceller :SensorCanceller);
    }
    name @0 :Text;
    controller @1 :SensorController;
    # ... stuff like value type, resolution, etc ...
  }
}

And the resulting cara code (in Python):

@tornado.gen.coroutine
def subscriber(sensor_server_endpoint, io_loop):
  client = pseud.Client(b'routing_id', io_loop=io_loop, security_plugin='plain', user_id=b'ignored', password=b'ignored')
  client.connect(sensor_server_endpoint)
  client = Sensors(cara_pseud.setup_client(client))

  sensors = yield client.query()
  sensor = sensors[0]  # Assume we have a sensor
  print('Sensor:', sensor.name)
  value = yield sensor.controller.query()
  print('Value:', value)
  ctrlr = sensor.controller

  # Since SensorSubscriber has only one method, we can also use a function directly.
  def subscribe(value):
    print('%s updated: %d' % (sensor.name, value))
  canceller = yield ctrlr.subscribe(subscribe)
  # Cancel in 5 seconds
  io_loop.add_timeout(datetime.timedelta(seconds=5), canceller.cancel)

© Fahrzin Hemmati. Built using Pelican. Theme by Giulio Fidente on github.