Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: compatibility warning


Warning
titleCompatibility

This page has not been adjusted for version 1.0 of the pico-engine

Having a pico that maintains a collection of other picos is a pattern that is often useful.

...

When cleanup is needed, this rule will be selected. Line 3 gets all the member subscriptions and the rest of the rule loops over them, with each one in turn bound to the name subs. Lines 5-6 removes each subscription. Line 7, which is only evaluated when handling the last subscription, signals the collection pico's readiness for deletion.

Creating a collection pico

We will show the creation of a collection pico, and providing it with member picos, using this example setup. Here we see a pico named "enMotion ITB", which we will call the "building pico", with four and twenty child picos. Each child pico has a tag id (visible as the pico name (ex. "ITB1208")) and a room name (not visible here, but maintained by a ruleset in the building pico). We've laid out the building pico to leave room for some collection picos which we will be creating. Its child picos are laid out automatically beside it.

Image Removed

We add a special ruleset to the building pico just for the purpose of creating collection picos. This rulest can be installed in the pico when a collection is needed, do its work, and then be removed from the pico until another collection is needed.

Identify child picos to be collected

This KRL code will provide an array of picos which we want in a collection. For simplicity, we are going to use a prefix of a child pico's room name, but any logic could be used to identify them.

Code Block
linenumberstrue
  meta {
    use module edu.byu.enMotion.building alias building
    shares __testing, by_prefix
  }
  global {
    __testing = { "queries": [ { "name": "__testing" },
                               { "name": "by_prefix", "args": [ "name_prefix" ] } ],
                  "events": [ {"domain":"grouper","type":"creation","attrs":["name_prefix"]}] }
    by_prefix = function(name_prefix) {
      prefix_re = ("^"+name_prefix).as("RegExp");
      building:dispenser_rooms().filter(function(v){v like prefix_re})
    }
    ...
  }

We'll use the primary ruleset of the building pico, with alias building, which provides a function dispenser_rooms. It computes a map from each child pico's tag_id to its room_name. We get the map in line 11, and return a map with only those entries whose room_name has the given name_prefix. This is done using the string operator like based on a regular expression built from the provided prefix.

Notice that we have allowed this function to be called from the "Testing" tab of the UI for convenience in seeing which child picos will be selected. This is done by sharing the function in line 3, and including it in the list of __testing queries (line 7).

Image Removed

Here we verify that we have the desired child picos.

Creating the collection pico

...

Note
titlemade up events

Note that the events wrangler:deletion_imminent and wrangler:ready_for_deletion are fictional, in the sense that Wrangler does not raise the former event before a pico is deleted (whether manually or programmatically) and does not listen for the latter event. To use this rule, you would first send the first event (likely from the "Testing" tab) and then manually delete your collection pico.

Ruleset

The complete code for this ruleset can be found at https://raw.githubusercontent.com/Picolab/pico-engine/master/packages/pico-engine/krl/io.picolabs.collection.krl

Creating a collection pico

We will show the creation of a collection pico, and providing it with member picos, using this example setup. Here we see a pico named "enMotion ITB", which we will call the "building pico", with four and twenty child picos. Each child pico has a tag id (visible as the pico name (ex. "ITB1208")) and a room name (not visible here, but maintained by a ruleset in the building pico). We've laid out the building pico to leave room for some collection picos which we will be creating. Its child picos are laid out automatically beside it.

Image Added

We add a special ruleset to the building pico just for the purpose of creating collection picos. This rulest can be installed in the pico when a collection is needed, do its work, and then be removed from the pico until another collection is needed.

Identify child picos to be collected

This KRL code will provide an array of picos which we want in a collection. For simplicity, we are going to use a prefix of a child pico's room name, but any logic could be used to identify them.

Code Block
linenumberstrue
  meta {
    use module edu.byu.enMotion.building alias building
    shares __testing, by_prefix
  }
  global {
    __testing = { "queries": [ { "name": "__testing" },
                               { "name": "by_prefix", "args": [ "name_prefix" ] } ],
                  "events": [ {"domain":"grouper","type":"creation","attrs":["name_prefix"]}] }
    by_prefix = function(name_prefix) {
      prefix_re = ("^"+name_prefix).as("RegExp");
      building:dispenser_rooms().filter(function(v){v like prefix_re})
    }
    ...
  }

We'll use the primary ruleset of the building pico, with alias building, which provides a function dispenser_rooms. It computes a map from each child pico's tag_id to its room_name. We get the map in line 11, and return a map with only those entries whose room_name has the given name_prefix. This is done using the string operator like based on a regular expression built from the provided prefix.

Notice that we have allowed this function to be called from the "Testing" tab of the UI for convenience in seeing which child picos will be selected. This is done by sharing the function in line 3, and including it in the list of __testing queries (line 7).

Image Added

Here we verify that we have the desired child picos.

Creating the collection pico

This KRL code will create the collection pico.

