Create Twin Models
The Twins from Model contain data about the real asset, concept or person, whereas the Digital Twin Model serves as the template or structure of Digital Twins from Model.
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
- Create Twin Models with the IOTICSpace User Interface
- Create Twin Models with the IOTICS API
- Create Digital Twins from Model
- Create Twins from Model with the IOTICSpace User Interface
- Create Twins from Model with the IOTICS API
Create Digital Twin Models
Digital Twin Models are used to describe the template or structure of one or more Digital Twin from Model. You may therefore think of a Digital Twin Model as the parent of one or more Digital Twins from Model. Consider that:
- 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;
- Digital Twins can be grouped by Model: this simplifies operations like searching, filtering or deleting more than one Twin;
- Models only serve as a template for other Digital Twins: they don't share data or follow feeds. These functions are only performed by Twins from Model.

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 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.
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 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.
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 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"
}
Don't forget to check the Digital Twin's access permissions. You can update the hostAllowList
and hostMetadataAllowList
properties to enable or restrict access permissions for the Digital Twins according to Selective Sharing for Metadata and Data.
{
"key": "http://data.iotics.com/public#hostAllowList",
"uriValue": {"value": "http://data.iotics.com/public#all"},
},
{
"key": "http://data.iotics.com/public#hostMetadataAllowList",
"uriValue": {"value": "http://data.iotics.com/public#all"},
}
Click here to see the prerequisites.
1. Create a Twin
2. Add Twin's metadata including the "special" property, Feeds and Values' metadata
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 get_host_id(self):
host_id = self._make_api_call(method="GET", url=f"{HOST}/qapi/host/id")
return host_id["hostId"]
def upsert_twin(
self,
twin_did: str,
host_id: str,
feeds: List[dict] = None,
inputs: List[dict] = None,
location: dict = None,
properties: List[dict] = None,
):
payload = {"twinId": {"hostId": host_id, "id": twin_did}}
if location:
payload["location"] = location
if feeds:
payload["feeds"] = feeds
if inputs:
payload["inputs"] = inputs
if properties:
payload["properties"] = properties
self._make_api_call(method="PUT", url=f"{HOST}/qapi/twins", json=payload)
def main():
iotics_rest = IoticsRest()
twin_did = iotics_rest.create_twin_identity(twin_key_name="TwinModel")
host_id = iotics_rest.get_host_id()
iotics_rest.upsert_twin(
twin_did=twin_did,
host_id=host_id,
properties=[
# Model Property
{
"key": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
"uriValue": {"value": "https://data.iotics.com/app#Model"},
},
# Label
{
"key": "http://www.w3.org/2000/01/rdf-schema#label",
"langLiteralValue": {"value": "Twin Model", "lang": "en"},
},
# Comment
{
"key": "http://www.w3.org/2000/01/rdf-schema#comment",
"langLiteralValue": {
"value": "An example of a Twin Model",
"lang": "en",
},
},
# Colour
{
"key": "https://data.iotics.com/app#color",
"stringLiteralValue": {"value": "#d6df23"},
},
# Space Name
{
"key": "https://data.iotics.com/app#spaceName",
"stringLiteralValue": {"value": "my-space-name"},
},
],
)
if __name__ == "__main__":
main()
When creating and updating Digital Twin Models, consider the following best practices:
- 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.
- Can I easily identify the Models from Twins from Model? 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.
- 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 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 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).
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 property to your new Digital Twins. All Twins that include the below property will be categorised by the IOTICS user interface as "Twins from Model".
{
"key": "https://data.iotics.com/app#model",
"uriValue": {"value": model_twin_did},
}
Since the Twin Model serves as a template for the Twins from Model, 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 property model: model_twin_did that identifies the Twins from Model;
Click here to see the prerequisites.
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" property for Twins from Model
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")
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=60,
)
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 get_host_id(self):
host_id = self._make_api_call(method="GET", url=f"{HOST}/qapi/host/id")
return host_id["hostId"]
def upsert_twin(
self,
twin_did: str,
host_id: str,
feeds: List[dict] = None,
inputs: List[dict] = None,
location: dict = None,
properties: List[dict] = None,
):
payload = {"twinId": {"hostId": host_id, "id": twin_did}}
if location:
payload["location"] = location
if feeds:
payload["feeds"] = feeds
if inputs:
payload["inputs"] = inputs
if properties:
payload["properties"] = properties
self._make_api_call(method="PUT", url=f"{HOST}/qapi/twins", json=payload)
def describe_feed(self, twin_did: str, host_id: str, feed_id: str):
feed_description = self._make_api_call(
method="GET",
url=f"{HOST}/qapi/hosts/{host_id}/twins/{twin_did}/feeds/{feed_id}",
)
return feed_description
def describe_input(self, twin_did: str, host_id: str, input_id: str):
input_description = self._make_api_call(
method="GET",
url=f"{HOST}/qapi/hosts/{host_id}/twins/{twin_did}/inputs/{input_id}",
)
return input_description
def search_twins(
self,
text: str = None,
location: dict = None,
properties: List[dict] = None,
scope: str = "LOCAL",
response_type: str = "FULL",
):
twins_list = []
search_headers = self._headers.copy()
# Search headers require a new header "Iotics-RequestTimeout".
# The latter is used to stop the request once the timeout is reached
search_headers.update(
{
"Iotics-RequestTimeout": (
datetime.now(tz=timezone.utc) + timedelta(seconds=5)
).isoformat(),
}
)
payload = {"responseType": response_type, "filter": {}}
if text:
payload["filter"]["text"] = text
if properties:
payload["filter"]["properties"] = properties
if location:
payload["filter"]["location"] = location
with request(
method="POST",
url=f"{HOST}/qapi/searches",
headers=search_headers,
stream=True,
verify=True,
params={"scope": scope},
json=payload,
) as resp:
resp.raise_for_status()
# Iterates over the response data, one Host at a time
for chunk in resp.iter_lines():
response = json.loads(chunk)
twins_found = []
try:
twins_found = response["result"]["payload"]["twins"]
except KeyError:
continue
finally:
if twins_found:
# Append the twins found to the list of twins
twins_list.extend(twins_found)
print(f"Found {len(twins_list)} twin(s)")
return twins_list
def create_twin_from_model(self, twin_model: dict, label: str, comment: str):
twin_model_did = twin_model["twinId"]["id"]
twin_model_host_id = twin_model["twinId"]["hostId"]
# Get the Twin Model's list of properties
twin_model_properties = twin_model["properties"]
# Build a list of properties for the Twin from Model
twin_from_model_property_list = [
# Add the Twins' from Model "special" property
{
"key": "https://data.iotics.com/app#model",
"uriValue": {"value": twin_model_did},
}
]
# 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
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"] = label
# Change the Comment value
if property["key"] == "http://www.w3.org/2000/01/rdf-schema#comment":
property["langLiteralValue"]["value"] = comment
twin_from_model_property_list.append(property)
# Build a list of Feeds for the Twin from Model
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["feedId"]["id"]
# Use the Feed Describe API
feed_description = self.describe_feed(
twin_did=twin_model_did, host_id=twin_model_host_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,
}
)
# Build a list of Inputs for the Twin from Model
inputs_metadata_list = []
# Scan the Twin Model's Inputs and use the Input Describe API to get the Inputs' metadata
for input in twin_model["inputs"]:
input_id = input["inputId"]["id"]
# Use the Input Describe API
input_description = self.describe_input(
twin_did=twin_model_did, host_id=twin_model_host_id, input_id=input_id
)
input_properties = input_description["result"]["properties"]
input_values = input_description["result"]["values"]
# Append to the list all the Inputs's metadata
inputs_metadata_list.append(
{"id": input_id, "properties": input_properties, "values": input_values}
)
return twin_from_model_property_list, feeds_metadata_list, inputs_metadata_list
def main():
iotics_rest = IoticsRest()
twin_did = iotics_rest.create_twin_identity(twin_key_name="TwinFromModel")
host_id = iotics_rest.get_host_id()
twins_list = iotics_rest.search_twins(
properties=[
{
"key": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
"uriValue": {"value": "https://data.iotics.com/app#Model"},
}
]
)
# In this example it is assumed the Twin Model to use is the first one in the list
twin_model = next(iter(twins_list))
(
twin_from_model_property_list,
feeds_metadata_list,
inputs_metadata_list,
) = iotics_rest.create_twin_from_model(
twin_model=twin_model,
label="Temperature Sensor",
comment="An example of a Twin from Model",
)
iotics_rest.upsert_twin(
twin_did=twin_did,
host_id=host_id,
feeds=feeds_metadata_list,
inputs=inputs_metadata_list,
properties=twin_from_model_property_list,
)
if __name__ == "__main__":
main()
Updated 5 months ago