Digital Twin Usage Examples

This section shows a few usage examples that can occur in common digital twin usage scenarios and cover handling of the modules DigitalTwin and Container. The examples in here are build around the management of data for heavy construction machines and cover common events link storing data, sharing data, etc.

  • manufacturer: original manufacturer of the heavy machine
  • customer: a user, that has bought the physical heavy machine
  • service-technician: hired by the customer to perform maintenance on the heavy machine

The examples in this section use these variables for aforementioned users:

const manufacturer = '0x0000000000000000000000000000000000000001';
const customer = '0x0000000000000000000000000000000000000002';
const serviceTechnician = '0x0000000000000000000000000000000000000003';

Create a Digital Twin

Digital Identities are collections of data related to a “thing”. A “thing” can be basically anything, a bird, a plane or someone from the planet Krypton. Jokes aside, it most commonly describes a physical object - like in our example here a heavy machine.

So let’s create a digital twin four our heavy machine “Big Crane 250”, which is done with the DigitalTwin.create function:

const bigCrane250 = await DigitalTwin.create(runtime, { accountId: manufacturer });

This creates a new digital twin for the account manufacturer, which can now add containers to it or set other properties.

The DigitalTwin.create config argument function supports more properties than just accountId.

You can and should give your digital twin a DBCP description. To do this pass it to the new digital twin in the config property.

const description = description: {
  name: 'Big Crane 250',
  description: 'Digital Twin for my heavy machine "Big Crane 250"',
  author: 'Manufacturer',
  version: '0.1.0',
  dbcpVersion: 2,
};
const bigCrane250 = await DigitalTwin.create(
  runtime, { accountId: manufacturer, description });

If you do not set a description, at creation time, a default description is set. This description is available at the DigitalTwin.defaultDescription and can be used as a starting point for your own description. A description can be updated later on as well, see digitalTwin.setDescription.

So let’s say, we have created a digital twin four our heavy machine with the setup from the last code example. We now have the following setup:

manufacturer created a digital twin

manufacturer created a digital twin


Add Containers to Digital Twin

Continuing with the digital twin from the last section we add a container, that holds manufacturers private data with information about the production process and a link to a manual file. This can be done with the digitalTwin.createContainers function:

const { data } = await bigCrane250.createContainers({
  data: {},
});

The manufacturer account now has created a Container instance called data. This can be customized as described at Container.create.

manufacturer added a container to the twin

manufacturer added a container to the twin


Add Data to the Container

Continuing the example, the manufacturer adds data to the container.

await data.setEntry(
  'productionProfile',
  {
    id: 'BC250-4711',
    dateOfManufacturing: '1554458858126',
    category: 'hem-c',
  },
);
await data.setEntry('manual', 'https://a-link-the-manual...');

As these properties are new, container.setEntry adds a role for each property and the owner of the digital twin joins this role. During this role 0 to 63 are skipped as they are system reserved and can be used for more complex contract role setups. So the roles 64 (for productionProfile) and 65 (for manual) are created.

For each new property a new encryption key is generated and stored in the contracts Sharings. When new properties are added, this key is only shared for the owner of the digital twin, so only the owner can access the data stored in the contract.

Data can be read from the containers with container.getEntry:

const productionProfile = await data.getEntry('productionProfile');
manufacturer added entries to the container

manufacturer added entries to the container


Share Container Properties

As already said, the manufacturer wants to keep production data for own usage and share a link to the manual to the account customer. When not explicitly shared, properties are kept private, so nothing to do for the field productionProfile. To allow other accounts to access manual, encryption keys have to be shared, which can be done with container.shareProperties:

await data.shareProperties([
  { accountId: customer, read: ['manual'] }
]);

With this call, the account customer is added to the role 1 (member), which allows basic contract interaction but not necessarily access to the data. And because manual has be specified as a read (-only) field, this account receives an encryption key for the property manual, so it is now able to read data from this field.

To load data from the twins, customer can now fetch the container from the digital twin and load its data. Let’s assume manufacturer has communicated the address of the digital twin (e.g. 0x00000000000000000000000000000000000000c1) to customer and the customer can access the link to the manual with:

const bigCrane250LoadedFromCustomer = new DigitalTwin(
  runtime, { accountId: customer, address: '0x00000000000000000000000000000000000000c1' });
const dataLoadedFromCustomer = await bigCrane250LoadedFromCustomer.getEntry('data');
const link = await dataLoadedFromCustomer.getEntry('manual');
customer can read entry "manual"

customer can read entry “manual”


Cloning Containers

If customer wants to re-use data from a data container or an entire data container but have ownership over it, it can clone it and use it in an own digital twin contract. This can be done with Container.clone:

const dataClone = await Container.clone(
  runtime, { accountId: customer }, dataLoadedFromCustomer);

This clone can be linked to a digital twin owner by customer. So let’s create a new one and add the clone to it:

const customersDescription = {
  name: 'My own Big Crane 250',
  description: 'I bought a Big Crane 250 and this is my collection of data for it',
  author: 'Customer',
  version: '0.1.0',
  dbcpVersion: 2,
};
const customersBigCrane250 = await DigitalTwin.create(
  runtime, { accountId: customer, description: customersDescription });

await customersBigCrane250.setEntry(
  'machine-data',
  dataClone,
  DigitalTwinEntryType.ContainerContract,
);

Note that the container is not named data like in the original twin but called machine-data here. Names can be reassigned as desired.

customer cloned data container

customer cloned data container


Granting Write Access

Properties at Containers can be “entries” as used in the last examples or “list entries”. To add data to lists call container.addListEntries:

await dataClone.addListEntries(
  'usagelog',
  [ 'I started using my new Big Crane 250' ]
);

Now customer wants to invite serviceTechnician and allow this account to add entries to the list usagelog as well. To do this, the list is shared the same way as in the previous example, but the field is shared as readWrite:

await dataClone.shareProperties([
  { accountId: customer, readWrite: ['usagelog'] }
]);

serviceTechnician can now write to the list usagelog and we now have the following setup:

customer invited service technician

customer invited service technician


Handling Files

Containers can hold files as well. File handling follows a few simple principles:

  • files are stored encrypted (as everything in containers is stored encrypted)
  • files are always stored as an array of files (think of it like a folder with files)
  • files are encrypted, uploaded and a reference is stored as a file at the contract (sounds like the default Hybrid Storage) approach, but is a reapplication to itself, as encrypted additional files with references to the original encrypted files are stored at the contract

Okay, let’s add some files to a container (taken from our tests).

A file needs to be provided as a buffer. In NodeJs, this can be done with fs.readFile

import { promisify } from 'util';
import { readFile } from 'fs';

const file = await promisify(readFile)(
`${__dirname}/testfiles/animal-animal-photography-cat-96938.jpg`);

The file is expected to be wrapped in a specific container format, which is defined in the ContainerFile interface. So let’s build such a file object and store it in an object including a property called files, as files are always provided as arrays of ContainerFile instances to the API:

const sampleFiles = {
  files:[{
    name: 'animal-animal-photography-cat-96938.jpg',
    fileType: 'image/jpeg',
    file,
  }]
};

If not already done, create (or load) a container:

const container = await Container.create(runtime, config);

If not already done, add a field for files to our container, for this the static property Container.defaultTemplates can be useful:

await container.ensureProperty('sampleFiles', Container.defaultSchemas.filesEntry);

So now everything is set up and we can store our file:

await container.setEntry('sampleFiles', sampleFiles);

And later on we can retrieve our file with:

await container.getEntry('sampleFiles');

That’s it for the simple case. If you want to get fancy, you can have a look at the more complex examples in the tests. With the build in file handling you can: