MDA - API pro podpůrné služby

Aplikace Mervis Data Terminal (MDT) a Mervis Data Concentrator (MDC) sdílejí jednotné API (MDA) pro poskytování přístupu k datům třetím stranám.

MDA je postavené na rámci gRPC a nabízí následující vlastnosti:

  • Používá HTTP/2 – přináší výhody jako multiplexing (více souběžných požadavků), server push, vyšší výkon a nižší latenci.
  • Serializace pomocí Protocol Buffers (Protobuf) – kompaktní binární formát, rychlejší a efektivnější než JSON nebo XML.
  • Podpora více jazyků – včetně C++, Java, Python, Go, C#, Node.js a dalších.
  • Generování klientského a serverového kódu ze souboru .proto – umožňuje snadné vytvoření a implementaci rozhraní bez opakovaného kódu.

Shrnutí toho, co tento kód provádí:

  • Připojí se k Mervis MDA serveru.
  • Ověří se pomocí tokenu.
  • Načte metadata (popisy) všech dostupných proměnných.
  • Volitelně:
    • Jednorázově načte všechny hodnoty.
    • Přihlásí se k odběru změn jedné hodnoty.
    • Zapíše dvě hodnoty zpět na server.
Program.cs
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using ESG.MervisDataApi.Shared;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using Grpc.Net.Client;
 
namespace SimpleDemo
{
 internal class Program
 {
  /// <summary>
  /// Authentication token used for secure access to the MervisDataApi.
  /// </summary>
  private static readonly Credentials m_Credentials = new Credentials
  {
   Token = new TokenCredentials { Token = "XXXXXXXXX" }
  };
 
  static async Task Main(string[] args)
  {
   // Create a gRPC channel to communicate with the MervisDataApi service
   using (GrpcChannel channel = GrpcChannel.ForAddress("https://mda_server:5555"))
   {
    var client = new MervisDataApiService.MervisDataApiServiceClient(channel);
 
    // Retrieve metadata (descriptions) for all available values
    GetValueDescriptionsResponse descriptionResponse = await RetrieveAllValueDescriptions(client);
 
    // Read current values for all available items once
    await ReadAllValuesOnce(client, descriptionResponse);
 
    const bool subscriptionDemo = true; // Toggle subscription demo
    const bool writeDemo = false;       // Toggle write demo
 
    if (subscriptionDemo)
    {
     // Subscribe to live updates for the first available value
     if (descriptionResponse.Descriptions.Count > 0)
     {
      await SubscribeAndRead(client, descriptionResponse.Descriptions[0]);
     }
    }
 
    if (writeDemo)
    {
     // Send (write) example values to the server
     await WriteValues(client);
    }
   }
  }
 
  /// <summary>
  /// Sends two hardcoded values to the API.
  /// </summary>
  private static async Task WriteValues(MervisDataApiService.MervisDataApiServiceClient client)
  {
   Console.WriteLine("-------------------------------");
   Console.WriteLine(nameof(WriteValues));
 
   var req = new SetValuesRequest { Credentials = m_Credentials };
 
   // Add first value to be written
   req.Values.Add(new MdaValue
   {
    Id = new ValueId { Native = "xxxx" },
    Quality = MdaValueQuality.Good,
    Float64 = 1,
    Timestamp = DateTime.UtcNow.ToTimestamp()
   });
 
   // Add second value to be written
   req.Values.Add(new MdaValue
   {
    Id = new ValueId { Native = "yyyy" },
    Quality = MdaValueQuality.Good,
    Float64 = 500,
    Timestamp = DateTime.UtcNow.ToTimestamp()
   });
 
   // Send request to the server
   var resp = await client.SetValuesAsync(req);
  }
 
