Create Twin Models

The "generic" Digital Twins contain data about the real asset, concept or person, whereas the Digital Twin Model serves as the template or structure of "generic" Digital Twins.

In line with our best practice, this page explains how to create Digital Twin Models and, in turn, how to create Digital Twins according to that same Model.


Create Digital Twin Models

Digital Twin Models are used to describe the template or structure of one or more "generic" Digital Twin. You may therefore think of a Digital Twin Model as the parent of one or more generic Digital Twins. Consider that:

  1. Digital Twin Models ensure standardisation: all Digital Twins have the same properties, Feeds and Values as their Model. Some exceptions apply to the Twin's settings, see here;
  2. Digital Twins can be grouped by Model: this simplifies operations like searching, filtering or deleting more than one Twin;
  3. Models only serve as a template for other Digital Twins: they don't share data or follow feeds. These functions are only performed by "generic" Digital Twins.
Twin Model representationTwin Model representation

Twin Model representation

Create Twin Models with the IOTICSpace User Interface

Navigate to the Models section on your IOTICSpace user interface to create a new Digital Twin Model, its metadata properties and data feeds.

📘

Go to https://{your-space-name}.iotics.space to access the IOTICSpace user interface.

Click for a step-by-step guide and screenshots

Step 1: Click + New Model to create a new Digital Twin Model.Step 1: Click + New Model to create a new Digital Twin Model.

Step 1: Click + New Model to create a new Digital Twin Model.

Step 2: Enter Name and Description of your new Digital Twin Model, and click Save.Step 2: Enter Name and Description of your new Digital Twin Model, and click Save.

Step 2: Enter Name and Description of your new Digital Twin Model, and click Save.

Step 3: Change Findability to Private to allow no other IOTICSpace to find your Digital Twin Model, or to Public to make it findable by all.Step 3: Change Findability to Private to allow no other IOTICSpace to find your Digital Twin Model, or to Public to make it findable by all.

Step 3: Change Findability to Private to allow no other IOTICSpace to find your Digital Twin Model, or to Public to make it findable by all.

🚧

We use Findability and Visibility interchangeably

Step 4: Add metadata properties by clicking the + icon next to Metadata. Search for specific properties and click Add once you're happy with the selection. If you would like to add more than one property group (or "Parent Class"), simply repeat the process as there is no limit on the number of classes per Model. Contact us to add properties which are not yet available.Step 4: Add metadata properties by clicking the + icon next to Metadata. Search for specific properties and click Add once you're happy with the selection. If you would like to add more than one property group (or "Parent Class"), simply repeat the process as there is no limit on the number of classes per Model. Contact us to add properties which are not yet available.

Step 4: Add metadata properties by clicking the + icon next to Metadata. Search for specific properties and click Add once you're happy with the selection. If you would like to add more than one property group (or "Parent Class"), simply repeat the process as there is no limit on the number of classes per Model. Contact us to add properties which are not yet available.

Step 5: Expand the "Parent Class" to see all the properties that have been added.Step 5: Expand the "Parent Class" to see all the properties that have been added.

Step 5: Expand the "Parent Class" to see all the properties that have been added.

Step 6: Click the + button next to Feeds to add data feeds to the Model. For each data feed, add a name and description as well as one or more data fields. Each Model can have none, one or more than one data feed, and each data feed can have one or more data fields.Step 6: Click the + button next to Feeds to add data feeds to the Model. For each data feed, add a name and description as well as one or more data fields. Each Model can have none, one or more than one data feed, and each data feed can have one or more data fields.

Step 6: Click the + button next to Feeds to add data feeds to the Model. For each data feed, add a name and description as well as one or more data fields. Each Model can have none, one or more than one data feed, and each data feed can have one or more data fields.

🚧

Don't forget to click Save once you're done editing your Digital Twin Model.

Create Twin Models with the IOTICS API

A Digital Twin Model can be created with the IOTICS API by creating a generic Digital Twin with Create Twin and specifying their property type = Model. The use of this property allows any Twin to be identified as a Twin Model.

{
    "key": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
    "uriValue": {"value": "https://data.iotics.com/app#Model"}
}

The following non-mandatory properties are important to be added to the Twin Model for it to show in the IOTICS user interface:

{
    "key": "http://www.w3.org/2000/01/rdf-schema#label",
    "langLiteralValue": {"value": "twin_label_model", "lang": "en"},  // e.g.: "Temperature Sensor Model"
},
{
    "key": "http://www.w3.org/2000/01/rdf-schema#comment",
    "langLiteralValue": {"value": "twin_comment", "lang": "en"},  // e.g.: "A temperature sensor that shares temperature data"
},
{
    "key": "https://data.iotics.com/app#color",
    "stringLiteralValue": {"value": "colour_hex_code"},  // e.g.: "#d6df23"
},
{
    "key": "https://data.iotics.com/app#spaceName",
    "stringLiteralValue": {"value": "space_name"},  // e.g. "uk-metoffice"
},
{
    "key": "https://data.iotics.com/app#createdAt",
    "literalValue": {"dataType": "dateTime", "value": "datetime_twin_creation"},
},
{
    "key": "https://data.iotics.com/app#updatedAt",
    "literalValue": {"dataType": "dateTime", "value": "datetime_twin_update"},
}

Don't forget to check the Digital Twin's access permissions. You can update the AllowList property to enable or restrict access permissions for the generic Digital Twins according to Selective Data Sharing.

{
    "key": "http://data.iotics.com/public#hostAllowList",
    "uriValue": {"value": "http://data.iotics.com/public#allHosts"},
}
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 a Twin
2. Add Twin's metadata including the "special" property, Feeds and Values' metadata
from datetime import datetime, timezone
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")


def make_api_call(method: str, url: str, json: dict):
    response = request(method=method, url=url, headers=headers, json=json)
    response.raise_for_status()

    return response.json()


def create_twin(twin_key_name: str):
    twin_registered_id = api.create_twin_with_control_delegation(
        twin_seed=AGENT_SEED,
        twin_key_name=twin_key_name,
        agent_registered_identity=agent_registered_id,
        delegation_name="#ControlDeleg",
    )

    return twin_registered_id.did


def upsert_twin(
    twin_id: str,
    visibility: str = "PRIVATE",
    feeds: List[dict] = None,
    location: dict = None,
    properties: List[dict] = None,
):
    payload = {"twinId": twin_id, "visibility": visibility}

    if location:
        payload["location"] = location
    if feeds:
        payload["feeds"] = feeds
    if properties:
        payload["properties"] = properties

    make_api_call(method="PUT", url=f"{HOST}/qapi/twins", json=payload)


# Set up the API with authentication delegation so the AGENT can authenticate on behalf of the USER
api = get_rest_high_level_identity_api(resolver_url=RESOLVER_URL)
(
    user_registered_id,
    agent_registered_id,
) = 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 = api.create_agent_auth_token(
    agent_registered_identity=agent_registered_id,
    user_did=user_registered_id.did,
    duration=600,
)

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

# Create Twin Model
twin_model_did = create_twin(twin_key_name="TwinModel")

print(f"Twin {twin_model_did} created succesfully")

feed_id = "currentTemp"

# Add Metadata, Feed and Value
upsert_twin(
    twin_id=twin_model_did,
    properties=[
        {
            "key": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
            "uriValue": {"value": "https://data.iotics.com/app#Model"},
        },
        {
            "key": "http://www.w3.org/2000/01/rdf-schema#label",
            "langLiteralValue": {"value": "Temperature Sensor Model", "lang": "en"},
        },
        {
            "key": "http://www.w3.org/2000/01/rdf-schema#comment",
            "langLiteralValue": {
                "value": "A temperature sensor that shares temperature data",
                "lang": "en",
            },
        },
        {
            "key": "https://data.iotics.com/app#color",
            "stringLiteralValue": {"value": "#d6df23"},
        },
        {
            "key": "https://data.iotics.com/app#spaceName",
            "stringLiteralValue": {"value": "my-space"},
        },
        {
            "key": "https://data.iotics.com/app#createdAt",
            "literalValue": {
                "dataType": "dateTime",
                "value": datetime.now(tz=timezone.utc).isoformat(),
            },
        },
        {
            "key": "https://data.iotics.com/app#updatedAt",
            "literalValue": {
                "dataType": "dateTime",
                "value": datetime.now(tz=timezone.utc).isoformat(),
            },
        },
    ],
    feeds=[
        {
            "id": feed_id,
            "storeLast": True,
            "properties": [
                {
                    "key": "http://www.w3.org/2000/01/rdf-schema#label",
                    "langLiteralValue": {"value": "Current Temperature", "lang": "en"},
                }
            ],
            "values": [
                {
                    "comment": "Temperature in degrees Celsius",
                    "dataType": "decimal",
                    "label": "reading",
                    "unit": "http://purl.obolibrary.org/obo/UO_0000027",
                }
            ],
        }
    ],
)

print("Twin Model updated with Metadata, Feed and Value")

When creating and updating Digital Twin Models, consider the following best practices:

  1. Could I make my code more efficient? When creating Twin Models with the IOTICS API, it may be easier to use the Upsert Twin API in order to create the Twin, create Feed(s), Values and adding metadata with a single call rather than using multiple APIs separately.
  2. Can I easily identify the Models from generic Twins? It is good practice to add the word "Model" to the name (label property) of the Twin (e.g. Weather Forecast Model). This way all the Twin Models can easily be distinguished by users of the API and the IOTICS user interface when listing all Twins.
  3. Have I added all properties? Check the list of properties above to make sure your Model shows up correctly in the IOTICS user interface too. Also consider that Twin Models don't need a Location, as it's meaningless.