Code Block
linenumberstrue
  rule collection_needed {
    select when grouper creation
    pre {
      name_prefix = event:attr("name_prefix");
      group_name = name_prefix + " Rooms";
      rids = "io.picolabs.collection;io.picolabs.subscription";
      child_specs = { "name": group_name, "name_prefix": name_prefix,
        "rids": rids, "color": "#002e5d" };
    }
    fired {
      raise wrangler event "new_child_request" attributes child_specs;
    }
  }

We will name the collection pico by the prefix, a space, and "Rooms" (and will change the name manually later – we could have instead added another event attribute for the desired name). Line 2 selects on a particular event domain and type (which are also available in the "Testing" tab because of an entry in events of __testing). Line 11 raises the event which will create the collection pico as a child of the building pico. The specific pico name, color, and rids are established in lines 5-8. We also include the name_prefix which is not required for the child creation, but will be needed in a later step.

Notice that we request that wrangler install two rulesets into the new pico (along with the rulesets which are installed into every pico). These are the collection ruleset described earlier, and the subscription ruleset. When the new child pico is completely ready, it will send us (its parent pico) the wrangler:child_initialized event.

Adding members to the collection

This KRL code will run when the new collection pico is ready.

Code Block
linenumberstrue
  rule collection_neededsubscriptions {
    select when grouperwrangler creationchild_initialized
    pre {
      name_prefixeci = event:attr("name_prefixeci");
      groupcollection_nameeci = name_prefix + " Rooms";
 get_wellKnown_eci(eci);
    }
    rids = "edu.byu.enMotion.collection;io.picolabs.subscription";if collection_eci then noop();
    fired {
  child_specs   = { "name": group_name, "name_prefix": name_prefix,raise grouper event "need_subscriptions"
        attributes "rids": rids, "color": "#002e5d" };
    }
    fired event:attrs().put("collection_eci",collection_eci);
    }
  }

This rule obtains the "well known" ECI for the collection pico (line 5) using a function get_wellKnown_eci shown below. It then passes that on to the next rule by raising an internal event (lines 9-10).

Code Block
linenumberstrue
    get_wellKnown_eci = function(eci) {
      raiseurl wrangler event "new_child_request" attributes child_specs= meta:host+"/sky/cloud/"+eci+"/io.picolabs.subscription/wellKnown_Rx";
    }
  }

We will name the collection pico by the prefix, a space, and "Rooms" (and will change the name manually later – we could have instead added another event attribute for the desired name). Line 2 selects on a particular event domain and type (which are also available in the "Testing" tab because of an entry in events of __testing). Line 11 raises the event which will create the collection pico as a child of the building pico. The specific pico name, color, and rids are established in lines 5-8. We also include the name_prefix which is not required for the child creation, but will be needed in a later step.

Notice that we request that wrangler install two rulesets into the new pico (along with the rulesets which are installed into every pico). These are the collection ruleset described earlier, and the subscription ruleset. When the new child pico is completely ready, it will send us (its parent pico) the wrangler:child_initialized event.

Adding members to the collection

This KRL code will run when the new collection pico is ready.

Code Block
linenumberstrue
  rule collection_subscriptions {
    select when wrangler child_initialized
    pre {
      eci = event:attr("eci");
      collection_eci = get_wellKnown_eci(eci);
    }
    if collection_eci then noop();
    fired {
      raise grouper event "need_subscriptions"
        attributes event:attr("rs_attrs").put("collection_eci",collection_eci);
    }
  }

This rule obtains the "well known" ECI for the collection pico (line 5) using a function get_wellKnown_eci shown below. It then passes that on to the next rule by raising an internal event (lines 9-10).

Code Block
linenumberstrue
    get_wellKnown_eci = function(eci) {
      url = meta:host+"/sky/cloud/"+eci+"/io.picolabs.subscription/wellKnown_Rx";
      http:get(url.klog("url")){"content"}.decode(){"id"}
    }

We had specified that the io.picolabs.subscription ruleset be installed in the new collection pico, so we can now query it for its well known ECI, using http:get. The result of this function will be the desired ECI if all goes well, and null otherwise.

This KRL code will respond to the internal event, expecting the collection_eci and the child_specs given to wrangler earlier (wrangler renames it rs_attrs for some reason).

Code Block
linenumberstrue
  rule collection_subscription_requests {
    select when grouper need_subscriptions
    foreach by_prefix(event:attr("name_prefix")) setting(room_name,tag_id)
    pre {
      eci = building:eci(tag_id);
      wellKnown_Tx = get_wellKnown_eci(eci).klog("wellKnown_Tx");
    }
    if wellKnown_Tx then every {
      event:send({"eci":eci,"domain":"wrangler","type":"subscription",
        "attrs": { "wellKnown_Tx": event:attr("collection_eci"),
          "Rx_role": "member", "Tx_role": "collection",
          "name": tag_id, "channel_type": "subscription" }
      })
    }
  }

This rule selects (line 2) on the internal event, and sets up a loop (line 3) over the desired member picos (produced by the by_prefix function). We get an ECI for each pico from the building pico (line 5). In lines 6 and 8 we ensure, for sanity, that the pico can do subscriptions. Finally (in lines 9-13) we send each soon-to-be member pico an event asking it to request a new subscription to the collection pico with the correct roles, and with the new channels created for the subscription named by the tag_id.

