ABB Warmstorage

Introduction

ABB Warmstorage refers to the Edgenius application that consists of ABB Ability™ database and interfacing software components. TSAPI (or TSModule) is the non-persistent edge module built for ABB Ability Platform, with the purpose of serving as an abstraction layer on top of ABB Ability™ History database. The component's main responsibility is collecting timeseries data through MQTT protocol, and writing the data to the database. In addition, the component exposes a web endpoint for the purpose of data retrieval, which is built as close as possible to the pre-existing Data Access Service on the ABB Ability Cloud. The combination of this module and database, can serve as a local storage on Edge.

This component is designed in a way that it does not store any data on its own, and it does not hold any state; In other words, after the initial deployment, it can be restarted if needed, and unlikely crashes would not cause any issues to the system as a whole. Incoming data is passed on to the database after the verification process, and retried in case of issues. The only exception of discarding data is when the data does not make sense to be retried(i.e. invalid data).

For the component to function properly, a setup is required. This setup involves passing in the appropriate configuration, and launching the component in conjunction with an MQTT broker, and ABB Ability™ History at the minimum. In a generic use case, TsModule is run inside of a docker container, and its configuration is automatically generated by Type Definition Registry and passed on to the module by Ability Edge Proxy; However, it is possible to setup TSAPI manually outside of the Ability Edge. This guide will go through both methods as thoroughly as possible, and provide samples to get you started.

Installation

Edgenius

The ABB Warmstorage application should be available in your Edgenius directory. You can install it to an edge through Edgenius Management Portal.

There are only couple of configuration options.

  • History length - determines how long data is stored in days
  • Toggle for variables, events and alarms. You can disable storing of any of these.

The software implements the data access api for edgenius. All information for accessing the api is found in Edgenius documentation and here: https://clientsuccess.ability.abb/api/instance/data.html

Edgenius SNO

To change Helm chart values before installing the "ABB Warmstorage application" chart on Edgenius SNO, you will need to update the following file: ansible_scripts/config/chart_values/warm_storage_app.j2.

To change persistent volume size for the persistent volume claim "pvc04" (mount path /var/lib/rtdbdata) of CpmPlus database, add following values for the abb-warmstorage-cpmmodule:

  persistence: 
    pvc04: 
      size: 200Gi 

To add or change environment values for containers, add them with the env list:

abb-warmstorage-tsmodule: 
  image: 
    repository: {{ project }}/cpmplustsmodule 
  env: 
    - name: VtrinConfiguration__RTDB__Histories__DefaultHistory__HistoryLengthInDays 
      value: "60" 

Full example:

global: 
  moduleCategory: App 
  storageClassName: {{ cluster_storage_class }} 
  registry: "{{ cluster_registry_svc_url }}" 
  imagePullSecrets: [] 
  serviceAccount: 
    name: {{ cluster_service_account }} 

abb-warmstorage-cpmmodule: 
  image: 
    repository: {{ project }}/cpmplushistory 
  persistence: 
    pvc04: 
      size: 200Gi 

abb-warmstorage-tsmodule: 
  image: 
    repository: {{ project }}/cpmplustsmodule 
  env: 
    - name: VtrinConfiguration__RTDB__Histories__DefaultHistory__HistoryLengthInDays 
      value: "60" 
    - name: Some_Other_Env_Variable_Name 
      value: "true" 

Usage

TsModule's core functionality is writing time series data into the database and allowing the readback of the said data. The current implementation uses the MQTT protocol to read the incoming data, and accepts data only in JSON format. The readback feature is done through an HTTP endpoint, and also only accepts queries in JSON format. Each section will be described separately.

Writing Data

ABB Ability specifications declare three types of timeseries data: Variable data, Event data, and Alarm data
The module subsribes to the warm/# topic and identifies the type of timeseries data based on it's subtopics. This will be explained in detail in upcoming sections.

Writing Variable Data

As a user, if you intend to write variable data to the storage, warm/variables is the topic you must write to. The payload must be in JSON format, and it can be a single element, or an array of elements(enclosed with brackets). Please note that there is no need to create variables on the database, as the component will create them for you if they do not exist; however, each variable may only contain one type of data; meaning that it can either be single values or arrays. This is determined by the first data point that the modules receives for each variable(if it has singular value or an array of values). You can find a sample below:

