Archive - Tutorial 4 - Adding properties

❗️

Opps

This page has been deprecated, if you have found your way here please head back to the Welcome page!

Now that we know how to search for Twins, we can start looking at specific properties of twins and how to work with them. By the end of this tutorial you will be able to add properties to a twin and then search by properties.

Overview

Properties allow you to add additional information Twins, enabling more granular information and complex relationships to be built into your ecosystem. For example you could give your twins Categories or list the Manu factory of machinery allowing you to easily find all the twins that match your interest.

Properties are expressed as key value pair with value expressed with an optional unit.

Before you begin

Make sure you create a file with the content in the “Bring it all together” of Tutorial 2 . You can then run that file in the REPL with:

exec(open("./iotics_tutorial.py").read())

Adding properties to a twin

To add properties to a Twin we just send a payload with
Let’s add properties to the hb_twin:

payload = {
    "properties": [
        {
            "key": "http://data.iotics.com/ns/category",
            "uriValue": {
                "value": "http://data.iotics.com/category/Health"
            }
        },
        {
            "key": "http://demo.iotics.com/ont/ev/has_backup",
            "literalValue": {
                "dataType": "boolean",
                "value": "true"
            }
        },
        {
            "key": "http://demo.iotics.com/ont/ev/maker",
            "stringLiteralValue": {
                "value": "HBMaker LTD"
            }
        }
    ]
}

r = requests.patch(f'{host.address}/qapi/twins/{hb_twin_identity.id}',
                   headers=iotics_headers(
                       agent_identity, user_identity, host.address),
                   data=json.dumps(payload))

You can find more information on properties in the Update a Twin section of our API reference.

Search by properties

To search by properties it’s necessary to set a properties filter to the search request.

stomp_client.set_listener("log_listener", PrintingListener())

token = agent2_identity.document_manager.new_token(principal_did=user_key.id, duration=3600, audience=host.address)
stomp_client.connect(wait=True, passcode=token)

search_payload={
  "filter": {
    "text": "monitor",
    "properties": [
      {
        "key": "http://demo.iotics.com/ont/ev/has_backup",
        "literalValue": {
          "dataType": "boolean",
          "value": "true"
        }
      }
    ]
  },
  "expiryTimeout": "2000-01-23T04:56:07+00:00"
}

stomp_client.subscribe(destination="/qapi/searches/results", id=1, headers=iotics_stomp_headers(agent2_identity))

stomp_client.send('/qapi/searches/dispatches', headers=iotics_stomp_headers(agent2_identity), body=json.dumps(search_payload))

Running the search request will print responses from the hosts in the network with results. Since we set a property filter on the twin we expect only one twin in the response as:

{
    "responseType": "FULL",
    "twins": [{
        "id": {
            "value": "did:iotics:iotACEiy53BLXeRyTmTbu3VL4pNF6VYAfyWf"
        },
        "location": {
            "lat": 0.0,
            "lon": 0.0
        },
        "label": "heartbeat monitor",
        "tags": ["cat_health", "monitor"],
        "properties": [{
            "key": "http://data.iotics.com/ns/category",
            "uriValue": {
                "value": "http://data.iotics.com/category/Health"
            }
        }, {
            "key": "http://demo.iotics.com/ont/ev/has_backup",
            "literalValue": {
                "dataType": "boolean",
                "value": "true"
            }
        }, {
            "key": "http://demo.iotics.com/ont/ev/maker",
            "stringLiteralValue": {
                "value": "HBMaker LTD"
            }
        }],
        "feeds": [{
            "feed": {
                "id": {
                  "value": "hb"
                  },
                "twinId": {
                    "value": "did:iotics:iotACEiy53BLXeRyTmTbu3VL4pNF6VYAfyWf"
                }
            },
            "label": "bpm",
            "storeLast": true
        }]
    }]
}

Bring it all together

Copy this content in iotics_tutorial.py to set the state of your env to include the work in this tutorial

import json
import base64
import requests
import shortuuid
from iotic.lib.identity import Identifier
from iotics_id import Host, NamedECDSAKey, DocumentManager, IdentifiableEntity
from random import randrange
from datetime import datetime
from iotic.web.stomp.client import StompWSConnection12
from stomp.listener import PrintingListener


host = Host("https://api01.demo02.space.iotics.com")

seed = None
try:
    seed = open('.seed', 'r').read()
except:
    # no file
    seed = Identifier.new_seed(256)
    f = open(".seed", "a")
    f.write(seed)
    f.close()


def getHB(min, max):
    return min + randrange(max - min)


def share(host: str, headers: dict, twin_id: str, feed_id: str):
    message = json.dumps({"bpm": getHB(50, 90)})
    message_b64 = base64.b64encode(message.encode('ascii')).decode('ascii')
    payload = {
        "sample": {
            "data": message_b64,
            "mime": "application/json;encoding=base64",
            "occurredAt": datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S%zZ')
        }
    }
    r = requests.post(f'{host}/qapi/twins/{twin_id}/feeds/{feed_id}/shares',
                      headers=headers, json=payload)
    print(f'"shared {message}: result code={r.status_code}')


def iotics_headers(agent_identity: IdentifiableEntity, user_identity: IdentifiableEntity, host: str, duration: int = 120) -> dict:
    user_did = user_identity.document_manager.named_key.id
    token = agent_identity.document_manager.new_token(
        principal_did=user_did, duration=duration, audience=host)
    headers = {
        "accept": "application/json",
        "Iotics-ClientRef": f'd-poc-{shortuuid.random(8)}',
        "Iotics-ClientAppId": agent_identity.document_manager.named_key.id,
        "Authorization": f'Bearer {token}',
        "Content-Type": "application/json"
    }
    return headers


