Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Learning Objectives

After completing this lesson, you will be able to do the following:

  • Explain what a pico subscription is

  • Understand pico-to-pico

    relationships based on

    subscriptions

  • Use the developer tools to manage

    pico subscriptions

    pico subscriptions

  • Programmatically link picos using

    a subscription

    a subscription and have them interact. 

Prerequisites 

You should have already completed the following lessons:

Contents

Table of Contents
maxLevel2

Subscriptions

Subscriptions play an important role in building systems of picos. In Pico-Based Systems Lesson, you created child picos. If the parent-child relationship were the only one that existed, we could create hierarchies of picos, but not peer-to-peer systems of picos. Subscriptions allow us to establish relationships between any two picos. 

A subscription represents a relationship between two picos. It has a name and two channels, one from each pico participating in the subscription to the other. Each side in the subscription can be given a role to further identify the purpose of the subscription. 

Image Removed

Managing Subscriptions Programmatically

Wrangler provides functions, actions, and rules for managing subscriptions from within your KRL code.

In this section, we'll use a simple example. Here the owner pico has three child picos, as shown by the familiar black parent-child edges. However, let's say the "Mischief Pico" needs to communicate with the other two picos.

Image Removed

The subscriptions which we will create to enable this pico to pico communication are shown as dashed magenta lines. (This diagram is available at localhost:8080/?subscr )

Info
titleEngine Compatibility

Very soon, the ability to manage subscriptions will be built-in to every pico. Right now, you will need to install the ruleset with RID "Subscriptions" into the three picos which will participate in subscriptions. This can be done in the Rulesets tab of each pico, by clicking on the "install ruleset" button beside the dropdown when it shows "Subscriptions".

The "Subscriptions" ruleset is automatically registered in the pico engine during start-up, because it exists (in a file named "io.picolabs.subscriptions.krl") in the folder name "krl". However, it is not automatically installed in any pico. Soon, it will be.

Since most of the heavy lifting is done by the Subscriptions ruleset, it is easy to initiate, approve, and use subscriptions inside of your own rulesets.

Prepare for this section by manually creating three child picos of your owner pico.

  1. Install this ruleset into your owner pico: https://raw.githubusercontent.com/Picolab/pico_lessons/master/subscriptions/mischief.owner.krl
  2. Install this ruleset into your Mischief pico: https://raw.githubusercontent.com/Picolab/pico_lessons/master/subscriptions/mischief.krl, and 
  3. Finally, install this ruleset into each of your thing picos: https://raw.githubusercontent.com/Picolab/pico_lessons/master/subscriptions/mischief.thing.krl

Initiating subscriptions by introduction

It is possible to initiate a subscription with a rule running in a pico which already has in its possession the ECI's of the two picos which need to be related by the subscription. Once you have set up the three child picos, Mischief, Thing1, and Thing2, and installed their respective rulesets, study the code and prepare for the introductions.

The owner pico already has all three of the other ECI's in its array of children (maintained by the io.picolabs.pico aka Wrangler ruleset), but it doesn't know which is which. So we added this rule to allow it to distinguish between the Mischief pico (which identifies itself by ECI) and the Thing1 and Thing2 picos.

Code Block
  rule mischief_who {
    select when mischief who
    pre {
      mischief = event:attr("eci")
      things = wrangler:children().map(function(v){v.eci})
                                  .filter(function(v){v != mischief})
    }
    always {
      ent:mischief := mischief;
      ent:things := things
    }
  }

The ruleset for the Mischief pico sets up a button "mischief/identity" in that pico's Testing tab which will send this event to the owner pico. Navigate to the Testing tab of the Mischief pico and click the button. Doing so will send the event mischief:who to the owner pico, and that event will trigger the rule shown above. You can check the result of this calculation by looking at the entity variables held by the mischief.owner ruleset in the Rulesets tab of the Owner Pico.

Once the owner pico has all of the ECI's, the code to introduce the Mischief pico to each of the thing picos is straight-forward.

Code Block
  rule mischief_subscriptions {
    select when mischief subscriptions
    pre {
      mischief = ent:mischief
      thing1 = ent:things[0]
      thing2 = ent:things[1]
    }
    if mischief && thing1 && thing2 then every {
      event:send(
        { "eci": mischief, "eid": "subscription",
          "domain": "wrangler", "type": "subscription",
          "attrs": { "name": "thing1",
                     "name_space": "mischief",
                     "my_role": "controller",
                     "subscriber_role": "thing",
                     "channel_type": "subscription",
                     "subscriber_eci": thing1 } } )
      event:send(
        { "eci": mischief, "eid": "subscription",
          "domain": "wrangler", "type": "subscription",
          "attrs": { "name": "thing2",
                     "name_space": "mischief",
                     "my_role": "controller",
                     "subscriber_role": "thing",
                     "channel_type": "subscription",
                     "subscriber_eci": thing2 } } )
    }
  }

The ruleset also sets up a button in the owner pico's Testing tab, "mischief/subscriptions" to send the event that causes this rule to evaluate. Navigate to the Testing tab for the Owner Pico to click on this button.

The final step is that a rule selecting on wrangler/inbound_pending_subscription_added is in each of the thing picos (the ruleset mischief.thing), and this will complete the subscription process. You can see the results by refreshing the UI and navigating to the Subscriptions tab for any of the picos involved in one of the two subscriptions.

Initiating subscriptions by direct request

A pico can directly initiate a subscription. The subscription is initiated by a rule installed in the pico raising the wrangler:subscription event in the postlude block of one of its rules.

Code Block
      raise wrangler event "subscription"
          with name = "thing1"
               name_space = "mischief"
               my_role = "controller"
               subscriber_role = "thing"
               channel_type = "subscription"
               subscriber_eci = thing1

As in the case of introductions, the target pico must have a rule selecting on wrangler/inbound_pending_subscription_added which will complete the subscription process by raising the wrangler/pending_subscription_approval event.

Writing rules that use subscriptions

Suppose that we wanted the Mischief pico to respond to a mischief/hat_lifted event and send the same event to all of the thing picos that it controls. Assuming that you have arranged to use Subscriptions as a module in the meta block, as in

Code Block
  use module Subscriptions

a rule like this one would accomplish this:

...

Motivation

In the previous lesson, you learned about creating pico parent-child relationships. However, in distributed systems, not everything is a parent-child relationship. In fact, there are many systems which are based off of peer-to-peer relationships.

Each node in a peer-to-peer system needs some way to communicate with a different node independent of a parent child hierarchy. For example, a flower store may wish to create a relationship with a delivery driver, so that the store can broadcast a new delivery request, and the driver can bid on delivering that request. This is where more general relationships, which we call subscriptions, come in. 

Subscriptions are a two-way link from one pico to another independent of the parent-child hierarchy. This lesson will show you how to create these subscriptions among picos so that they may communicate directly with each other rather than through some other medium.

Prerequisites 

You should have already completed the following lessons:

Contents

Table of Contents
maxLevel2

Video Tutorial

Widget Connector
overlayyoutube
_templatecom/atlassian/confluence/extra/widgetconnector/templates/youtube.vm
width400px
urlhttps://www.youtube.com/watch?v=KxjpDqlNXmo&feature=youtu.be
height300px

Subscriptions

A subscription has a name and two channels, one from each pico participating in the subscription to the other. Each side in the subscription can be given a role to further identify the purpose of the subscription relationship. 

...

In this diagram, from the perspective of pico A, the top channel is the one on which it receives incoming events and queries, so A knows this channel as Rx, and the bottom channel is the one on which pico A transmits events and queries to pico B, so A knows this channel as Tx. Conversely, pico B knows the top channel as Tx and the bottom channel as Rx. Each pico has its own perspective on the subscription.

Subscriptions may also include a role for each pico. From the perspective of pico A, its role in the subscription is known as Rx_role while pico A knows the role of pico B as Tx_role. On the other hand, from pico B's perspective, the role of pico A is Tx_role and its own role is Rx_role. Putting yourself in a pico's place, you can think of Rx_role as my role and Tx_role as the other's role.

Managing Subscriptions Manually with the Testing tab

The experimental setup

