Update Twins

This page covers how to set a Digital Twin's metadata properties as well as the data feeds' properties and each of their values. Settings such as a Digital Twin's Location, Visibility and Accessibility can be updated using this function.

🚧

The Update Twin function can only be performed against an existing Digital Twin. If you'd like to combine Create and Update in a single call, you can use the Upsert Twin function instead.


Introduction to Update Digital Twins

There are different types of updates that can be performed against a Digital Twin:

  1. Add or update metadata of an existing Digital Twin
    • this includes special settings such as Location, Visibility and Accessibility
    • how to distinguish a generic Digital Twin from a Digital Twin Model, and
    • special settings to visualise and categorise a Digital Twin on the IOTICS user interface
  2. Add or update metadata of an existing Digital Twin's data feed
    • set each data feed's metadata and settings, and
    • specify each of the data feed's values

Add or update metadata of an existing Digital Twin

Adding and updating a Digital Twin's metadata properties is typically done by using the Update Digital Twin function. The tables below describes the most common properties, however any custom property can be added and there is no limit on the quantity of properties per twin.

Special metadata properties

Please note that some properties like Location and Visibility are special settings that have to be treated differently. The more generic or custom properties will be listed in the List of Properties.

PropertyDescriptionExample
LocationLatitude and Longitude of the Digital Twin. It serves to correctly locate the Digital Twin on the IOTICSpace user interface map.

Digital Twins without a Location property won't show on the map. They still exist though and can be found, accessed and interoperated
{"lat": 51.5, "lon": -0.1}
hostMetadataAllowListDefines the Visibility of a Digital Twin: whether its metadata is visible or not to another IOTICSpace[{"key": "http://data.iotics.com/public#hostMetadataAllowList", "uriValue": {"value": "http://data.iotics.com/public#allHosts"}}]
hostAllowListDefines the Accessibility of the Twin: whether the Digital Twin's data can be accessed (or not) by a Digital Twin from another IOTICSpace[{"key": "http://data.iotics.com/public#hostAllowList", "uriValue": {"value": "http://data.iotics.com/public#allHosts"}}]
PropertiesList of other Properties to add to the Twin. Every property needs to be defined according to a pre-existing ontology in a predicate-object fashion (see table below).[{"key": "http://www.w3.org/2000/01/rdf-schema#label", "langLiteralValue": {"value": "Temperature Sensor", "lang": "en"}}]

Generic metadata properties

The generic properties described in the table below make use of an existing Ontology and should be added to the Digital Twin's list of properties. Any custom property can be created and there is no limit to the number of properties per Digital Twin.

Please note that some properties are mandatory, some are best practice and some are required in order for the Twin to be visualised and categorised in the IOTICSpace User Interface.

PropertyDescriptionExample
LabelA short description of the Digital Twin. Corresponds to "Name" in the IOTICSpace interface.

Not mandatory but best practice.
"key": "http://www.w3.org/2000/01/rdf-schema#label", "langLiteralValue": {"value": "Temperature Sensor", "lang": "en"}
CommentAn explanatory description of the Digital Twin. Corresponds to "Description" in the IOTICSpace interface.

Not mandatory but best practice.
"key": "http://www.w3.org/2000/01/rdf-schema#comment", "langLiteralValue": { "value": "A temperature sensor that shares temperature data", "lang": "en", }
TypeUsed to define the specific type of the Twin such as HostTwin, Model or any other type defined by an existing Ontology.

Mandatory for Digital Twin Models.
"key": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", "uriValue": {"value": "https://data.iotics.com/app#Model"}
From ModelReferences the DID of the Digital Twin Model they have been created from.

Mandatory for "generic" Digital Twins.
"key": "https://data.iotics.com/app#model", "uriValue": {"value": model_twin_did}
ColourDefines the colour of the Twins to be shown in the User Interface.

Not mandatory but best practice.
"key": "https://data.iotics.com/app#color", "stringLiteralValue": {"value": "#d6df23"}
IOTICSpace nameDefines the specific IOTICSpace name where the Twin belongs to.

Not mandatory but best practice.
"key": "https://data.iotics.com/app#spaceName", "stringLiteralValue": {"value": "uk-metoffice"}

How to update metadata properties of an existing Digital Twin with the IOTICS API

❗️

Reflecting changes on the IOTICS user interface

Changes to updated Digital Twins will be shown immediately on the IOTICS user interface:

  1. adding new key-value property pairs works instantly
  2. replacing existing property pairs: please note that in order for the user interface to reflect the replacement of a property's value, the property's key needs to be included in the deletedByKey parameter of the same API call. The pair will be deleted and re-created with a new value.
Click to see the prerequisites

  1. Create your user credentials (guide);
  2. Delegate the agent so it can work on behalf of the user (guide);
  3. Create your token to interact with your Host (guide);
  4. Create headers to be added alongside the API request (guide);