{
  "objectId": "0c055b6c-004d-493d-8caa-96c952c0e207",
  "model": "abb.ability.device",
  "timestamp": "2020-01-01T00:00:00Z",
  "variable": "sampleVariable",
  "quality": 0,
  "value": 15
}
ParameterMandatoryDescriptionData Type
objectIdYesUnique ObjectId for the variable in the format of GUIDString
modelYesAbb Ability Information Model format of model for variableString
timestampYesDatetime offset in the ISO 8601 formatString
variableYesName of the variableString
valueYesValue of the data point. Can be a single or array of primitive data types: integer, floating point, boolean OR can be single string. Every other type will be treated as a string in the database.Object
qualityNoQuality value of the data point. Follows the format of "IEC 62361-2 Data Value Quality"Unsigned Integer

Writing Event Data

As a user, if you intend to write event data to the storage, warm/events is the topic you must write to. The payload must be in json format, and it can be a single element, or an array of elements(enclosed with brackets). You can find a sample below:

{
  "event": "ArbitraryEventName",
  "model": "abb.ability.device",
  "timestamp": "2020-01-01T00:00:00Z",
  "objectId": "0c055b6c-004d-493d-8caa-96c952c0e207",
  "value":{
    "sampleIntegerProperty": 15
  }
}
ParameterMandatoryDescriptionData Type
objectIdYesUnique ObjectId for the variable in the format of GUIDString
modelYesAbb Ability Information Model format of model for variableString
timestampYesDatetime offset in the ISO 8601 formatString
eventYesName of the eventString
valueYesDictionary of key/value pair. What you put inside is arbitrary.Dictionary

Writing Alarm Data

As a user, if you intend to write alarm data to the storage, warm/alarms is the topic you must write to. The payload must be in json format, and it can be a single element, or an array of elements(enclosed with brackets). You can find a sample below:

{
  "alarm": "ArbitraryEventName",
  "alarmKey":"ArbitraryAlarmKey",
  "model": "abb.ability.device",
  "timestamp": "2020-01-01T00:00:00Z",
  "objectId": "0c055b6c-004d-493d-8caa-96c952c0e207",
  "value":{
    "sampleIntegerProperty": 15
  }
}
ParameterMandatoryDescriptionData Type
objectIdYesUnique ObjectId for the variable in the format of GUIDString
modelYesAbb Ability Information Model format of model for variableString
timestampYesDatetime offset in the ISO 8601 formatString
alarmYesName of the alarmString
alarmKeyNoArbitrary Key for the alarmString
valueYesDictionary of key/value pair. What you put inside is arbitrary.Dictionary

Reading Data

The reading of data is performed through an HTTP endpoint, exposed on port 8001 by default. You need to send an HTTP POST to the address specified for each type. This will be explained below.

Reading Single Variable Data

Send an http post to ~/edge/variables in order to read variable data. The request body must contain a json in the following format:

{
  "date": {
    "from": "<string>",
    "to": "<string>"
  },
  "filter": "<string>",
  "select": {
  	"properties": "<string>"
  },
  "orderBy": {
    "property": "<string>",
    "order": "<string>"
  },
  "limit": "<int>"
}

You can find info about each property in the table below:

ParameterMandatoryDescriptionData Type
dateYesDate objectObject
date.fromYesStarting date range for querystring (ISO 8601 format)
date.toNoEnding date range for query. If not provided, will default to api's server current UTC time.string (ISO 8601 format)
filterNoA predicate string adhering to ABB's Query Expression Language formatstring
selectNoSelect objectObject
select.propertiesNoA comma separated list of properties that will return in the response. If this field is set, then the items in this property's value will be the only properties in the response. Example: "properties": "objectId,model".string
orderByNoObject used to order the data.Object
orderBy.propertyYesProperty in which to order.string
orderBy.orderYesOrder by property. Available options are asc or descstring
limitNoPost processing limit. Example: If the warm data storage returns 10,000 records but this property is set to 100, then the top 100 records of that 10,000 will be returned.int