Create Digital Twins from Model

Create Twins from Model with the IOTICSpace User Interface

Navigate to the Twins section on your IOTICSpace user interface to create a new Digital Twin from an existing Digital Twin Model.

Click for a step-by-step guide and screenshots

Step 1: Click + New Twin to create a new Digital Twin.Step 1: Click + New Twin to create a new Digital Twin.

Step 1: Click + New Twin to create a new Digital Twin.

Step 2: Enter Name and Description, and select the Digital Twin Model for your new Digital Twin. Click Save.Step 2: Enter Name and Description, and select the Digital Twin Model for your new Digital Twin. Click Save.

Step 2: Enter Name and Description, and select the Digital Twin Model for your new Digital Twin. Click Save.

Step 3: The new Digital Twin has been created. Add values for each or some of the properties, and click the green button to Save.Step 3: The new Digital Twin has been created. Add values for each or some of the properties, and click the green button to Save.

Step 3: The new Digital Twin has been created. Add values for each or some of the properties, and click the green button to Save.

Step 4: Similarly, for each data feed, add values for each or some data fields, and click the green button to Save. Repeat Step 3 and 4 each time you'd like to add or update a value manually. Use the API to update Digital Twins in bulk.Step 4: Similarly, for each data feed, add values for each or some data fields, and click the green button to Save. Repeat Step 3 and 4 each time you'd like to add or update a value manually. Use the API to update Digital Twins in bulk.

Step 4: Similarly, for each data feed, add values for each or some data fields, and click the green button to Save. Repeat Step 3 and 4 each time you'd like to add or update a value manually. Use the API to update Digital Twins in bulk.

Step 5: Navigate back to the Twins section to see a list of all your Digital Twins. Alternatively, in the Models section, you can see all Digital Twins for each Model (as seen here).Step 5: Navigate back to the Twins section to see a list of all your Digital Twins. Alternatively, in the Models section, you can see all Digital Twins for each Model (as seen here).

Step 5: Navigate back to the Twins section to see a list of all your Digital Twins. Alternatively, in the Models section, you can see all Digital Twins for each Model (as seen here).

Create Twins from Model with the IOTICS API

In order to easily identify all the Digital Twins that have been created from a specific Model, add the following properties to your new Digital Twins. All Twins that include the below properties will be categorised by the IOTICS user interface as "Twins from Model".

{
    "key": "https://data.iotics.com/app#model",
    "uriValue": {"value": model_twin_did},
},
{
    "key": "https://data.iotics.com/app#createdFrom",
    "uriValue": {"value": "https://data.iotics.com/app#ByModel"},
}

Since the Twin Model serves as a template for the generic Twins, you also need to make sure that the new Twins follow the same structure as their Model - this means the metadata properties of the Twin, Feed(s) and each of the Feed's Values need to match.

❗️

Exceptions: adapt Label, Comment, Type

Although the new Twins need to follow the structure of their Model in terms of metadata, there are some properties that require some modification:

  • Each of the Twin's Label and potentially Comment property values should be unique, and therefore different from both their Model and any other Twin from that Model;
  • The property type: Model that identifies a Twin Model needs to be replaced by the Model and createdFrom properties that identify the "generic" Twins;
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);
  5. Create a Twin Model (guide);
1. Search and describe the Twin Model
2. Create a Twin
3. Add Twin's metadata, Feed(s) and Value(s) according to the Model
4. Add the "special" properties for generic Twins
5. Consider the exceptions
import json
from datetime import datetime, timedelta, timezone
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")


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

    return response.json()


def create_twin(twin_key_name: str):
    twin_registered_id = api.create_twin_with_control_delegation(
        twin_seed=AGENT_SEED,
        twin_key_name=twin_key_name,
        agent_registered_identity=agent_registered_id,
        delegation_name="#ControlDeleg",
    )

    return twin_registered_id.did


def upsert_twin(
    twin_id: str,
    visibility: str = "PRIVATE",
    feeds: List[dict] = None,
    location: dict = None,
    properties: List[dict] = None,
):
    payload = {"twinId": twin_id, "visibility": visibility}

    if location:
        payload["location"] = location
    if feeds:
        payload["feeds"] = feeds
    if properties:
        payload["properties"] = properties

    make_api_call(method="PUT", url=f"{HOST}/qapi/twins", json=payload)