  /// <summary>
  /// Subscribes to value updates from the API and prints them in real-time.
  /// </summary>
  private static async Task SubscribeAndRead(MervisDataApiService.MervisDataApiServiceClient client, MdaValueDescription whatToSubscribe)
  {
   Console.WriteLine("-------------------------------");
   Console.WriteLine(nameof(SubscribeAndRead));
 
   // Prepare filter to subscribe only to one specific value
   ValueIdFilter idFilter = new ValueIdFilter();
   idFilter.Ids.Add(new ValueId { Native = whatToSubscribe.Id.Native });
 
   // Start the subscription call to receive updates
   using (var readingCall = client.SubscribeValues(new SubscribeValuesRequest
   {
    Credentials = m_Credentials,
    Filter = new SelectionFilter { ById = idFilter }
   }))
   {
    // Read updates from the stream
    await foreach (var data in readingCall.ResponseStream.ReadAllAsync(CancellationToken.None))
    {
     foreach (var value in data.Values)
     {
      Console.WriteLine($"{value.Id.Native}: {GetValueText(value)} [{value.Timestamp.ToDateTime()}]");
     }
 
     await Task.Delay(100); // Wait briefly before continuing (helps readability)
    }
   }
  }
 
  /// <summary>
  /// Reads all values once and prints their latest data.
  /// </summary>
  private static async Task ReadAllValuesOnce(MervisDataApiService.MervisDataApiServiceClient client, GetValueDescriptionsResponse descriptions)
  {
   Console.WriteLine("-------------------------------");
   Console.WriteLine(nameof(ReadAllValuesOnce));
 
   ValueIdFilter idFilter = new ValueIdFilter();
 
   // Prepare a list of all IDs to request their values
   foreach (var desc in descriptions.Descriptions)
   {
    idFilter.Ids.Add(new ValueId { Native = desc.Id.Native });
   }
 
   // Send the request to get current values
   GetValuesResponse values = await client.GetValuesAsync(new GetValuesRequest
   {
    Credentials = m_Credentials,
    Filter = new SelectionFilter { ById = idFilter },
    ReturnIdKind = MdaValueIdKind.Native
   });
 
   Console.WriteLine("Values:");
   foreach (var value in values.Values)
   {
    Console.WriteLine($"{value.Id.Native}: {GetValueText(value)} [{value.Timestamp.ToDateTime()}]");
   }
 
   Console.WriteLine("Invalid IDs:");
   foreach (var invalidId in values.InvalidIds)
   {
    Console.WriteLine($"{invalidId.Native}");
   }
  }
 
  /// <summary>
  /// Converts the MdaValue to a string representation based on its type.
  /// </summary>
  private static string GetValueText(MdaValue value)
  {
   switch (value.ValueCase)
   {
    case MdaValue.ValueOneofCase.Float64: return XmlConvert.ToString(value.Float64);
    case MdaValue.ValueOneofCase.Boolean: return XmlConvert.ToString(value.Boolean);
    case MdaValue.ValueOneofCase.Text: return value.Text;
    default: return "N/A"; // Unknown or unsupported value type
   }
  }
 
  /// <summary>
  /// Retrieves metadata (descriptions) for all available variables.
  /// </summary>
  private static async Task<GetValueDescriptionsResponse> RetrieveAllValueDescriptions(MervisDataApiService.MervisDataApiServiceClient client)
  {
   Console.WriteLine("-------------------------------");
   Console.WriteLine(nameof(RetrieveAllValueDescriptions));
 
   // Create a path filter with an empty path (meaning: retrieve everything)
   PathFilter pf = new PathFilter();
   pf.Paths.Add(new Path()); // empty path = wildcard = all paths
 
   var request = new GetValueDescriptionsRequest
   {
    Credentials = m_Credentials,
    Filter = new SelectionFilter { ByPath = pf }
   };
 
   var descs = await client.GetValueDescriptionsAsync(request);
 
   // Print out the descriptions for inspection
   foreach (var desc in descs.Descriptions)
   {
    Console.WriteLine($"{desc.Id.Native} [{desc.Type}] {WritePath(desc.Paths.ElementAtOrDefault(0))}");
   }
 
   return descs;
  }
 
  /// <summary>
  /// Formats a `Path` object as a readable string.
  /// </summary>
  private static string WritePath(Path path)
  {
   if (path != null)
   {
    const char delimiter = '/';
    return delimiter + string.Join(delimiter, path.Segments);
   }
   else
   {
    return "N/A";
   }
  }
 }
}
  • © Energocentrum Plus, s.r.o. 2017 - 2025