1. Create Twin with control delegation and get a new "twin_registered_id"
2. Use the Create Twin API call
3. Use the Update Twin API call
from requests import request

response = request(
    method="PATCH",
    url=f"{HOST}/qapi/hosts/{host_id}/twins/{twin_did}",
    headers=headers,
    json={
        "location": {"location": {"lat": 6.1, "lon": 1.4}},
        "properties": {
            "added": [
                # Add a Label
                {
                    "key": "http://www.w3.org/2000/01/rdf-schema#label",
                    "langLiteralValue": {"value": "Temperature Sensor", "lang": "en"},
                },
                # Add a comment
                {
                    "key": "http://www.w3.org/2000/01/rdf-schema#comment",
                    "langLiteralValue": {
                        "value": "A temperature sensor that shares temperature data",
                        "lang": "en",
                    },
                },
            ]
        },
    },
)

response.raise_for_status()

Add or update metadata of an existing Digital Twin's data feed

A Digital Twin can have none, one or more data feeds. Each data feed is distinct, with its own identity, settings and list of properties (values).

Data feed metadata

Each data feed has to be described separately. The following table provides an indication on what the data feed's metadata can include.

PropertyDescriptionExample
storeLastDefines whether or not the feed should store the latest shared value."True" / "False"
Values metadataDefines the metadata of the data feed's Values (see table below).[{ "comment": "Temperature in degrees Celsius", "dataType": "decimal", "label": "value", "unit": "http://purl.obolibrary.org/obo/UO_0000027", }]
PropertiesList of Properties to add to the Twin's Feed. Similar to the Twin's Properties, every feed's property needs to be defined according to a pre-existing ontology in a predicate-object fashion.[{"key": "http://www.w3.org/2000/01/rdf-schema#label", "langLiteralValue": {"value": "Current Temperature", "lang": "en"}}]

Data field (Value) metadata

Each data field (or Value) of each data feed also needs to be described with metadata properties.

The following table provides an indication on what the data field's metadata can include.

PropertyDescriptionExample
CommentAn explanatory description of the Value. Corresponds to "Field Description" in the IOTICSpace user interface.

Best practice
"Temperature in degrees Celsius"
Data TypeDefines the xsd type in shorthand notation. Corresponds to "Type" in the IOTICSpace user interface.

Mandatory
"decimal"
LabelA short description of the Value. Corresponds to "Field Name" in the IOTICSpace user interface.

Mandatory
"reading"
UnitThe unit of measure of the Value's content. This field needs to be passed as a URI to a pre-existing Ontology.

Optional
"http://purl.obolibrary.org/obo/UO_0000027"

How to update an existing data feed with the IOTICS API

Click to see the prerequisites

  1. Create your user credentials (guide);
  2. Delegate the agent so it can work on behalf of the user (guide);
  3. Create your token to interact with your Host (guide);
  4. Create headers to be added alongside the API request (guide);
1. Create Twin with control delegation and get a new "twin_registered_id"
2. Use the Create Twin API call
3. Use the Create Feed API call
4. Use the Update Feed API call
from requests import request

response = request(
    method="PATCH",
    url=f"{HOST}/qapi/hosts/{host_id}/twins/{twin_did}/feeds/{feed_id}",
    headers=headers,
    json={
        "storeLast": True,
        "properties": {
            "added": [
                # Add a Label
                {
                    "key": "http://www.w3.org/2000/01/rdf-schema#label",
                    "langLiteralValue": {"value": "Current Temperature", "lang": "en"},
                },
                # Add a Comment
                {
                    "key": "http://www.w3.org/2000/01/rdf-schema#comment",
                    "langLiteralValue": {
                        "value": "The current temperature reading",
                        "lang": "en",
                    },
                },
            ]
        },
        "values": {
            "added": [
                {
                    "comment": "Temperature in degrees Celsius",
                    "dataType": "decimal",
                    "label": "reading",
                    "unit": "http://purl.obolibrary.org/obo/UO_0000027",
                }
            ]
        },
    },
)

response.raise_for_status()

Update Twin Tutorial

Click to see the entire code on how to update a Twin and a Feed

from typing import List

from iotics.lib.identity.api.high_level_api import get_rest_high_level_identity_api
from requests import request

RESOLVER_URL = "resolver_url"
HOST = "host_url"

USER_KEY_NAME = "user_key_name"
AGENT_KEY_NAME = "agent_key_name"
USER_SEED = bytes.fromhex("user_seed")
AGENT_SEED = bytes.fromhex("agent_seed")