Reading Aggregated Variable Data

Send an http post to ~/edge/variables in order to read variable data. The request body must contain a json in the following format:

{
  "date": {
    "from": "<string>",
    "to": "<string>"
  },
  "filter": "<string>",
  "select": {
  	"count": "",
        "min": "<string>",
        "max": "<string>",
        "first": "<string>",
        "last": "<string>",
        "sum": "<string>",
        "avg": "<string>",
  },
  "groupBy": {
    "time": "<string>",
    "properties": "<string>"
  },
  "orderBy": {
    "property": "<string>",
    "order": "<string>"
  },
  "limit": "<int>"
}

You can find info about each property in the table below:

ParameterMandatoryDescriptionData Type
dateYesDate objectObject
date.fromYesStarting date range for querystring (ISO 8601 format)
date.toNoEnding date range for query. If not provided, will default to api's server current UTC time.string (ISO 8601 format)
filterNoA predicate string adhering to ABB's Query Expression Language formatstring
selectYesSelect object. At least one of the below select objects is required.Object
select.countNoCount of results based upon the current filter. Requires an empty string value (ie: "count": "").int
select.minNoMinimum value, only applicable to numeric and date time properties. Supports multiple comma-separated list of property names. Example: value,timestamp.string
select.maxNoMaximum value, only applicable to numeric and date time properties. Supports multiple comma-separated list of property names. Example: value,timestamp.string
select.firstNoFirst value. Supports multiple comma-separated list of property names. Example: value,timestamp.string
select.lastNoLast value. Supports multiple comma-separated list of property names. Example: value,timestamp. Using timestamp will provide the exact timestamp of the value's occurrence. Therefore, using timestamp with last provides a true last known value searching capability.string
select.sumNoSum value, only applicable to numeric properties. Supports multiple comma-separated list of property names.string
select.avgNoAverage value, only applicable to numeric. Supports multiple comma-separated list of property names.string
groupByNoObject used to group the data.Object
groupBy.timeNoUsed to group by a user-specified time interval. Supported time intervals are m, h, d. Example: 1m, 10h, 5d. By specifying this property, timestamp will automatically be returned in the response. The timestamp value will be the histogram timestamp nearest the specified time interval, not the precise timestamp of the occurring value.Object
groupBy.propertiesNoGroup by values of specified properties, supports comma-separated list of property names. All properties for a given event type are supported.Object
orderByNoObject used to order the data.Object
orderBy.propertyYesProperty in which to order.string
orderBy.orderYesOrder by property. Available options are asc or descstring
limitNoPost processing limit. Example: If the warm data storage returns 10,000 records but this property is set to 100, then the top 100 records of that 10,000 will be returned.int

Please note that the main difference between a single read query and an aggregated query is determined from the Select property in the query. If Select does not exist in your query, or it exists and Select.Properties is defined, the query will be treated as a single query; Otherwise, the query will be treated as an aggregate query. Setting aggregate properties such as Select.Max or Select.Count in a query which has also has its Select.Properties set will result in a single query and the aggregates will be ignored.

Reading Single Event Data

Send an http post to ~/edge/events in order to read single event data. The request body must contain a json in the following format:

{
  "date": {
    "from": "<string>",
    "to": "<string>"
  },
  "filter": "<string>",
  "select": {
  	"properties": "<string>"
  },
  "orderBy": {
    "property": "<string>",
    "order": "<string>"
  },
  "limit": "<int>"
}

You can find info about each property in the table below:

ParameterMandatoryDescriptionData Type
dateYesDate objectObject
date.fromYesStarting date range for querystring (ISO 8601 format)
date.toNoEnding date range for query. If not provided, will default to api's server current UTC time.string (ISO 8601 format)
filterNoA predicate string adhering to ABB's Query Expression Language formatstring
selectNoSelect objectObject
select.propertiesNoA comma separated list of properties that will return in the response. If this field is set, then the items in this property's value will be the only properties in the response. Example: "properties": "objectId,model".string
orderByNoObject used to order the data.Object
orderBy.propertyYesProperty in which to order.string
orderBy.orderYesOrder by property. Available options are asc or descstring
limitNoPost processing limit. Example: If the warm data storage returns 10,000 records but this property is set to 100, then the top 100 records of that 10,000 will be returned.int

