/// <param name="baseAddress">The OSIsoft Cloud Services HTTP endpoint.</param> /// <param name="accountId">The Guid account Id.</param> public IngressManagementClient(string baseAddress, string accountId, QiSecurityHandler qiSecurityHandler) { _accountId = accountId; qiSecurityHandler.InnerHandler = new HttpClientHandler(); _client = new HttpClient(qiSecurityHandler); _client.BaseAddress = new Uri(baseAddress); }
private static async Task MainAsync() { IConfigurationBuilder builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json"); IConfiguration configuration = builder.Build(); // ==== Client constants ==== var tenantId = configuration["TenantId"]; var namespaceId = configuration["NamespaceId"]; var address = configuration["Address"]; var resource = configuration["Resource"]; var clientId = configuration["ClientId"]; var clientKey = configuration["ClientKey"]; // ==== Metadata IDs ==== string streamId = "SampleStream"; string typeId = "SampleType"; string behaviorId = "SampleBehavior"; string targetTypeId = "SampleType_Target"; string targetIntTypeId = "SampleType_TargetInt"; string autoViewId = "SampleAutoView"; string manualViewId = "SampleManualView"; // Get Qi Services to communicate with server QiSecurityHandler securityHandler = new QiSecurityHandler(resource, tenantId, clientId, clientKey); QiService qiService = new QiService(new Uri(address), securityHandler); var metadataService = qiService.GetMetadataService(tenantId, namespaceId); var dataService = qiService.GetDataService(tenantId, namespaceId); LoggerCallbackHandler.UseDefaultLogging = false; Console.WriteLine(@"---------------------------------------------------"); Console.WriteLine(@"________ .__ _______ ______________________"); Console.WriteLine(@"\_____ \ |__| \ \ \_ _____/\__ ___/"); Console.WriteLine(@" / / \ \| | / | \ | __)_ | | "); Console.WriteLine(@"/ \_/. \ | / | \| \ | | "); Console.WriteLine(@"\_____\ \_/__| /\ \____|__ /_______ / |____| "); Console.WriteLine(@" \__> \/ \/ \/ "); Console.WriteLine(@"---------------------------------------------------"); Console.WriteLine(); Console.WriteLine($"Qi endpoint at {address}"); Console.WriteLine(); try { // create a QiType Console.WriteLine("Creating a QiType"); QiType type = QiTypeBuilder.CreateQiType <WaveData>(); type.Id = typeId; type = await metadataService.GetOrCreateTypeAsync(type); // create a QiStream Console.WriteLine("Creating a QiStream"); var stream = new QiStream { Id = streamId, Name = "Wave Data Sample", TypeId = type.Id, Description = "This is a sample QiStream for storing WaveData type measurements" }; stream = await metadataService.GetOrCreateStreamAsync(stream); // insert data Console.WriteLine("Inserting data"); // insert a single event var wave = GetWave(0, 200, 2); await dataService.InsertValueAsync(stream.Id, wave); // insert a list of events var waves = new List <WaveData>(); for (var i = 2; i <= 18; i += 2) { waves.Add(GetWave(i, 200, 2)); } await dataService.InsertValuesAsync(stream.Id, waves); // get last event Console.WriteLine("Getting latest event"); var latest = await dataService.GetLastValueAsync <WaveData>(streamId); Console.WriteLine(latest.ToString()); Console.WriteLine(); // get all events Console.WriteLine("Getting all events"); var allEvents = (List <WaveData>) await dataService.GetWindowValuesAsync <WaveData>(streamId, "0", "180"); Console.WriteLine($"Total events found: {allEvents.Count}"); foreach (var evnt in allEvents) { Console.WriteLine(evnt.ToString()); } Console.WriteLine(); // update events Console.WriteLine("Updating events"); // update one event var updatedWave = UpdateWave(allEvents.First(), 4); await dataService.UpdateValueAsync(stream.Id, updatedWave); // update all events, adding ten more var updatedCollection = new List <WaveData>(); for (int i = 2; i < 40; i = i + 2) { updatedCollection.Add(GetWave(i, 400, 4)); } await dataService.UpdateValuesAsync(stream.Id, updatedCollection); allEvents = (List <WaveData>) await dataService.GetWindowValuesAsync <WaveData>(stream.Id, "0", "180"); Console.WriteLine("Getting updated events"); Console.WriteLine($"Total events found: {allEvents.Count}"); foreach (var evnt in allEvents) { Console.WriteLine(evnt.ToString()); } Console.WriteLine(); // replacing events Console.WriteLine("Replacing events"); // replace one event var replaceEvent = allEvents.First(); replaceEvent.Sin = 0.717; replaceEvent.Cos = 0.717; replaceEvent.Tan = Math.Sqrt(2 * (0.717 * 0.717)); await dataService.ReplaceValueAsync <WaveData>(streamId, replaceEvent); // replace all events foreach (var evnt in allEvents) { evnt.Sin = 5.0 / 2; evnt.Cos = 5 * Math.Sqrt(3) / 2; evnt.Tan = 5 / Math.Sqrt(3); } await dataService.ReplaceValuesAsync <WaveData>(streamId, allEvents); Console.WriteLine("Getting replaced events"); var replacedEvents = (List <WaveData>) await dataService.GetWindowValuesAsync <WaveData>(streamId, "0", "180"); Console.WriteLine($"Total events found: {replacedEvents.Count}"); foreach (var evnt in replacedEvents) { Console.WriteLine(evnt.ToString()); } Console.WriteLine(); // Stream behaviors Console.WriteLine("QiStreamBehaviors determine whether Qi interpolates or extrapolates data at the requested index location"); Console.WriteLine(); // Stream behaviors modify retrieval. We will retrieve three events using the default behavior, Continuous var retrieved = await dataService .GetRangeValuesAsync <WaveData>(stream.Id, "1", 3, QiBoundaryType.ExactOrCalculated); Console.WriteLine("Default (Continuous) stream behavior, requesting data starting at index location '1', Qi will interpolate this value:"); foreach (var value in retrieved) { Console.WriteLine($"Order: {value.Order}, Radians: {value.Radians}"); } Console.WriteLine(); // create a Discrete stream behavior var behavior = new QiStreamBehavior { Id = behaviorId, Mode = QiStreamMode.Discrete }; behavior = await metadataService.GetOrCreateBehaviorAsync(behavior); // update the stream stream.BehaviorId = behavior.Id; await metadataService.CreateOrUpdateStreamAsync(stream); retrieved = await dataService .GetRangeValuesAsync <WaveData>(stream.Id, "1", 3, QiBoundaryType.ExactOrCalculated); Console.WriteLine("Discrete stream behavior, Qi does not interpolate and returns the data starting at the next index location containing data:"); foreach (var value in retrieved) { Console.WriteLine($"Order: {value.Order}, Radians: {value.Radians}"); } Console.WriteLine(); // Stream views Console.WriteLine("QiViews"); // create target types var targetType = QiTypeBuilder.CreateQiType <WaveDataTarget>(); targetType.Id = targetTypeId; var targetIntType = QiTypeBuilder.CreateQiType <WaveDataInteger>(); targetIntType.Id = targetIntTypeId; await metadataService.CreateOrUpdateTypeAsync(targetType); await metadataService.CreateOrUpdateTypeAsync(targetIntType); // create views var autoView = new QiView() { Id = autoViewId, SourceTypeId = typeId, TargetTypeId = targetTypeId }; // create explicit mappings var vp1 = new QiViewProperty() { SourceId = "Order", TargetId = "OrderTarget" }; var vp2 = new QiViewProperty() { SourceId = "Sin", TargetId = "SinInt" }; var vp3 = new QiViewProperty() { SourceId = "Cos", TargetId = "CosInt" }; var vp4 = new QiViewProperty() { SourceId = "Tan", TargetId = "TanInt" }; var manualView = new QiView() { Id = manualViewId, SourceTypeId = typeId, TargetTypeId = targetIntTypeId, Properties = new List <QiViewProperty>() { vp1, vp2, vp3, vp4 } }; await metadataService.CreateOrUpdateViewAsync(autoView); await metadataService.CreateOrUpdateViewAsync(manualView); Console.WriteLine("Here is some of our data as it is stored on the server:"); foreach (var evnt in retrieved) { Console.WriteLine($"Sin: {evnt.Sin}, Cos: {evnt.Cos}, Tan {evnt.Tan}"); } Console.WriteLine(); // get autoview data var autoViewData = await dataService.GetRangeValuesAsync <WaveDataTarget>(stream.Id, "1", 3, QiBoundaryType.ExactOrCalculated, autoViewId); Console.WriteLine("Specifying a view with a QiType of the same shape returns values that are automatically mapped to the target QiType's properties:"); foreach (var value in autoViewData) { Console.WriteLine($"SinTarget: {value.SinTarget} CosTarget: {value.CosTarget} TanTarget: {value.TanTarget}"); } Console.WriteLine(); // get manaulview data Console.WriteLine("QiViews can also convert certain types of data, here we return integers where the original values were doubles:"); var manualViewData = await dataService.GetRangeValuesAsync <WaveDataInteger>(stream.Id, "1", 3, QiBoundaryType.ExactOrCalculated, manualViewId); foreach (var value in manualViewData) { Console.WriteLine($"SinInt: {value.SinInt} CosInt: {value.CosInt} TanInt: {value.TanInt}"); } Console.WriteLine(); // get QiViewMap Console.WriteLine("We can query Qi to return the QiViewMap for our QiView, here is the one generated automatically:"); var autoViewMap = await metadataService.GetViewMapAsync(autoViewId); PrintViewMapProperties(autoViewMap); Console.WriteLine("Here is our explicit mapping, note QiViewMap will return all properties of the Source Type, even those without a corresponding Target property:"); var manualViewMap = await metadataService.GetViewMapAsync(manualViewId); PrintViewMapProperties(manualViewMap); // delete values Console.WriteLine("Deleting values from the QiStream"); // delete one event await dataService.RemoveValueAsync(stream.Id, 0); // delete all events await dataService.RemoveWindowValuesAsync(stream.Id, 1, 200); retrieved = await dataService.GetWindowValuesAsync <WaveData>(stream.Id, "0", "200"); if (retrieved.ToList <WaveData>().Count == 0) { Console.WriteLine("All values deleted successfully!"); } Console.WriteLine(); } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { Console.WriteLine("Cleaning up"); // Delete the stream, types, views and behavior Console.WriteLine("Deleteing stream"); await metadataService.DeleteStreamAsync(streamId); Console.WriteLine("Deleteing types"); await metadataService.DeleteTypeAsync(typeId); await metadataService.DeleteTypeAsync(targetTypeId); await metadataService.DeleteTypeAsync(targetIntTypeId); Console.WriteLine("Deleteing views"); await metadataService.DeleteViewAsync(autoViewId); await metadataService.DeleteViewAsync(manualViewId); Console.WriteLine("Deleteing behavior"); await metadataService.DeleteBehaviorAsync(behaviorId); Console.WriteLine("done"); Console.ReadKey(); } }
static int Main(string[] args) { var options = new Options(); var errors = new List <Error>(); var result = Parser.Default.ParseArguments <Options>(args); result.WithParsed(opts => options = opts).WithNotParsed(errs => errors = errs.ToList()); if (errors.Any()) { foreach (var error in errors) { Console.WriteLine(error.Tag); } return(1); } NameValueCollection appSettings = ConfigurationManager.AppSettings; string accountId = appSettings["accountId"]; string namespaceId = appSettings["namespaceId"]; string clusterAddress = appSettings["address"]; string ingressServiceUrl = clusterAddress + @"/api/omf"; // Use a client secret, retrieved from the OSIsoft Cloud Services portal for your account, to // create a SecurityHandler used to authenticate this app. string resource = appSettings["resource"]; string clientId = appSettings["clientId"]; string clientSecret = appSettings["clientSecret"]; var securityHandler = new QiSecurityHandler(resource, accountId, clientId, clientSecret); // Create a client to manage OSIsoft Cloud Services Ingress resources. using (var managementClient = new IngressManagementClient(clusterAddress, accountId, securityHandler)) { // Connect to a PI server and select PI points for which to move data to OCS. var piServerName = appSettings["PIDataArchive"]; var piServer = new PIServers()[piServerName]; var points = PIPoint.FindPIPoints(piServer, options.TagMask).ToList(); if (!points.Any()) { Console.WriteLine($"No PI points found matching the tagMask query!"); return(1); } // Create OCS data ingress objects. string publisherName = appSettings["publisherName"]; string topicName = appSettings["topicName"]; string subscriptionName = appSettings["subscriptionName"]; Console.WriteLine("Setting up OSIsoft Cloud Services OMF ingress objects."); string publisherId = managementClient.GetOrCreatePublisherAsync(publisherName).GetAwaiter().GetResult(); string producerToken = managementClient.GetOrCreateToken(publisherId).GetAwaiter().GetResult(); string topicId = managementClient.GetOrCreateTopic(topicName, publisherId).GetAwaiter().GetResult(); string subscriptionId = managementClient.GetOrCreateSubscription(subscriptionName, topicId, namespaceId).GetAwaiter().GetResult(); // Each PI point type will be written to an OSIsoft Cloud Services(OCS) QiStream. // The structure of each stream is defined by an OCS QiType. We create this QiType // by posting an OSIsoft Message Format(OMF) type message to OCS. // PI point value types need to translate to OCS QiTypes. We create a limited number // of QiTypes in OCS and then map PI point value types to those QiTypes. // A mapping between PI point value types and the Ids of the QiType that represents // them in OCS is shown below. Dictionary <OmfTypeCode, string> typeIdsByOmfType = new Dictionary <OmfTypeCode, string>(); typeIdsByOmfType.Add(OmfTypeCode.Number, "numberValueAndTimestamp"); typeIdsByOmfType.Add(OmfTypeCode.Integer, "integerValueAndTimestamp"); typeIdsByOmfType.Add(OmfTypeCode.String, "stringValueAndTimestamp"); typeIdsByOmfType.Add(OmfTypeCode.Time, "timeValueAndTimestamp"); typeIdsByOmfType.Add(OmfTypeCode.ByteArray, "byteArrayValueAndTimestamp"); using (var client = new IngressClient(ingressServiceUrl, producerToken) { UseCompression = true }) { // Create and send OMF Type messages. Console.WriteLine("Creating basic types in OCS to represent the format of PI points."); List <OmfType> types = GetOmfTypes(typeIdsByOmfType); var omfTypeMessageContent = new OmfTypeMessageContent() { Types = types }; client.SendMessageAsync(omfTypeMessageContent.ToByteArray(), MessageType.Type, MessageAction.Create).GetAwaiter().GetResult(); // Generate containers for each of the point with the correct OMF message type. List <OmfContainer> containers = GetOmfContainers(points, typeIdsByOmfType); if (options.WriteMode == Options.DataWriteMode.clearExistingData) { // Deleting the OMF container deletes the underlying QiStream and its data. Console.WriteLine("Deleting OMF containers corresponding to the selected PI points that existed before the sample was run."); var omfContainerMessageContent = new OmfContainerMessageContent() { Containers = containers }; client.SendMessageAsync(omfContainerMessageContent.ToByteArray(), MessageType.Container, MessageAction.Delete).GetAwaiter().GetResult(); } Console.WriteLine("Creating corresponding containers for the PI points whose data will be written to OCS."); // OSIsoft Cloud Services' OMF Ingress sets a size limit on the request accepted by its external endpoint. We may need to split, or chunk, // containers into multiple OMF messages sent to the endpoint. for (int chunkStartIndex = 0; chunkStartIndex < containers.Count; chunkStartIndex += MaxChunkSize) { int numberOfContainersToSendInThisChunk = Math.Min(containers.Count - chunkStartIndex, MaxChunkSize); var containersToSendInThisChunk = containers.GetRange(chunkStartIndex, numberOfContainersToSendInThisChunk).ToList(); var omfContainerMessageContent = new OmfContainerMessageContent() { Containers = containersToSendInThisChunk }; client.SendMessageAsync(omfContainerMessageContent.ToByteArray(), MessageType.Container, MessageAction.Create).GetAwaiter().GetResult(); } // Write data from each PI point to a QiStream. foreach (PIPoint point in points) { Console.WriteLine($"Writing PI point data for point {point.Name} to OCS."); string containerId = GetContainerId(point); AFValues values = point.RecordedValues(new AFTimeRange(options.StartTime, options.EndTime), AFBoundaryType.Inside, null, true); // OSIsoft Cloud Services' OMF Ingress sets a size limit on the request accepted by its external endpoint. We may need to split, or chunk, // events into multiple OMF messages sent to the endpoint. for (int chunkStartIndex = 0; chunkStartIndex < values.Count; chunkStartIndex += MaxChunkSize) { int numberOfEventsToReadForThisChunk = Math.Min(values.Count - chunkStartIndex, MaxChunkSize); // If there are multiple events at a single timestamp for the PI point, the most recently added event will be written to OCS. List <AFValue> distinctValuesInChunk = values.GetRange(chunkStartIndex, numberOfEventsToReadForThisChunk).GroupBy(value => value.Timestamp).Select(valuesAtTimestamp => valuesAtTimestamp.Last()).ToList(); List <PIData> piDataEvents = GetPIData(distinctValuesInChunk, ToOmfTypeCode(point.PointType)); OmfDataMessageContent omfDataMessageContent = new OmfDataMessageContent(containerId, piDataEvents); Console.WriteLine($"Sending PI point data from index {distinctValuesInChunk.First().Timestamp} to index {distinctValuesInChunk.Last().Timestamp} to OCS ({distinctValuesInChunk.Count} values)."); client.SendMessageAsync(omfDataMessageContent.ToByteArray(), MessageType.Data, MessageAction.Create).GetAwaiter().GetResult(); } } } // Delete OCS data ingress objects. if (options.DeleteIngressObjects) { Console.WriteLine($"Deleting subscription with Id {subscriptionId}."); managementClient.DeleteSubscription(subscriptionId).GetAwaiter().GetResult(); Console.WriteLine($"Deleting topic with Id {topicId}."); managementClient.DeleteTopicAsync(topicId).GetAwaiter().GetResult(); Console.WriteLine($"Deleting publisher with Id {publisherId}."); managementClient.DeletePublisherAsync(publisherId).GetAwaiter().GetResult(); } } return(0); }
/// <summary> /// This will return a QiSecurityHandler using the values held in Constants /// </summary> /// <returns></returns> protected static QiSecurityHandler GetQiSecurityHandler() { QiSecurityHandler qiSecurityHandler = new QiSecurityHandler(Constants.SecurityResource, Constants.TenantId, Constants.SecurityAppId, Constants.SecurityAppKey); return qiSecurityHandler; }