class IoticsRest:
    def __init__(self):
        self._high_level_api = get_rest_high_level_identity_api(
            resolver_url=RESOLVER_URL
        )

        (
            self._user_registered_id,
            self._agent_registered_id,
        ) = self._high_level_api.create_user_and_agent_with_auth_delegation(
            user_seed=USER_SEED,
            user_key_name=USER_KEY_NAME,
            agent_seed=AGENT_SEED,
            agent_key_name=AGENT_KEY_NAME,
            delegation_name="#AuthDeleg",
        )

        token = self._high_level_api.create_agent_auth_token(
            agent_registered_identity=self._agent_registered_id,
            user_did=self._user_registered_id.did,
            duration=10,
        )

        self._headers = {
            "accept": "application/json",
            "Iotics-ClientAppId": "example_code",
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
        }

    def _make_api_call(self, method: str, url: str, json: dict = None):
        response = request(method=method, url=url, headers=self._headers, json=json)
        response.raise_for_status()

        return response.json()

    def create_twin_identity(self, twin_key_name: str):
        twin_registered_id = self._high_level_api.create_twin_with_control_delegation(
            twin_seed=AGENT_SEED,
            twin_key_name=twin_key_name,
            agent_registered_identity=self._agent_registered_id,
            delegation_name="#ControlDeleg",
        )

        return twin_registered_id.did

    def create_twin(self, twin_did: str):
        twin = self._make_api_call(
            method="POST", url=f"{HOST}/qapi/twins", json={"id": twin_did}
        )

        return twin

    def create_feed(self, twin_did: str, host_id: str, feed_id: str):
        feed = self._make_api_call(
            method="POST",
            url=f"{HOST}/qapi/hosts/{host_id}/twins/{twin_did}/feeds",
            json={"id": feed_id},
        )

        return feed

    def update_twin(
        self,
        twin_did: str,
        host_id: str,
        location: dict = None,
        properties_to_add: List[dict] = [],
        properties_to_remove: List[str] = [],
        delete_all_properties: bool = False,
    ):
        payload = {}

        if location:
            payload["location"] = location
        if properties_to_add:
            payload["properties"] = {"added": properties_to_add}
        if properties_to_remove:
            payload["properties"] = {"deletedByKey": properties_to_remove}
        if delete_all_properties:
            payload["properties"] = {"clearedAll": True}

        self._make_api_call(
            method="PATCH",
            url=f"{HOST}/qapi/hosts/{host_id}/twins/{twin_did}",
            json=payload,
        )

    def update_feed(
        self,
        twin_did: str,
        host_id: str,
        feed_id: str,
        properties_to_add: List[dict] = [],
        properties_to_remove: List[str] = [],
        delete_all_properties: bool = False,
        values_metadata: List[dict] = None,
        store_last=True,
    ):
        payload = {"storeLast": store_last}

        if properties_to_add:
            payload["properties"] = {"added": properties_to_add}
        if properties_to_remove:
            payload["properties"] = {"deletedByKey": properties_to_remove}
        if delete_all_properties:
            payload["properties"] = {"clearedAll": True}

        if values_metadata:
            payload["values"] = {"added": values_metadata}

        self._make_api_call(
            method="PATCH",
            url=f"{HOST}/qapi/hosts/{host_id}/twins/{twin_did}/feeds/{feed_id}",
            json=payload,
        )


def main():
    iotics_rest = IoticsRest()
    twin_did = iotics_rest.create_twin_identity(twin_key_name="SensorTwin")
    twin = iotics_rest.create_twin(twin_did=twin_did)
    host_id = twin["twinId"]["hostId"]
    iotics_rest.update_twin(
        twin_did=twin_did,
        host_id=host_id,
        properties_to_add=[
            # Add a Label
            {
                "key": "http://www.w3.org/2000/01/rdf-schema#label",
                "langLiteralValue": {"value": "Temperature Sensor", "lang": "en"},
            },
            # Add a comment
            {
                "key": "http://www.w3.org/2000/01/rdf-schema#comment",
                "langLiteralValue": {
                    "value": "A temperature sensor that shares temperature data",
                    "lang": "en",
                },
            },
        ],
        location={"location": {"lat": 51.5, "lon": -0.1}},
    )
    iotics_rest.create_feed(twin_did=twin_did, host_id=host_id, feed_id="temperature")
    iotics_rest.update_feed(
        twin_did=twin_did,
        host_id=host_id,
        feed_id="temperature",
        properties_to_add=[
            # Add a Label
            {
                "key": "http://www.w3.org/2000/01/rdf-schema#label",
                "langLiteralValue": {"value": "Current Temperature", "lang": "en"},
            },
            # Add a comment
            {
                "key": "http://www.w3.org/2000/01/rdf-schema#comment",
                "langLiteralValue": {
                    "value": "The current temperature reading",
                    "lang": "en",
                },
            },
        ],
        # Add Value's metadata
        values_metadata=[
            {
                "comment": "Temperature in degrees Celsius",
                "dataType": "decimal",
                "label": "reading",
                "unit": "http://qudt.org/vocab/unit/DEG_C",
            }
        ],
    )


if __name__ == "__main__":
    main()