This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

How to

Interact with the platform

  • Creating and reading entities and characteristics → Orchestrator REST API
  • Saving and reading real-time metrics and states → IIoT Middleware
  • Reading historical metrics and states → HDM REST API

1 - Orchestrator REST API

Create and read entities

The Orchestrator REST API enables to create and read all the different kinds of Digital Twin entities described in Model section.

This API is available through a dedicated Swagger interface for each Clawdite instance. Furthermore, it is possible to generate API clients for several languages, such as Python, Java and JavaScript. The mentioned clients are already generated and available within each Clawdite instance and make it possible to interact with Clawdite’s Orchestrator from external components.

In the following a few examples on entities management via API clients will be provided. For mandatory entity attributes refer to the Model section.

Install the Orchestrator client dependencies

In order to use the generated API clients inside external components it is needed to correctly setup and install the dependencies. Note that you need a personal GitLab token for accessing the registries. In case you don’t have it please contact the project’s maintainers.

pip install orchestrator-python-client --index-url https://__token__:<your_personal_token>@gitlab-core.supsi.ch/api/v4/projects/86/packages/pypi/simple
<dependencies>
    <dependency>
      <groupId>ch.supsi.dti.isteps.hdt</groupId>
      <artifactId>orchestrator-java-client</artifactId>
      <version>x.y.z</version>
    </dependency>
</dependencies>

<repositories>
  <repository>
    <id>gitlab-maven</id>
    <url>https://gitlab-core.supsi.ch/api/v4/projects/86/packages/maven</url>
  </repository>
</repositories>
npm install orchestrator-javascript-client --registry https://token:<your_personal_token>@gitlab-core.supsi.ch/api/v4/projects/86/packages/npm/

Create a Factory

The creation of a Factory is independent of other entities. While not mandatory, defining this entity can be very useful when modeling a Clawdite instance. A Factory serves as the “container” for all related FactoryEntities, such as workers, cells, machines, and equipment.

import os
from datetime import datetime
import orchestrator_python_client as hdt_client
from orchestrator_python_client import ApiClient, FactoryDto

# NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables

configuration = hdt_client.Configuration(host=os.getenv('HDT_ENDPOINT'))
configuration.api_key['apiKeyAuth'] = os.getenv('HDT_API_KEY')
api_client = hdt_client.ApiClient(configuration)

factory_api = hdt_client.FactoryApi(api_client)

clawdite_factory = factory_api.create_factory(
    FactoryDto(name="Clawdite Factory")).payload
import java.util.Collections;
import java.time.LocalDateTime;
import org.openapitools.client.ApiClient;
import org.openapitools.client.api.FactoryApi;
import org.openapitools.client.model.FactoryDto;

private void createFactory() {
    // NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
    ApiClient apiClient = new ApiClient().setBasePath(System.getenv("HDT_ENDPOINT"));
    String apiKey = System.getenv("HDT_API_KEY");
    if(apiKey != null && !apiKey.isEmpty())
        apiClient.addDefaultHeader("x-api-key", apiKey);

    final FactoryApi factoryApi = new FactoryApi(apiClient);

    FactoryDto clawditeFactory = factoryApi.createFactory(
            new FactoryDto()
                    .setName("Clawdite Factory"));
}
import { Configuration, FactoryApi, FactoryDto } from 'orchestrator-javascript-client';

// NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
const configuration = new Configuration({
    basePath: process.env.HDT_ENDPOINT,
    apiKey: { apiKeyAuth: process.env.HDT_API_KEY }
});

const factoryApi = new FactoryApi(configuration);

async function createFactory() {
    await factoryApi.createFactory(new FactoryDto({ name: "Clawdite Factory" }));
}

Create a Worker

In order to create a Worker entity (or a FactoryEntity in general) it is mandatory to define the associated FactoryEntityModelCategoty and FactoryEntityModel in advance. This entity represents all the human beings characterizing the Factory.

import os
from datetime import datetime
import orchestrator_python_client as hdt_client
from orchestrator_python_client import ApiClient, FactoryEntityModelCategoryDto, FactoryEntityModelDto, WorkerDto

# NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables

configuration = hdt_client.Configuration(host=os.getenv('HDT_ENDPOINT'))
configuration.api_key['apiKeyAuth'] = os.getenv('HDT_API_KEY')
api_client = hdt_client.ApiClient(configuration)

factory_entity_model_category_api = hdt_client.FactoryEntityModelCategoryApi(api_client)
factory_entity_model_api = hdt_client.FactoryEntityModelApi(api_client)
worker_api = hdt_client.WorkerApi(api_client)

operator_category = factory_entity_model_category_api.create_factory_entity_model_category(
                        FactoryEntityModelCategoryDto(name="Operator",
                            description="description")).payload

worker_model = factory_entity_model_api.create_factory_entity_model(
                        FactoryEntityModelDto(name="Worker",
                            description="description",
                            factory_entity_model_categories_id=[operator_category.id])).payload

worker = worker_api.create_worker(
                        WorkerDto(creation_date=datetime.now().isoformat(),
                            factory_entity_model_id=worker_model.id)).payload
import java.util.Collections;
import java.time.LocalDateTime;
import org.openapitools.client.ApiClient;
import org.openapitools.client.api.FactoryEntityModelCategoryApi;
import org.openapitools.client.api.FactoryEntityModelApi;
import org.openapitools.client.api.WorkerApi;
import org.openapitools.client.model.FactoryEntityModelCategoryDto;
import org.openapitools.client.model.FactoryEntityModelDto;
import org.openapitools.client.model.WorkerDto;

private void createWorker() {
    // NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
    ApiClient apiClient = new ApiClient().setBasePath(System.getenv("HDT_ENDPOINT"));
    String apiKey = System.getenv("HDT_API_KEY");
    if(apiKey != null && !apiKey.isEmpty())
        apiClient.addDefaultHeader("x-api-key", apiKey);

    final FactoryEntityModelCategoryApi factoryEntityModelCategoryApi = new FactoryEntityModelCategoryApi(apiClient);
    final FactoryEntityModelApi factoryEntityModelApi = new FactoryEntityModelApi(apiClient);
    final WorkerApi workerApi = new WorkerApi(apiClient);

    FactoryEntityModelCategoryDto operatorCategory = factoryEntityModelCategoryApi.createFactoryEntityModelCategory(
            new FactoryEntityModelCategoryDto()
                    .setName("Operator")
                    .setDescription("description"));

    FactoryEntityModelDto workerModel = factoryEntityModelApi.createFactoryEntityModel(
            new FactoryEntityModelDto()
                    .setName("Worker")
                    .setDescription("description")
                    .setFactoryEntityModelCategoriesId(Collections.singletonList(operatorCategory.getId())));

    WorkerDto worker = workerApi.createWorker(
            new WorkerDto()
                    .setCreationDate(LocalDateTime.now().toString())
                    .setFactoryEntityModelId(workerModel.getId()));

}
import { Configuration, FactoryEntityModelCategoryApi, FactoryEntityModelApi, WorkerApi } from 'orchestrator-javascript-client';

// NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
const configuration = new Configuration({
    basePath: process.env.HDT_ENDPOINT,
    apiKey: { apiKeyAuth: process.env.HDT_API_KEY }
});

const factoryEntityModelCategoryApi = new FactoryEntityModelCategoryApi(configuration);
const factoryEntityModelApi = new FactoryEntityModelApi(configuration);
const workerApi = new WorkerApi(configuration);

async function createWorker() {
    const operatorCategory = await factoryEntityModelCategoryApi.createFactoryEntityModelCategory({
        name: "Operator",
        description: "description"
    });
    
    const workerModel = await factoryEntityModelApi.createFactoryEntityModel({
        name: "Worker",
        description: "description",
        factoryEntityModelCategoriesId: [operatorCategory.payload.id]
    });
    
    await workerApi.createWorker({
        creationDate: new Date().toISOString(),
        factoryEntityModelId: workerModel.payload.id
    });
}

Create a FactoryEntity

Similarly to Worker entity creation, in order to create a FactoryEntity it is mandatory to define the associated FactoryEntityModelCategoty and FactoryEntityModel in advance. A typical instance of FactoryEntity is the physical factory’s cell to which workers are assigned or a machine.

This example details the creation of a Device, which is a particular type of FactoryEntity. In fact, it requires a DeviceModel to be defined, a specialization of FactoryEntityModel.

import os
from datetime import datetime
import orchestrator_python_client as hdt_client
from orchestrator_python_client import ApiClient, FactoryEntityModelCategoryDto, DeviceModelDto, FactoryEntityDto

# NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables

configuration = hdt_client.Configuration(host=os.getenv('HDT_ENDPOINT'))
configuration.api_key['apiKeyAuth'] = os.getenv('HDT_API_KEY')
api_client = hdt_client.ApiClient(configuration)

factory_entity_model_category_api = hdt_client.FactoryEntityModelCategoryApi(api_client)
device_model_api = hdt_client.DeviceModelApi(api_client)
factory_entity_api = hdt_client.FactoryEntityApi(api_client)

device_category = factory_entity_model_category_api.create_factory_entity_model_category(
    FactoryEntityModelCategoryDto(name="Wearable",
    description="description")).payload

device_model = device_model_api.create_device_model(
    DeviceModelDto(name="Device",
    description="description",
    brand="brand",
    model="model",
    factory_entity_model_categories_id=[device_category.id])).payload

device = factory_entity_api.create_factory_entity(
    FactoryEntityDto(creation_date=datetime.now().isoformat(),
    factory_entity_model_id=device_model.id)).payload
import java.util.Collections;
import java.time.LocalDateTime;
import org.openapitools.client.ApiClient;
import org.openapitools.client.api.FactoryEntityModelCategoryApi;
import org.openapitools.client.api.DeviceModelApi;
import org.openapitools.client.api.FactoryEntityApi;
import org.openapitools.client.model.FactoryEntityModelCategoryDto;
import org.openapitools.client.model.DeviceModelDto;
import org.openapitools.client.model.FactoryEntityDto;

private void createDevice() {
// NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
    ApiClient apiClient = new ApiClient().setBasePath(System.getenv("HDT_ENDPOINT"));
    String apiKey = System.getenv("HDT_API_KEY");
    if(apiKey != null && !apiKey.isEmpty())
        apiClient.addDefaultHeader("x-api-key", apiKey);

    final FactoryEntityModelCategoryApi factoryEntityModelCategoryApi = new FactoryEntityModelCategoryApi(apiClient);
    final DeviceModelApi deviceModelApi = new DeviceModelApi(apiClient);
    final FactoryEntityApi factoryEntityApi = new FactoryEntityApi(apiClient);

    FactoryEntityModelCategoryDto deviceCategory = factoryEntityModelCategoryApi.createFactoryEntityModelCategory(
            new FactoryEntityModelCategoryDto()
                    .setName("Wearable")
                    .setDescription("description"));

    DeviceModelDto deviceModel = deviceModelApi.createDeviceModel(
            new DeviceModelDto()
                    .setName("Device")
                    .setDescription("description")
                    .setBrand("brand")
                    .setModel("model")
                    .setFactoryEntityModelCategoriesId(Collections.singletonList(deviceCategory.getId())));

    FactoryEntityDto device = factoryEntityApi.createFactoryEntity(
            new FactoryEntityDto()
                    .setCreationDate(LocalDateTime.now().toString())
                    .setFactoryEntityModelId(deviceModel.getId()));

}
import { Configuration, FactoryEntityModelCategoryApi, DeviceModelApi, FactoryEntityApi } from 'orchestrator-javascript-client';

// NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
const configuration = new Configuration({
    basePath: process.env.HDT_ENDPOINT,
    apiKey: { apiKeyAuth: process.env.HDT_API_KEY }
});

const factoryEntityModelCategoryApi = new FactoryEntityModelCategoryApi(configuration);
const deviceModelApi = new DeviceModelApi(configuration);
const factoryEntityApi = new FactoryEntityApi(configuration);

async function createDevice() {
    const deviceCategory = await factoryEntityModelCategoryApi.createFactoryEntityModelCategory({
        name: "Wearable",
        description: "description"
    });
    
    const deviceModel = await deviceModelApi.createDeviceModel({
        name: "Device",
        description: "description",
        brand: "brand",
        model: "model",
        factoryEntityModelCategoriesId: [deviceCategory.payload.id]
    });
    
    await factoryEntityApi.createFactoryEntity({
        creationDate: new Date().toISOString(),
        factoryEntityModelId: deviceModel.payload.id
    });
}

Create a CharacteristicDescriptor & CharacteristicValue

In order to create a CharacteristicDescriptor it is mandatory to define the associated FactoryEntityModel (with its FactoryEntityModelCategory) and optionally the entities associated to the base AbstractDescriptor (such as UnitOfMeasure, Category, Scale and Taxonomy).

Once the CharacteristicDescriptor is created, it is possible to assign a concrete CharacteristicValue, by specifying the value itself and the FactoryEntity it refers to.

import os
from datetime import datetime
import orchestrator_python_client as hdt_client
from orchestrator_python_client import ApiClient, FactoryEntityModelCategoryDto, FactoryEntityModelDto, 
    WorkerDto, CharacteristicDescriptorDto, CharacteristicValueDto, FieldType

# NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables

configuration = hdt_client.Configuration(host=os.getenv('HDT_ENDPOINT'))
configuration.api_key['apiKeyAuth'] = os.getenv('HDT_API_KEY')
api_client = hdt_client.ApiClient(configuration)

factory_entity_model_category_api = hdt_client.FactoryEntityModelCategoryApi(api_client)
factory_entity_model_api = hdt_client.FactoryEntityModelApi(api_client)
worker_api = hdt_client.WorkerApi(api_client)
characteristic_descriptor_api = hdt_client.CharacteristicDescriptorApi(api_client)
characteristic_value_api = hdt_client.CharacteristicValueApi(api_client)

factory_entity_model_category = factory_entity_model_category_api.create_factory_entity_model_category(
    FactoryEntityModelCategoryDto(name="Operator",
    description="description")).payload

factory_entity_model = factory_entity_model_api.create_factory_entity_model(
    FactoryEntityModelDto(name="Worker",
    description="description",
    factory_entity_model_categories_id=[factory_entity_model_category.id])).payload

worker = worker_api.create_worker(
    WorkerDto(creation_date=datetime.now().isoformat(),
    factory_entity_model_id=factory_entity_model.id)).payload

characteristic_descriptor = characteristic_descriptor_api.create_characteristic_descriptor(
    CharacteristicDescriptorDto(name="Sex",
    description="description",
    factory_entity_model_id=factory_entity_model.id)).payload

characteristic_value = characteristic_value_api.create_characteristic_value(
    CharacteristicValueDto(values={datetime.now().isoformat(): "Male"},
    type=FieldType.STRING,
    characteristic_descriptor_id=characteristic_descriptor.id,
    factory_entity_id=worker.id)).payload
import java.util.Collections;
import java.time.LocalDateTime;
import org.openapitools.client.ApiClient;
import org.openapitools.client.api.FactoryEntityModelCategoryApi;
import org.openapitools.client.api.FactoryEntityModelApi;
import org.openapitools.client.api.WorkerApi;
import org.openapitools.client.api.CharacteristicDescriptorApi;
import org.openapitools.client.api.CharacteristicValueApi;
import org.openapitools.client.model.FactoryEntityModelCategoryDto;
import org.openapitools.client.model.FactoryEntityModelDto;
import org.openapitools.client.model.WorkerDto;
import org.openapitools.client.model.CharacteristicDescriptorDto;
import org.openapitools.client.model.CharacteristicValueDto;

private void createCharacteristics() {
    // NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
    ApiClient apiClient = new ApiClient().setBasePath(System.getenv("HDT_ENDPOINT"));
    String apiKey = System.getenv("HDT_API_KEY");
    if(apiKey != null && !apiKey.isEmpty())
        apiClient.addDefaultHeader("x-api-key", apiKey);

    final FactoryEntityModelCategoryApi factoryEntityModelCategoryApi = new FactoryEntityModelCategoryApi(apiClient);
    final FactoryEntityModelApi factoryEntityModelApi = new FactoryEntityModelApi(apiClient);
    final WorkerApi workerApi = new WorkerApi(apiClient);
    final CharacteristicDescriptorApi characteristicDescriptorApi = new CharacteristicDescriptorApi(apiClient);
    final CharacteristicValueApi characteristicValueApi = new CharacteristicValueApi(apiClient);

    FactoryEntityModelCategoryDto operatorCategory = factoryEntityModelCategoryApi.createFactoryEntityModelCategory(
            new FactoryEntityModelCategoryDto()
                    .setName("Operator")
                    .setDescription("description"));

    FactoryEntityModelDto workerModel = factoryEntityModelApi.createFactoryEntityModel(
            new FactoryEntityModelDto()
                    .setName("Worker")
                    .setDescription("description")
                    .setFactoryEntityModelCategoriesId(Collections.singletonList(operatorCategory.getId())));

    WorkerDto worker = workerApi.createWorker(
            new WorkerDto()
                    .setCreationDate(LocalDateTime.now().toString())
                    .setFactoryEntityModelId(workerModel.getId()));

    CharacteristicDescriptorDto characteristicDescriptor = characteristicDescriptorApi.createCharacteristicDescriptor(
            new CharacteristicDescriptorDto()
                    .setName("Sex")
                    .setDescription("description")
                    .setFactoryEntityModelId(factoryEntityModel.getId()));

    CharacteristicValueDto characteristicValue = characteristicValueApi.createCharacteristicValue(
            new CharacteristicValueDto()
                    .setValues(Collections.singletonMap(LocalDateTime.now().toString(), "Male"))
                    .setType(FieldType.STRING)
                    .setCharacteristicDescriptorId(characteristicDescriptor.getId())
                    .setFactoryEntityId(worker.getId()));
}
import { Configuration, FactoryEntityModelCategoryApi, FactoryEntityModelApi, WorkerApi, CharacteristicDescriptorApi, CharacteristicValueApi } from 'orchestrator-javascript-client';

// NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
const configuration = new Configuration({
    basePath: process.env.HDT_ENDPOINT,
    apiKey: { apiKeyAuth: process.env.HDT_API_KEY }
});

const factoryEntityModelCategoryApi = new FactoryEntityModelCategoryApi(configuration);
const factoryEntityModelApi = new FactoryEntityModelApi(configuration);
const workerApi = new WorkerApi(configuration);
const characteristicDescriptorApi = new CharacteristicDescriptorApi(configuration);
const characteristicValueApi = new CharacteristicValueApi(configuration);

async function createCharacteristics() {
    const operatorCategory = await factoryEntityModelCategoryApi.createFactoryEntityModelCategory({
        name: "Operator",
        description: "description"
    });
    
    const workerModel = await factoryEntityModelApi.createFactoryEntityModel({
        name: "Worker",
        description: "description",
        factoryEntityModelCategoriesId: [operatorCategory.payload.id]
    });
    
    const worker = await workerApi.createWorker({
        creationDate: new Date().toISOString(),
        factoryEntityModelId: workerModel.payload.id
    });
    
    const characteristicDescriptor = await characteristicDescriptorApi.createCharacteristicDescriptor({
        name: "Sex",
        description: "description",
        factoryEntityModelId: workerModel.payload.id
    });
    
    await characteristicValueApi.createCharacteristicValue({
        values: { [new Date().toISOString()]: "Male" },
        type: "STRING",
        characteristicDescriptorId: characteristicDescriptor.payload.id,
        factoryEntityId: worker.payload.id
    });
}

Create a MeasurementDescriptor

In order to create a MeasurementDescriptor it is mandatory to define the associated FactoryEntityModel (with its FactoryEntityModelCategory) and optionally the entities associated to the base AbstractDescriptor (such as UnitOfMeasure, Category, Scale and Taxonomy).

In this example the MeasurementDescriptor refers to a simple metric named HR from a Polar device. Note that the fields keys (in this case only hr) are mandatory and have to correspond with the JSON message keys when publishing a metric over the IIoT Middleware. In case of complex metrics with more than a single field, list all the fields in both MeasurementDescriptor fields and OutputMap parameterList using the exact same syntax.

Despite not essential, it is highly suggested to model also the OutputMap(s) associated to a MeasurementDescriptor (along with corresponding DeviceModel). This enables to map the same metric coming from different devices to the same MeasurementDescriptor. Additionally, it permits to specify the logical order in which to interpret the metric fields, particularly useful when they are complex. For example, in the case of a spatial value, [x-y-z] or [z-x-y].

import os
from datetime import datetime
import orchestrator_python_client as hdt_client
from orchestrator_python_client import ApiClient, FactoryEntityModelCategoryDto, FactoryEntityModelDto,
    WorkerDto, DeviceModelDto, MeasurementDescriptorDto, OutputMapDto, FieldType

# NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables

configuration = hdt_client.Configuration(host=os.getenv('HDT_ENDPOINT'))
configuration.api_key['apiKeyAuth'] = os.getenv('HDT_API_KEY')
api_client = hdt_client.ApiClient(configuration)

factory_entity_model_category_api = hdt_client.FactoryEntityModelCategoryApi(api_client)
factory_entity_model_api = hdt_client.FactoryEntityModelApi(api_client)
worker_api = hdt_client.WorkerApi(api_client)
device_model_api = hdt_client.DeviceModelApi(api_client)
measurement_descriptor_api = hdt_client.MeasurementDescriptorApi(api_client)
output_map_api = hdt_client.OutputMapApi(api_client)

factory_entity_model_category = factory_entity_model_category_api.create_factory_entity_model_category(
    FactoryEntityModelCategoryDto(name="Operator",
    description="description")).payload

factory_entity_model = factory_entity_model_api.create_factory_entity_model(
    FactoryEntityModelDto(name="Worker",
    description="description",
    factory_entity_model_categories_id=[factory_entity_model_category.id])).payload

worker = worker_api.create_worker(
    WorkerDto(creation_date=datetime.now().isoformat(),
    factory_entity_model_id=factory_entity_model.id)).payload

wearable_category = factory_entity_model_category_api.create_factory_entity_model_category(
    FactoryEntityModelCategoryDto(name="Wearable",
    description="description")).payload

device_model_polar_h10 = device_model_api.create_device_model(
    DeviceModelDto(brand="Polar",
    model="H10",
    name="Polar H10",
    description="description",
    factory_entity_model_categories_id=[wearable_category.id])).payload

hr_descriptor = measurement_descriptor_api.create_measurement_descriptor(
    MeasurementDescriptorDto(fields={"hr": FieldType.NUMBER}, # sub-metric name on Clawdite
    name="HR",
    description="Heart Rate",
    factory_entity_model_id=factory_entity_model_worker.id)).payload

output_map_hr_polar_h10 = output_map_api.create_output_map(
    OutputMapDto(parameters_list=["hr"],
    measurement_descriptor_id=measurement_descriptor_hr.id,
    device_model_id=device_model_polar_h10.id)).payload
import java.util.Collections;
import java.time.LocalDateTime;
import org.openapitools.client.ApiClient;
import org.openapitools.client.api.FactoryEntityModelCategoryApi;
import org.openapitools.client.api.FactoryEntityModelApi;
import org.openapitools.client.api.WorkerApi;
import org.openapitools.client.api.DeviceModelApi;
import org.openapitools.client.api.MeasurementDescriptorApi;
import org.openapitools.client.api.OutputMapApi;
import org.openapitools.client.model.FactoryEntityModelCategoryDto;
import org.openapitools.client.model.FactoryEntityModelDto;
import org.openapitools.client.model.WorkerDto;
import org.openapitools.client.model.DeviceModelDto;
import org.openapitools.client.model.MeasurementDescriptorDto;
import org.openapitools.client.model.OutputMapDto;

private void createMeasurement() {
    // NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
    ApiClient apiClient = new ApiClient().setBasePath(System.getenv("HDT_ENDPOINT"));
    String apiKey = System.getenv("HDT_API_KEY");
    if(apiKey != null && !apiKey.isEmpty())
        apiClient.addDefaultHeader("x-api-key", apiKey);

    final FactoryEntityModelCategoryApi factoryEntityModelCategoryApi = new FactoryEntityModelCategoryApi(apiClient);
    final FactoryEntityModelApi factoryEntityModelApi = new FactoryEntityModelApi(apiClient);
    final WorkerApi workerApi = new WorkerApi(apiClient);
    final DeviceModelApi deviceModelApi = new DeviceModelApi(apiClient);
    final MeasurementDescriptorApi measurementDescriptorApi = new MeasurementDescriptorApi(apiClient);
    final OutputMapApi outputMapApi = new OutputMapApi(apiClient);

    FactoryEntityModelCategoryDto operatorCategory = factoryEntityModelCategoryApi.createFactoryEntityModelCategory(
            new FactoryEntityModelCategoryDto()
                    .setName("Operator")
                    .setDescription("description"));

    FactoryEntityModelDto workerModel = factoryEntityModelApi.createFactoryEntityModel(
            new FactoryEntityModelDto()
                    .setName("Worker")
                    .setDescription("description")
                    .setFactoryEntityModelCategoriesId(Collections.singletonList(operatorCategory.getId())));

    WorkerDto worker = workerApi.createWorker(
            new WorkerDto()
                    .setCreationDate(LocalDateTime.now().toString())
                    .setFactoryEntityModelId(workerModel.getId()));

    FactoryEntityModelCategoryDto wearableCategory = factoryEntityModelCategoryApi.createFactoryEntityModelCategory(
            new FactoryEntityModelCategoryDto()
                    .setName("Wearable")
                    .setDescription("description"));

    DeviceModelDto device_model_polar_h10 = deviceModelApi.createDeviceModel(
            new DeviceModelDto()
                    .setBrand("Polar")
                    .setModel("H10")
                    .setName("Polar H10")
                    .setDescription("description")
                    .setFactoryEntityModelCategoriesId(Collections.singletonList(wearable_category.getId())));

    MeasurementDescriptorDto hr_descriptor = measurementDescriptorApi.createMeasurementDescriptor(
            new MeasurementDescriptorDto()
                    .setFields(Collections.singletonMap("hr", FieldType.NUMBER))
                    .setName("HR")
                    .setDescription("Heart Rate")
                    .setFactoryEntityModelId(factory_entity_model_worker.getId()));

    OutputMapDto output_map_hr_polar_h10 = outputMapApi.createOutputMap(
            new OutputMapDto()
                    .setParametersList(Collections.singletonList("hr"))
                    .setMeasurementDescriptorId(measurement_descriptor_hr.getId())
                    .setDeviceModelId(device_model_polar_h10.getId()));
}
import { Configuration, FactoryEntityModelCategoryApi, FactoryEntityModelApi, WorkerApi, DeviceModelApi, MeasurementDescriptorApi, OutputMapApi } from 'orchestrator-javascript-client';

// NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
const configuration = new Configuration({
    basePath: process.env.HDT_ENDPOINT,
    apiKey: { apiKeyAuth: process.env.HDT_API_KEY }
});

const factoryEntityModelCategoryApi = new FactoryEntityModelCategoryApi(configuration);
const factoryEntityModelApi = new FactoryEntityModelApi(configuration);
const workerApi = new WorkerApi(configuration);
const deviceModelApi = new DeviceModelApi(configuration);
const measurementDescriptorApi = new MeasurementDescriptorApi(configuration);
const outputMapApi = new OutputMapApi(configuration);