Reading Aggregated Event Data

This features is currently unimplemented and will be added at a future date.

Reading Single Alarm Data

Send an http post to ~/edge/alarms in order to read single alarm data. The request body must contain a json in the following format:

{
  "date": {
    "from": "<string>",
    "to": "<string>"
  },
  "filter": "<string>",
  "select": {
  	"properties": "<string>"
  },
  "orderBy": {
    "property": "<string>",
    "order": "<string>"
  },
  "limit": "<int>"
}

You can find info about each property in the table below:

ParameterMandatoryDescriptionData Type
dateYesDate objectObject
date.fromYesStarting date range for querystring (ISO 8601 format)
date.toNoEnding date range for query. If not provided, will default to api's server current UTC time.string (ISO 8601 format)
filterNoA predicate string adhering to ABB's Query Expression Language formatstring
selectNoSelect objectObject
select.propertiesNoA comma separated list of properties that will return in the response. If this field is set, then the items in this property's value will be the only properties in the response. Example: "properties": "objectId,model".string
orderByNoObject used to order the data.Object
orderBy.propertyYesProperty in which to order.string
orderBy.orderYesOrder by property. Available options are asc or descstring
limitNoPost processing limit. Example: If the warm data storage returns 10,000 records but this property is set to 100, then the top 100 records of that 10,000 will be returned.int

Reading Aggregated Alarm Data

This features is currently unimplemented and will be added at a future date.

Response Format

There are a total of 4 different response codes to your query:

HTTP Status Code 200 OK
HTTP Status Code 400 BadRequest
HTTP Status Code 500 InternalServerError
HTTP Status Code 503 ServiceUnavailable

BadRequest is responded when your query is malformed and cannot be parsed into the correct structure. InternalSeverError happens when an error happens on querying process and must be reported to the development team with the response body, as it containts the error.
ServiceUnavailable happens when the connectivity to CpmPlus database is not present. This usually is resolved after a few seconds.

Configuration

Configuring TsAPI can be done with three methods: Platform, file, and environment values. Each method will be documented here, but please not that you should only choose one and not combine two different methods as the results will be unpredictable.

In case you are deploying TsAPI as a part of the "Warm Storage Component" from the "Edge Management Portal", you can ignore the configuration methods, and only review the Structure section"

Structure

The configuration of TsModule, can be declared as the JSON below. Please note that the behavior of the component can significantly change based on these values and therefore, it is strongly adviced to to customize the values based on your requirements, or consult with the development team in case of ambiguity:

{
  "mqttConfiguration": {
    "clientId": "ModuleNameHere",
    "username": "Mqttusername",
    "password": "mqttpassword",
    "brokerUri": "tcp://localhost:2883",
    "Throttling": 0
  },
  "VtrinConfiguration": {
    "TSDBConnectionString": "wss://cpmplushistory:9443/history",
    "username": "root",
    "password": "root",
    "RTDB": {
      "histories": {
        "defaultHistory": {
          "Name": "StreamHistory"
        },
        "unimportantKey": {
          "Name": "firstHistory",
          "HistoryLengthInDays": 1000
        },
        "anotherUnimportantKey": {
          "Name": "secondHistory",
          "HistoryLengthInDays": 10
        }
      },
      "defaultArrayLengths": 15,
      "enableNonChronologicalWrite": true,
      "PropertyToHistoryMappings": {
        "ArbitraryKeyOne": {
          "variable": "variableNameOne",
          "objectId": "b560357f-7612-4cba-9b1f-84b78b7179ae",
          "model": "abb.ability.device",
          "historyName": "secondHistory"
        },
        "ArbitraryKeyTwo": {
          "variable": "variableNameTwo",
          "objectId": "b560357f-7612-4cba-9b1f-84b78b7179ae",
          "model": "abb.ability.device",
          "historyName": "firstHistory"
        }
      }
    }
  }
}

The explanation for each key and value are listed in the table below:

