Archive - Tutorial 1 - Make a heart beat device twin

❗️

Opps

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

At the end of this tutorial you’ll be able to create a digital twin in IOTICS, enriched with metadata and a feed; you’ll also be able to share and retrieve the shared value and search for the twin you just made.

Precondition

At the end of the prerequisites page you should have created a file called iotics_tutorial.py. You can run this file in your REPL:

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

and carry on with the tutorial line by line or keep appending at the end of the file and re-run it.

from datetime import datetime
import json
import base64
import requests

Step 1: Create the twin and retrieve it

To create a twin you must first create the keys and respective identity so the HOST can verify control delegation from the agent.

Thinking in IOTICS terms, the agent IS the logic of the twin that interfaces with the hardware device

hb_twin_key = NamedECDSAKey(seed=seed, purpose="twin", name="hb_device_001")
hb_twin_identity=IdentifiableEntity(named_key=hb_twin_key, host=host)
hb_twin_identity.id
#output: 'did:iotics:iotJ1gNRs3ZRuD8cgeh2XTeRUiQc1VsymTfj'

The agent must request control delegation:

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'
hb_twin_identity.document_manager.add_control_delegation(proof=agent_proof, 
                  controller_id=agent_issuer, name=deleg_name)

Now the agent IS the twin and it is enabled to control the twin.

To create the actual twin:

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)
r.status_code
#output: 201

Then to retrieve the twin:

r = requests.get(f'{host.address}/qapi/twins', headers=iotics_headers(agent_identity, 
                  user_identity, host.address))
json.loads(r.text)
#output: { 
#output:   "twins": [
#output:     // ...
#output:     {'id': {'value': 'did:iotics:iotJ1gNRs3ZRuD8cgeh2XTeRUiQc1VsymTfj'}, 'visibility': 'PRIVATE'}
#output:     // ...
#output:   ]
#output: }

🚧

Note

By default Twins are created with PRIVATE visibility.

The twin can also be described to observe all it’s parts:

r = requests.get(f'{host.address}/qapi/twins/{hb_twin_identity.id}?lang=en', 
                     headers=iotics_headers(agent_identity, user_identity, host.address))
json.loads(r.text)
#output: {
#output:   'twin': {
#output:     'id': {
#output:       'value': 'did:iotics:iotJ1gNRs3ZRuD8cgeh2XTeRUiQc1VsymTfj'
#output:     }, 
#output:     'visibility': 'PRIVATE'
#output:   }, 
#output:   'result': {
#output:     'labels': [], 
#output:     'comments': [], 
#output:     'feeds': [], 
#output:     'tags': [], 
#output:     'properties': []
#output:   }
#output: }

Since this is a vanilla twin, no meta is available (except its visibility).

Step 2: Add basic metadata

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))
r.status_code
  • tags are a list of searchable words
  • labels is an array of label objects, where a label is in a given specific language, only one label for each language is allowed
  • comments is an array of comment objects; only one comment for each language is allowed.

When describing the twin after the update:

r = requests.get(f'{host.address}/qapi/twins/{hb_twin_identity.id}?lang=en', 
  headers=iotics_headers(agent_identity, user_identity, host.address))
json.loads(r.text)

#output: {'twin': {
#output:   'id': {'value': 'did:iotics:iotJ1gNRs3ZRuD8cgeh2XTeRUiQc1VsymTfj'}, 
#output:   'visibility': 'PRIVATE'}, 
#output:   'result': {
#output:     'labels': [{'lang': 'en', 'value': 'heartbeat monitor'}], 
#output:     'comments': [{'lang': 'en', 'value': 'an heartbeat monitor'}], 
#output:     'tags': ['cat_health', 'monitor'], 'feeds': [], 
#output:     'properties': []
#output:   }
#output: }

Step 3: Add the feed with a value

A feed represents the concept of fast moving data; a feed may have one or more values encoded in the feed.

The HOST can be configured to store the last published value for a feed:

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)
r.status_code
#output: 201

Adding the value:

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)
r.status_code
#output: 200

Describing the twin shows the newly created feed:

r = requests.get(f'{host.address}/qapi/twins/{hb_twin_identity.id}?lang=en', 
                     headers=iotics_headers(agent_identity, user_identity, host.address))
json.loads(r.text)
#output: {'twin': 
#output: ...
#output:     'feeds': [
#output:         {'feedId': {'value': 'hb'}, 
#output:           'labels': [{'lang': 'en', 'value': 'bpm'}], 
#output:           'storeLast': True}], 
#output: ...
#output: }

Then describing the feed, shows its details:

r = requests.get(f'{host.address}/qapi/twins/{hb_twin_identity.id}/feeds/{name}?lang=en', 
                     headers=iotics_headers(agent_identity, user_identity, host.address))
json.loads(r.text)
#output: {'feed': {
#output:   'id': {'value': 'hb'}, 
#output:   'twinId': {'value': 'did:iotics:iotJ1gNRs3ZRuD8cgeh2XTeRUiQc1VsymTfj'}}, 
#output:   'result': {
#output:       'labels': [{'lang': 'en', 'value': 'bpm'}], 
#output:       'values': [
#output:           {
#output:             'label': 'bpm', 
#output:             'comment': 'bpm', 
#output:             'unit': 'http://purl.obolibrary.org/obo/UO_0000148', 
#output:             'dataType': 'decimal'
#output:           }
#output:         ], 
#output:       'comments': [
#output:         {'lang': 'en', 'value': 'the beat per minutes'}], 
#output:         'storeLast': True, 
#output:         'tags': []
#output:       }
#output: }

Step 4: share a value and read it back

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')
  }
}

r = 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)
r.status_code
#output: 202

🚧

Note

The JSON attribute in the message must match the value label in the feed definition: bmp

r = requests.get(f'{host.address}/qapi/twins/{hb_twin_identity.id}/interests/twins/{hb_twin_identity.id}/feeds/{name}/samples/last',
  headers=iotics_headers(agent_identity, user_identity, host.address))
data=json.loads(r.text)
data
#output: {
#output:   'interest': {
#output:   'followerTwinId': { 'value': 'did:iotics:iotJ1gNRs3ZRuD8cgeh2XTeRUiQc1VsymTfj'}, 
#output:   'followedFeed': {
#output:     'feed': {
#output:       'id': {'value': 'hb'}, 
#output:       'twinId': {'value': 'did:iotics:iotJ1gNRs3ZRuD8cgeh2XTeRUiQc1VsymTfj'}}}}, 
#output:    'feedData': {
#output:       'occurredAt': '2021-02-16T02:00:12Z', 
#output:       'mime': 'application/json;encoding=base64', 
#output:       'data': 'eyJicG0iOiA3MH0='
#output:   }
#output: }
#output: 
message = base64.b64decode(data['feedData']['data'].encode('ascii')).decode('ascii')
message
#output: '{"bpm": 70}'

Bringing it all together

In order to reuse the output of this tutorial for the next, make sure you have a file named iotics_tutorial.py with the following content (which is the merge of the prerequisite and the work of this tutorial).

import shortuuid
from iotic.lib.identity import Identifier
from iotics_id import Host, NamedECDSAKey, DocumentManager, IdentifiableEntity
from datetime import datetime
import json
import base64
import requests

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 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')
    }
}

r = 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)