async function createMeasurement() {
    const operatorCategory = await factoryEntityModelCategoryApi.createFactoryEntityModelCategory({
        name: "Operator",
        description: "description"
    });
    
    const workerModel = await factoryEntityModelApi.createFactoryEntityModel({
        name: "Worker",
        description: "description",
        factoryEntityModelCategoriesId: [operatorCategory.payload.id]
    });
    
    await workerApi.createWorker({
        creationDate: new Date().toISOString(),
        factoryEntityModelId: workerModel.payload.id
    });
    
    const wearableCategory = await factoryEntityModelCategoryApi.createFactoryEntityModelCategory({
        name: "Wearable",
        description: "description"
    });
    
    const deviceModelPolarH10 = await deviceModelApi.createDeviceModel({
        brand: "Polar",
        model: "H10",
        name: "Polar H10",
        description: "description",
        factoryEntityModelCategoriesId: [wearableCategory.payload.id]
    });
    
    const hrDescriptor = await measurementDescriptorApi.createMeasurementDescriptor({
        fields: { hr: "NUMBER" },
        name: "HR",
        description: "Heart Rate",
        factoryEntityModelId: workerModel.payload.id
    });
    
    await outputMapApi.createOutputMap({
        parametersList: ["hr"],
        measurementDescriptorId: hrDescriptor.payload.id,
        deviceModelId: deviceModelPolarH10.payload.id
    });
}

Create a FunctionalModule

Despite the creation of a FunctionalModule entity in not dependent on other entities, it is important to define the associated FunctionalModuleInput and FunctionalModuleOutput. This will enable the creation of the StateDescriptor, representing the result of the FunctionalModule processing (i.e. the output that will be sent to the IIoT Middleware).

The FunctionalModuleInput is not mandatory, but it is essential when the FunctionalModule necessitates as input some data from Clawdite, such as metrics or characteristics of the entities. In this example the input is the HearthRate saved through a dedicate MeasurementDescriptor, which is supposed to exist already. Refer to Create a MeasurementDescriptor.

import os
from datetime import datetime
import orchestrator_python_client as hdt_client
from orchestrator_python_client import ApiClient, FunctionalModuleDto, FunctionalModuleInputDto, FunctionalModuleOutputDto

# NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables

configuration = hdt_client.Configuration(host=os.getenv('HDT_ENDPOINT'))
configuration.api_key['apiKeyAuth'] = os.getenv('HDT_API_KEY')
api_client = hdt_client.ApiClient(configuration)

functional_module_api = hdt_client.FunctionalModuleApi(api_client)
functional_module_input_api = hdt_client.FunctionalModuleInputApi(api_client)
functional_module_output_api = hdt_client.FunctionalModuleOutputApi(api_client)

custom_module = functional_module_api.create_functional_module(
                        FunctionalModuleDto(name="CustomModule", description="description")).payload

# NOTE: this supposes that hr_descriptor already exists
custom_module_input = functional_module_input_api.create_functional_module_input(
                        FunctionalModuleInputDto(input_param_name="hr", path="HR", functional_module_id=custom_module.id,
                                    descriptor_id=hr_descriptor.id)).payload

custom_module_output = functional_module_output_api.create_functional_module_output(
                        FunctionalModuleOutputDto(functional_module_id=custom_module.id,
                                    output_param_name="prediction")).payload
import java.util.Collections;
import java.time.LocalDateTime;
import org.openapitools.client.ApiClient;
import org.openapitools.client.api.FunctionalModuleApi;
import org.openapitools.client.api.FunctionalModuleOutputApi;
import org.openapitools.client.api.FunctionalModuleInputApi;
import org.openapitools.client.model.FunctionalModuleDto;
import org.openapitools.client.model.FunctionalModuleInputDto;
import org.openapitools.client.model.FunctionalModuleOutputDto;

private void createFunctionalModule() {
    // NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
    ApiClient apiClient = new ApiClient().setBasePath(System.getenv("HDT_ENDPOINT"));
    String apiKey = System.getenv("HDT_API_KEY");
    if(apiKey != null && !apiKey.isEmpty())
        apiClient.addDefaultHeader("x-api-key", apiKey);
    
    final FunctionalModuleApi functionalModuleApi = new FunctionalModuleApi(apiClient);
    final FunctionalModuleInputApi functionalModuleInputApi = new FunctionalModuleInputApi(apiClient);
    final FunctionalModuleOutputApi functionalModuleOutputApi = new FunctionalModuleOutputApi(apiClient);

    FunctionalModuleDto customModule = FunctionalModuleApi.createFunctionalModule(
            new FunctionalModuleDto()
                    .setName("CustomModule")
                    .setDescription("description"));
    
    // NOTE: this supposes that hr_descriptor already exists
    FunctionalModuleInputDto customModuleInput = FunctionalModuleInputApi.createFunctionalModuleInput(
            new FunctionalModuleInputDto()
                    .setInputParameterName("hr")
                    .setPath("HR")
                    .setFunctionalModuleId(customModule.getId())
                    .setDescriptorId(hrDescriptor.getId()));

    FunctionalModuleOutputDto customModuleOutput = FunctionalModuleOutputApi.createFunctionalModuleOutput(
            new FunctionalModuleOutputDto()
                    .setFunctionalModuleId(customModule.getId())
                    .setOutputParameterName("prediction"));
}
import { Configuration, FunctionalModuleApi, FunctionalModuleInputApi, FunctionalModuleOutputApi } from 'orchestrator-javascript-client';

// NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
const configuration = new Configuration({
    basePath: process.env.HDT_ENDPOINT,
    apiKey: { apiKeyAuth: process.env.HDT_API_KEY }
});

const functionalModuleApi = new FunctionalModuleApi(configuration);
const functionalModuleInputApi = new FunctionalModuleInputApi(configuration);
const functionalModuleOutputApi = new FunctionalModuleOutputApi(configuration);

async function createFunctionalModule(hrDescriptorId) {
    const customModule = await functionalModuleApi.createFunctionalModule({
        name: "CustomModule",
        description: "description"
    });
    
    await functionalModuleInputApi.createFunctionalModuleInput({
        inputParamName: "hr",
        path: "HR",
        functionalModuleId: customModule.payload.id,
        descriptorId: hrDescriptorId
    });
    
    await functionalModuleOutputApi.createFunctionalModuleOutput({
        functionalModuleId: customModule.payload.id,
        outputParamName: "prediction"
    });
}

Create a StateDescriptor

In order to create a StateDescriptor it is mandatory to model the associated FunctionalModule. In fact, the FunctionalModuleOutput, together with the Block, enable to correctly create a StateDescriptor. Despite the Block is not mandatory, it facilitates to understand and parse the structure of the State.

In this example the FunctionalModule takes no input, and its output is a single numeric value named prediction. Note that the outputParameterName has to correspond with the JSON message key when publishing a State over the IIoT Middleware.

import os
from datetime import datetime
import orchestrator_python_client as hdt_client
from orchestrator_python_client import ApiClient, FunctionalModuleDto, FunctionalModuleOutputDto, 
NumberBasedDto, StateDescriptorDto

# NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables

configuration = hdt_client.Configuration(host=os.getenv('HDT_ENDPOINT'))
configuration.api_key['apiKeyAuth'] = os.getenv('HDT_API_KEY')
api_client = hdt_client.ApiClient(configuration)

functional_module_api = hdt_client.FunctionalModuleApi(api_client)
functional_module_output_api = hdt_client.FunctionalModuleOutputApi(api_client)
number_based_api = hdt_client.NumberBasedApi(api_client)
state_descriptor_api = hdt_client.StateDescriptorApi(api_client)

custom_module = functional_module_api.create_functional_module(
    FunctionalModuleDto(name="CustomModule", description="description")).payload

custom_module_output = functional_module_output_api.create_functional_module_output(
    FunctionalModuleOutputDto(functional_module_id=custom_module.id,
    output_param_name="prediction")).payload

number_based_dto = number_based_api.create_number_based(NumberBasedDto()).payload

custom_module_state_descriptor = state_descriptor_api.create_state_descriptor(
    StateDescriptorDto(functional_module_output_id=custom_module_output.id,  # to know the origin of the State
    blockId=number_based_dto.id,  # link the data structure
    name="CustomModuleState",
    description="prediction",
    factory_entity_model_id=worker_model.id)  # the entities model this state refers to
).payload
import java.util.Collections;
import java.time.LocalDateTime;
import org.openapitools.client.ApiClient;
import org.openapitools.client.api.FunctionalModuleApi;
import org.openapitools.client.api.FunctionalModuleOutputApi;
import org.openapitools.client.api.NumberBasedApi;
import org.openapitools.client.api.StateDescriptorApi;
import org.openapitools.client.model.FunctionalModuleDto;
import org.openapitools.client.model.FunctionalModuleOutputDto;
import org.openapitools.client.model.NumberBasedDto;
import org.openapitools.client.model.StateDescriptorDto;

private void createStateDescriptor() {
    // NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
    ApiClient apiClient = new ApiClient().setBasePath(System.getenv("HDT_ENDPOINT"));
    String apiKey = System.getenv("HDT_API_KEY");
    if(apiKey != null && !apiKey.isEmpty())
        apiClient.addDefaultHeader("x-api-key", apiKey);

    final FunctionalModuleApi functionalModuleApi = new FunctionalModuleApi(apiClient);
    final FunctionalModuleOutputApi functionalModuleOutputApi = new FunctionalModuleOutputApi(apiClient);
    final NumberBasedApi numberBasedApi = new NumberBasedApi(apiClient);
    final StateDescriptorApi = new StateDescriptorApi(apiClient);

    FunctionalModuleDto customModule = FunctionalModuleApi.createFunctionalModule(
            new FunctionalModuleDto()
                    .setName("CustomModule")
                    .setDescription("description"));

    FunctionalModuleOutputDto customModuleOutput = FunctionalModuleOutputApi.createFunctionalModuleOutput(
            new FunctionalModuleOutputDto()
                    .setFunctionalModuleId(customModule.getId())
                    .setOutputParameterName("prediction"));

    NumberBasedDto numberBased = NumberBasedApi.createNumberBased(new NumberBasedDto());

    StateDescriptorDto customModuleStateDescriptor = StateDescriptorApi.createStateDescriptor(
            new FunctionalModuleOutputDto()
                    .setFunctionalModuleOutputId(customModuleOutput.getId()) // to know the origin of the State
                    .setBlockId(numberBased.getId()) // link the data structure
                    .setName("CustomModuleState")
                    .setDescription("description")
                    .setFactoryEntityModelId(workerModel.getId())); // the entities model this state refers to 
}
import { Configuration, FunctionalModuleApi, FunctionalModuleOutputApi, NumberBasedApi, StateDescriptorApi } from 'orchestrator-javascript-client';

// NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
const configuration = new Configuration({
    basePath: process.env.HDT_ENDPOINT,
    apiKey: { apiKeyAuth: process.env.HDT_API_KEY }
});

const functionalModuleApi = new FunctionalModuleApi(configuration);
const functionalModuleOutputApi = new FunctionalModuleOutputApi(configuration);
const numberBasedApi = new NumberBasedApi(configuration);
const stateDescriptorApi = new StateDescriptorApi(configuration);

async function createStateDescriptor(workerModelId) {
    const customModule = await functionalModuleApi.createFunctionalModule({
        name: "CustomModule",
        description: "description"
    });
    
    const customModuleOutput = await functionalModuleOutputApi.createFunctionalModuleOutput({
        functionalModuleId: customModule.payload.id,
        outputParamName: "prediction"
    });
    
    const numberBased = await numberBasedApi.createNumberBased({});
    
    await stateDescriptorApi.createStateDescriptor({
        functionalModuleOutputId: customModuleOutput.payload.id,
        blockId: numberBased.payload.id,
        name: "CustomModuleState",
        description: "prediction",
        factoryEntityModelId: workerModelId
    });
}

Read an entity

In order to retrieve any type of entity from Clawdite (in this example a Worker entity) it is mandatory to specify the associated ID and use the API dedicated to such entity (in this case the Worker API).

import os
import orchestrator_python_client as hdt_client
from orchestrator_python_client import ApiClient

# NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables

configuration = hdt_client.Configuration(host=os.getenv('HDT_ENDPOINT'))
configuration.api_key['apiKeyAuth'] = os.getenv('HDT_API_KEY')
api_client = hdt_client.ApiClient(configuration)

worker_api = hdt_client.WorkerApi(api_client)

# NOTE: you need to specify the worker_id UUID
worker_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"

worker = worker_api.get_worker_by_id(worker_id)
import java.util.Collections;
import java.time.LocalDateTime;
import org.openapitools.client.ApiClient;
import org.openapitools.client.api.WorkerApi;
import org.openapitools.client.model.WorkerDto;

private void readWorker() {
// NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
ApiClient apiClient = new ApiClient().setBasePath(System.getenv("HDT_ENDPOINT"));
String apiKey = System.getenv("HDT_API_KEY");
if(apiKey != null && !apiKey.isEmpty())
apiClient.addDefaultHeader("x-api-key", apiKey);

    final WorkerApi workerApi = new WorkerApi(apiClient);

    // NOTE: you need to specify the workerId UUID
    workerId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";

    WorkerDto worker = workerApi.getWorkerById(workerId);
}
import { Configuration, WorkerApi } from 'orchestrator-javascript-client';

// NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
const configuration = new Configuration({
    basePath: process.env.HDT_ENDPOINT,
    apiKey: { apiKeyAuth: process.env.HDT_API_KEY }
});

const workerApi = new WorkerApi(configuration);

async function readWorker(workerId) {
    return workerApi.getWorkerById(workerId);
}

2 - IIoT Middleware

Save and read real-time metrics and states

The IIoT Middleware enables dynamic data published from Gateways and Functional Modules to be collected and made available to other interested modules, such as the HDM which is in charge of persisting them.

Currently only the MQTT protocol is supported, so modules need to publish and subscribe to specific topics. MQTT clients are available for different languages, in the following a few examples in Python and Java will be provided. Please refer to the official documentation to learn more about how to use the client in your project.

The topic schema is: HDT/{factory_entity_id}/{measurement/state}/{descriptor_id}.

The message content has to be in JSON format, including a timestamp key together with all the required fields from a measurement or the parameter from a state.

Publish a Metric

In order to publish a metric value to the MQTT IIoT Middleware for making it available to other components, specific topics structure and message format need to be used. Specifically, the topic schema is: HDT/{factory_entity_id}/measurement/{measurement_descriptor_id}.

For publishing a simple metric, such as the HR (Hearth Rate), it is sufficient to send a JSON message containing the value together with the timestamp. Example:

{
  "timestamp": 1753940931000,
  "hr": 91.0  // (The key name, in this case `hr`, must match a key in the fields map of the corresponding `MeasurementDescriptor`.)
}

Note that, if the metric to be saved is composed by multiple fields, then the associated values need to be flattened at the first level of the JSON message, using the exact same key names as in the MeasurementDescriptor fields. Example: consider the case of the ACC (Accelerometer) metric. The fields are X, Y and Z. The resulting message keys will be:

{
  "timestamp": 1753940931000,
  "acc_x": 13.0,
  "acc_y": 276.0,
  "acc_z": -56.0
}

Here is the complete code snippet for properly publishing a metric.

import os
from datetime import datetime
from paho.mqtt.client import Client
from paho.mqtt.enums import CallbackAPIVersion

def on_connect(client, userdata, flags, reason_code, properties):
    loguru.logger.debug(f'Connected with result code "{reason_code}"')

# NOTE: you need to specify the MQTT_HOST, MQTT_PORT, MQTT_USER and MQTT_PASSWORD environment variables

mqtt_client = Client(client_id='unique-id', callback_api_version=CallbackAPIVersion.VERSION2)
mqtt_client.username_pw_set(os.getenv("MQTT_USER"), os.getenv("MQTT_PASSWORD"))
mqtt_client.on_connect = on_connect
mqtt_client.connect(os.getenv('MQTT_HOST'), int(os.getenv("MQTT_PORT")))
quality_of_service = 1

# NOTE: you need to specify the measurement_descriptor_id and worker_id UUIDs

measurement_descriptor_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
worker_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
value = f'{{"timestamp": {int(datetime.now().timestamp() * 1000)}, "acc_x": 13.0, "acc_y": 276.0, "acc_z": -56.0}}'

mqtt_client.publish(f'HDT/{worker_id}/measurement/{measurement_descriptor_id}',
    value, quality_of_service)

time.sleep(1)  # wait for the messages to be sent
mqtt_client.disconnect()
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

private void publishMetric() {
    // NOTE: you need to specify the MQTT_HOST, MQTT_PORT, MQTT_USER and MQTT_PASSWORD environment variables
    String broker = "tcp://" + System.getenv("MQTT_HOST") + ":" + System.getenv("MQTT_PORT");
    String clientId = "unique-id";
    int qos = 1;

    // NOTE: you need to specify the measurement_descriptor_id and worker_id UUIDs
    String measurementDescriptorId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
    String workerId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
    
    try {
        MqttClient mqttClient = new MqttClient(broker, "unique-id");
        MqttConnectOptions connOpts = new MqttConnectOptions();
        connOpts.setUserName(System.getenv("MQTT_USER"));
        connOpts.setPassword(System.getenv("MQTT_PASSWORD").toCharArray());
    
        // Set callback for connection
        mqttClient.setCallback(new MqttCallbackExtended() {
            @Override
            public void connectComplete(boolean reconnect, String serverURI) {
                System.out.println("Connected to " + serverURI);
            }
    
            @Override
            public void connectionLost(Throwable cause) {
                System.out.println("Connection lost: " + cause.getMessage());
            }
    
            @Override
            public void messageArrived(String topic, MqttMessage message) throws Exception {
                // Not used in this case, since we are publishing only
            }
    
            @Override
            public void deliveryComplete(IMqttDeliveryToken token) {
                System.out.println("Message delivered successfully");
            }
        });
    
        mqttClient.connect(connOpts);
        
        long timestamp = System.currentTimeMillis();
        String value = String.format(
            "{\"timestamp\": %d, \"acc_x\": %.1f, \"acc_y\": %.1f, \"acc_z\": %.1f}",
            timestamp, 13.0, 276.0, -56.0
        );
        mqttClient.publish("HDT/" + workerId + "/measurement/" + measurementDescriptorId,
                new MqttMessage(value.getBytes()));
    
        TimeUnit.SECONDS.sleep(1);
        mqttClient.disconnect();
    
    
    } catch (MqttException | InterruptedException e) {
        e.printStackTrace();
    }
}