ParameterMandatoryDescriptionData Type
mqttConfigurationYesConfiguration for the mqtt client of the programObject
mqttConfiguration.clientIdYesClientId that the mqtt client will use to connect to the brokerString
mqttConfiguration.usernameNoUsername for mqtt client for broker connectionString
mqttConfiguration.passwordNoPassword for mqtt client for broker connectionString
mqttConfiguration.brokerUriYesUri that the mqtt client will connect toUri
mqttConfiguration.throttlingNoMaximum allowed number of incoming mqtt messages per secondUnsigned Integer
vtrinConfigurationYesConfiguration for the storage databaseObject
vtrinConfiguration.TSDBConnectionStringYesThe URI for the cpmPlusHistory databaseUri
vtrinConfiguration.usernameYesUsername for connecting to cpmPlusHistory databaseString
vtrinConfiguration.passwordYesPassword for connecting to cpmPlusHistory databaseString
vtrinConfiguration.rtdbYesInternal configuration for cpmPlusHistoryObject
vtrinConfiguration.rtdb.historiesYesDictionary of histories in use. They will be created at the started if not existingObject
vtrinConfiguration.rtdb.histories.defaultHistoryYesThe default history which any variable unmapped to a certain history will be written toObject
vtrinConfiguration.rtdb.histories.defaultHistory .nameYesName of the default historyString
vtrinConfiguration.rtdb.histories.defaultHistory .historyLengthInDaysNoNumber of days that the data will be stored in the default historyInteger
vtrinConfiguration.rtdb.histories.arbitraryKeyOneNoCustom history to be created. There can be many of these. Key is unimportant.Object
vtrinConfiguration.rtdb.histories.arbitraryKeyOne .nameYesCustom History name.String
vtrinConfiguration.rtdb.histories.arbitraryKeyOne .historyLengthInDaysNoNumber of days that the data will be stored in this custom historyInteger
vtrinConfiguration.rtdb.defaultArrayLengthsYesArrays are stored on RTDB with a fixed length. Set this to the max length of your arrays.Integer
vtrinConfiguration.rtdb.enableNonChronologicalWriteNoRTDB discards values with out of order timestamp by default. This Changes the behavior.Boolean
vtrinConfiguration.rtdb.PropertyToHistoryMappingsNoMapping each variable to go to be written to specific historyObject
vtrinConfiguration.rtdb.PropertyToHistoryMappings .KeyOneNoOne Mapping Object. Key is unimportant and unused.Object
vtrinConfiguration.rtdb.PropertyToHistoryMappings .KeyOne.variableYesVariable NameString
VtrinConfiguration.rtdb.PropertyToHistoryMappings .KeyOne.objectIdYesObjectId for the variableString
VtrinConfiguration.rtdb.PropertyToHistoryMappings .KeyOne.modelYesModel for the variableString
VtrinConfiguration.rtdb.PropertyToHistoryMappings .KeyOne.historyNameYesThe name of the history which the variable should be written toString

Configuration Through Normal Edge Deployement

From the Abb Ability IoT Platform version 19.0.9 and beyond, TsModule has to be declared as a system module. This is due to the specific MQTT access control granted to different types of modules on a normal edge deployement, and TsApi requiring elevated permissions. Configuring TsModule on platform versions prior to 19.0.9 is not possible from the typedefinitions, as the configuration will not be passed on to the module by the Ability Edge Proxy.

Inside the abb.ability.edge.configuration type definition of your edge model, you can find a key named systemModules. Under that key, add the following json and customize it to your liking. This sample is the same as what was provided in the previous section, modified to be compatible with the platform. Please remember that the other MQTT values are determined and set by the Ability Edge Proxy and are not configurable from TsApi's configuration.

  "TsModule": {
    "image": {
      "isMandatory": true,
      "dataType": "string",
      "value": "TsModuleImageNameHere:latest"
    },
    "configuration": {
      "mqttConfiguration": {
        "Throttling": {
          "dataType": "integer",
          "value": 0
        }
      },
      "VtrinConfiguration": {
        "TSDBConnectionString": {
          "dataType": "string",
          "value": "wss://cpmplushistory:9443/history"
        },
        "username": {
          "dataType": "string",
          "value": "root"
        },
        "password": {
          "dataType": "string",
          "value": "root"
        },
        "RTDB": {
          "histories": {
            "defaultHistory": {
              "Name": {
                "dataType": "string",
                "value": "StreamHistory"
              }
            },
            "unimportantKey": {
              "Name": {
                "dataType": "string",
                "value": "firstHistory"
              },
              "HistoryLengthInDays": {
                "dataType": "integer",
                "value": 1000
              }
            },
            "anotherUnimportantKey": {
              "Name": {
                "dataType": "string",
                "value": "secondHistory"
              },
              "HistoryLengthInDays": {
                "dataType": "integer",
                "value": 10
              }
            }
          },
          "defaultArrayLengths": {
            "dataType": "integer",
            "value": 15
          },
          "enableNonChronologicalWrite": {
            "dataType": "boolean",
            "value": true
          },
          "PropertyToHistoryMappings": {
            "ArbitraryKeyOne": {
              "variable": {
                "dataType": "string",
                "value": "variableNameOne"
              },
              "objectId": {
                "dataType": "string",
                "value": "b560357f-7612-4cba-9b1f-84b78b7179ae"
              },
              "model": {
                "dataType": "string",
                "value": "abb.ability.device"
              },
              "historyName": {
                "dataType": "string",
                "value": "secondHistory"
              }
            },
            "ArbitraryKeyTwo": {
                "variable": {
                    "dataType": "string",
                    "value": "variableNameTwo"
                  },
                  "objectId": {
                    "dataType": "string",
                    "value": "b560357f-7612-4cba-9b1f-84b78b7179ae"
                  },
                  "model": {
                    "dataType": "string",
                    "value": "abb.ability.device"
                  },
                  "historyName": {
                    "dataType": "string",
                    "value": "firstHistory"
                  }
            }
          }
        }
      }
    }
  }

Configuration Through File

It is also possible to configure the module through a configuration file. This method is platform independant; as the component always checks the existance of this file first for its configuration. It is worth noting that this file is automatically created by the Ability Edge Proxy from version 19.0.9 onwards.

The file must be located at /app/config/configuration.json Please note that in case you are running the component on a Windows system outside of a container, this file may have to be located at your main drive(usually C drive).

The content of the file is the exact same as what was provided in the Structure section.

Configuration Through Environment Values

Lastly, you are also able to configure the module through environment values. This is the most unconvential way and should only be used when the other two options are not available.

The following table presents the key/value pairs that you need to declare on the environment for this method:

KeySample Value
mqtt_client_idModuleNameHere
module_idMqttusername
mqtt_password_fileMqttpassword
mqtt_urltcp://localhost:2883
VtrinConfiguration__TSDBConnectionStringwss://cpmplushistory:9443/history
VtrinConfiguration__usernameroot
VtrinConfiguration__passwordroot
VtrinConfiguration__RTDB__EnableNonChronologicalWritetrue
VtrinConfiguration__RTDB__defaultArrayLengths15
VtrinConfiguration__RTDB__Histories__DefaultHistory__NameStreamHistory
VtrinConfiguration__RTDB__Histories__ArbitraryKeyOne__NameFirstHistory
VtrinConfiguration__RTDB__Histories__ArbitraryKeyOne__HistoryLengthInDays1000
VtrinConfiguration__RTDB__Histories__ArbitraryKeyTwo__NamesecondHistory
VtrinConfiguration__RTDB__Histories__ArbitraryKeyTwo__HistoryLengthInDays10
VtrinConfiguration__RTDB__PropertyToHistoryMappings__ArbitraryKeyOne__VariablevariableNameOne
VtrinConfiguration__RTDB__PropertyToHistoryMappings__ArbitraryKeyOne__ObjectIdb560357f-7612-4cba-9b1f-84b78b7179ae
VtrinConfiguration__RTDB__PropertyToHistoryMappings__ArbitraryKeyOne__Modelabb.ability.device
VtrinConfiguration__RTDB__PropertyToHistoryMappings__ArbitraryKeyOne__HistoryNamesecondHistory
VtrinConfiguration__RTDB__PropertyToHistoryMappings__ArbitraryKeyTwo__VariablevariableNameTwo
VtrinConfiguration__RTDB__PropertyToHistoryMappings__ArbitraryKeyTwo__ObjectIdb560357f-7612-4cba-9b1f-84b78b7179ae
VtrinConfiguration__RTDB__PropertyToHistoryMappings__ArbitraryKeyTwo__Modelabb.ability.device
VtrinConfiguration__RTDB__PropertyToHistoryMappings__ArbitraryKeyTwo__HistoryNameFirstHistory

