GENESIS64 includes a service called “ICONICS OData Connector Point Manager” (or OData Connector for short) which layers a REST API on top of FrameWorX Server and optionally also an OData v4 compliant Endpoint.
NOTE: ICONICS OData Connector is now considered a legacy solution. It has been superseded by the Web API provider in the Workbench. The following information will remain available for users of the OData Connector. The OData Connector is disabled by default for security reasons. Users who wish to continue using it can change the startup type of the ICONICS OData Connector Point Manager (FwxODataService) service from Disabled to Automatic or Automatic (Delayed).
The REST API can be used by clients to request data from FrameWorX without the constraint of using .NET and the FrameWorX client API.
By default, the OData Connector endpoint is not secured and allows unauthenticated HTTP requests. Security between the OData Connector and FrameWorX is regulated by standard ICONICS Security and, by default, the OData Connector requests to FrameWorX are executed under the default security group.
With this in mind, securing the OData Connector consists of two tasks:
Securing access from the OData Connector to FrameWorX. This defines what data the OData Connector can request from FrameWorX.
Securing access to the HTTP endpoints. This prevents unauthenticated access to the REST API.
To secure requests from the OData Connector to FrameWorX we have two possible options. Since the OData Connector uses the default security group, the first option is to configure security for the default group as required. The second option is to configure the OData Connector to use a specific ICONICS security account when requesting data from FrameWorX.
This is done via the Platform Services Configuration utility, under the Passwords tab:
In the picture above, we are instructing the OData Connector to use the odata user account when requesting data from FrameWorX. Security permissions for the odata user can be then configured appropriately using the ICONICS Security Server configurator. Please note that the user name does not have to be necessarily odata and can be configured as desired.
Securing access to the REST endpoints is again done via the Platform Services Configuration utility. Under the Point Managers tab, locate the OData Connector Point Manager to access its settings:
The two settings used to configure access to the REST API are RequireAuthentication and Impersonate. As the name suggests, after changing the value of RequireAuthenticationto true, the OData Connector will start requesting authentication credentials to access the endpoints. Authentication is implemented as HTTP basic access authentication.
IMPORTANT: since basic access authentication exchanges credentials between client and server in clear text, it is highly recommended to configure the OData Connector to use HTTPS if authentication is enabled. Please see the Endpoint Protocol section below for more details.
When authentication is enabled, the OData Connector validates the received credentials against the ICONICS Security Server: if the credentials are valid, the request is processed - if not, the client receives an HTTP status code of 401 – Unauthorized.
Up to this point, credentials passed by the client are only used to authorize access to the REST endpoints. Which user account is ultimately used by the OData Connector to request data from FrameWorX is configured by the Impersonate setting. When Impersonate is set to false (the default value) the OData Connector will request data from FrameWorX under the default security group or, if configured, the ODATA -> FWX user account. When Impersonate is set to true, the OData Connector will request data from FrameWorX by logging in with Security using the credentials passed by the client.
By default, the OData Connector is configured to run on HTTP over port 80. To use HTTPS, it is necessary to install a valid certificate that can be used for this purpose on the machine where the OData Connector is installed. The certificate needs then to be bound to the port that will be used by the OData Connector using the netsh utility via command line:
netsh http add sslcert ipport=<IP:Port> certhash=<thumbprint> appid={768c5052-9306-4d2b-ace2-8aa3002bf70a}
Where <IP:Port> is the IP address and the port where the certificate will be bound to, and <thumbprint> is the certificate thumbprint.
GENESIS64 also installs a batch file that can create a self-signed certificate and bind it to the selected port for testing purposes. The batch file is located at:
C:\Program Files\ICONICS\GENESIS64\InstCert\ODataCert.bat
The following command creates a new certificate and binds is to the specified port; if port is not specified then the default 443 is used:
> ODataCert -i [port]
The following command unbinds and deletes an existing certificate:
> ODataCert -u
Once the certificate has been installed and bound to the desired port, the OData Connector can be configured using the EndpointProtocol, EndpointPort and EndpointMachineNamefields in the Platform Services Configuration utility:
·
EndpointProtocol must be changed to https
EndpointPort must be changed to the port to which the certificate has been bound (the default is 443)
EndpointMachineName must be changed to the value of the common name (CN) attribute of the certificate.
NOTE: changing any of the settings under the Point Managers tab in the Platform Services Configuration Utility requires a restart of the OData Connector Point Manager Service.
The OData Connector exposes several REST endpoints that allow access to different types of FrameWorX Server data. Endpoints can be accessed both via HTTP GET or HTTP POST, where POST usually allows submitting more than one request at the time, thus increasing efficiency.
In the next sections, we are going to describe the most commonly used endpoints. For the complete list – including parameter descriptions and examples - the OData Connector exposes online help pages at:
http://localhost/ODataConnector/rest/Help/Html?method=get
http://localhost/ODataConnector/rest/Help/Html?method=post
This endpoint allows to read the current value of data points or to write to data points.
Reading real-time values (HTTP GET)
The RealtimeData endpoint accepts the name of the point to read as a query string parameter:
GET http://localhost/ODataConnector/rest/RealtimeData?PointName=svrsim:sine double med -100 100 HTTP/1.1
Host: localhost
Connection: keep-alive
The response is a single-value array containing a JSON object carrying the name, value, timestamp and quality of the requested point:
HTTP/1.1 200 OK
Content-Length: 136
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Tue, 22 May 2018 19:16:34 GMT
[{
"PointName": "svrsim:sine double med -100 100",
"Value": 66.498011645316581,
"Timestamp": "2018-05-22T19:16:34.2406812+00:00",
"Quality": 0
}]
Reading Real-time Values (HTTP POST)
For increased efficiency, it is possible to request multiple points at the same time from the RealtimeData endpoint when using HTTP POST. The POST body must contain a JSON object with a single property called PointName, which carries an array of point names:
POST http://localhost/ODataConnector/rest/RealtimeData HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 89
Cache-Control: no-cache
Content-Type: application/json
{ "PointName": [ "svrsim:sine double med -100 100", "svrsim:ramp double med -100 100" ] }
The response is an array containing a JSON object for each point requested, each object carrying the name, value, timestamp and quality of the requested point:
HTTP/1.1 200 OK
Content-Length: 272
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: *
Date: Tue, 22 May 2018 19:24:56 GMT
[{
"PointName": "svrsim:sine double med -100 100",
"Value": -70.186717085644588,
"Timestamp": "2018-05-22T19:24:55.8691661+00:00",
"Quality": 0
}, {
"PointName": "svrsim:ramp double med -100 100",
"Value": 25.265000000037261,
"Timestamp": "2018-05-22T19:24:56.3694864+00:00",
"Quality": 0
}]
Writing Real-time Values
One or more points can be written to using the RealtimeData/Write endpoint via HTTP POST. The POST body must contain a JSON array of objects containing the name of the point and the value to write:
POST http://localhost/ODataConnector/rest/RealtimeData/Write HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 105
Cache-Control: no-cache
Content-Type: application/json
[ { "PointName": "svrsim:out int32", "Value": 42 }, { "PointName": "svrsim:out double", "Value": 42.0 } ]
The response is an array containing a JSON object for each write operation, each object carrying the point name, whether the write succeeded and an error message in the case the write failed:
HTTP/1.1 200 OK
Content-Length: 138
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Wed, 23 May 2018 13:43:58 GMT
[{
"PointName": "svrsim:out int32",
"Success": true,
"ErrorMessage": null
}, {
"PointName": "svrsim:out double",
"Success": true,
"ErrorMessage": null
}]
This endpoint allows to read dataset points, like the ones exposed by GridWorX. The DatasetData endpoint accepts the name of the point to read as a query string parameter. If the dataset point itself has parameters, the parameter values can be included in the point name:
GET http://localhost/ODataConnector/rest/DatasetData?PointName=db:Northwind.OrdersByCustomerID<@CustomerID=ALFKI> HTTP/1.1
Host: localhost
Connection: keep-alive
Cache-Control: no-cache
Content-Type: application/json
The response is an array of JSON objects, the objects have properties with the same name and equivalent data type as the columns of the dataset:
HTTP/1.1 200 OK
Content-Length: 2051
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Wed, 23 May 2018 13:54:45 GMT
[{
"OrderID": 10643,
"CustomerID": "ALFKI",
"EmployeeID": 6,
"OrderDate": "1997-08-25T00:00:00",
"RequiredDate": "1997-09-22T00:00:00",
"ShippedDate": "1997-09-02T00:00:00",
"ShipVia": 1,
"Freight": 29.4600,
"ShipName": "Alfreds Futterkiste",
"ShipAddress": "Obere Str. 57",
"ShipCity": "Berlin",
"ShipRegion": null,
"ShipPostalCode": "12209",
"ShipCountry": "Germany"
},
...
{
"OrderID": 11011,
"CustomerID": "ALFKI",
"EmployeeID": 3,
"OrderDate": "1998-04-09T00:00:00",
"RequiredDate": "1998-05-07T00:00:00",
"ShippedDate": "1998-04-13T00:00:00",
"ShipVia": 1,
"Freight": 1.2100,
"ShipName": "Alfred's Futterkiste",
"ShipAddress": "Obere Str. 57",
"ShipCity": "Berlin",
"ShipRegion": null,
"ShipPostalCode": "12209",
"ShipCountry": "Germany"
}]
This endpoint allows to read historical data, for example data that has been logged in Hyper Historian.
Reading Raw Historical Data (HTTP GET)
The HistoricalData endpoint accepts the point name, start time and end time as query string parameters:
Host: localhost
Connection: keep-alive
Cache-Control: no-cache
Content-Type: application/json
The response is an array of JSON objects, each object representing a sample carrying the name, value, timestamp and quality of the requested point:
HTTP/1.1 200 OK
Content-Length: 65103
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Wed, 23 May 2018 15:28:08 GMT
[{
"PointName": "hh:\\Configuration\\Signals:SineFast",
"Value": 45.894377320185733,
"Timestamp": "2018-05-23T15:25:00.011+00:00",
"Quality": 0
}, {
"PointName": "hh:\\Configuration\\Signals:SineFast",
"Value": 52.433772224711312,
"Timestamp": "2018-05-23T15:25:00.26+00:00",
"Quality": 0
},
...
{
"PointName": "hh:\\Configuration\\Signals:SineFast",
"Value": 44.669442277460647,
"Timestamp": "2018-05-23T15:26:59.963+00:00",
"Quality": 0
}]
Reading Aggregated Historical Data (HTTP GET)
When also specifying an aggregate name and a processing interval as query string parameters, the HistoricalData endpoint returns aggregated data:
Host: localhost
Connection: keep-alive
Cache-Control: no-cache
Content-Type: application/json
The response is an array of JSON objects, each object representing a sample carrying the name, value, timestamp and quality of the requested point:
HTTP/1.1 200 OK
Content-Length: 397
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Wed, 23 May 2018 15:33:42 GMT
[{
"PointName": "hh:\\Configuration\\Signals:SineFast",
"Value": 50.000000000059146,
"Timestamp": "2018-05-23T15:27:00+00:00",
"Quality": 0
}, {
"PointName": "hh:\\Configuration\\Signals:SineFast",
"Value": 50.000000000059146,
"Timestamp": "2018-05-23T15:28:00+00:00",
"Quality": 0
}, {
"PointName": "hh:\\Configuration\\Signals:SineFast",
"Value": 50.019209953947929,
"Timestamp": "2018-05-23T15:29:00+00:00",
"Quality": 0
}]
Reading Historical Data (HTTP POST)
For increased efficiency, it is possible to request more than one point at the same time using HTTP POST. The HistoricalData endpoint can be used or, if the goal is just to consume the data in a forward-only way, there is an even more efficient endpoint at ForwardOnlyHistoricalData that allows to read large amounts of historical data with a lower memory impact on the OData Connector Service.
To read historical data using the standard HistoricalData endpoint the POST body must be a JSON object containing all the relative parameters. The PointName property must be an array and may contain multiple point names – when multiple point names are specified, the response will contain all the samples for the first point, followed by all the samples for the second point and so on. When omitting the AggregateName and ProcessingInterval parameters the response will contain raw data samples, otherwise aggregated samples will be returned:
POST http://localhost/ODataConnector/rest/HistoricalData HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 171
Cache-Control: no-cache
{ "PointName": [ "hh:\\Configuration\\Signals:Sine", "hh:\\Configuration\\Signals:Ramp" ], "StartDate": "2018-05-23T11:25:00-0400", "EndDate": "2018-05-23T11:27:00-0400" }
The response is an array of JSON objects, each object representing a sample carrying the name, value, timestamp and quality of the requested points:
HTTP/1.1 200 OK
Content-Length: 124896
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Wed, 23 May 2018 19:00:09 GMT
[{
"PointName": "hh:\\Configuration\\Signals:Sine",
"Value": 2.1995849579170397,
"Timestamp": "2018-05-23T15:25:00.011+00:00",
"Quality": 0
},
...
{
"PointName": "hh:\\Configuration\\Signals:Sine",
"Value": 2.1279758529826935,
"Timestamp": "2018-05-23T15:26:59.963+00:00",
"Quality": 0
}, {
"PointName": "hh:\\Configuration\\Signals:Ramp",
"Value": 29.738333333333333,
"Timestamp": "2018-05-23T15:25:00.011+00:00",
"Quality": 0
},
...
{
"PointName": "hh:\\Configuration\\Signals:Ramp",
"Value": 29.659999999999997,
"Timestamp": "2018-05-23T15:26:59.963+00:00",
"Quality": 0
}]
The ForwardOnlyHistoricalData endpoint allows to read historical data in a paged, forward-only way that is more efficient. To request data from this endpoint, a client-generated unique ID string must be passed in the query string via the uid parameter. The query string also accepts two paging parameters called $top and $skip. $top specifies how many samples to return in the page response, while $skip specifies how many samples to skip. The POST body is identical as the one used for the HistoricalData endpoint, with the difference that it is only required for the first POST request – all requests for subsequent pages using the same uid can omit the POST body.
The following request asks the server for aggregated data for a 5 minutes interval with a 1-minute processing interval for two tags (a total of 10 samples). The first request asks for a page size of 2 samples:
POST http://localhost/ODataConnector/rest/ForwardOnlyHistoricalData?uid=00001&$skip=0&$top=2 HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 229
Cache-Control: no-cache
Content-Type: application/json
{ "PointName": [ "hh:\\Configuration\\Signals:Sine", "hh:\\Configuration\\Signals:Ramp" ], "StartDate": "2018-05-23T11:25:00-0400", "EndDate": "2018-05-23T11:30:00-0400", AggregateName: "Average", ProcessingInterval: "00:01:00" }
And the server responds with the first two samples:
HTTP/1.1 200 OK
Content-Length: 257
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Wed, 23 May 2018 19:25:04 GMT
[{
"PointName": "hh:\\Configuration\\Signals:Sine",
"Value": 50.000000000004746,
"Timestamp": "2018-05-23T15:25:00+00:00",
"Quality": 0
}, {
"PointName": "hh:\\Configuration\\Signals:Sine",
"Value": 49.871412141169763,
"Timestamp": "2018-05-23T15:26:00+00:00",
"Quality": 0
}]
The next request skips the first two samples and requests the next two. Note that the POST body is empty – the server knows which historical reader the request belongs to thanks to the uid parameter:
POST http://localhost/ODataConnector/rest/ForwardOnlyHistoricalData?uid=00001&$skip=2&$top=2 HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 0
Cache-Control: no-cache
Content-Type: application/json
And we get the next two samples:
HTTP/1.1 200 OK
Content-Length: 257
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Wed, 23 May 2018 19:25:16 GMT
[{
"PointName": "hh:\\Configuration\\Signals:Sine",
"Value": 50.000000000011866,
"Timestamp": "2018-05-23T15:27:00+00:00",
"Quality": 0
}, {
"PointName": "hh:\\Configuration\\Signals:Sine",
"Value": 50.000000000011866,
"Timestamp": "2018-05-23T15:28:00+00:00",
"Quality": 0
}]
We keep requesting pages until the last page:
POST http://localhost/ODataConnector/rest/ForwardOnlyHistoricalData?uid=00001&$skip=8&$top=2 HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 0
Cache-Control: no-cache
Content-Type: application/json
And we receive the last two samples:
HTTP/1.1 200 OK
Content-Length: 256
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Wed, 23 May 2018 19:25:30 GMT
[{
"PointName": "hh:\\Configuration\\Signals:Ramp",
"Value": 49.86833333333324,
"Timestamp": "2018-05-23T15:28:00+00:00",
"Quality": 0
}, {
"PointName": "hh:\\Configuration\\Signals:Ramp",
"Value": 50.211805555555571,
"Timestamp": "2018-05-23T15:29:00+00:00",
"Quality": 0
}]
The OData Connector also implements an OData v4 compliant endpoint. From www.odata.org, OData is:
[…] an ISO/IEC approved, OASIS standard that defines a set of best practices for building and consuming RESTful APIs. OData helps you focus on your business logic while building RESTful APIs without having to worry about the various approaches to define request and response headers, status codes, HTTP methods, URL conventions, media types, payload formats, query options, etc.
OData services are described in terms of an Entity Model, which means entities and entity sets describe the endpoints exposed by the service. For this reason, the ICONICS OData Connector has been built to expose entities from AssetWorX via OData, namely Equipment (assets) and Equipment Properties (asset properties).
Equipment in AssetWorX can now be exposed via OData with a simple checkbox in Workbench Desktop:
If an Equipment is configured not to be exposed via OData (the OData checkbox is unchecked), children Equipment will also not be exposed. Equipment Properties have a similar configuration:
To enable the OData Endpoint, open the Platform Services Configuration Utility, switch to the Point Managers tab and select the OData Connector Point Manager from the list. Identify the EnableODataEndpoint parameter and change it to true:
The parameters exposed by the Platform Services Configuration utility can be used to configure the OData endpoint behavior. Any change to the configuration parameters requires a restart of the OData Connector Point Manager Service after the changes have been applied.
EnableODataEndpoint. This parameter enables or disables the OData endpoint. When enabled, metadata for the endpoint is available at http://localhost/ODataConnector/odata/$metadata
ODataEndpointCustomPrefix. This parameter allows to specify a custom prefix for the OData endpoint. For example, if it were set to “v1”, the metadata for OData endpoint would be available at http://localhost/ODataConnector/odata/v1/$metadata
ODataConfigurationLoadTimeoutMins. For performance reasons, the OData Connector loads the OData-enabled assets in the AssetWorX hierarchy in memory during startup and keeps them cached: changes in the AssetWorX structure are not reflected in the OData Connector until the OData Connector service is restarted. This parameter determines the timeout for loading the AssetWorX structure in memory.
·ODataPropertiesRefreshPeriodMins. For performance reasons, Equipment Properties that have a real-time data source associated in the AssetWorX configuration have their value queried and cached by the OData Connector. This means that the OData Connector will expose a cached value for properties, and not the actual current value. This parameter determines how often the OData Connector re-queries the real-time data sources associated with the Equipment Properties to refresh the cached value. This polling behavior can be disabled via the ODataUseSubscriptionForPropertyValues parameter.
ODataEnableAssetOnlineChangesDetection. This parameter is currently not used.
·ODataUseSubscriptionForPropertyValues. When a periodic polling for Equipment Property values is not desirable (for example in cases where it is necessary to expose the actual value of an Equipment Property as quickly as possible through the OData interface) it is possible to configure the OData Connector to subscribe to all Equipment Properties with real-time data sources and that have the OData checkbox selected during startup, and to keep subscription for the lifetime of the OData Connector service. This subscription behavior is enabled when this property is set to true, and it disables the default periodic polling behavior.
ODataSubscriptionScanRateMsec. This parameter configures the scan rate used by the OData Connector when subscribing to Equipment Properties when ODataUseSubscriptionForPropertyValues is set to true.
ODataLoadExtededAssetProperties. Configures whether extended properties of Equipment Properties (i.e.: EURangeMin, EuRangeMax, TimeZone, etc.) are loaded and exposed by the OData Connector.
ODataEnableExtendedPropertiesRefresh. Configures whether extended properties for Equipment Properties should refresh or not. This setting applies to both polled or subscribed refresh of Equipment Properties.
Since goals of OData as a protocol include interoperability and standardization of the REST API, all the information required to query the ICONICS OData Connector OData endpoint is exposed via the service’s metadata at http://localhost/ODataConnector/odata/$metadata.
The entity model is relatively simple with only three entities, the main one being the Asset:
<EntityType Name="Asset" OpenType="true">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int32" Nullable="false" />
<Property Name="ParentId" Type="Edm.Int32" />
<Property Name="Name" Type="Edm.String" />
<Property Name="DisplayName" Type="Edm.String" />
<Property Name="CustomIdentifier" Type="Edm.String" />
<Property Name="Path" Type="Edm.String" />
<Property Name="Class" Type="Edm.String" />
<Property Name="HasDatasetProperties" Type="Edm.Boolean" Nullable="false" />
<Property Name="HasHistoryProperties" Type="Edm.Boolean" Nullable="false" />
<NavigationProperty Name="DatasetProperties" Type="Collection(Model.DatasetProperty)" />
<NavigationProperty Name="HistoryProperties" Type="Collection(Model.HistoryProperty)" />
</EntityType>
Asset has a few explicit properties (like Name, Display Name, etc.) but it is also defined as an open type which, in OData, allows instances of the type to expose dynamic properties that are not explicitly defined in the entity type definition. By taking advantage of this, the OData Connector can “inline” Equipment Properties as actual properties of asset entities.
Let’s consider, for example, the default AssetWorX configuration that is installed with GENESIS64. The Heating Control, Ingredient Selector and Pump Control assets children of Ingredient Charger all expose different properties:
When queried via the OData endpoint, these assets are returned as:
{
"@odata.context": "http://localhost/ODataConnector/odata/$metadata#Assets",
"value": [
{
"Id": 190,
"ParentId": 189,
"Name": "Ingredient selector",
"DisplayName": "",
"CustomIdentifier": "190",
"Path": "Company/Foxboro bakery/North/Baking line/Ingredients mixing tank/Ingredient charger/Ingredient selector/",
"Class": "Ingredient selector valves/",
"HasDatasetProperties": false,
"HasHistoryProperties": false,
"SelectedMaterial": -50.90414157503709,
"SwitchingInProgress": -50.90414157503709
},
{
"Id": 191,
"ParentId": 189,
"Name": "Pump control",
"DisplayName": "",
"CustomIdentifier": "191",
"Path": "Company/Foxboro bakery/North/Baking line/Ingredients mixing tank/Ingredient charger/Pump control/",
"Class": "Variable speed pump/",
"HasDatasetProperties": false,
"HasHistoryProperties": false,
"PumpHealth": -50.90414157503709,
"CurrentFlow": -50.90414157503709,
"TargetFlow": -50.90414157503709
},
{
"Id": 192,
"ParentId": 189,
"Name": "Heating control",
"DisplayName": "",
"CustomIdentifier": "192",
"Path": "Company/Foxboro bakery/North/Baking line/Ingredients mixing tank/Ingredient charger/Heating control/",
"Class": "Heating control/",
"HasDatasetProperties": false,
"HasHistoryProperties": false,
"TargetTemperature": -50.90414157503709,
"CurrentTemperature": -50.90414157503709,
"HeatingLevel": -50.90414157503709
}
]
}
The inlined Equipment Properties have been highlighted in the JSON above.
NOTE: Equipment Properties that are connected to a writeable tag or configured as a static value using the AssetWorX database cache are also writeable via the OData endpoint.
Along with the Asset entity type, the ICONICS OData Connector OData endpoint exposes two other entity types: DatasetProperty and HistoryProperty, which are both children of Asset:
<EntityType Name="DatasetProperty">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int32" Nullable="false" />
<Property Name="ParentId" Type="Edm.Int32" Nullable="false" />
<Property Name="Name" Type="Edm.String" />
<NavigationProperty Name="Asset" Type="Model.Asset" />
</EntityType>
<EntityType Name="HistoryProperty">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int32" Nullable="false" />
<Property Name="ParentId" Type="Edm.Int32" Nullable="false" />
<Property Name="Name" Type="Edm.String" />
<NavigationProperty Name="Asset" Type="Model.Asset" />
</EntityType>
The entity sets associated to these two types contain, respectively, all the Equipment Properties that have a dataset tag associated with them (Dataset tab in the Equipment Property configuration in Workbench) and a historical tag associated with them (Historical Data tab in the Equipment Property configuration in Workbench).
The Assets entity sets returns the full list of Equipment from AssetWorX that are enabled and have their “OData” checkbox checked:
GET http://localhost/ODataConnector/odata/Assets
{
"@odata.context": "http://localhost/ODataConnector/odata/$metadata#Assets",
"value": [
{
"Id": 1,
"ParentId": null,
"Name": "Company",
"DisplayName": "",
"CustomIdentifier": "1",
"Path": "Company/",
"Class": null,
"HasDatasetProperties": false,
"HasHistoryProperties": false
},
{
"Id": 2,
"ParentId": null,
"Name": "WaterIrrigation",
"DisplayName": "",
"CustomIdentifier": "2",
"Path": "WaterIrrigation/",
"Class": null,
"HasDatasetProperties": false,
"HasHistoryProperties": false
},
...
{
"Id": 232,
"ParentId": 228,
"Name": "Sub Area A",
"DisplayName": "",
"CustomIdentifier": "232",
"Path": "Alarms/Plant Area B/Sub Area A/",
"Class": null,
"HasDatasetProperties": false,
"HasHistoryProperties": false
}
]
}
As per OData protocol, it is possible to retrieve a single entity by ID:
GET http://localhost/ODataConnector/odata/Assets(189)
{
"@odata.context": "http://localhost/ODataConnector/odata/$metadata#Assets/$entity",
"Id": 189,
"ParentId": 181,
"Name": "Ingredient charger",
"DisplayName": "",
"CustomIdentifier": "189",
"Path": "Company/Foxboro bakery/North/Baking line/Ingredients mixing tank/Ingredient charger/",
"Class": "Ingredient charger/",
"HasDatasetProperties": false,
"HasHistoryProperties": false
}
The ICONICS OData Connector OData entity model also specifies to custom entity-bound functions for the Asset entity type:
Parent() – gets the parent of an asset
Children() – gets the direct children of an asset (not recursive)
GET http://localhost/ODataConnector/odata/Assets(189)/Model.Parent()
{
"@odata.context": "http://localhost/ODataConnector/odata/$metadata#Assets/$entity",
"Id": 181,
"ParentId": 106,
"Name": "Ingredients mixing tank",
"DisplayName": "",
"CustomIdentifier": "181",
"Path": "Company/Foxboro bakery/North/Baking line/Ingredients mixing tank/",
"Class": null,
"HasDatasetProperties": false,
"HasHistoryProperties": false
}
GET http://localhost/ODataConnector/odata/Assets(189)/Model.Children()
{
"@odata.context": "http://localhost/ODataConnector/odata/$metadata#Assets",
"value": [
{
"Id": 190,
"ParentId": 189,
"Name": "Ingredient selector",
"DisplayName": "",
"CustomIdentifier": "190",
"Path": "Company/Foxboro bakery/North/Baking line/Ingredients mixing tank/Ingredient charger/Ingredient selector/",
"Class": "Ingredient selector valves/",
"HasDatasetProperties": false,
"HasHistoryProperties": false,
"SelectedMaterial": -50.90414157503709,
"SwitchingInProgress": -50.90414157503709
},
{
"Id": 191,
"ParentId": 189,
"Name": "Pump control",
"DisplayName": "",
"CustomIdentifier": "191",
"Path": "Company/Foxboro bakery/North/Baking line/Ingredients mixing tank/Ingredient charger/Pump control/",
"Class": "Variable speed pump/",
"HasDatasetProperties": false,
"HasHistoryProperties": false,
"PumpHealth": -50.90414157503709,
"CurrentFlow": -50.90414157503709,
"TargetFlow": -50.90414157503709
},
{
"Id": 192,
"ParentId": 189,
"Name": "Heating control",
"DisplayName": "",
"CustomIdentifier": "192",
"Path": "Company/Foxboro bakery/North/Baking line/Ingredients mixing tank/Ingredient charger/Heating control/",
"Class": "Heating control/",
"HasDatasetProperties": false,
"HasHistoryProperties": false,
"TargetTemperature": -50.90414157503709,
"CurrentTemperature": -50.90414157503709,
"HeatingLevel": -50.90414157503709
}
]
}
First, let’s create an Equipment Property that is connected to a writeable tag. Open Workbench and navigate to Assets > Equipment > Company, right-click and select “Add Equipment Property”. Name the property Count, switch to the Real Time Data tab, select Dynamic Tag as Source Type and connect the Real time data tag to svrsim:out.int32:
Restart the ICONICS OData Connector Point Manager service (remember that for performance reasons the AssetWorX hierarchy is only read once during startup and cached) and request the Company asset:
GET http://localhost/ODataConnector/odata/Assets(1)
{
"@odata.context": "http://localhost/ODataConnector/odata/$metadata#Assets/$entity",
"Id": 1,
"ParentId": null,
"Name": "Company",
"DisplayName": "",
"CustomIdentifier": "1",
"Path": "Company/",
"Class": null,
"HasDatasetProperties": false,
"HasHistoryProperties": false,
"Count": 0
}
As described in the OData Protocol, properties of an entity can be written using HTTP PATCH. The PATCH body must carry the name of the property and the value to write as a JSON object:
PATCH http://localhost/ODataConnector/odata/Assets(1) HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 27
Cache-Control: no-cache
Content-Type: application/json
{ "Count": 42 }
After the PATCH has completed, we can verify that the property was indeed updated:
GET http://localhost/ODataConnector/odata/Assets(1)
{
"@odata.context": "http://localhost/ODataConnector/odata/$metadata#Assets/$entity",
"Id": 1,
"ParentId": null,
"Name": "Company",
"DisplayName": "",
"CustomIdentifier": "1",
"Path": "Company/",
"Class": null,
"HasDatasetProperties": false,
"HasHistoryProperties": false,
"Count": 42
}
NOTE: The OData protocol allows multiple properties to be updated with a single PATCH request, however the ICONICS OData Connector only allows to update one property per PATCH request. If more than one property is passed in the PATCH body the ICONICS OData Connector will respond with a 400 Bad Request error.
First, let’s create an Equipment Property that is connected to a parameterized dataset. Open Workbench and navigate to Assets > Equipment > Company, right-click and select “Add Equipment Property”. Name the property Orders, switch to the Dataset tab and connect the Dataset tag to:
db:Northwind.OrdersByCustomerID<@CustomerID=<<customerid>>>
Note that we are connecting the @CustomerID parameter of the underlying dataset point with the AssetWorX parameter <<customerid>>, which we must define under the General tab:
After restarting the ICONICS OData Connector Point Manager we can verify that the Company asset now has dataset properties:
GET http://localhost/ODataConnector/odata/Assets(1)
{
"@odata.context": "http://localhost/ODataConnector/odata/$metadata#Assets/$entity",
"Id": 1,
"ParentId": null,
"Name": "Company",
"DisplayName": "",
"CustomIdentifier": "1",
"Path": "Company/",
"Class": null,
"HasDatasetProperties": true,
"HasHistoryProperties": false,
"Count": 42
}
And we can request all the dataset properties for the asset using the associated navigation property:
GET http://localhost/ODataConnector/odata/Assets(1)/DatasetProperties
{
"@odata.context": "http://localhost/ODataConnector/odata/$metadata#Collection(Model.DatasetProperty)",
"value": [
{
"Id": 148,
"ParentId": 1,
"Name": "Orders"
}
]
}
There is a custom entity-bound function called GetParameters() in the OData Connector entity model that allows to retrieve the list of parameters for a dataset property:
GET http://localhost/ODataConnector/odata/DatasetProperties(148)/Model.GetParameters()
{
"@odata.context": "http://localhost/ODataConnector/odata/$metadata#Collection(Model.DatasetParameter)",
"value": [
{
"Name": "customerid",
"Value": "ALFKI"
}
]
}
Another function called GetData(parameters) can be invoked to retrieve the dataset data. parameters must be a JSON array containing all the required parameter values (the list of which can be retrieved using the GetParameters function above):
{
"@odata.context": "http://localhost/ODataConnector/odata/$metadata#Collection(Model.DatasetRow)",
"value": [
{
"OrderID": 10308,
"CustomerID": "ANATR",
"EmployeeID": 7,
"OrderDate": "1996-09-18T00:00:00-04:00",
"RequiredDate": "1996-10-16T00:00:00-04:00",
"ShippedDate": "1996-09-24T00:00:00-04:00",
"ShipVia": 3,
"Freight": 1.61,
"ShipName": "Ana Trujillo Emparedados y helados",
"ShipAddress": "Avda. de la Constitución 2222",
"ShipCity": "México D.F.",
"ShipPostalCode": "05021",
"ShipCountry": "Mexico"
},
...
{
"OrderID": 10926,
"CustomerID": "ANATR",
"EmployeeID": 4,
"OrderDate": "1998-03-04T00:00:00-05:00",
"RequiredDate": "1998-04-01T00:00:00-05:00",
"ShippedDate": "1998-03-11T00:00:00-05:00",
"ShipVia": 3,
"Freight": 39.92,
"ShipName": "Ana Trujillo Emparedados y helados",
"ShipAddress": "Avda. de la Constitución 2222",
"ShipCity": "México D.F.",
"ShipPostalCode": "05021",
"ShipCountry": "Mexico"
}
]
}
First, let’s create an Equipment Property that is connected to n historical tag. Make sure that ICONICS Hyper Historian Logger is started, then open Workbench and navigate to Assets > Equipment > Company, right-click and select “Add Equipment Property”. Name the property SineHistory, switch to the Historical Data tab and connect the Historical data tag to:
hh:\Configuration\Signals:Sine
After restarting the ICONICS OData Connector Point Manager we can verify that the Company asset now has history properties:
GET http://localhost/ODataConnector/odata/Assets(1)
{
"@odata.context": "http://localhost/ODataConnector/odata/$metadata#Assets/$entity",
"Id": 1,
"ParentId": null,
"Name": "Company",
"DisplayName": "",
"CustomIdentifier": "1",
"Path": "Company/",
"Class": null,
"HasDatasetProperties": true,
"HasHistoryProperties": true,
"Count": 42
}
And we can request all the history properties for the asset using the associated navigation property:
GET http://localhost/ODataConnector/odata/Assets(1)/HistoryProperties
{
"@odata.context": "http://localhost/ODataConnector/odata/$metadata#Collection(Model.HistoryProperty)",
"value": [
{
"Id": 149,
"ParentId": 1,
"Name": "SineHistory"
}
]
}
There are two custom entity-bound functions to the HistoryProperty entity class:
GetRawData(startTime, endTime) – retrieves the raw data for the property for the specified interval
GetAggregatedData(startTime, endTime, interval, aggregate) – retrieves aggregated data for the property for the specified interval
Requesting raw data:
{
"@odata.context": "http://localhost/ODataConnector/odata/$metadata#Collection(Model.DataValue)",
"value": [
{
"Quality": 0,
"Timestamp": "2018-05-25T16:15:00.228-04:00",
"Value": 0.0033172470977902435
},
...
{
"Quality": 0,
"Timestamp": "2018-05-25T16:19:59.978-04:00",
"Value": 0.035526368222335236
}
]
}
Requesting aggregated data:
{
"@odata.context": "http://localhost/ODataConnector/odata/$metadata#Collection(Model.DataValue)",
"value": [
{
"Quality": 0,
"Timestamp": "2018-05-25T16:15:00.001-04:00",
"Value": 0.032563129078877096
},
{
"Quality": 0,
"Timestamp": "2018-05-25T16:16:00.001-04:00",
"Value": 0.032563129078877096
},
{
"Quality": 0,
"Timestamp": "2018-05-25T16:17:00.001-04:00",
"Value": 0.03257493481251637
},
{
"Quality": 0,
"Timestamp": "2018-05-25T16:18:00.001-04:00",
"Value": 0.032563129078877096
},
{
"Quality": 0,
"Timestamp": "2018-05-25T16:19:00.001-04:00",
"Value": 0.032563129078877096
}
]
}
NOTE: the interval parameter is a timespan, and by OData protocol conventions it needs to be passed in the format:
duration'PdDThHmMsS'
Where:
· d represents the number of days in the interval
· h represents the number of hours in the interval
· m represents the number of minutes in the interval
· s represents the number of seconds in the interval
The example above used a 1-minute interval, which is represented as P0DT0H1M0S.
This section explains all the OData Connector parameters that can be configured via the Platform Services Configuration utility.
EndpointMachineName. Sets the machine name for the OData Connector endpoint. See the Endpoint Protocol section of this document for more details.
EndpointPort. Sets the port for the OData Connector endpoint. See the Endpoint Protocol section of this document for more details.
EndpointProtocol. Sets the protocol for the OData Connector endpoint, supported protocols are either http or https. See the Endpoint Protocol section of this document for more details.
RequireAuthentication. When set to true, the OData Connector will start requesting authentication credentials to access the endpoints. Authentication is implemented as HTTP basic access authentication. When authentication is enabled, the OData Connector validates the received credentials against the ICONICS Security Server: if the credentials are valid, the request is processed - if not, the client receives an HTTP status code of 401 – Unauthorized. Please see the security section in this document for more details.
Impersonate. When set to false (the default value) the OData Connector will request data from FrameWorX under the default security group or, if configured, the ODATA -> FWX user account. When Impersonate is set to true, the OData Connector will request data from FrameWorX by logging in with Security using the credentials passed by the client. This requires RequireAuthentication to be set to true as well.
DataRetrievalTimeoutSecs. Sets the timeout to apply to real-time read operations from the OData Connector.
EnableCrossOriginResourceSharing. Configures whether to enable Cross Origin Resource Sharing. CORS should be enabled in order to consume REST and OData endpoints from applications not running on the same machine as the OData Connector.
AllowedCrossOriginResourceSharingDomains. When CORS is enabled this parameter configures which domains are allowed for Cross Origin calls. Domains must be semicolon separated.
EnableErrorDetails. When enabled, errors reported by the OData Connector to clients will include details and stack trace of the exception occurred on the server.
EnableHelp. Enables help pages for the OData Connector (located at http://localhost/OdataConnector/rest/Help/Html?method=get and http://localhost/OdataConnector/rest/Help/Html?method=post)
GCCollectScheduleMinutesAfterMidnight. Instructs the server to manually run a forced Garbage Collection with Large Object Heap compaction. When set to a negative value (default) this feature is disabled. A value greater than or equal to 0 specifies the number of minutes after local midnight to wait before scheduling the garbage collection. This forced garbage collection should not be necessary for most systems as .NET Framework should be managing the memory usage and releasing the memory as needed over time. However, some systems may want to use this daily check to prevent the perception that the OData Connector is leaking memory or to better manage a high-memory system.
WriteTimeoutMsec. Sets the timeout to apply to real-time write operations from the OData Connector.
MaxReceivedMessageSizeBytes. Defines the maximum received message size, in bytes.
EnableODataEndpoint. This parameter enables or disables the OData endpoint. When enabled, metadata for the endpoint is available at http://localhost/ODataConnector/odata/$metadata
ODataEndpointCustomPrefix. This parameter allows to specify a custom prefix for the OData endpoint. For example, if it were set to “v1”, the metadata for OData endpoint would be available at http://localhost/ODataConnector/odata/v1/$metadata
ODataConfigurationLoadTimeoutMins. For performance reasons, the OData Connector loads the OData-enabled assets in the AssetWorX hierarchy in memory during startup and keeps them cached: changes in the AssetWorX structure are not reflected in the OData Connector until the OData Connector service is restarted. This parameter determines the timeout for loading the AssetWorX structure in memory.
ODataPropertiesRefreshPeriodMins. For performance reasons, Equipment Properties that have a real-time data source associated in the AssetWorX configuration have their value queried and cached by the OData Connector. This means that the OData Connector will expose a cached value for properties, and not the actual current value. This parameter determines how often the OData Connector re-queries the real-time data sources associated with the Equipment Properties to refresh the cached value. This polling behavior can be disabled via the ODataUseSubscriptionForPropertyValues parameter.
ODataUseSubscriptionForPropertyValues. When a periodic polling for Equipment Property values is not desirable (for example in cases where it is necessary to expose the actual value of an Equipment Property as quickly as possible through the OData interface) it is possible to configure the OData Connector to subscribe to all Equipment Properties with real-time data sources and that have the OData checkbox selected during startup, and to keep subscription for the lifetime of the OData Connector service. This subscription behavior is enabled when this property is set to true, and it disables the default periodic polling behavior.
ODataSubscriptionScanRateMsec. This parameter configures the scan rate used by the OData Connector when subscribing to Equipment Properties when ODataUseSubscriptionForPropertyValues is set to true.
·ODataLoadExtededAssetProperties. Configures whether extended properties of Equipment Properties (i.e.: EURangeMin, EuRangeMax, TimeZone, etc.) are loaded and exposed by the OData Connector.
ODataEnableExtendedPropertiesRefresh. Configures whether extended properties for Equipment Properties should refresh or not. This setting applies to both polled or subscribed refresh of Equipment Properties.
AsyncClientCacheSlidingTimeoutMins. Communication between the OData Connector and AssetWorX is performed – in a similar fashion as the regular REST interface – using FrameWorX Communication and in particular FwxAsyncClient. Since creating a connection and initializing security requires some roundtrips, this advanced parameter specifies how long an initialized FwxAsyncClient instance should be retained to reduce latency of subsequent calls.
EnableRequestTracing. Configures whether to enable or disable more detailed tracing for HTTP requests to the OData Connector. Request traces will be logged to TraceWorX along with normal trace logs.
MaxConcurrentRequests. Configures the maximum number of concurrent requests allowed. When left to the default of -1 it sets the maximum number of concurrent requests to 100 x number of CPU cores.
RealTimeWaitOnBadQualityDelaySecs. Allows to configure an interval for which to wait for other updates after a real-time read comes back with bad quality. If a good quality update comes within the configured interval, that value will be returned instead of the initial bad quality value.
SecurityStatusPollingPeriodMins. When RequireAuthentication is set to false (thus enabling anonymous access) the OData Connector periodically polls the security server to check if security has been enabled. If security is enabled in the security server, the OData Connector will automatically force RequireAuthentication.
See Also: