This page provides an in depth description of how the io.picolabs.prototypes ruleset works. This includes a description of the prototype map declaration.
Introduction
A prototype is an abstract definition of a pico or system of picos. You can think of it like an abstract class in C++ or Java. To instantiate a prototype, you would raise the wrangler:prototype_instance_requested event and provide this abstract definition of your system.
There are two primary things you can currently define in a Prototype:
- The picos to create in the system
- The subscriptions among the picos in this system, including subscriptions to other picos besides the ones created from the prototype.
How To Define a Prototype
A prototype declaration is a Map with two keys:
Key | Value Datatype | Description |
---|---|---|
new_picos | Array | This array is filled with maps which define each individual pico that will be created. See below. |
subscriptions | Array | This array is filled with tuples (for defining a single subscription amongst the new_picos array) or a map (for defining a subscription to some external pico). See below. |
new_picos
Here is an example array defining the new_picos in a prototype:
[ <-- start of the new_picos array { name: "uniqueName1", //names MUST be unique rids: [] //see }, { name: "uniqueName2", rids: [], //other attributes that will be passed into the wrangler child_creation event }, ... ]
The prototypes ruleset allows you to easily pass in any attributes you want during the creation process. Any extra attributes you provide (besides the ones explicitly listed above) are still sent through to the wrangler:child_creation event.
subscriptions
There are two different kinds of input into the subscriptions array:
- A tuple (an Array)
- A map
A tuple declaration creates a subscription from one pico defined in the new_picos array to another pico inside the new_picos array. A special exception is when the keyword "this" is used. "this" refers to the pico which initially raised the wrangler:prototype_instance_requested event, and may be referenced inside of a tuple declaration, but not a map declaration. The first two items in the tuple array define which picos the subscription will refer to and their roles in that subscription. See below for an example. There is also an optional third parameter which is a map of key value pairs to pass into the initial wrangler:subscription event. For your reference, the initial wrangler:subscription event is sent to the first pico in this tuple. A key feature of tuple declarations is that you don't need to manually accept the subscription to establish it. The prototypes ruleset will auto accept subscriptions for you.
The map declaration is used when you want a subscription from one pico defined in the new_picos array to a different pico currently existing outside this prototype. It looks identical to the attributes you must provide defined in the Wrangler Full API with the exception that you must also provide the "picoName" attribute, which refers to one of the picos created in the new_picos array. This is needed so the prototypes ruleset knows which pico you want the subscription in.
Here is an example array defining the subscriptions in a prototype:
[ <-- start of the subscriptions array //option 1: Define a tuple [ { picoName: "uniqueName1", //refers to a pico defined in the new_picos array Rx_role: "temperature sensor" //other attributes here are ignored and NOT propagated anywhere }, { picoName: "this", //special case that refers to the bootstrapping pico (the one which raises the initial wrangler:prototype_instance_requested event) Rx_role: "controller" //other attributes here are ignored and NOT propagated anywhere }, { //this third map is the only optional one //any other attributes you want to pass along to the wrangler:subscription event } ], //option 2: Define a map { picoName: "uniqueName1", wellKnown_Tx: <pico eci>, //as required in the wrangler API //any other attributes you want to provide as defined in the Wrangler Full API for the wrangler:subscription event }, ... //continue defining as many tuples/maps as desired ]
Full Prototype Example
{ "new_picos": [{ "name": "fan controller1", "rids": ["io.picolabs.fanController"] }, { "name": "fan controller2", "rids": ["io.picolabs.fanController"] }, { "name": "fan1", "rids": ["io.picolabs.fan"] }, { "name": "fan2", "rids": ["io.picolabs.fan"] }], "subscriptions": [ [ {"picoName": "fan controller1", "Rx_role": "controller"}, {"picoName": "fan1", "Rx_role": "fan"} ], [ {"picoName": "fan2", "Rx_role": "fan"}, {"picoName": "fan controller1", "Rx_role": "controller"} ], [ {"picoName": "this", "Rx_role": "controllerManager"}, {"picoName": "fan controller1", "Rx_role": "controller"} ], [ {"picoName": "fan controller2", "Rx_role": "controller"}, {"picoName": "this", "Rx_role": "controllerManager"} ], [ {"picoName": "fan1"}, {"picoName": "fan2"} ], { "picoName": "fan1", "wellKnown_Tx": <some eci> } ], //any other attributes provided here will be propagated in the wrangler:prototype_initialized event }
Under the Hood
This section details how the prototypes ruleset implements the above API. Only those who intend to contribute to the ruleset itself would need to know the following. NOTE: This is a high level overview. The ruleset itself contains many comments to help you understand the actual implementation better.
Throughout the entire process, a correlationID is used to keep track of each prototype instantiation event. This is necessary for the scatter-gather pattern, which will be referred to below. The correlationID may also be used after a prototype instantiation is finished or if an error occurred to gain more information about what happened.
Here are the general steps followed for instantiating a prototype
- Validate all the input
Since we don't want to worry about input validation halfway through the process, we begin by ensuring the following preconditions:
1) Each name in new_picos is unique (this includes unique among existing child picos of the bootstrapping pico as well).
2) The input doesn't use reserved names. The keyword "this" may not be used as the name of a pico in new_picos.
3) All tuples must have at least the first two map entries with picoName defined. The third map is optional, but if provided, it must be a map.
4) Tuple declarations refer to defined picos in new_picos. - Begin the scatter-gather pattern (for creating children)
Creates a record to determine how many picos have reported back to the bootstrapping pico. - Wait for all the child_initialized events with the given correlationID to be completed before creating the subscriptions
This raises the wrangler:prototype_picos_initialized event.
The eci of each new pico is recorded for use in the auto-accept events. - Start analyzing the subscription definitions
Foreach subscription, separate them into a tuple or a map event.
If there are no subscriptions defined in this system, then the wrangler:prototype_initialized event is automatically raised and the following steps are forgone. - Handle the tuple/map declarations individually
This handles the special case of "this" - Automatically accept the tuple subscription events
- Accepted tuple events report back to the bootstrapping pico
- Wait for the subscriptions to finish before raising the wrangler:prototype_initialized event