Features

Variable links

Variables can be links to other variables. This is an extension to informaion model object definition. Regularly object model does not store information about variables but only properties. In the link case the variable is stored in the object model along with a link field which points to another variable.

If an object has variables where some of them are links to other variables these can be supported to some extent. For example the following object has a regular variable and a linked variable. The regular variable is not expressed in the object model but only the variable that is a link. To get the link resolved, TsApi module has to receive the regular variable data of this object to trigger the link resolving.

{
	"type": "abb.processAutomation.sensors.tempSensor",
	"name": "bestsensor",
	"model": "abb.ability.device",
	"objectid": "944bdd85-644f-47f7-bac5-2c8c76a120b0",
	"version": 54,
	"properties": {
		"serialNumber": {
			"value": "string"
		}
	},
	"variables": {
		"temperature": {
			"link": "2e3dbd5c-2012-48ed-aba4-c62f2887ec31/abb.robotics.ioModel#/variables/gripperOpen"
		}
	}
}

The link format listed above must be followed exactly and is currently the only supported format. This allows to link to a specific variable.

If this object has for example another variable called "Power", TsApi needs to receive for example following data. This will trigger TSApi to investigate this object and populate its links.

 {
    "objectId": "944bdd85-644f-47f7-bac5-2c8c76a120b0",
    "model": "abb.ability.device",
    "timestamp": "2020-01-01T00:00:02Z",
    "variable": "Power",
    "quality": 0,
    "value": 4
  }

To give better example of creating such objects, a type definition for this type would be:

{
	"typeid": "abb.processAutomation.sensors.tempSensor",
	"model": "abb.ability.device",
	"version": "1.0.0",
	"isExtensible": true,
	"properties": {
		"serialNumber": {
			"datatype": "string"
		}
	},
	"variables": {
		"power": {
			"datatype": "integer"
		}
	},
	"attributes": {
		"link": {
			"dataType": "string",
			"appliesTo": [
				"string",
				"number",
				"integer",
				"boolean",
				"array"
			]
		}
	}
}

The power variable could directly have the link attribute or we can extend the type by adding this temperature variable. An object of this type should then be extended for exapmle with this extension:

{
	"variables": {
		"temperature": {
			"link": "2e3dbd5c-2012-48ed-aba4-c62f2887ec31/abb.robotics.ioModel#/variables/gripperOpen",
		}
	}
}

Then query can be made to query the temperature variable but the query will return values from another object which data should be sent as:

 {
    "objectId": "2e3dbd5c-2012-48ed-aba4-c62f2887ec31",
    "model": "abb.robotics.ioModel",
    "timestamp": "2020-01-01T00:00:02Z",
    "variable": "gripperOpen",
    "quality": 0,
    "value": 33
  }

This could be then queried with

{
    "date":{
        "from" : "2000-01-01T00:00:00Z"
    },
    "filter": "variable='temperature' and objectid='944bdd85-644f-47f7-bac5-2c8c76a120b0'
}

This returns the values of gipperOpen as temperature values of the object 9444.

Same data can be queried through the link destination:

{
    "date":{
        "from" : "2000-01-01T00:00:00Z"
    },
    "filter": "variable='gripperOpen' and objectid='2e3dbd5c-2012-48ed-aba4-c62f2887ec31'
}

Which would return the values of io.gripperOpen but expressed as values of temperature.

The version where variable links is available is:
application version 5.1.0
abbiapcpdev.azurecr.io/cpmplustsmodule:2.7.0
abbiapcpdev.azurecr.io/cpmplushistory:530.2204.1r