Publish a State

In order to publish a state value to the MQTT IIoT Middleware for making it available to other components, specific topics structure and message format need to be used. Specifically, the topic schema is: HDT/{factory_entity_id}/state/{state_descriptor_id}.

Independently of the state content, the only mandatory JSON keys are the timestamp and the name of the output parameter that is getting saved (as specified in the FunctionalModuleOutput outputParamName). Then the whole JSON object value corresponding to the parameter will be serialized and saved as it is.

import os
from datetime import datetime
from paho.mqtt.client import Client
from paho.mqtt.enums import CallbackAPIVersion

def on_connect(client, userdata, flags, reason_code, properties):
    loguru.logger.debug(f'Connected with result code "{reason_code}"')

# NOTE: you need to specify the MQTT_HOST, MQTT_PORT, MQTT_USER and MQTT_PASSWORD environment variables

mqtt_client = Client(client_id='unique-id', callback_api_version=CallbackAPIVersion.VERSION2)
mqtt_client.username_pw_set(os.getenv("MQTT_USER"), os.getenv("MQTT_PASSWORD"))
mqtt_client.on_connect = on_connect
mqtt_client.connect(os.getenv('MQTT_HOST'), int(os.getenv("MQTT_PORT")))
quality_of_service = 1

# NOTE: you need to specify the state_descriptor_id and worker_id UUIDs

state_descriptor_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
worker_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
value = f'{{"timestamp": {int(datetime.now().timestamp() * 1000)}, "fatigue": [5, 10]}'

mqtt_client.publish(f'HDT/{worker_id}/state/{state_descriptor_id}',
    value, quality_of_service)

time.sleep(1)  # wait for the messages to be sent
mqtt_client.disconnect()
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

private void publishState() {
    // NOTE: you need to specify the MQTT_HOST, MQTT_PORT, MQTT_USER and MQTT_PASSWORD environment variables
    String broker = "tcp://" + System.getenv("MQTT_HOST") + ":" + System.getenv("MQTT_PORT");
    String clientId = "unique-id";
    int qos = 1;

    // NOTE: you need to specify the state_descriptor_id and worker_id UUIDs
    String stateDescriptorId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
    String workerId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";

    try {
        MqttClient mqttClient = new MqttClient(broker, "unique-id");
        MqttConnectOptions connOpts = new MqttConnectOptions();
        connOpts.setUserName(System.getenv("MQTT_USER"));
        connOpts.setPassword(System.getenv("MQTT_PASSWORD").toCharArray());

        // Set callback for connection
        mqttClient.setCallback(new MqttCallbackExtended() {
            @Override
            public void connectComplete(boolean reconnect, String serverURI) {
                System.out.println("Connected to " + serverURI);
            }

            @Override
            public void connectionLost(Throwable cause) {
                System.out.println("Connection lost: " + cause.getMessage());
            }

            @Override
            public void messageArrived(String topic, MqttMessage message) throws Exception {
                // Not used in this case, since we are publishing only
            }

            @Override
            public void deliveryComplete(IMqttDeliveryToken token) {
                System.out.println("Message delivered successfully");
            }
        });

        mqttClient.connect(connOpts);

        long timestamp = System.currentTimeMillis();
        String value = String.format(
            "{\"timestamp\": %d, \"fatigue\": [%d, %d]}",
            timestamp, 5, 10
        );
        mqttClient.publish("HDT/" + workerId + "/state/" + stateDescriptorId,
                new MqttMessage(value.getBytes()));

        TimeUnit.SECONDS.sleep(1);
        mqttClient.disconnect();


    } catch (MqttException | InterruptedException e) {
        e.printStackTrace();
    }
}

Read a Metric

In order to read real-time metric values published to the MQTT IIoT Middleware, it is mandatory to subscribe to the topics of interest and to specify a callback for managing the received message.

import os
import time
from paho.mqtt.client import Client
from paho.mqtt.enums import CallbackAPIVersion

# NOTE: you need to specify the measurement_descriptor_id and worker_id UUIDs

def on_connect(client, userdata, flags, reason_code, properties):
    measurement_descriptor_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    worker_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    topic = f'HDT/{worker_id}/measurement/{measurement_descriptor_id}'
    client.subscribe(topic, qos=1)

def on_message(client, userdata, msg):
    print(f'Received message: {msg.payload.decode()} on topic {msg.topic}')

# NOTE: you need to specify the MQTT_HOST, MQTT_PORT, MQTT_USER, and MQTT_PASSWORD environment variables

mqtt_client = Client(client_id='unique-id', callback_api_version=CallbackAPIVersion.VERSION2)
mqtt_client.username_pw_set(os.getenv("MQTT_USER"), os.getenv("MQTT_PASSWORD"))
mqtt_client.on_connect = on_connect
mqtt_client.on_message = on_message

mqtt_client.connect(os.getenv('MQTT_HOST'), int(os.getenv("MQTT_PORT")))
mqtt_client.loop_start()
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import java.util.UUID;

private void readMetric() {
    // NOTE: you need to specify the MQTT_HOST, MQTT_PORT, MQTT_USER, and MQTT_PASSWORD environment variables
    String broker = "tcp://" + System.getenv("MQTT_HOST") + ":" + System.getenv("MQTT_PORT");
    String clientId = UUID.randomUUID().toString();

    // NOTE: you need to specify the measurementDescriptorId and workerId UUIDs
    String measurementDescriptorId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
    String workerId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
    topic = "HDT/"+workerId+"/measurement/"+measurementDescriptorId;

    try {
        MqttClient mqttClient = new MqttClient(broker, clientId);
        MqttConnectOptions connOpts = new MqttConnectOptions();
        connOpts.setUserName(System.getenv("MQTT_USER"));
        connOpts.setPassword(System.getenv("MQTT_PASSWORD").toCharArray());
    
        // Set callback for connection and message reception
        mqttClient.setCallback(new MqttCallbackExtended() {
            @Override
            public void connectComplete(boolean reconnect, String serverURI) {
                System.out.println("Connected to " + serverURI);
            }
    
            @Override
            public void connectionLost(Throwable cause) {
                System.out.println("Connection lost: " + cause.getMessage());
            }
    
            @Override
            public void messageArrived(String topic, MqttMessage message) throws Exception {
                String receivedMessage = new String(message.getPayload());
                System.out.println("Message received on topic " + topic + ": " + receivedMessage);
            }
    
            @Override
            public void deliveryComplete(IMqttDeliveryToken token) {
                // Not used in this case, since we are subscribing only
            }
        });
    
        mqttClient.connect(connOpts);
        mqttClient.subscribe(topic);
    
    } catch (MqttException e) {
        e.printStackTrace();
    }
}

Read a State

In order to read real-time state values published to the MQTT IIoT Middleware, it is mandatory to subscribe to the topics of interest and to specify a callback for managing the received message.

import os
import time
from paho.mqtt.client import Client
from paho.mqtt.enums import CallbackAPIVersion

# NOTE: you need to specify the state_descriptor_id and worker_id UUIDs

def on_connect(client, userdata, flags, reason_code, properties):
    state_descriptor_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    worker_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    topic = f'HDT/{worker_id}/state/{state_descriptor_id}'
    client.subscribe(topic, qos=1)

def on_message(client, userdata, msg):
    print(f'Received message: {msg.payload.decode()} on topic {msg.topic}')

# NOTE: you need to specify the MQTT_HOST, MQTT_PORT, MQTT_USER, and MQTT_PASSWORD environment variables

mqtt_client = Client(client_id='unique-id', callback_api_version=CallbackAPIVersion.VERSION2)
mqtt_client.username_pw_set(os.getenv("MQTT_USER"), os.getenv("MQTT_PASSWORD"))
mqtt_client.on_connect = on_connect
mqtt_client.on_message = on_message

mqtt_client.connect(os.getenv('MQTT_HOST'), int(os.getenv("MQTT_PORT")))
mqtt_client.loop_start()
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import java.util.UUID;

private void readState() {
    // NOTE: you need to specify the MQTT_HOST, MQTT_PORT, MQTT_USER, and MQTT_PASSWORD environment variables
    String broker = "tcp://" + System.getenv("MQTT_HOST") + ":" + System.getenv("MQTT_PORT");
    String clientId = UUID.randomUUID().toString();

    // NOTE: you need to specify the stateDescriptorId and workerId UUIDs
    String stateDescriptorId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
    String workerId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
    topic = "HDT/"+workerId+"/state/"+stateDescriptorId;

    try {
        MqttClient mqttClient = new MqttClient(broker, clientId);
        MqttConnectOptions connOpts = new MqttConnectOptions();
        connOpts.setUserName(System.getenv("MQTT_USER"));
        connOpts.setPassword(System.getenv("MQTT_PASSWORD").toCharArray());
    
        // Set callback for connection and message reception
        mqttClient.setCallback(new MqttCallbackExtended() {
            @Override
            public void connectComplete(boolean reconnect, String serverURI) {
                System.out.println("Connected to " + serverURI);
            }
    
            @Override
            public void connectionLost(Throwable cause) {
                System.out.println("Connection lost: " + cause.getMessage());
            }
    
            @Override
            public void messageArrived(String topic, MqttMessage message) throws Exception {
                String receivedMessage = new String(message.getPayload());
                System.out.println("Message received on topic " + topic + ": " + receivedMessage);
            }
    
            @Override
            public void deliveryComplete(IMqttDeliveryToken token) {
                // Not used in this case, since we are subscribing only
            }
        });
    
        mqttClient.connect(connOpts);
        mqttClient.subscribe(topic);
    
    } catch (MqttException e) {
        e.printStackTrace();
    }
}