In this section, we'll continue the registration example. Here the owner pico has two child picos, one belonging to Jane, a student, and the other being the registration system.

...

What we need is a relationship between Jane's pico and each section in which she is registered.

...

The subscriptions which we will create to enable this pico to pico communication are shown here as dashed magenta lines. Here there is a relationship between Jane's pico and the pico for American Heritage 100 section one.

Prepare for this section by (re-)creating the registration system that you built in the previous lesson.

  1. Install this ruleset into your section collection pico: https://raw.githubusercontent.com/Picolab/pico_lessons/master/subscriptions/app_section_collection.krl

  2. Create a pico for a student

  3. Be sure the io.picolabs.subscription ruleset is installed in the student pico and in the section picos you have created

Initiating subscriptions by introduction

To establish a subscription relationship, wrangler will set up a new channel in each of the two picos. To bootstrap this process, we need to use an existing channel in each of the picos.

Finding a well-known channel

Start by determining the ECI of the "well-known" subscription channel of the section pico. Visit the section pico, select the ECI which is tagged "#wellknown_rx #tx_rx" and for the io.picolabs.subscription ruleset, click on the "wellKnown_Rx" button. In the Results section, you will see the channel. Double-click on the "id" value and copy it. We'll use it in the actual introduction step.

...

Setting up an invitation

Next, select your student pico, and go to its Testing tab, using its well-known ECI. Find the form for the wrangler:subscription event and provide some values:

  1. Paste the ECI of the section pico into the box labelled "wellKnown_Tx"

  2. Provide this pico's role: in the box labelled "Rx_role" enter "student"

  3. Provide the other pico's role: in the box labelled "Tx_role" enter "class"

  4. Provide a name for the subscription. For example, "Jane–A HTG 100-1"

  5. For the type, enter "subscription"

  6. Since both picos are running in the same pico-engine, you can leave the box labelled "Tx_host" blank

  7. Also leave the password blank (it is used with auto configuration, discussed elsewhere)

When you are ready, you screen should look like this, and you can click the "wrangler:subscription" button:

...

Creation of an inbound channel

Now, let's examine the situation. First, go to the student pico's Channels tab. Notice that there is a new channel created for this subscription. This is this pico's Rx channel (and as we'll see later also the other pico's Tx channel):

...

Outbound and Inbound subscriptions

Next, visit the Testing tab, and select the ECI of the new subscription channel. Click on the "outbound" button:

...

The subscription is considered "outbound" because it has been proposed to the other pico (identified by its well-known ECI), but not yet accepted by it. To manually accept the subscription, visit the other pico, the section pico.

Now if the subscription is "outbound" in this pico, it must be "inbound" in the section pico, right? To verify, visit the Testing tab of the section pico, and using its new Rx channel, click on the "inbound" button. Compare this view of the subscription with the outbound view in the student pico.

...

Note that the "Id" is the only field which has the same value on both ends of the subscription. This is because it is the ID of the subscription itself. There is only one subscription, but it has two ends. The "Rx_role" and "Tx_role" are swapped, as are the two ECIs, "Tx" and "Rx". You will note that the initiating pico does not yet have access to the other pico's ECI. This is because the subscription hasn't yet been accepted.

Accepting an inbound subscription

Double click on the "Id" value, scroll up until you see the form for the wrangler:pending_subscription_approval event, past in the ID value and click the button.

...

Then scroll back to the top and click on the "established" button to see that the subscription is now established.

...

Verifying an established subscription

Finally, visit the student pico's Testing tab, and verify that it too sees the subscription as established.

...

Notice that the student pico has two established subscriptions, both with the same role. Double check that the "Id" value is the same in this pico's (second) subscription and the section pico's subscription. The other values are swapped, reflecting the opposite perspective each end of the subscription has. You can also verify that the second "Rx" value is the same as the ECI selected in the Testing tab.

Managing Subscriptions Programmatically

In the previous section, you learned how to create a subscription manually, using the Testing tab. Here we will learn how to write KRL code to create an inbound subscription and accept it.

It is possible to initiate a subscription with a rule running in a pico which already has in its possession the ECI's of the two picos which need to be related by the subscription.