print("creating/retrieving user and agent identity + delegation")
user_key = NamedECDSAKey(seed=seed, purpose="user", name="fred_at_gmail-0")
user_identity = IdentifiableEntity(named_key=user_key, host=host)

agent_key = NamedECDSAKey(seed=seed, purpose="agent", name="hb-mon-0")
agent_identity = IdentifiableEntity(named_key=agent_key, host=host)
agent_proof = agent_identity.document_manager.new_proof(did=user_key.id)

agent_issuer = agent_identity.document_manager.issuer()

user_identity.document_manager.add_auth_delegation(
    proof=agent_proof, authorizer_id=agent_issuer, name="agent_deleg_0")

print("creating twin identity")
hb_twin_key = NamedECDSAKey(seed=seed, purpose="twin", name="hb_device_001")
hb_twin_identity = IdentifiableEntity(named_key=hb_twin_key, host=host)
agent_proof = agent_identity.document_manager.new_proof(
    did=hb_twin_identity.id)
agent_issuer = agent_identity.document_manager.issuer()
deleg_name = f'{agent_identity.document_manager.named_key.name}_0'
print("delegating agent to control twin")
hb_twin_identity.document_manager.add_control_delegation(
    proof=agent_proof, controller_id=agent_issuer, name=deleg_name)

print("making the twin resource")
payload = {'twinId': {'value': hb_twin_identity.id}}
r = requests.post(f'{host.address}/qapi/twins', headers=iotics_headers(
    agent_identity, user_identity, host.address), json=payload)

payload = {
    "tags": {
        "added": ["cat_health", "monitor"]
    },
    "labels": {
        "added": [
            {"lang": "en", "value": "heartbeat monitor"}
        ]
    },
    "comments": {
        "added": [
            {"lang": "en", "value": "an heartbeat monitor"}
        ]
    }
}

r = requests.patch(f'{host.address}/qapi/twins/{hb_twin_identity.id}',
                   headers=iotics_headers(
                       agent_identity, user_identity, host.address),
                   data=json.dumps(payload))

print("making the feed resource")
name = 'hb'
payload = {"feedId": {"value": name}, "storeLast": True}
r = requests.post(f'{host.address}/qapi/twins/{hb_twin_identity.id}/feeds',
                  headers=iotics_headers(agent_identity, user_identity, host.address), json=payload)

payload = {
    "comments": {
        "added": [
          {
              "lang": "en",
              "value": "the beat per minutes"
          }
        ]
    },
    "storeLast": True,
    "labels": {
        "added": [
            {
                "lang": "en",
                "value": "bpm"
            }
        ]
    },
    "values": {
        "added": [
            {
                "comment": "bpm",
                "dataType": "decimal",
                "label": "bpm",
                "unit": "http://purl.obolibrary.org/obo/UO_0000148"
            }
        ]
    }
}
r = requests.patch(f'{host.address}/qapi/twins/{hb_twin_identity.id}/feeds/{name}',
                   headers=iotics_headers(agent_identity, user_identity, host.address), json=payload)

print("publishing sample data")
message = json.dumps({"bpm": 70})
message_b64 = base64.b64encode(message.encode('ascii')).decode('ascii')
payload = {
    "sample": {
        "data": message_b64,
        "mime": "application/json;encoding=base64",
        "occurredAt": datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S%zZ')
    }
}

requests.post(f'{host.address}/qapi/twins/{hb_twin_identity.id}/feeds/{name}/shares',
              headers=iotics_headers(agent_identity, user_identity, host.address), json=payload)

print("create another agent")

agent2_key = NamedECDSAKey(seed=seed, purpose="agent", name="hrate_mon_0")
agent2_identity = IdentifiableEntity(named_key=agent2_key, host=host)

agent2_proof = agent2_identity.document_manager.new_proof(did=user_key.id)
agent2_issuer = agent2_identity.document_manager.issuer()
user_identity.document_manager.add_auth_delegation(
    proof=agent2_proof, authorizer_id=agent2_issuer, name="hrate_mon_0_deleg_0")

print("create another twin")
hrate_monitor_twin_key = NamedECDSAKey(
    seed=seed, purpose="twin", name="hrate_monitor_001")
hrate_monitor_twin_identity = IdentifiableEntity(
    named_key=hrate_monitor_twin_key, host=host)

payload = {'twinId': {'value': hrate_monitor_twin_identity.id}}
requests.post(f'{host.address}/qapi/twins', headers=iotics_headers(
    agent_identity, user_identity, host.address), json=payload)

print("create a stomp client")

resp = requests.get(f'{host.address}/index.json').json()
stomp_endpoint = resp["stomp"]
stomp_client = StompWSConnection12(endpoint=stomp_endpoint)
stomp_client.set_ssl(verify=False)


def iotics_stomp_headers(agent_identity: IdentifiableEntity) -> dict:
    return {
        "Iotics-ClientRef": f'd-poc-{shortuuid.random(8)}',
        "Iotics-ClientAppId": agent_identity.document_manager.named_key.id
    }


payload = {
    "properties": {
        "added": [
            {
                "key": "http://data.iotics.com/ns/category",
                "uriValue": {
                    "value": "http://data.iotics.com/category/Health"
                }
            },
            {
                "key": "http://demo.iotics.com/ont/ev/has_backup",
                "literalValue": {
                    "dataType": "boolean",
                    "value": "true"
                }
            },
            {
                "key": "http://demo.iotics.com/ont/ev/maker",
                "stringLiteralValue": {
                    "value": "HBMaker LTD"
                }
            }
        ]}
}


r = requests.patch(f'{host.address}/qapi/twins/{hb_twin_identity.id}',
                   headers=iotics_headers(
                       agent_identity, user_identity, host.address),
                   data=json.dumps(payload))