def describe_feed(twin_id: str, feed_id: str):
    feed_description = make_api_call(
        method="GET",
        url=f"{HOST}/qapi/twins/{twin_id}/feeds/{feed_id}",
    )

    return feed_description


# Set up the API with authentication delegation so the AGENT can authenticate on behalf of the USER
api = get_rest_high_level_identity_api(resolver_url=RESOLVER_URL)
(
    user_registered_id,
    agent_registered_id,
) = 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 = api.create_agent_auth_token(
    agent_registered_identity=agent_registered_id,
    user_did=user_registered_id.did,
    duration=600,
)

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

# Initialise an empty list.
# It will contain the list of Twins retrieved by the search
twins_list = []

# Add a new field in the headers.
# Client request timeout is used to stop the request processing once the timeout is reached
headers.update(
    {
        "Iotics-RequestTimeout": (
            datetime.now(tz=timezone.utc) + timedelta(seconds=10)
        ).isoformat(),
    }
)

with request(
    method="POST",
    url=f"{HOST}/qapi/searches",  # Replace with URL of the Host
    headers=headers,  # It includes the token and the Request Timeout
    stream=True,
    verify=False,
    params={"scope": "LOCAL"},
    json={
        "responseType": "FULL",
        "filter": {
            "properties": [
                {
                    "key": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
                    "uriValue": {"value": "https://data.iotics.com/app#Model"},
                }
            ],
            "text": "Temperature",
        },
    },
) as resp:
    # Raises HTTPError, if one occurred
    resp.raise_for_status()
    # Iterates over the response data, one line at a time
    for chunk in resp.iter_lines():
        response = json.loads(chunk)
        twins_found = []
        try:
            twins_found = response["result"]["payload"]["twins"]
            host_id = response["result"]["payload"]["remoteHostId"]["value"]
        except KeyError:
            host_id = "local"
        finally:
            if twins_found:
                # Add the host id value to each twin
                for twin in twins_found:
                    twin["hostId"] = host_id

                # Append the twins found to the list of twins
                twins_list.extend(twins_found)

headers.pop("Iotics-RequestTimeout")  # RequestTimeout no longer needed in the headers

print(f"Found {len(twins_list)} twin(s)")

# For demo purpose the Twin Model will be the only Twin in this list
twin_model = next(iter(twins_list))
twin_model_id = twin_model["id"]["value"]

# Get the Twin Model's list of properties
twin_model_properties = twin_model["properties"]
# Build a list of properties for the generic Twin
twin_property_list = [
    # Add the generic twins' "special" properties
    {
        "key": "https://data.iotics.com/app#model",
        "uriValue": {"value": twin_model_id},
    },
    {
        "key": "https://data.iotics.com/app#createdFrom",
        "uriValue": {"value": "https://data.iotics.com/app#ByModel"},
    },
]
# Add all the Twin Model's properties but consider the exceptions
for property in twin_model_properties:
    # This was the "special" property of the Twin Model, we don't need it here
    if property["key"] == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type":
        continue
    # Change the Label value
    if property["key"] == "http://www.w3.org/2000/01/rdf-schema#label":
        property["langLiteralValue"]["value"] = "Temperature Sensor 1"
    # Change the Created At value
    if property["key"] == "https://data.iotics.com/app#createdAt":
        property["literalValue"]["value"] = datetime.now(tz=timezone.utc).isoformat()
    # Change the Updated At value
    if property["key"] == "https://data.iotics.com/app#updatedAt":
        property["literalValue"]["value"] = datetime.now(tz=timezone.utc).isoformat()

    twin_property_list.append(property)

# Build a list of feeds for the generic Twin
feeds_metadata_list = []
# Scan the Twin Model's feeds and use the Feed Describe API to get the Feeds' metadata
for feed in twin_model["feeds"]:
    feed_id = feed["feed"]["id"]["value"]

    # Use the Feed Describe API
    feed_description = describe_feed(twin_id=twin_model_id, feed_id=feed_id)
    feed_properties = feed_description["result"]["properties"]
    feed_values = feed_description["result"]["values"]
    store_last = feed_description["result"]["storeLast"]

    # Append to the list all the Feed's metadata
    feeds_metadata_list.append(
        {
            "id": feed_id,
            "properties": feed_properties,
            "values": feed_values,
            "storeLast": store_last,
        }
    )

# Create Twin from Model
twin_did = create_twin(twin_key_name="GenericTwin1")

print(f"Twin {twin_did} created succesfully")

# Add Metadata, Feed and Value
upsert_twin(
    twin_id=twin_did,
    properties=twin_property_list,
    feeds=feeds_metadata_list,
    location={"lat": 51.5, "lon": -0.1},  # Add a location
)

print("Twin from Model updated with Metadata, Feed and Value")

Did this page help you?