Warning | ||
---|---|---|
| ||
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.
...
Here is KRL code which will accept subscription requests from other picos which are becoming members of this collection. We will use subscriptions subscription roles to identify the subscriptions of interest. Doing so allows the collection pico to participate in other kinds of subscriptions. The collection pico will play the role "collection"
and the role "member"
will be played by the picos we are collecting.
...
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.
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 | ||
---|---|---|
| ||
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).
Here we verify that we have the desired child picos.
Creating the collection pico
This KRL code will create the collection pico
Note | ||
---|---|---|
| ||
Note that the events |
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.
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 | ||
---|---|---|
| ||
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).
Here we verify that we have the desired child picos.
Creating the collection pico
This KRL code will create the collection pico.
Code Block | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
rule collection_neededsubscriptions { select when grouperwrangler creationchild_initialized pre { name_prefixeci = event:attr("name_prefixeci"); groupcollection_nameeci = name_prefix + " Rooms"; get_wellKnown_eci(eci); } if ridscollection_eci = "edu.byu.enMotion.collection;io.picolabs.subscription";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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 (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:
and after renaming manually (in the collection pico's "About" tab)
Repeating the action to create more collection picos give us a final configuration:
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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:
and after renaming manually (in the collection pico's "About" tab)
Repeating the action to create more collection picos give us a final configuration:
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
|
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 | ||
---|---|---|
| ||
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
}
} |