3 - HDM REST API

Read historical metrics and states

The HDM REST API enables to get all the historical data related to metrics and states (i.e. dynamic data) that were published to the IIoT Middleware and persisted through the HDM, as described in Architecture section.

This API is available through a dedicated Swagger interface for each Clawdite instance. Furthermore, it is possible to generate API clients for several languages, such as Python, Java and JavaScript. The mentioned clients are already generated and available within each Clawdite instance and make it possible to interact with Clawdite’s HDM from external components.

In the following a few examples on data retrieval will be provided. Independently on the type of data, there are 4 retrieval modalities:

  • Get the last X data
  • Get the first X data from a specific point in time
  • Get the data included in a specific time-range (i.e. 1 hour, 1 day, 1 week…)
  • Get the data included in a specific time-interval (i.e. from date X to date Y)

Install the HDM client dependencies

In order to use the generated API clients inside external components it is needed to correctly setup and install the dependencies. Note that you need a personal GitLab token for accessing the registries. In case you don’t have it please contact the project’s maintainers.

pip install orchestrator-python-client --index-url https://__token__:<your_personal_token>@gitlab-core.supsi.ch/api/v4/projects/86/packages/pypi/simple
<dependencies>
<dependency>
<groupId>ch.supsi.dti.isteps.hdt</groupId>
<artifactId>orchestrator-java-client</artifactId>
<version>x.y.z</version>
</dependency>
</dependencies>

<repositories>
  <repository>
    <id>gitlab-maven</id>
    <url>https://gitlab-core.supsi.ch/api/v4/projects/86/packages/maven</url>
  </repository>
</repositories>
npm install orchestrator-javascript-client --registry https://token:<your_personal_token>@gitlab-core.supsi.ch/api/v4/projects/86/packages/npm/

Read Metrics

In this example the last 10 metrics are retrieved. The use of the other retrieval modalities is similar.

import os
from datetime import datetime
import hdm_web_python_client as hdm_client
from orchestrator_python_client import ApiClient


# NOTE: you need to specify the HDM_ENDPOINT and HDM_API_KEY environment variables
configuration = hdm_client.Configuration(host=os.getenv('HDM_ENDPOINT'))
configuration.api_key['apiKeyAuth'] = os.getenv('HDM_API_KEY')
api_client = hdm_client.ApiClient(configuration)
hdm_api = hdm_client.HdmControllerApi(api_client)

# NOTE: you need to specify the measurement_descriptor_id and worker_id UUIDs
measurement_descriptor_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
worker_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"

values = hdm_api.read_last_values(abstract_descriptor_id=measurement_descriptor_id,
                                          factory_entity_id=worker_id,
                                          number_of_last_values=10)                            
import java.util.Collections;
import java.time.LocalDateTime;
import org.openapitools.client.ApiClient;
import org.openapitools.client.api.HdmApi;
import org.openapitools.client.model.GenericValueDto;
import org.springframework.data.domain.Page;

private void readMetrics() {
    // NOTE: you need to specify the HDM_ENDPOINT and HDM_API_KEY environment variables
    ApiClient apiClient = new ApiClient().setBasePath(System.getenv("HDM_ENDPOINT"));
    String apiKey = System.getenv("HDM_API_KEY");
    if (apiKey != null && !apiKey.isEmpty())
        apiClient.addDefaultHeader("x-api-key", apiKey);
    final HdmApi hdmApi = new HdmApi(apiClient);
    
    // NOTE: you need to specify the measurementDescriptorId and workerId UUIDs
    measurementDescriptorId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
    workerId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
    numberOfLastValues = 10;

    Page<GenericValueDto> values = hdmApi.readLastValues(measurementDescriptorId, workerId, numberOfLastValues);
}
import { Configuration, HdmApi } from 'orchestrator-javascript-client';

// NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
const configuration = new Configuration({
    basePath: process.env.HDM_ENDPOINT,
    apiKey: { apiKeyAuth: process.env.HDM_API_KEY }
});

const hdmApi = new HdmApi(configuration);

async function readMetrics() {
    // NOTE: you need to specify the measurementDescriptorId and workerId UUIDs
    const measurementDescriptorId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
    const workerId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
    const numberOfLastValues = 10;
    
    const values = await hdmApi.readLastValues(measurementDescriptorId, workerId, numberOfLastValues);
    return values;
}

Read States

In this example the last 10 states are retrieved. The use of the other retrieval modalities is similar.

import os
from datetime import datetime
import hdm_web_python_client as hdm_client
from orchestrator_python_client import ApiClient


# NOTE: you need to specify the HDM_ENDPOINT and HDM_API_KEY environment variables
configuration = hdm_client.Configuration(host=os.getenv('HDM_ENDPOINT'))
configuration.api_key['apiKeyAuth'] = os.getenv('HDM_API_KEY')
api_client = hdm_client.ApiClient(configuration)
hdm_api = hdm_client.HdmControllerApi(api_client)

# NOTE: you need to specify the state_descriptor_id and worker_id UUIDs
state_descriptor_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
worker_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"

values = hdm_api.read_last_values(abstract_descriptor_id=state_descriptor_id,
                                          factory_entity_id=worker_id,
                                          number_of_last_values=10)
import java.util.Collections;
import java.time.LocalDateTime;
import org.openapitools.client.ApiClient;
import org.openapitools.client.api.HdmApi;
import org.openapitools.client.model.GenericValueDto;
import org.springframework.data.domain.Page;

private void readStates() {
    // NOTE: you need to specify the HDM_ENDPOINT and HDM_API_KEY environment variables
    ApiClient apiClient = new ApiClient().setBasePath(System.getenv("HDM_ENDPOINT"));
    String apiKey = System.getenv("HDM_API_KEY");
    if (apiKey != null && !apiKey.isEmpty())
        apiClient.addDefaultHeader("x-api-key", apiKey);
    final HdmApi hdmApi = new HdmApi(apiClient);

    // NOTE: you need to specify the stateDescriptorId and workerId UUIDs
    stateDescriptorId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
    workerId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
    numberOfLastValues = 10;

    Page<GenericValueDto> values = hdmApi.readLastValues(stateDescriptorId, workerId, numberOfLastValues);
}
import { Configuration, HdmApi } from 'orchestrator-javascript-client';

// NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
const configuration = new Configuration({
    basePath: process.env.HDM_ENDPOINT,
    apiKey: { apiKeyAuth: process.env.HDM_API_KEY }
});

const hdmApi = new HdmApi(configuration);

async function readStates() {
    // NOTE: you need to specify the stateDescriptorId and workerId UUIDs
    const stateDescriptorId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
    const workerId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
    const numberOfLastValues = 10;
    
    const values = await hdmApi.readLastValues(stateDescriptorId, workerId, numberOfLastValues);
    return values;
}

4 - Skills modelling

Model operators skills

The Orchestrator REST API enables to create and read all the Digital Twin entities described in Model section, in particular the characteristics (i.e. quasi-static data). CharacteristicDescriptor and CharacteristicValue are used in order to model operator skills (e.g., work experience, manual dexterity), besides the general features (e.g., sex, age, handedness).

This API is available through a dedicated Swagger interface for each Clawdite instance. Furthermore, it is possible to generate API clients for several languages, such as Python, Java and JavaScript. The mentioned clients are already generated and available within each Clawdite instance and make it possible to interact with Clawdite’s Orchestrator from external components.

In the following a few examples about skills modelling will be provided.

Install the Orchestrator client dependencies

In order to use the generated API clients inside external components it is needed to correctly setup and install the dependencies. Note that you need a personal GitLab token for accessing the registries. In case you don’t have it please contact the project’s maintainers.

pip install orchestrator-python-client --index-url https://__token__:<your_personal_token>@gitlab-core.supsi.ch/api/v4/projects/86/packages/pypi/simple
<dependencies>
<dependency>
<groupId>ch.supsi.dti.isteps.hdt</groupId>
<artifactId>orchestrator-java-client</artifactId>
<version>x.y.z</version>
</dependency>
</dependencies>

<repositories>
  <repository>
    <id>gitlab-maven</id>
    <url>https://gitlab-core.supsi.ch/api/v4/projects/86/packages/maven</url>
  </repository>
</repositories>
npm install orchestrator-javascript-client --registry https://token:<your_personal_token>@gitlab-core.supsi.ch/api/v4/projects/86/packages/npm/

Create a Skill