The well-known channel

Every pico has the io.picolabs.subscription ruleset installed. This ruleset, on installation, creates a well-known channel with a limited policy. This is by design so that the ECI of that channel can be widely shared, as it can only be used to initiate subscriptions.

In this example, we will write code in a ruleset for the student picos which will create a subscription to the Section Collection pico. Then we will use that to create a subscription between the student pico and the section picos in which they wish to enroll.

Our first problem will be to find the well-known ECI of the section pico we want. When we did this manually, we just went to the section pico and found its well-known ECI in the developer UI. But to use this well-known (so named) ECI programmatically, we have to be able to find it! It needs to be published somewhere, where we can get at it.

One way to do this is to have a new pico inform its parent pico of its well-known ECI, and then other picos can get it from there.

Recording well-known channels IDs for section picos

Here is a modification to a rule that is already in the app_section ruleset to do this:

Code Block
  rule pico_ruleset_added {
    select when wrangler ruleset_installed
      where event:attr("rids") >< meta:rid
    pre {
      section_id = event:attr("section_id")
      parent_eci = wrangler:parent_eci()
      wellKnown_eci = subs:wellKnown_Rx(){"id"}
    }
    event:send({"eci":parent_eci,
      "domain": "section", "type": "identify",
      "attrs": {
        "section_id": section_id,
        "wellKnown_eci": wellKnown_eci
      }
    })
    always {
      ent:section_id := section_id
    }
  }

The new lines are: 6, where we get the family channel ECI for our parent (the Section Collection pico); 7, where we get the well-known ECI from the subscription ruleset (be sure to include a use module io.picolabs.subscription alias subs line in the meta block); 10-16 which send a section:identify event to the parent pico.

And, then this new rule in the app_section_collection ruleset to react to that event:

Code Block
  rule accept_wellKnown {
    select when section identify
      section_id re#(.+)#
      wellKnown_eci re#(.+)#
      setting(section_id,wellKnown_eci)
    fired {
      ent:sections{[section_id,"wellKnown_eci"]} := wellKnown_eci
    }
  }

So now, the sections function of that ruleset will show us this kind of structure:

Code Block
{
  "C S 462-1": {
    "eci": "ckit4ayd20051qb2r8icddvwh",
    "wellKnown_eci": "ckit4aydf0054qb2radgwbbj2"
  },
  "C S 462-2": {
    "eci": "ckit4b5it005dqb2r3534e5l3",
    "wellKnown_eci": "ckit4b5j6005gqb2rawjb3ucs"
  },
  "A HTG 100-1": {
    "eci": "ckit4bbt6005pqb2rfkid1bdc",
    "wellKnown_eci": "ckit4bbti005sqb2ra8spfeqk"
  }
}

And the well-known ECIs for the sections are now better known. And when we need them, we can ask the Section Collection pico for them by name (aka the section id).

Recording the well-known channel ID of the Section Collection pico

Similarly, we can have the Section Collection pico report its well-known ECI to the Registration pico.

The code is left as an exercise for the reader. It's a bit simpler, because there's only one such pico known to the Registration pico.

Creating student picos

Now, we follow the journey of well-known ECIs another step.

We have the Registration pico create a student pico when a student "arrives" to register. Here's the rule (for the app_registration ruleset):

Code Block
  rule allow_student_to_register {
    select when student arrives
      name re#(.+)# setting(name)
    pre {
      backgroundColor = event:attr("backgroundColor") || "#CCCCCC"
    }
    event:send({"eci":wrangler:parent_eci(),
      "domain":"wrangler", "type":"new_child_request",
      "attrs":{
        "name":name,
        "backgroundColor": backgroundColor,
        "wellKnown_Rx":ent:sectionCollectionECI
      }
    })
  }

Here, the rule selects on the event (lines 2-3), provides a default color (line 5), and sends an event to its parent pico, asking for the creation of a new student pico (lines 7-14).

The well-known ECI of the Section Collection pico is shared with the new student pico by virtue of line 12. You may need to change this if you stored the ECI in a different entity variable.

Subscription between student and Section Collection

