Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
en:sw:01-mervis:03-mervis-db [2021/05/18 11:05]
avsetula
en:sw:01-mervis:03-mervis-db [2024/04/03 10:40] (current)
avsetula [The service is available in cloud and local variants]
Line 1: Line 1:
 ====== Mervis DB ====== ====== Mervis DB ======
-The Mervis DB is software for a long-term saving of data from the PLC. It is available as a cloud service and also as an on-premises installation+{{ cs:sw:01-mervis:​mervis_db.png?​nolink&​400 |}}
  
-The main advantage ​is when it comes to analysis ​of the historical ​data using [[en:​sw:​01-mervis:​05-mervis-scada|Mervis SCADA]].+Mervis DB is a highly optimized database designed for long-term storage ​of data from units, applications and many other devices that use an open API database.
  
-To view your data you can alternatively use the online Viewer: ​[[https://​db.unipi.technology/​viewer]]+For Unipi units is the main use as storage of historical data and their analysis using [[en:sw:​01-mervis:​05-mervis-scada|Mervis SCADA]].
  
 +===== The service is available in cloud and local variants =====
 +
 +<​html><​span class="​kbBlue-H5alt">​Part of Mervis SCADA (cloud)</​span></​html>​\\
 +The standard way of using Mervis DB is the cloud service variant. The Mervis DB credentials are generated **together with the Mervis SCADA project** and are intended for all PLCs connected to this SCADA project.
 +
 +To use Mervis DB together with Mervis SCADA, follow the instructions [[en:​sw:​01-mervis:​mervis-scada-project-hidden|]].
 +
 +----
 +
 +<​html><​span class="​kbBlue-H5alt">​Only Mervis DB (cloud)</​span></​html>​\\
 +The Mervis DB cloud service can also be operated **separately**,​ without Mervis SCADA. In this case, credentials are generated individually and their validity must be agreed in advance.
 +
 +<WRAP center round box 95%>
 +If you are only interested in the Mervis DB, please [[https://​www.unipi.technology/​contacts|contact our sales department]] for an individual offer.
 +</​WRAP>​
 +
 +
 +Instructions for setting up a unit and saving data to the Mervis DB can be found in the manual: [[en:​sw:​01-mervis:​storing-data-to-mervis-db-hidden|]]
 +
 +----
 +
 +<​html><​span class="​kbBlue-H5alt">​Local installation option</​span></​html>​\\
 +The Mervis DB service can also run on a local server. For more information contact our [[[email protected]|Sales Department]].
 +
 +
 +===== Data migration and management =====
 +As part of the paid service, we also offer several options of data management:
 +   * Transfer of data between Mervis DB accounts, including possible timestamp editing
 +   * Import data from Excel or CSV file
 +   * Export data to Excel or CSV file
 +   * Delete unwanted data from Mervis DB
 +
 +If you are interested in any of the above mentioned services, contact our [[[email protected]|Sales Department]].
 +
 +\\
 +{{ en:​sw:​01-mervis:​migrace_mervis-db_en.png?​direct |}}
 +
 +
 +===== Mervis DB Viewer =====
 +The Mervis DB Viewer can be used to view stored data from a cloud instalation of Mervis DB on Unipi servers. It is an online application that connects to the database and displays database data according to the specified parameters (e.g. time range).
 +
 +
 +URL address: [[https://​db.unipi.technology/​viewer]].
 +
 +{{ cs:​sw:​01-mervis:​db-mervis-info-viewer.png?​direct |}}
 +
 +
 +===== API =====
 +Mervis DB supports open SOAP API described by this WSDL: 
 +
 +  * {{cs:​sw:​01-mervis:​mervisdb_wsdl-2017-07-12.zip|}}
 +
 +For special use, there are other interfaces for communication with Mervis DB - for their use and description,​ please contact [[en:​contact:​00-start|technical support]].
 +
 +
 +==== List of supported functions ====
 +
 +^ Function ​                                   ^ Description ​                                                                                                                                          ^
 +| SaveData ​                                   | Save one or multiple records from one or multiple variables ​                                                                                          |
 +| GetData/​GetDataAsync ​                       | Read one or multiple records from one or multiple variables. ​                                                                                         |
 +| GetParticularData/​GetParticulatDataAsync ​   | Optimized method for reading of „special“ data - last value, last value after certain time moment etc.                                                |
 +| GetVariables ​                               | Get the list of variables. ​                                                                                                                           |
 +| GetAllVariables ​                            ​| ​                                                                                                                                                      |
 +| ServerAlive ​                                | Obsolete ​                                                                                                                                             |
 +| DeleteVariables ​                            ​| ​                                                                                                                                                      |
 +| DeleteData ​                                 |                                                                                                                                                       |
 +| EnsureVariables ​                            ​| ​                                                                                                                                                      |
 +| GetTransformedData/​GetTransformedDataAsync ​ | Method for reading of aggregated results with given time period, the aggregation may be Min, Max, Integral, Weighted average, First, Last and Median ​ |
 +| GetServerVersion ​                           |                                                                                                                                                       |
 +| GetVariableStats ​                           | Obsolete ​                                                                                                                                             |
 +| GetVariableStatsAsync ​                      | Obsolete ​                                                                                                                                             |
 +| CheckCredentials ​                           |                                                                                                                                                       |
 +
 +
 +==== Object Properties ====
 +  * (UTC)TimeStamp - UTC time stamp of the record
 +  * GoodThrough - UTC time stamp of the end of validity of particular record
 +  * Interval - period of time (in seconds) when next value should be recorded
 +
 +
 +==== Examples ====
 +
 +=== C# ===
 +
 +{{ cs:​sw:​01-mervis:​mervis_db_csharp.png?​direct |}}
 +
 +<​html><​span class="​kbBlueText">​Download:</​span></​html>​ \\
 +  * {{cs:​sw:​01-mervis:​mervisdb_getdata_example.zip|}}
 +  * {{cs:​sw:​01-mervis:​mervisdb_savedata_example.zip|}} ​   ​
 +    ​
 +<​html><​span class="​kbBlueText">​Code example:</​span></​html>​ \\
 +
 +<code csharp| MervisDB_GetData_Example.cs>​
 +using System;
 +using System.Linq;​
 +using System.Threading.Tasks;​
 +using MervisDb_GetData_Example.MervisDbService;​
 + 
 +namespace MervisDb_GetData_Example {
 + class Program {
 +  static void Main(string[] args) {
 +   //
 +   // Demo of asynchronous methods.
 +   //
 +   var task = Task.Run(async() => {
 + //
 + // Create access to the real server.
 + // Without SSL connections,​ you have to remove binding parameters "<​security mode="​Transport"/>"​ in the App.config.
 + // The client is automatically generated from the WSDL file available here: https://​kb.mervis.info/​doku.php/​en:​mervis-db:​10-api
 + //
 + using(HistoryDbAccessClient client = new HistoryDbAccessClient("​HistoryAccess",​ "​http://​db.unipi.technology/​dbaccess"​)) {
 + // Authentication credetials in the database.
 + Credentials credentials = new Credentials {
 +   Name = "​XXXX",​ Password = "​YYYY"​
 + };
 + 
 + // Specification of the variables through Key-Value.
 + // Here we use 2 variables.
 + KeyValuePair[][] keys = new KeyValuePair[][] {
 +   new KeyValuePair[] {
 + new KeyValuePair {
 + Key = "​DPGuid",​ Value = "​338E2882-D72B-4D17-A241-73E8BC30F458"​
 + }, new KeyValuePair {
 + Key = "​StationName",​ Value = "​AAABBB"​
 + }
 +    },
 +    new KeyValuePair[] {
 + new KeyValuePair {
 + Key = "​DPGuid",​ Value = "​CC80211D-3D29-4CC2-91A2-F69483D566B5"​
 + }, new KeyValuePair {
 + Key = "​StationName",​ Value = "​AAABBB"​
 + }
 +    }
 + };
 + 
 + // From-To dates. The dates must be specified in the UTC time zone.
 + DateTime utcTo = DateTime.UtcNow;​
 + DateTime utcFrom = DateTime.UtcNow.AddDays(-1);​
 + 
 + // Retrieving the data goes through cycling of values and variables. The server returns recommended values needed for the the next cycle.
 + int variableOffset = 0; // the offset of the variable
 + int variableCount = 10; // maximal number of variables returned in one request
 + int valueOffset;​ // the offset of the value
 + int valueCount = 1000; // maximal values in on request
 + 
 + Console.WriteLine("​Reading values..."​);​
 + 
 + do {
 +   valueOffset = 0;
 +   do {
 +    // Execute the request.
 +    var result = await client.GetDataAsyncAsync(credentials,​ keys, utcFrom, utcTo, variableOffset,​ variableCount,​ valueOffset,​ valueCount);​
 + 
 +    // Check the return code. "​0;​OK"​ is what we want.
 +    if (!result.ReturnCode.StartsWith("​0;"​)) {
 + Console.WriteLine("​Error on reading: {0}", result.ReturnCode);​
 +    }
 + 
 +    // Cycle through the data and print it out.
 +    ​foreach(var valRecord in result.Data) {
 + Console.WriteLine("​Variable:​ {0}", String.Concat(valRecord.Keys.Select((i) => {
 + return String.Format("​{0}={1};​ ", i.Key, i.Value);
 + })));
 + 
 + foreach(var value in valRecord.Vals) {
 + Console.WriteLine("​{0}->​{1} [{2}] {3}", value.Ts, value.Gt, value.Hvt, value.Dv /* Use value type according to the Hvt (History Value Type) */ );
 + }
 +    }
 + 
 +    ​valueOffset = result.NextValueOffset;​
 +    ​variableOffset = result.NextVariableOffset;​
 +   }
 +   while (valueOffset != -1);
 + }
 + while (variableOffset != -1);
 + }
 +   });
 + 
 +   ​Console.WriteLine("​DB communication is running in background"​);​
 + 
 +   try {
 + task.Wait();​
 +   } catch (Exception exc) {
 + Console.WriteLine("​Exception:​ {0}", exc.ToString());​
 +   }
 + 
 +   ​Console.WriteLine("​DB communication finished"​);​
 + 
 +   ​Console.ReadLine();​
 +  }
 + }
 +}
 +</​code>​
 +
 +<code csharp| MervisDB_SaveData_Example.cs>​
 +using System;
 +using System.Collections.Generic;​
 +using System.Threading.Tasks;​
 +using MervisDb_SaveData_Example.MervisDbService;​
 + 
 +namespace MervisDb_SaveData_Example
 +{
 + class Program
 + {
 + static void Main(string[] args)
 + {
 + //
 + // Demo of asynchronous methods.
 + //
 + var task = Task.Run(async () =>
 + {
 + //
 + // Create access to the real server.
 + // Without SSL connections,​ you have to remove binding parameters "<​security mode="​Transport"/>"​ in the App.config.
 + // The client is automatically generated from the WSDL file available here: https://​kb.mervis.info/​doku.php/​en:​mervis-db:​10-api
 + //
 + using (HistoryDbAccessClient client = new HistoryDbAccessClient("​HistoryAccess",​ "​http://​db.unipi.technology/​dbaccess"​))
 + {
 + // Authentication credetials in the database.
 + Credentials credentials = new Credentials { Name = "​XXXX",​ Password = "​YYYY"​ };
 + 
 + // Adding records...
 + List<​ValueRecord>​ records = new List<​ValueRecord>​();​
 + 
 + DateTime utcTimeStamp = new DateTime(2018,​ 1, 1, 12, 0, 0, DateTimeKind.Utc);​
 + 
 + // Each value is stored in a ValueRecord structure
 + ValueRecord rec = new ValueRecord();​
 + 
 + rec.UtcTimeStamp = utcTimeStamp; ​               // time stamp of the value, UTC date time must be used
 + rec.HistoryValueType = HistoryValueType.Double;​ // type of the value
 + rec.DoubleValue = 23.4;                         // value itself
 + rec.Interval = 60.0;                            // sampling period in seconds. When the difference of timestamps of subsequent values if greater than 1.1 multiple of the interval, the data should be considered incomplete.
 + rec.Keys = new KeyValuePair[] //​ identification of the variable. At least one key-value pair must be "​IsKey"​ to identify a variable.
 + {
 + new KeyValuePair { IsKey = true, Key = "​Root",​ Value = "​Building A"},
 + new KeyValuePair { IsKey = true, Key = "​Level1",​ Value = "Floor 1"},
 + new KeyValuePair { IsKey = true, Key = "​Level2",​ Value = "Room 7"},
 + new KeyValuePair { IsKey = true, Key = "​Level3",​ Value = "​Temperature"​},​
 + new KeyValuePair { IsKey = false, Key = "​Description",​ Value = "​Temperature in the room 7"},
 + new KeyValuePair { IsKey = false, Key = "​Unit",​ Value = "​°C"​},​
 + };
 + 
 + records.Add(rec);​
 + 
 + // Save data method returns the number of stored records
 + int recordsSaved = await client.SaveDataAsync(credentials,​ records.ToArray());​
 + 
 + Console.WriteLine("​Records saved: {0}", recordsSaved);​
 + }
 + });
 + 
 + Console.WriteLine("​DB communication is running in background"​);​
 + 
 + try
 + {
 + task.Wait();​
 + }
 + catch (Exception exc)
 + {
 + Console.WriteLine("​Exception:​ {0}", exc.ToString());​
 + }
 + 
 + Console.WriteLine("​DB communication finished"​);​
 + 
 + Console.ReadLine();​
 + }
 + }
 +}
 +</​code>​
 +
 +
 +<code csharp| MervisDb_GetTransformedData_Example.cs>​
 +using System;
 +using System.Linq;​
 +using System.Threading.Tasks;​
 +using MervisDb_GetTransformedData_Example.MervisDbService;​
 + 
 +namespace MervisDb_GetTransformedData_Example
 +{
 + class Program
 + {
 + static void Main(string[] args)
 + {
 + //
 + // Demo of asynchronous method.
 + //
 + var task = Task.Run(async () =>
 + {
 + //
 + // Create access to the real server.
 + // Without SSL connections,​ you have to remove binding parameters "<​security mode="​Transport"/>"​ in the App.config.
 + // The client is automatically generated from the WSDL file available here: https://​kb.mervis.info/​doku.php/​en:​mervis-db:​10-api
 + //
 + using (HistoryDbAccessClient client = new HistoryDbAccessClient("​HistoryAccess",​ "​http://​db.unipi.technology/​dbaccess"​))
 + {
 + // Authentication credentials in the database.
 + Credentials credentials = new Credentials { Name = "​XXXXX",​ Password = "​YYYYY"​ };
 + 
 + // Specification of the variables through Key-Value.
 + // Here we use 2 variables.
 + KeyValuePair[][] variableKeys = new KeyValuePair[][] {
 + new KeyValuePair[] {
 + new KeyValuePair (false, /*Key*/ "​DPGuid",​ /*Value*/ "​338E2882-D72B-4D17-A241-73E8BC30F458"​),​
 + new KeyValuePair (false, /*Key*/ "​StationName",​ /*Value*/ "​AAABBB"​)
 + },
 + new KeyValuePair[] {
 + new KeyValuePair (false, /*Key*/ "​DPGuid",​ /*Value*/ "​CC80211D-3D29-4CC2-91A2-F69483D566B5"​),​
 + new KeyValuePair (false, /*Key*/ "​StationName",​ /*Value*/ "​AAABBB"​)
 + }
 + };
 + 
 + // Aggregation request
 + var aggregation = new AggregationRequest();​
 + aggregation.Types = new AggregationType[] { AggregationType.Last };
 + aggregation.Interpolation = InterpolationType.None;​
 + //​aggregation.MaxNeighborDistance = ..only for interpolation;​
 + aggregation.IsoPattern = new string[] { "​PT15M"​ }; // 15 min period (ISO 8601 duration pattern)
 + aggregation.From = new DateTime(2018,​ 11, 24, 0, 0, 0, 0, DateTimeKind.Unspecified);​
 + aggregation.To =   new DateTime(2018,​ 11, 25, 0, 0, 0, 0, DateTimeKind.Unspecified);​
 + aggregation.TimeZone = @"​Europe/​Prague";​
 + 
 + // Retrieving the data goes through cycling of intervals and variables. The server returns recommended intervals needed for the the next cycle.
 + int variableOffset = 0; // the offset of the variable
 + int variableCount = 10; // maximal number of variables returned in one request
 + int intervalOffset; ​      // the offset of the interval
 + int intervalCount = 1000; // maximal values in on request
 + 
 + Console.WriteLine("​Reading values..."​);​
 + 
 + do
 + {
 + intervalOffset = 0;
 + 
 + do
 + {
 + // Execute the request.
 + var result = await client.GetTransformedDataAsyncAsync(credentials,​ aggregation,​ variableKeys,​ variableOffset,​ variableCount,​ intervalOffset,​ intervalCount);​
 + 
 + // Check the return code. "​0;​OK"​ is what we want.
 + if (!result.ReturnCode.StartsWith("​0;"​))
 + {
 + Console.WriteLine("​Error on reading: {0}", result.ReturnCode);​
 + }
 + 
 + // Cycle through the data and print it out.
 + if (result.Data != null)
 + {
 + foreach (VariableAggregate vag in result.Data)
 + {
 + Console.WriteLine("​Variable:​ {0}", String.Concat(vag.Keys.Select((i) => { return String.Format("​{0}={1};​ ", i.Key, i.Value); })));
 + 
 + foreach (Interval value in vag.Data)
 + {
 + Console.Write("​ {0}->{1} quality: {2:0.00} ", value.Begin,​ value.End, value.DataQuality);​
 + 
 + if (value.Last != null)
 + {
 + Console.Write("​ Last Value: {0} (stamp {1}, origin {2})", value.Last.Value,​ value.Last.Stamp,​ value.Last.Origin);​
 + }
 + 
 + Console.WriteLine();​
 + }
 + }
 + }
 + 
 + variableOffset = result.NextVariableOffset;​
 + intervalOffset = result.NextIntervalOffset;​
 + 
 + } while (intervalOffset != -1);
 + 
 + } while (variableOffset != -1);
 + }
 + });
 + 
 + Console.WriteLine("​DB communication is running in background"​);​
 + 
 + try
 + {
 + task.Wait();​
 + }
 + catch (Exception exc)
 + {
 + Console.WriteLine("​Exception:​ {0}", exc.ToString());​
 + }
 + 
 + Console.WriteLine("​DB communication finished"​);​
 + 
 + Console.ReadLine();​
 + }
 + 
 + }
 +}
 +</​code>​
 +
 +
 +=== Matlab ===
 +
 +{{ cs:​sw:​01-mervis:​mervisdb_matlab_api.png?​direct |}}
 +
 +== Supported functions: ==
 +
 +^ Function ​              ^ Description ​                                                   ^
 +| SaveData ​              | Save one or multiple records from one or multiple variables. ​  |
 +| GetData ​               | Read one or multiple records from one or multiple variables. ​  |
 +| GetDataUnmerged ​       | Read one or multiple records from one or multiple variables. Allow more setting options than GetData function. |
 +| GetDataOrigin ​         | Read data exactly as are stored in the database. ​              |
 +| DeleteVariable ​        | Delete entire variable or some certain interval. ​              |
 +| GetRequiredTimestamp ​  | Get time stamp depending on input options. ​                    |
 +
 +
 +== Toolbox ==
 +You need to import specialized toolbox to communicate with Mervis DB from Matlab: ​
 +  * {{cs:​sw:​01-mervis:​mervisdb_matlabtoolbox.zip|}}
 +  * There is a help available for all toolbox functions. To display the help use: <code matlab>​help MervisDatabse.supported_function_name</​code> ​
 +
 +
 +== Code example ==
 +<code matlab| MervisDB_GetData_example.m>​
 +% Instance of Mervis DB 
 +mervisDB = MervisDatabase('​username',​ '​password',​ '​dbUrl'​);​
 + 
 +% Time interval definition
 +from = localMatlab2utcMatlab(datenum(2017,​ 1, 1)); %1.1.2017 in utc
 +to = localMatlab2utcMatlab(datenum(2017,​ 31, 1)); %31.1.2017 in utc
 +samplePeriod = 5*60; % 5 minutes
 +outputTimeZone = '​Local';​ % possible '​Local'​ or  '​Utc'​ parameter;
 + 
 +% Specifying varibles to download
 +varKeys1 = {'​DPGuid',​ '​338E2882-D72B-4D17-A241-73E8BC30F458';​...
 +            '​StationName',​ '​XXXXXXX'​};​ % Keys definition of first variable
 +varKeys2 = {'​DPGuid',​ '​CC80211D-3D29-4CC2-91A2-F69483D566B5';​...
 +            '​StationName',​ '​YYYYYYY'​};​ % Keys definition of second variable
 + 
 +variable1 = MervisDbVariable(varKeys1);​ % Create MervisDbVariable object from defined keys
 +variable2 = MervisDbVariable(varKeys2);​
 + 
 +arrayOfVarKeys = [variable1; variable2]; % array of MervisDbVariables objects
 + 
 +% help for function getData
 +help MervisDatabase.getData ​
 + 
 +% Download required data
 +[data, time] = mervisDB.getData(arrayOfVarKeys,​ from, to, samplePeriod,​ outputTimeZone);​
 + 
 +% Get all variables with defined keys
 +allVaribales = mervisDB.getAllVars(varKeys1);​
 +</​code>​
 +
 +=== PHP ===
 +
 +== Code example ==
 +<code php| MervisDB_GetData_example.php>​
 +<?php
 +/*
 +Mervis DB PHP example - getdata
 +*/
 +try {
 + 
 +  $soapClient = new SoapClient('​./​wsdl/​singlewsdl.xml',​ array(
 + '​compression'​ => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP,​
 + '​trace'​ => 0,
 + '​features'​ => SOAP_SINGLE_ELEMENT_ARRAYS
 +  ));
 +  $soapClient->​__setLocation('​http://​db.unipi.technology/​dbaccess'​);​
 + 
 +  $credentials = array(
 + '​Name'​ => '​XXXXXXX',​
 + '​Password'​ => '​YYYYYYY'​
 +  );
 + 
 +  $utcTZ ​  = new DateTimezone('​UTC'​);​ //all time realted values are expected in UTC
 +  $utcFrom = new DateTime('​-120minutes',​ $utcTZ); //
 +  $utcTo ​  = new DateTime('​now',​ $utcTZ); //
 + 
 +  $valOffset = 0;
 +  $valCount ​ = 20; //how many values should be returned in one request at most. Recommended value: 5000
 +  $varOffset = 0;
 +  $varCount ​ = 4; //how many variable should be returned in on request at most
 + 
 +  $variablesKey = array(
 + array(
 +   array(
 + '​IsKey'​ => true,
 + '​Key'​ => '​DPGuid',​
 + '​Value'​ => '​338E2882-D72B-4D17-A241-73E8BC30F458'​
 +   ),
 +   array(
 + '​IsKey'​ => true,
 + '​Key'​ => '​StationName',​
 + '​Value'​ => '​AAAAAAAAAA'​
 +   )
 + ),
 + array(
 +   array(
 + '​IsKey'​ => true,
 + '​Key'​ => '​DPGuid',​
 + '​Value'​ => '​CC80211D-3D29-4CC2-91A2-F69483D566B5'​
 +   ),
 +   array(
 + '​IsKey'​ => true,
 + '​Key'​ => '​StationName',​
 + '​Value'​ => '​BBBBBBB'​
 +   )
 + )
 +  );
 + 
 +  echo "​Reading values from: {$utcFrom->​format('​c'​)} to: {$utcTo->​format('​c'​)}\n\n";​
 + 
 +  $counter = 0;
 + 
 +  do {
 + $response ​ = null;
 + $valOffset = 0;
 + do {
 +   $response ​ = $soapClient->​GetData(array(
 + '​credentials'​ => $credentials,​
 + '​variablesKey'​ => $variablesKey,​
 + '​utcFrom'​ => $utcFrom->​format('​Y-m-d\TH:​i:​s\Z'​),​
 + '​utcTo'​ => $utcTo->​format('​Y-m-d\TH:​i:​s\Z'​),​
 + '​valueOffset'​ => $valOffset,
 + '​valueCount'​ => $valCount,
 + '​variableCount'​ => $varCount,
 + '​variableOffset'​ => $varOffset
 +   ));
 +   $valOffset = $response->​nextValueOffset;​
 +   $varOffset = $response->​nextVariableOffset;​
 + 
 +   foreach ($response->​GetDataResult->​Mvr as $varArray) {
 + foreach ($varArray->​Keys->​KeyValuePair as $kvp) //the set of keys depends on the source of the data
 +   {
 +   if ($kvp->​Key == '​DPName'​) {
 + echo "​\n{$kvp->​Value}\n";​
 + $counter++;​
 +   }
 + }
 + 
 + if (isset($varArray->​Vals->​I)) {
 +   foreach ($varArray->​Vals->​I as $val) {
 + echo " ​ t: {$val->​Ts} ​  ​Interval:​ {$val->​Ivl} ​ {$valOffset} {$varOffset}\n";​
 +   }
 + } else {
 +   echo "​empty\n";​
 + }
 + 
 +   }
 + 
 + } while ($response->​nextValueOffset != -1); //-1 - no more data available
 +  } while ($response->​nextVariableOffset != -1); //-1 - no more data available
 +}
 +catch (Exception $e) {
 +  print_r($e);​
 +}
 +?>
 +</​code>​