public static void ProgrammaticAnalysisRecalculation(PISystem system, AFDatabase database, AFCategory category) { // we start by generating timestamps we want to recalculate // we could get them from existing recorded values, etc... // // here we simply generate yesterdays hourly values ... var recalculationTimeStamps = new List <AFTime>(); for (int i = 0; i < 24; i++) { recalculationTimeStamps.Add(new AFTime(DateTime.Today.Subtract(TimeSpan.FromDays(1)) + TimeSpan.FromDays(i) + TimeSpan.FromSeconds(2))); } AFNamedCollectionList <AFAnalysis> analysislist; analysislist = AFAnalysis.FindAnalyses(database, null, null, null, category, null, null, AFStatus.None, AFSortField.ID, AFSortOrder.Ascending, 0, 1000); Console.WriteLine(category.Name); Console.WriteLine(analysislist.Count); foreach (var afAnalysis in analysislist) { // we recalculate results var results = Calculate(afAnalysis, recalculationTimeStamps); // we could delete values here, but I simply replce them instead // we insert our new values AFListData.UpdateValues(results, AFUpdateOption.Replace); } }
private void WriteData() { List <AFValue> values; var allValues = new List <AFValue>(); // gets all currently available values from the queue while (SharedData.DataWriterQueue.TryDequeue(out values)) { if (values != null) { allValues.AddRange(values); } } // writes data only if there is data if (allValues.Count != 0) { _logger.InfoFormat("Sorting and writing {0} values.", allValues.Count); // writing into PI : we sort all the values per timestamp, that will make life easier for the PI Server allValues.Sort(); AFListData.UpdateValues(allValues, AFUpdateOption.NoReplace, AFBufferOption.BufferIfPossible); // clear the values allValues.Clear(); _logger.InfoFormat("Write completed. Waiting for new data."); } }
public void UpdateValues(AFValues cityValues) { AFErrors <AFValue> errors = AFListData.UpdateValues(cityValues, AFUpdateOption.NoReplace); if ((errors != null) && (errors.HasErrors)) { log.Error(errors.Errors.First().Value.Message); } }
public static void SwapValues(AFDatabase database, string meter1, string meter2, string startTime, string endTime) { Console.WriteLine(string.Format("Swap values for meters: {0}, {1} between {2} and {3}", meter1, meter2, startTime, endTime)); // NOTE: This method does not ensure that there is no data loss if there is failure. // Persist the data first in case you need to rollback. AFAttribute attr1 = AFAttribute.FindAttribute(@"\Meters\" + meter1 + @"|Energy Usage", database); AFAttribute attr2 = AFAttribute.FindAttribute(@"\Meters\" + meter2 + @"|Energy Usage", database); AFTime start = new AFTime(startTime); AFTime end = new AFTime(endTime); AFTimeRange timeRange = new AFTimeRange(start, end); // Get values to delete for meter1 AFValues valsToRemove1 = attr1.Data.RecordedValues( timeRange: timeRange, boundaryType: AFBoundaryType.Inside, desiredUOM: null, filterExpression: null, includeFilteredValues: false); // Get values to delete for meter2 AFValues valsToRemove2 = attr2.Data.RecordedValues( timeRange: timeRange, boundaryType: AFBoundaryType.Inside, desiredUOM: null, filterExpression: null, includeFilteredValues: false); if (valsToRemove1.Count == 0 && valsToRemove2.Count == 0) { throw new Exception("There are no values to swap."); } List <AFValue> valsToRemove = valsToRemove1.ToList(); valsToRemove.AddRange(valsToRemove2.ToList()); // Remove the values AFListData.UpdateValues(valsToRemove, AFUpdateOption.Remove); // Create new AFValues from the other meter and assign them to this meter List <AFValue> valsToAdd1 = valsToRemove2.Select(v => new AFValue(attr1, v.Value, v.Timestamp)).ToList(); List <AFValue> valsToAdd2 = valsToRemove1.Select(v => new AFValue(attr2, v.Value, v.Timestamp)).ToList(); List <AFValue> valsCombined = valsToAdd1; valsCombined.AddRange(valsToAdd2); AFListData.UpdateValues(valsCombined, AFUpdateOption.Insert); Console.WriteLine(); }
private void WriteData(CancellationToken cancelToken, int writingDelay, string resultsToFile) { List <AFValue> values; var allValues = new List <AFValue>(); while (true) { if (cancelToken.IsCancellationRequested) { return; } // gets currently available values from the queue while (DataQueue.TryDequeue(out values)) { allValues.AddRange(values); } // we go into writing data only if there is data if (allValues.Count != 0) { _logger.InfoFormat("Data Writer is sorting {0} values to be written", allValues.Count); _logger.InfoFormat("Data Writer values sorting completed. Now Writing..."); if (string.IsNullOrEmpty(resultsToFile)) { // writing into PI : we sort all the values per timestamp, that will make life easier for the PI Server allValues.Sort(); AFListData.UpdateValues(allValues, AFUpdateOption.Replace, AFBufferOption.BufferIfPossible); } else { // writing into a text file: we sort by tagname, then by timestamp. For readability. // This is a way to check calculation results var sortedvalues = allValues.OrderBy(a => a.PIPoint.Name).ThenBy(a => a.Timestamp).ToList(); File.WriteAllLines(resultsToFile + "_" + DateTime.Now.ToIsoReadable() + ".csv", sortedvalues.Select((v) => v.Timestamp.LocalTime.ToIsoReadable() + "," + v.Value + "," + v.Attribute.PIPoint.Name + "," + v.Attribute.Name)); } // after values are written, we dont need them anymore allValues.Clear(); _logger.InfoFormat("Writing completed, sleeping for now..."); } Thread.Sleep(TimeSpan.FromSeconds(writingDelay)); } }
public void Flush() { var sw = Stopwatch.StartNew(); int numValuesWritten = 0; AFErrors <AFValue> writeErrors = null; try { // Write results with "Replace" mode. Ideally you would first want to delete existing data and then publish new results, in order to make sure we don't end up with mixed old & new events. writeErrors = AFListData.UpdateValues(_values, AFUpdateOption.Replace); numValuesWritten += _values.Count; } catch (Exception ex) { Console.WriteLine("Failed to publish outputs using bulk call. {0}", ex.Message); return; } finally { _values.Clear(); } if (!ReferenceEquals(writeErrors, null) && writeErrors.HasErrors) { foreach (var afError in writeErrors.PISystemErrors) { Console.WriteLine("Error while writing outputs to AF Server {0}. {1}", afError.Key, afError.Value.Message); } foreach (var piError in writeErrors.PIServerErrors) { Console.WriteLine("Error while writing outputs to Data archive {0}. {1}", piError.Key, piError.Value.Message); } if (writeErrors.Errors.Count > 0) { numValuesWritten -= writeErrors.Errors.Count; Console.WriteLine("{0} Errors when publishing output results.", writeErrors.Errors.Count); foreach (var error in writeErrors.Errors) { Console.WriteLine("/t{0}: {1}", error.Key.Attribute.Name, error.Value.Message); } } } Console.WriteLine("Published {0} values. ({1} ms)", numValuesWritten, sw.ElapsedMilliseconds); }
public void Run() { PISystems piSystems = new PISystems(); PISystem piSystem = piSystems["<AFSERVER>"]; AFDatabase afDatabase = piSystem.Databases["Basic-AFSDK-Sample"]; AFElement boilerA = afDatabase.Elements["Region_0"].Elements["BoilerA"]; AFElementTemplate elementTemplate = afDatabase.ElementTemplates["BasicBoilerTemplate"]; AFAttributeTemplate temperatureAttrTemplate = elementTemplate.AttributeTemplates["Temperature"]; AFAttributeTemplate modeAttrTemplate = elementTemplate.AttributeTemplates["Mode"]; AFElement.LoadAttributes(new[] { boilerA }, new[] { temperatureAttrTemplate, modeAttrTemplate }); AFEnumerationSet digSet = afDatabase.EnumerationSets["Modes"]; IList <AFValue> valuesToWrite = new List <AFValue>(); for (int i = 0; i < 10; i++) { AFTime time = new AFTime(new DateTime(2015, 1, 1, i, 0, 0, DateTimeKind.Local)); AFValue afValueFloat = new AFValue(i, time); // Associate the AFValue to an attribute so we know where to write to. afValueFloat.Attribute = boilerA.Attributes["Temperature"]; AFEnumerationValue digSetValue = i % 2 == 0 ? digSet["Auto"] : digSet["Manual"]; AFValue afValueDigital = new AFValue(digSetValue, time); afValueDigital.Attribute = boilerA.Attributes["Mode"]; valuesToWrite.Add(afValueFloat); valuesToWrite.Add(afValueDigital); } // Perform a bulk write. Use a single local call to PI Buffer Subsystem if possible. // Otherwise, make a single call to the PI Data Archive. // We use no compression just so we can check all the values are written. // AFListData is the class that provides the bulk write method. AFListData.UpdateValues(valuesToWrite, AFUpdateOption.InsertNoCompression, AFBufferOption.BufferIfPossible); }