With a well-known ECI in hand, we can write code to request/propose a subscription from the new student pico to the Section Collection pico. We do this by reacting to the wrangler:ruleset_installed event in the app_student ruleset:

Code Block
  rule capture_initial_state {
    select when wrangler ruleset_installed
      where event:attr("rids") >< meta:rid
    if ent:student_eci.isnull() then
      wrangler:createChannel(tags,eventPolicy,queryPolicy) setting(channel)
    fired {
      ent:name := event:attr("name")
      ent:wellKnown_Rx := event:attr("wellKnown_Rx")
      ent:student_eci := channel{"id"}
      raise student event "new_subscription_request"
    }
  }

We create a channel for the newly created student pico in line 5 (guarded by line 4 so we don't do it twice), and store away the incoming well-known channel for future use in line 9. Then, we raise an event so that we will create a new subscription with the Section Collection pico. We'll see later how we will use that subscription once it is established.

It is a separate rule so that we can raise the event if and when we need a new subscription:

Code Block
  rule make_a_subscription {
    select when student new_subscription_request
    event:send({"eci":ent:wellKnown_Rx,
      "domain":"wrangler", "name":"subscription",
      "attrs": {
        "wellKnown_Tx":subs:wellKnown_Rx(){"id"},
        "Rx_role":"registration", "Tx_role":"student",
        "name":ent:name+"-registration", "channel_type":"subscription"
      }
    })
  }

Here the student pico sends an event to the Section Collection pico, using its ECI (line 3). So it is as if were were in the Testing tab of that pico entering the rest of the fields for the wrangler:subscription event. That is, the invitation is being done from the perspective of the Section Collection pico, even though it is being sent by the student pico. So, the wellKnown_Tx is the student pico's well-known ECI (line 6), and the role of the Section Collection pico is "registration" while the other role is "student" (line 7). Once we have sent the event, the student pico will have a new channel

...

and also a new inbound subscription:

...

Programmatically accepting an inbound subscription

You'll remember that the next manual step was to copy the "Id" value and paste it into the form for the wrangler:pending_subscription_approval event. We can do that with a rule that selects on the wrangler:inbound_pending_subscription_added event:

Code Block
  rule auto_accept {
    select when wrangler inbound_pending_subscription_added
    pre {
      my_role = event:attr("Rx_role")
      their_role = event:attr("Tx_role")
    }
    if my_role=="student" && their_role=="registration" then noop()
    fired {
      raise wrangler event "pending_subscription_approval"
        attributes event:attrs
      ent:subscriptionTx := event:attr("Tx")
    } else {
      raise wrangler event "inbound_rejection"
        attributes event:attrs
    }
  }

The event is raised to us when the inbound subscription arrives. We check it out to make sure it is what we expect (lines 3-7) and if so, we raise the wrangler:pending_subscription_approval event (lines 9-10) and the subscription become established at both ends, and we save away the ECI for use with the Sections Collection pico (line 11). In case it is not an inbound subscription we are expecting, we raise the wrangler:inbound_rejection event to refuse it (lines 13-14).

Writing rules that use subscriptions

Now that we have a relationship with the Section Collection pico, we (a student pico) can use it to add a class.

Student pico requests to add a class

Here is a rule for the app_student ruleset:

Code Block
  rule add_a_class {
    select when mischiefsection hatadd_liftedrequest
      foreach Subscriptions:getSubscriptions() setting (subscription)
 section_id re#(.+)# setting(section_id)
    pre {
      already_enrolled =  subs_attrs = subscription{"attributes"classes() >< section_id
    }
    if not }already_enrolled then
     if subs_attrs event:send({"subscriber_role"} == "thing" theneci":ent:subscriptionTx,
        "domain":"section", "name":"add_request",
         event:send("attrs":{
          { "eciwellKnown_Tx": subs:wellKnown_attrsRx(){"outbound_eciid"},
          "eidsection_id":section_id,
"hat-lifted",          "name":ent:name
        }
   "domain": "mischief", "type": "hat_lifted" }
        )
  }

Notice that the rule evaluates for each of the subscriptions. It only acts for those subscriptions with a subscriber_role of "thing", and is able to reach that pico through the outbound_eci which was established when the subscription was accepted.

Deleting subscriptions

Info
titleEngine Compatibility

Very soon, Wrangler will fully integrate subscriptions, so that when you delete a child pico any subscriptions it participates in will also be deleted. In the meantime, you may wish to programmatically delete such subscriptions before requesting the deletion of the child pico.

To delete a subscription, either of the picos which participate in the subscription would raise the event wrangler/subscription_cancellation including an attribute named "subscription_name" whose value is the subscription name_space concatenated with a colon and the subscription name. For example, if the Thing2 pico wanted to cancel its subscription, it might include this code in the postlude of one of its rules.

Code Block
    raise wrangler event "subscription_cancellation")
  }

We first check to ensure that we don't already have an enrollment for that section (line 7). Then we send (in lines 8-15) a section:add_request event to the Section Collection pico (line 8, using the saved ent:subscriptionTx ECI) and passing our own well-known ECI (line 11) along with the section ID and our name.

Notice that we use the same event, section:add_request for student picos and for the Section Collection pico. We are considering a single application, registration, but the code is distributed across several rulesets for the various kinds of entities.

Section Collection pico passes it on to the section pico

Here is a rule for the app_section_collection ruleset, which is installed in the Section Collection pico. It will react the event that student picos send to it:

Code Block
  rule introduce_section_to_student {
    select when section add_request
    pre {
      section_id = event:attr("section_id")
      with subscription_name = "mischief:thing2"

The raised event triggers a rule in the Subscriptions ruleset. This rule takes care of deleting both sides of the subscription.

Managing Subscriptions Manually

We showed above how to write KRL code to initiate a subscription. Here, we will create a subscription manually (using a RESTful interface such as the Postman REST client, or curl, or the browser–in this example we'll use the browser).

Prepare a URL like this one in your browser (the line breaks shown here are for readability–your URL will not contain line breaks):

Code Block
http://localhost:8080/sky/event/cj0dea39q0001khpqy4su0ru3/subscr/wrangler/subscription?
name=thing1&
name_space=mischief&
my_role=controller&
subscriber_role=thing&
channel_type=subscription&
attrs=&
subscriber_eci=cj0dea81g0003khpqy7j9knqz

Your URL will be different because the ECI to which the event is sent (in the example, cj0dea39q0001khpqy4su0ru3) needs to be replaced by the main ECI of your Mischief Pico, and the subscriber_eci parameter needs to indicate the ECI of your Thing1 Pico.

A subscription has a name within a name_space, and we indicate the role that the picos involved in the subscription will each play (my_role and subscriber_role).

When you visit this URL, the wrangler/subscription event will be sent to the Mischief Pico and the subscription will be created. This will result in each of the two picos involved having a subscription. Each of them will also have a new channel, especially created for use with the subscription. Here is the new channel created for the Thing1 pico:

Image Removed

Repeat the process for the Thing2 pico.

Listing Subscriptions

If we were now to query the Mischief Pico to see its subscriptions, using a URL like

Code Block
http://localhost:8080/sky/cloud/cj0dea39q0001khpqy4su0ru3/Subscriptions/getSubscriptions

we would see that it participates in two subscriptions:

Code Block
{
  "mischief:thing1": {
    "eci": "cj0deky300006khpqn7ucdvhj",
    "name": "mischief:thing1",
    "type": "subscription",
    "attributes": {
      "subscription_name": "thing1",
      "name_space": "mischief",
      "relationship": "controller<->thing",
      "my_role": "controller",
      "subscriber_role": "thing",
      "subscriber_eci": "cj0dea81g0003khpqy7j9knqz",
      "status": "outbound",
      "attributes": ""
    }
  },
  "mischief:thing2": {
    "eci": "cj0devoqc0008khpqenjg11xc",
    "name": "mischief:thing2",
    "type": "subscription",
    "attributes": {
      "subscription_name": "thing2",
      "name_space": "mischief",
      "relationship": "controller<->thing",
      "my_role": "controller",
      "subscriber_role": "thing",
      "subscriber_eci": "cj0deacyn0005khpq8q0qk080",
      "status": "outbound",
      "attributes": ""
    }
  }
}

Similarly, the Thing1 Pico participates in just one subscription. Doing the same sky/cloud query to its ECI will show something like this:

Code Block
{
  "mischief:thing1": {
    "eci": "cj0deky3l0007khpqdmo4037y",
    "name": "mischief:thing1",
    "type": "subscription",
    "attributes": {
      "subscription_name": "thing1",
      "name_space": "mischief",
      "relationship": "thing<->controller",
      "my_role": "thing",
      "subscriber_role": "controller",
      "outbound_eci": "cj0deky300006khpqn7ucdvhj",
      "status": "inbound",
      "attributes": ""
    }
  }
}

Accepting Subscriptions

There is one step remaining in the subscription process. Just because one pico offers a subscription to another pico doesn't mean that target pico is forced to accept it. This is manifest by the "status" of the subscriptions. For the originating pico, the status is "outbound" (short for outbound, pending), and for the other pico, the status is "inbound" (i.e. inbound, pending).

In fact, after the pending subscriptions were created, the Subscriptions rule for wrangler/subscription raises the wrangler event "inbound_pending_subscription_added" passing along all of the attributes. It is up to your pico to react by either accepting or rejecting. This cookbook example shows how to write a rule that auto-approves subscriptions.

This shows the event attributes associated with the event wrangler:inbound_pending_subscription_added:

Code Block
{ name_space: 'mischief',
  relationship: 'thing<->controller',
  my_role: 'thing', 
  subscriber_role: 'controller',
  outbound_eci: 'cj0lk0my70009widdmk6bbnsk',
  status: 'inbound',
  channel_type: 'subscription',
  channel_name: 'mischief:thing2',
  attributes: 'status',  
  name: thing2 }

Generally, promiscuously approving pending subscriptions is a security risk, so most auto-approvals take place where something about the subscription requestor can be verified. For example, a child might be programmed to automatically approve subscription requests from its parent, after checking that the invitation came in on a channel that can be verified as being from the parent.

For now, to complete the manual process, we will send this event to the Thing1 pico (line break added for readability–your URL will be all in one line):

Code Block
http://localhost:8080/sky/event/cj0deky3l0007khpqdmo4037y/approve/wrangler/pending_subscription_approval?
subscription_name=mischief:thing1

Checking the Thing1 pico again, you will see that its one subscription now has the the status of "subscribed". At the same time, that subscription in the Mischief pico also now has the status of "subscribed".

Deleting Subscriptions

You can cancel a subscription manually by sending an event to either of the picos involved in the subscription, in this example, the Thing1 pico.

Code Block
http://localhost:8080/sky/event/cj0deky3l0007khpqdmo4037y/del/wrangler/subscription_cancellation?
subscription_name=mischief:thing1

Endnotes

...

event:attr("name")
    }
    event:send({"eci":ent:sections{[section_id,"wellKnown_eci"]},
      "domain":"wrangler", "name":"subscription",
      "attrs":{
        "wellKnown_Tx":event:attr("wellKnown_Tx"),
        "Rx_role":"section", "Tx_role":"student",
        "name":name+"-"+section_id, "channel_type":"subscription",
        "section_id":section_id
      }
    })
  }

This rule binds short names to two incoming attributes in its prelude (line 3-6). Then it sends a wrangler:subscription event to the appropriate section pico (lines 7-15), using the saved well-known ECI for that section (line 7), and passing along the student pico's well-known ECI (line 10), appropriate roles for the subscription (line 11), a name and channel_type (line 12), and passes through the section_id (line 13).

Section pico proposes a subscription

The section pico will react to this event by proposing a subscription to the student pico. We don't need to add any code to the app_section ruleset, because this event is handled by the io.picolabs.subscription ruleset.

Student pico accepts the subscription adding the class

This rule for the app_student ruleset will see the inbound subscription and automatically accept it:

Code Block
  rule auto_accept_add {
    select when wrangler inbound_pending_subscription_added
      Rx_role re#^student$#
      Tx_role re#^section$#
    pre {
      section_id = event:attr("section_id")
    }
    if section_id then noop()
    fired {
      ent:classes{event:attr("Id")} := section_id
      raise wrangler event "pending_subscription_approval"
        attributes  event:attrs
      last
    }
  }

Lines 3-4 declare that the rule is to be selected only when there are attributes Rx_role and Tx_role which have the values "student" and "section" respectively. The local name section_id is bound (line 6) to the attribute value of the same name, and if there is one (line 8), then we record (in line 10) both the "Id" of the subscription and the section ID in an entity variable named ent:classes, a map whose keys are subscription identifiers and whose values are section IDs. Then, lines 11-12 accept the inbound subscription, completing the relationship between this student pico and that section pico.

Special notice to line 13, which ends handling of the wrangler:inbound_pending_subscription_added event. This is very important. The auto_accept_add rule must be placed in the ruleset before the auto_accept ruleset (see its definition earlier (the section entitled "Programmatically accepting an inbound subscription")). Both of these rulesets will select on that event, and if this one fires, the other one must not be evaluated. That is the purpose of the last keyword, meaning that this is the last rule which is to be evaluated for the event.

Maintaining a class list

The entity variable ent:classes is a map whose values are the classes we have added. So this function will provide our current class list (in the app_student ruleset):

Code Block
    classes = function(){
      ent:classes.values()
    }

Be sure to share the function name in the meta block of the ruleset.

Deleting subscriptions

To delete a subscription, either of the picos which participate in the subscription can raise the event wrangler/subscription_cancellation including either the attribute named "Tx" or the subscription ID. For example, when a student wants to drop a class, it might use this rule (in the app_student ruleset):

Code Block
  rule drop_a_class {
    select when section drop_request
    pre {
      section_id = event:attr("section_id")
      Id = ent:classes
        .filter(function(x){x==section_id})
        .keys()
        .head()
    }
    if Id then noop()
    fired {
      clear ent:classes{Id}
      raise wrangler event "subscription_cancellation"
        attributes {"Id":Id}
    }
  }

When a student pico receives a section:drop_request event, this rule will be selected (as declared in line 2). Line 4 binds a local name to the passed in attribute, section_id. Lines 5-8 compute the subscription identifier that corresponds to that section: operating on the ent:classes entity variable (line 5), we first filter, retaining those entries in the map whose value equals the passed in section ID (line 6), giving us a smaller map containing only those entries from which we get an array of the keys of that smaller map (line 7), from which we retain only the first entry (line 8). Assuming we find such a value, it will be the identifier of the subscription between our pico and the corresponding section pico. We ask for the subscription to be deleted (lines 12-13), severing the relationship between the student and the section. Finally, we delete the entry from our ent:my_classes map.

The raised event triggers a rule in the io.picolabs.subscription ruleset in this pico. This rule takes care of deleting both sides of the subscription.

Managing Subscriptions in the UI

We will create a new pico named "Delta" to illustrate this section. Notice that it begins with the normal complement of channels, including a well-known channel for subscriptions, "ckja93foz0016uq2r17dt782z":

...

It also shows a "Subscriptions" tab, which initially looks like this, with its well-known channel prominently displayed:

...

Now, by double-clicking on the ECI of the well-known channel, the developer can select it and then copy it. Now, it takes two picos to make a subscription, so let's visit another pico, say Beta, and its Subscriptions tab, which shows that it already has one established subscription:

...

We create a new subscription by filling in the fields of the New Subscription form, with the copy/pasted ECI of Delta's well-known channel, "greek" as both Tx_role and Rx_role, with the name "Delta-Beta" and a channel_type of "subscription". When you click the "Add" button, you will have an "Outbound" subscription along with the "Established" subscription.

Meanwhile, back in the Delta pico, we see the "Inbound" subscription:

...

Clicking on the checkbox opens it up and we see the details of the proffered subscription, along with an "Accept" button:

...

Notice that Delta has a new channel, with tags "delta-beta, subscription" and the ECI "ckjc0hor000ko122r8ghp56ty" (which corresponds to the "Rx" field of the subscription):

...

Clicking on the "Accept" button establishes the subscription:

...

Closing the pico, we see the two subscriptions, as magenta lines, in the topological view of the developer UI:

...