Remember that this rule is running in the building pico. The building pico thus introduces each of its prefix-matching child picos to the newly created collection pico.

This is how the new collection pico fits into things, after re-positioning it manually:

Image Removed

and after renaming manually (in the collection pico's "About" tab)

Image Removed

Repeating the action to create more collection picos give us a final configuration:

Image Removed

Using a collection pico

Suppose we wanted to use one of the collection picos to collect some information from its member picos.

To do this we would install another ruleset in the collection pico. It would include code like the following KRL:

Code Block
linenumberstrue
  meta {
    use module edu.byu.enMotion.collection alias collection
    shares some_function
  }
  global {
    some_function = function() {
      collection:members()// some further processing
    }
  }
  rule some_rule {
    select when something required
    foreach collection:members() setting(subs)
    // some action/postludes for each subs
  }

...

  http:get(url.klog("url")){"content"}.decode(){"id"}
    }

We had specified that the io.picolabs.subscription ruleset be installed in the new collection pico, so we can now query it for its well known ECI, using http:get. The result of this function will be the desired ECI if all goes well, and null otherwise.

This KRL code will respond to the internal event, expecting the collection_eci and the child_specs given to wrangler earlier.

Code Block
linenumberstrue
  rule collection_subscription_requests {
    select when grouper need_subscriptions
    foreach by_prefix(event:attr("name_prefix")) setting(room_name,tag_id)
    pre {
      eci = building:eci(tag_id);
      wellKnown_Tx = get_wellKnown_eci(eci).klog("wellKnown_Tx");
    }
    if wellKnown_Tx then every {
      event:send({"eci":eci,"domain":"wrangler","type":"subscription",
        "attrs": { "wellKnown_Tx": event:attr("collection_eci"),
          "Rx_role": "member", "Tx_role": "collection",
          "name": tag_id, "channel_type": "subscription" }
      })
    }
  }

This rule selects (line 2) on the internal event, and sets up a loop (line 3) over the desired member picos (produced by the by_prefix function). We get an ECI for each pico from the building pico (line 5). In lines 6 and 8 we ensure, for sanity, that the pico can do subscriptions. Finally (in lines 9-13) we send each soon-to-be member pico an event asking it to request a new subscription to the collection pico with the correct roles, and with the new channels created for the subscription named by the tag_id.

Remember that this rule is running in the building pico. The building pico thus introduces each of its prefix-matching child picos to the newly created collection pico.

This is how the new collection pico fits into things, after re-positioning it manually:

Image Added

and after renaming manually (in the collection pico's "About" tab)

Image Added

Repeating the action to create more collection picos give us a final configuration:

Image Added

Ruleset

The complete code for this ruleset can be found at https://raw.githubusercontent.com/Picolab/pico_lessons/master/collections/edu.byu.enMotion.building.grouper.krl

Using a collection pico

Suppose we wanted to use one of the collection picos to collect some information from its member picos.

To do this we would install another ruleset in the collection pico. It would include code like the following KRL:

Code Block
linenumberstrue
  meta {
    use module io.picolabs.collection alias collection
    shares some_function
  }
  global {
    some_function = function() {
      collection:members()// some further processing
    }
  }
  rule some_rule {
    select when something required
    foreach collection:members() setting(subs)
    // some action/postludes for each subs
  }

and specify additional processing in some_function (defined in lines 6-8) that it shares (in line 3) to endpoints, and/or specify rules like some_rule (defined in lines 10-14) that take action on the collection's members when certain events occur.

When a member is added

Since members of the collection are determined by subscriptions, your ruleset can listen for wrangler:subscription_added events to do something when a new member joins your collection. Since your pico might participate in other subscriptions, only those in which the roles are "collection" and "member" would be applicable.

Code Block
linenumberstrue
  rule new_member {
    select when wrangler subscription_added
    pre {
      pertinent = event:attr("Rx_role")=="collection"
                && event:attr("Tx_role")=="member";
    }
    if pertinent then noop();
    fired {
      raise collection event "new_member" attributes event:attrs
    }
  }

The event attributes available are those surrounding a subscription in general. Some of these might be useful when a new member arrives into your collection

Tip
titleUseful event attributes

Tx is the event channel identifier (ECI) which your collection would use to send events (use event:send) or queries (use Wrangler:skyQuery) to the member pico

name may be useful as a human-readable identifier for the member pico

Id is a unique identifier of the subscription

When a member is removed

Your ruleset might wish to listen to the event wrangler:subscription_removed which will be raised when a subscription is removed from your pico. Such events which are applicable to membership in your collection could then be handled appropriately.

Code Block
linenumberstrue
  rule new_member {
    select when wrangler subscription_removed
    pre {
      pertinent = event:attr("Rx_role")=="collection"
                && event:attr("Tx_role")=="member";
    }
    if pertinent then noop();
    fired {
      raise collection event "member_removed" attributes event:attrs
    }
  }