In order to define a skill it is needed to create a CharacteristicDescriptor with an associated CharacteristicValue. Note that a CharacteristicDescriptor is uniquely linked to an already existing FactoryEntityModel. Moreover, the Worker (or FactoryEntity in general) which possesses the skill (i.e., CharacteristicValue) needs to be defined in advance.

import os
from datetime import datetime
from collections import OrderedDict
import orchestrator_python_client as hdt_client
from orchestrator_python_client import ApiClient, FactoryEntityModelCategoryDto, FactoryEntityModelDto, WorkerDto, 
    CharacteristicDescriptorDto, CharacteristicValueDto, FieldType

# NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables

configuration = hdt_client.Configuration(host=os.getenv('HDT_ENDPOINT'))
configuration.api_key['apiKeyAuth'] = os.getenv('HDT_API_KEY')
api_client = hdt_client.ApiClient(configuration)

factory_entity_model_category_api = hdt_client.FactoryEntityModelCategoryApi(api_client)
factory_entity_model_api = hdt_client.FactoryEntityModelApi(api_client)
worker_api = hdt_client.WorkerApi(api_client)
characteristic_descriptor_api = hdt_client.CharacteristicDescriptorApi(api_client)
characteristic_value_api = hdt_client.CharacteristicValueApi(api_client)

operator_category = factory_entity_model_category_api.create_factory_entity_model_category(
                        FactoryEntityModelCategoryDto(name="Operator",
                            description="description")).payload

worker_model = factory_entity_model_api.create_factory_entity_model(
                        FactoryEntityModelDto(name="Worker",
                            description="description",
                            factory_entity_model_categories_id=[operator_category.id])).payload

worker = worker_api.create_worker(
                        WorkerDto(creation_date=datetime.now().isoformat(),
                            factory_entity_model_id=worker_model.id)).payload

# "Gender" characteristic creation
gender_descriptor = characteristic_descriptor_api.create_characteristic_descriptor(
                        CharacteristicDescriptorDto(name="Gender",
                            description="Operator gender",
                            factory_entity_model_id=worker_model.id)).payload

worker_gender = characteristic_value_api.create_characteristic_value(
                        CharacteristicValueDto(values=OrderedDict({datetime.now().isoformat(): "Male"}),
                            type=FieldType.STRING,
                            characteristic_descriptor_id=gender_descriptor.id,
                            factory_entity_id=worker.id)).payload

# "Manual dexterity" skill creation
manual_dexterity_descriptor = characteristic_descriptor_api.create_characteristic_descriptor(
                        CharacteristicDescriptorDto(name="Manual dexterity",
                            description="Operator manual dexterity",
                            factory_entity_model_id=worker_model.id)).payload

worker_manual_dexterity = characteristic_value_api.create_characteristic_value(
                        CharacteristicValueDto(values=OrderedDict({datetime.now().isoformat(): "41"}),
                            type=FieldType.NUMBER,
                            characteristic_descriptor_id=manual_dexterity_descriptor.id,
                            factory_entity_id=worker.id)).payload
import java.util.Collections;
import java.time.LocalDateTime;
import org.openapitools.client.ApiClient;
import org.openapitools.client.api.FactoryEntityModelCategoryApi;
import org.openapitools.client.api.FactoryEntityModelApi;
import org.openapitools.client.api.WorkerApi;
import org.openapitools.client.api.CharacteristicDescriptorApi;
import org.openapitools.client.api.CharacteristicValueApi;
import org.openapitools.client.model.FactoryEntityModelCategoryDto;
import org.openapitools.client.model.FactoryEntityModelDto;
import org.openapitools.client.model.WorkerDto;
import org.openapitools.client.model.CharacteristicDescriptorDto;
import org.openapitools.client.model.CharacteristicValueDto;
import org.openapitools.client.model.FieldType

private void createSkill() {
    // NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
    ApiClient apiClient = new ApiClient().setBasePath(System.getenv("HDT_ENDPOINT"));
    String apiKey = System.getenv("HDT_API_KEY");
    if(apiKey != null && !apiKey.isEmpty())
        apiClient.addDefaultHeader("x-api-key", apiKey);

    final FactoryEntityModelCategoryApi factoryEntityModelCategoryApi = new FactoryEntityModelCategoryApi(apiClient);
    final FactoryEntityModelApi factoryEntityModelApi = new FactoryEntityModelApi(apiClient);
    final WorkerApi workerApi = new WorkerApi(apiClient);
    final CharacteristicDescriptorApi characteristicDescriptorApi = new CharacteristicDescriptorApi(apiClient);
    final CharacteristicValueApi characteristicValueApi = new CharacteristicValueApi(apiClient);

    FactoryEntityModelCategoryDto operatorCategory = factoryEntityModelCategoryApi.createFactoryEntityModelCategory(
            new FactoryEntityModelCategoryDto()
                    .setName("Operator")
                    .setDescription("description"));

    FactoryEntityModelDto workerModel = factoryEntityModelApi.createFactoryEntityModel(
            new FactoryEntityModelDto()
                    .setName("Worker")
                    .setDescription("description")
                    .setFactoryEntityModelCategoriesId(Collections.singletonList(operatorCategory.getId())));

    WorkerDto worker = workerApi.createWorker(
            new WorkerDto()
                    .setCreationDate(LocalDateTime.now().toString())
                    .setFactoryEntityModelId(workerModel.getId()));

    // "Gender" characteristic creation

    CharacteristicDescriptorDto genderDescriptor = characteristicDescriptorApi.createCharacteristicDescriptor(
            new CharacteristicDescriptorDto()
                    .setName("Gender")
                    .setDescription("Worker gender")
                    .setFactoryEntityModelId(workerModel.getId()));

    CharacteristicValueDto workerGender = characteristicValueApi.createCharacteristicValue(
            new CharacteristicValueDto()
                    .setValues(new TreeMap<>() {{
                        put(LocalDateTime.now().toString(), "Male");
                    }})
                    .setType(FieldType.STRING)
                    .setCharacteristicDescriptorId(genderDescriptor.getId())
                    .setFactoryEntityId(worker.getId()));

    // "Manual dexterity" skill creation

    CharacteristicDescriptorDto manualDexterityDescriptor = characteristicDescriptorApi.createCharacteristicDescriptor(
            new CharacteristicDescriptorDto()
                    .setName("Manual dexterity")
                    .setDescription("Worker anual dexterity")
                    .setFactoryEntityModelId(workerModel.getId()));

    CharacteristicValueDto workerManualDexterity = characteristicValueApi.createCharacteristicValue(
            new CharacteristicValueDto()
                    .setValues(new TreeMap<>() {{
                        put(LocalDateTime.now().toString(), "41");
                    }})
                    .setType(FieldType.NUMBER)
                    .setCharacteristicDescriptorId(manualDexterityDescriptor.getId())
                    .setFactoryEntityId(worker.getId()));

}
import { Configuration, FactoryEntityModelCategoryApi, FactoryEntityModelApi, WorkerApi, CharacteristicDescriptorApi,
    CharacteristicValueApi } from 'orchestrator-javascript-client';

// NOTE: you need to specify the HDT_ENDPOINT and HDT_API_KEY environment variables
const configuration = new Configuration({
    basePath: process.env.HDT_ENDPOINT,
    apiKey: { apiKeyAuth: process.env.HDT_API_KEY }
});

const factoryEntityModelCategoryApi = new FactoryEntityModelCategoryApi(configuration);
const factoryEntityModelApi = new FactoryEntityModelApi(configuration);
const workerApi = new WorkerApi(configuration);
const characteristicDescriptorApi = new CharacteristicDescriptorApi(configuration);
const characteristicValueApi = new CharacteristicValueApi(configuration);

async function createSkill() {
    const operatorCategory = await factoryEntityModelCategoryApi.createFactoryEntityModelCategory({
        name: "Operator",
        description: "description"
    });
    
    const workerModel = await factoryEntityModelApi.createFactoryEntityModel({
        name: "Worker",
        description: "description",
        factoryEntityModelCategoriesId: [operatorCategory.payload.id]
    });
    
    const worker = await workerApi.createWorker({
        creationDate: new Date().toISOString(),
        factoryEntityModelId: workerModel.payload.id
    });
    
    // "Gender" characteristic creation
    const genderDescriptor = await characteristicDescriptorApi.createCharacteristicDescriptor({
        name: "Gender",
        description: "Operator gender",
        factoryEntityModelId: workerModel.payload.id
    });
    
    await characteristicValueApi.createCharacteristicValue({
        values: { [new Date().toISOString()]: "Male" },
        type: "STRING",
        characteristicDescriptorId: genderDescriptor.payload.id,
        factoryEntityId: worker.payload.id
    });
    
    // "Manual dexterity" skill creation
    const manualDexterityDescriptor = await characteristicDescriptorApi.createCharacteristicDescriptor({
        name: "Manual dexterity",
        description: "Operator manual dexterity",
        factoryEntityModelId: workerModel.payload.id
    });
    
    await characteristicValueApi.createCharacteristicValue({
        values: { [new Date().toISOString()]: "41" },
        type: "NUMBER",
        characteristicDescriptorId: manualDexterityDescriptor.payload.id,
        factoryEntityId: worker.payload.id
    });
}