private void DoCount(WI_Count req) { var promise = req.Promise; try { Channel?ch = GetChannelOrNull(req.Variable); if (ch == null) { promise.SetResult(0); } else { Timestamp start = req.StartInclusive; Timestamp end = req.EndInclusive; QualityFilter filter = req.Filter; long res; if (start == Timestamp.Empty && end == Timestamp.Max && filter == QualityFilter.ExcludeNone) { res = ch.CountAll(); } else { res = ch.CountData(start, end, Map(filter)); } promise.SetResult(res); } } catch (Exception exp) { promise.SetException(exp); } }
public WI_Count(VariableRef variable, Timestamp startInclusive, Timestamp endInclusive, QualityFilter filter, TaskCompletionSource <long> promise) { Variable = variable; StartInclusive = startInclusive; EndInclusive = endInclusive; Filter = filter; Promise = promise; }
public WI_ReadRaw(VariableRef variable, Timestamp startInclusive, Timestamp endInclusive, int maxValues, BoundingMethod bounding, QualityFilter filter, TaskCompletionSource <List <VTTQ> > promise) { Variable = variable; StartInclusive = startInclusive; EndInclusive = endInclusive; MaxValues = maxValues; Bounding = bounding; Filter = filter; Promise = promise; }
private Timeseries.QualityFilter Map(QualityFilter f) { switch (f) { case QualityFilter.ExcludeNone: return(Timeseries.QualityFilter.ExcludeNone); case QualityFilter.ExcludeBad: return(Timeseries.QualityFilter.ExcludeBad); case QualityFilter.ExcludeNonGood: return(Timeseries.QualityFilter.ExcludeNonGood); } throw new Exception("Unknown quality filter"); }
public async Task <ReqResult> UiReq_LoadData(TimeRange timeRange) { LastTimeRange = timeRange; Timestamp tStart = timeRange.GetStart(); Timestamp tEnd = timeRange.GetEnd(); int maxDataPoints = configuration.PlotConfig.MaxDataPoints; var listHistories = new List <VTTQs>(); QualityFilter filter = configuration.PlotConfig.FilterByQuality; foreach (var variable in Variables) { try { VTTQs data = await Connection.HistorianReadRaw(variable, tStart, tEnd, maxDataPoints, BoundingMethod.CompressToN, filter); listHistories.Add(data); } catch (Exception) { listHistories.Add(new VTTQs()); } } IsLoaded = true; var(windowLeft, windowRight) = GetTimeWindow(timeRange, listHistories); dataRevision += 1; var res = MemoryManager.GetMemoryStream("LoadHistory"); try { using (var writer = new StreamWriter(res, Encoding.ASCII, 8 * 1024, leaveOpen: true)) { writer.Write("{ \"WindowLeft\": " + windowLeft.JavaTicks); writer.Write(", \"WindowRight\": " + windowRight.JavaTicks); writer.Write(", \"DataRevision\": " + dataRevision); writer.Write(", \"Data\": "); WriteUnifiedData(new JsonDataRecordArrayWriter(writer), listHistories); writer.Write('}'); } res.Seek(0, SeekOrigin.Begin); } catch (Exception) { res.Dispose(); throw; } return(new ReqResult(200, res)); }
public static List <ItemModel> ApplyFilter(List <IBaseFilter> filters, List <QualityModel> qualities) { var items = new ItemReadLogic().ReadItems(); foreach (var filter in filters) { items = filter.GetUnfilteredItems(items); } var qualityFilter = new QualityFilter(); qualityFilter.Qualities = qualities; items = qualityFilter.GetUnfilteredItems(items); return(items); }
public abstract long CountData(Timestamp startInclusive, Timestamp endInclusive, QualityFilter filter);
public abstract List <VTTQ> ReadData(Timestamp startInclusive, Timestamp endInclusive, int maxValues, BoundingMethod bounding, QualityFilter filter);
internal static QualityFilterHelper Make(QualityFilter filter) { return(new QualityFilterHelper(filter)); }
private QualityFilterHelper(QualityFilter filter) { IncludeAll = filter == QualityFilter.ExcludeNone; IncludeUncertain = filter != QualityFilter.ExcludeNonGood; }
public async Task <long> HistorianCount(VariableRef variable, Timestamp startInclusive, Timestamp endInclusive, QualityFilter filter) { HistoryDBWorker?worker = WorkerByVarRef(variable); if (worker == null) { throw new Exception("Failed to find DB worker for variable " + variable); } CheckExistingVariable(variable); return(await worker.Count(variable, startInclusive, endInclusive, filter)); }
public override long CountData(Timestamp startInclusive, Timestamp endInclusive, QualityFilter filter) { PreparedStatement stmt = stmtCountAllQuality; if (filter == QualityFilter.ExcludeBad) { stmt = stmtCountNonBad; } else if (filter == QualityFilter.ExcludeNonGood) { stmt = stmtCountGood; } stmt[0] = startInclusive.ToDateTime(); stmt[1] = endInclusive.ToDateTime(); return((long)stmt.ExecuteScalar()); }
public Task <long> Count(VariableRef variable, Timestamp startInclusive, Timestamp endInclusive, QualityFilter filter) { var promise = new TaskCompletionSource <long>(); if (CheckPrecondition(promise)) { queue.Post(new WI_Count(variable, startInclusive, endInclusive, filter, promise)); } return(promise.Task); }
public Task <List <VTTQ> > ReadRaw(VariableRef variable, Timestamp startInclusive, Timestamp endInclusive, int maxValues, BoundingMethod bounding, QualityFilter filter) { var promise = new TaskCompletionSource <List <VTTQ> >(); if (CheckPrecondition(promise)) { queue.Post(new WI_ReadRaw(variable, startInclusive, endInclusive, maxValues, bounding, filter, promise)); } return(promise.Task); }
public async Task <ReqResult> UiReq_DownloadFile( TimeRange timeRange, VariableRef[] variables, string[] variableNames, FileType fileType) { QualityFilter filter = configuration.PlotConfig.FilterByQuality; Timestamp tStart = timeRange.GetStart(); Timestamp tEnd = timeRange.GetEnd(); var listHistories = new List <VTTQs>(); foreach (var variable in variables) { try { const int ChunckSize = 5000; VTTQs data = await Connection.HistorianReadRaw(variable, tStart, tEnd, ChunckSize, BoundingMethod.TakeFirstN, filter); if (data.Count < ChunckSize) { listHistories.Add(data); } else { var buffer = new List <VTTQ>(data); do { Timestamp t = data[data.Count - 1].T.AddMillis(1); data = await Connection.HistorianReadRaw(variable, t, tEnd, ChunckSize, BoundingMethod.TakeFirstN, filter); buffer.AddRange(data); }while (data.Count == ChunckSize); listHistories.Add(buffer); } } catch (Exception) { listHistories.Add(new VTTQs()); } } var columns = new List <string>(); columns.Add("Time"); columns.AddRange(variableNames); var res = MemoryManager.GetMemoryStream("DownloadFile"); try { string contentType; switch (fileType) { case FileType.CSV: contentType = "text/csv"; using (var writer = new StreamWriter(res, Encoding.UTF8, 8 * 1024, leaveOpen: true)) { WriteUnifiedData(new CsvDataRecordArrayWriter(writer, columns, configuration.DataExport.CSV), listHistories); } break; case FileType.Spreadsheet: contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; using (var excel = new ExcelPackage(res)) { ExcelWorksheet sheet = excel.Workbook.Worksheets.Add("Data Export"); WriteUnifiedData(new ExcelDataRecordArrayWriter(sheet, columns, configuration.DataExport.Spreadsheet), listHistories); excel.Save(); } break; default: throw new Exception($"Unknown file type: {fileType}"); } res.Seek(0, SeekOrigin.Begin); return(new ReqResult(200, res, contentType)); } catch (Exception) { res.Dispose(); throw; } }
public override List <VTTQ> ReadData(Timestamp startInclusive, Timestamp endInclusive, int maxValues, BoundingMethod bounding, QualityFilter filter) { long N = CountData(startInclusive, endInclusive, filter); //double millis = (endInclusive - startInclusive).TotalMilliseconds + 1; //double avgInterval_MS = millis / N; //double requestInterval_MS = 60 * 1000; //double N_2 = N; //if (requestInterval_MS > avgInterval_MS) { // N_2 = N / (requestInterval_MS / avgInterval_MS); //} PreparedStatement statement; switch (bounding) { case BoundingMethod.TakeFirstN: statement = stmtRawFirstN; break; case BoundingMethod.TakeLastN: statement = stmtRawLastN; break; case BoundingMethod.CompressToN: if (N <= maxValues) { return(ReadData(startInclusive, endInclusive, maxValues, BoundingMethod.TakeFirstN, filter)); } else { return(ReadDataCompressed(startInclusive, endInclusive, maxValues, N, filter)); } default: throw new Exception($"Unknown BoundingMethod: {bounding}"); } statement[0] = startInclusive.JavaTicks; statement[1] = endInclusive.JavaTicks; statement[2] = maxValues; var filterHelper = QualityFilterHelper.Make(filter); int initSize = N < maxValues ? (int)N : maxValues; var res = new List <VTTQ>(initSize); using (var reader = statement.ExecuteReader()) { while (reader.Read()) { VTTQ vttq = ReadVTTQ(reader); if (filterHelper.Include(vttq.Q)) { res.Add(vttq); } } } if (bounding == BoundingMethod.TakeLastN) { res.Reverse(); } return(res); }
public override List <VTTQ> ReadData(Timestamp startInclusive, Timestamp endInclusive, int maxValues, BoundingMethod bounding, QualityFilter filter) { long N = CountData(startInclusive, endInclusive, filter); PreparedStatement statement; switch (bounding) { case BoundingMethod.TakeFirstN: statement = stmtRawFirstN; break; case BoundingMethod.TakeLastN: statement = stmtRawLastN; break; case BoundingMethod.CompressToN: if (N <= maxValues) { return(ReadData(startInclusive, endInclusive, maxValues, BoundingMethod.TakeFirstN, filter)); } else { return(ReadDataCompressed(startInclusive, endInclusive, maxValues, N, filter)); } default: throw new Exception($"Unknown BoundingMethod: {bounding}"); } statement[0] = startInclusive.ToDateTime(); statement[1] = endInclusive.ToDateTime(); statement[2] = maxValues; var filterHelper = QualityFilterHelper.Make(filter); int initSize = N < maxValues ? (int)N : maxValues; var res = new List <VTTQ>(initSize); using (var reader = statement.ExecuteReader()) { while (reader.Read()) { VTTQ vttq = ReadVTTQ(reader); if (filterHelper.Include(vttq.Q)) { res.Add(vttq); } } } if (bounding == BoundingMethod.TakeLastN) { res.Reverse(); } return(res); }
public override async Task <VTTQs> HistorianReadRaw(VariableRef variable, Timestamp startInclusive, Timestamp endInclusive, int maxValues, BoundingMethod bounding, QualityFilter filter = QualityFilter.ExcludeNone) { var request = MakeSessionRequest <HistorianReadRawReq>(); request.Variable = variable; request.StartInclusive = startInclusive; request.EndInclusive = endInclusive; request.MaxValues = maxValues; request.Bounding = bounding; request.Filter = filter; return(await Post <VTTQs>(request, binaryDeserializer : BinSeri.VTTQ_Serializer.Deserialize)); }
private List <VTTQ> ReadDataCompressed(Timestamp startInclusive, Timestamp endInclusive, int maxValues, long count, QualityFilter filter) { PreparedStatement statement = stmtRawFirst; statement[0] = startInclusive.ToDateTime(); statement[1] = endInclusive.ToDateTime(); var result = new List <VTTQ>(maxValues); int maxIntervals = maxValues / 3; int itemsPerInterval = (maxValues < 6) ? (int)count : (int)Math.Ceiling(((double)count) / maxIntervals); var buffer = new List <VTTQ_D>(itemsPerInterval); var filterHelper = QualityFilterHelper.Make(filter); using (var reader = statement.ExecuteReader()) { while (reader.Read()) { VTTQ x = ReadVTTQ(reader); if (!x.V.IsEmpty) { double?value = x.V.AsDouble(); if (value.HasValue && filterHelper.Include(x.Q)) { buffer.Add(new VTTQ_D(x, value.Value)); } } if (buffer.Count >= itemsPerInterval) { FlushBuffer(result, buffer, maxValues); } } } if (buffer.Count > 0) { FlushBuffer(result, buffer, maxValues); } return(result); }
public override Task <List <VTTQ> > HistorianReadRaw(VariableRef variable, Timestamp startInclusive, Timestamp endInclusive, int maxValues, BoundingMethod bounding, QualityFilter filter = QualityFilter.ExcludeNone) { throw new InvalidOperationException("HistorianReadRaw on closed connection"); }
public override async Task <long> HistorianCount(VariableRef variable, Timestamp startInclusive, Timestamp endInclusive, QualityFilter filter = QualityFilter.ExcludeNone) { var request = MakeSessionRequest <HistorianCountReq>(); request.Variable = variable; request.StartInclusive = startInclusive; request.EndInclusive = endInclusive; request.Filter = filter; return(await Post <long>(request)); }
public async Task <List <VTTQ> > HistorianReadRaw(VariableRef variable, Timestamp startInclusive, Timestamp endInclusive, int maxValues, BoundingMethod bounding, QualityFilter filter) { HistoryDBWorker?worker = WorkerByVarRef(variable); if (worker == null) { throw new Exception("Failed to find DB worker for variable " + variable); } CheckExistingVariable(variable); return(await worker.ReadRaw(variable, startInclusive, endInclusive, maxValues, bounding, filter)); }
public override async Task <ReqResult> OnUiRequestAsync(string command, DataValue parameters) { switch (command) { case "Init": { var para = parameters.Object <InitParams>() ?? throw new Exception("InitParams is null"); var(windowLeft, windowRight) = GetTimeWindow(para.TimeRange, new List <VTTQs>()); var res = new LoadHistoryResult(); res.Tabs = configuration.Tabs.Select(t => new TabRes() { Name = t.Name, MaxDataPoints = t.PlotConfig.MaxDataPoints, Variables = t.Items.Select(it => it.Variable).ToArray(), Options = MakeGraphOptions(t), Configuration = t }).ToArray(); res.WindowLeft = windowLeft.JavaTicks; res.WindowRight = windowRight.JavaTicks; tabStates = GetInitialTabStates(configuration); ObjectRef[] objects = configuration.Tabs.SelectMany(t => t.Items.Select(it => it.Variable.Object)).Distinct().ToArray(); ObjectInfos infos; try { infos = await Connection.GetObjectsByID(objects); } catch (Exception) { infos = new ObjectInfos(objects.Length); for (int i = 0; i < objects.Length; ++i) { ObjectRef obj = objects[i]; try { infos.Add(await Connection.GetObjectByID(obj)); } catch (Exception) { infos.Add(new ObjectInfo(obj, "???", "???")); } } } foreach (ObjectInfo info in infos) { var numericVariables = info.Variables.Where(IsNumericOrBool).Select(v => v.Name).ToArray(); res.ObjectMap[info.ID.ToEncodedString()] = new ObjInfo() { Name = info.Name, Variables = numericVariables }; } res.Modules = modules; await EnableEvents(configuration); return(ReqResult.OK(res)); } case "LoadTabData": { var para = parameters.Object <LoadHistoryParams>() ?? throw new Exception("LoadHistoryParams is null"); TabState tabState = tabStates.FirstOrDefault(ts => ts.TabName == para.TabName); if (tabState == null) { throw new Exception($"Failed to load history data: tab '{para.TabName}' not found"); } tabState.LastTimeRange = para.TimeRange; Timestamp tStart = para.TimeRange.GetStart(); Timestamp tEnd = para.TimeRange.GetEnd(); TabConfig tabConfig = configuration.Tabs.First(t => t.Name == para.TabName); QualityFilter filter = tabConfig.PlotConfig.FilterByQuality; var listHistories = new List <VTTQs>(); foreach (var variable in para.Variables) { try { VTTQs data = await Connection.HistorianReadRaw(variable, tStart, tEnd, para.MaxDataPoints, BoundingMethod.CompressToN, filter); listHistories.Add(data); } catch (Exception) { listHistories.Add(new VTTQs()); } } tabState.IsLoaded = true; var(windowLeft, windowRight) = GetTimeWindow(para.TimeRange, listHistories); var res = MemoryManager.GetMemoryStream("LoadHistory"); try { using (var writer = new StreamWriter(res, Encoding.ASCII, 8 * 1024, leaveOpen: true)) { writer.Write("{ \"WindowLeft\": " + windowLeft.JavaTicks); writer.Write(", \"WindowRight\": " + windowRight.JavaTicks); writer.Write(", \"Data\": "); WriteUnifiedData(new JsonDataRecordArrayWriter(writer), listHistories); writer.Write('}'); } res.Seek(0, SeekOrigin.Begin); } catch (Exception) { res.Dispose(); throw; } return(new ReqResult(200, res)); } case "DownloadFile": { var para = parameters.Object <DownloadDataFileParams>() ?? throw new Exception("DownloadDataFileParams is null"); TabConfig tabConfig = configuration.Tabs.First(t => t.Name == para.TabName); QualityFilter filter = tabConfig.PlotConfig.FilterByQuality; Timestamp tStart = para.TimeRange.GetStart(); Timestamp tEnd = para.TimeRange.GetEnd(); var listHistories = new List <VTTQs>(); foreach (var variable in para.Variables) { try { const int ChunckSize = 5000; VTTQs data = await Connection.HistorianReadRaw(variable, tStart, tEnd, ChunckSize, BoundingMethod.TakeFirstN, filter); if (data.Count < ChunckSize) { listHistories.Add(data); } else { var buffer = new VTTQs(data); do { Timestamp t = data[data.Count - 1].T.AddMillis(1); data = await Connection.HistorianReadRaw(variable, t, tEnd, ChunckSize, BoundingMethod.TakeFirstN, filter); buffer.AddRange(data); }while (data.Count == ChunckSize); listHistories.Add(buffer); } } catch (Exception) { listHistories.Add(new VTTQs()); } } var columns = new List <string>(); columns.Add("Time"); columns.AddRange(para.VariableNames); var res = MemoryManager.GetMemoryStream("DownloadFile"); try { string contentType; switch (para.FileType) { case FileType.CSV: contentType = "text/csv"; using (var writer = new StreamWriter(res, Encoding.UTF8, 8 * 1024, leaveOpen: true)) { WriteUnifiedData(new CsvDataRecordArrayWriter(writer, columns, configuration.DataExport.CSV), listHistories); } break; case FileType.Spreadsheet: contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; using (var excel = new ExcelPackage(res)) { ExcelWorksheet sheet = excel.Workbook.Worksheets.Add("Data Export"); WriteUnifiedData(new ExcelDataRecordArrayWriter(sheet, columns, configuration.DataExport.Spreadsheet), listHistories); excel.Save(); } break; default: throw new Exception($"Unknown file type: {para.FileType}"); } res.Seek(0, SeekOrigin.Begin); return(new ReqResult(200, res, contentType)); } catch (Exception) { res.Dispose(); throw; } } case "SaveItems": { var para = parameters.Object <SaveItemsParams>() ?? throw new Exception("SaveItemsParams is null"); TabState tabState = tabStates.First(ts => ts.TabName == para.TabName); VariableRef[] newVariables = para.Items.Select(it => it.Variable).ToArray(); bool reloadData = !Arrays.Equals(newVariables, tabState.Variables); tabState.Variables = newVariables; TabConfig tabConfig = configuration.Tabs.FirstOrDefault(t => t.Name == para.TabName); tabConfig.Items = para.Items; DataValue newConfig = ConfigToIndentedDataValue(configuration); await Context.SaveViewConfiguration(newConfig); if (reloadData) { await EnableEvents(configuration); } return(ReqResult.OK(new { Options = MakeGraphOptions(tabConfig), Variables = newVariables, Configuration = tabConfig, ReloadData = reloadData })); } case "SavePlot": { var para = parameters.Object <SavePlotParams>() ?? throw new Exception("SavePlotParams is null"); TabConfig tabConfig = configuration.Tabs.First(t => t.Name == para.TabName); bool reloadData = tabConfig.PlotConfig.MaxDataPoints != para.Plot.MaxDataPoints || tabConfig.PlotConfig.FilterByQuality != para.Plot.FilterByQuality || tabConfig.PlotConfig.LeftAxisLimitY != para.Plot.LeftAxisLimitY || tabConfig.PlotConfig.RightAxisLimitY != para.Plot.RightAxisLimitY; tabConfig.PlotConfig = para.Plot; DataValue newConfig = ConfigToIndentedDataValue(configuration); await Context.SaveViewConfiguration(newConfig); return(ReqResult.OK(new { Options = MakeGraphOptions(tabConfig), Configuration = tabConfig, ReloadData = reloadData })); } case "DeleteTab": { var para = parameters.Object <DeleteTabParams>() ?? throw new Exception("DeleteTabParams is null"); configuration.Tabs = configuration.Tabs.Where(t => t.Name != para.TabName).ToArray(); DataValue newConfig = ConfigToIndentedDataValue(configuration); await Context.SaveViewConfiguration(newConfig); return(ReqResult.OK("")); } case "RenameTab": { var para = parameters.Object <RenameTabParams>() ?? throw new Exception("RenameTabParams is null"); if (para.NewName != para.TabName && configuration.Tabs.Any(t => t.Name == para.NewName)) { throw new Exception("Tab name already exists!"); } TabConfig tabConfig = configuration.Tabs.First(t => t.Name == para.TabName); tabConfig.Name = para.NewName; TabState tabState = tabStates.First(ts => ts.TabName == para.TabName); tabState.TabName = para.NewName; DataValue newConfig = ConfigToIndentedDataValue(configuration); await Context.SaveViewConfiguration(newConfig); return(ReqResult.OK(new { Configuration = tabConfig })); } case "AddTab": { var para = parameters.Object <AddTabParams>() ?? throw new Exception("AddTabParams is null"); var(windowLeft, windowRight) = GetTimeWindow(para.TimeRange, new List <VTTQs>()); if (configuration.Tabs.Any(t => t.Name == para.NewName)) { throw new Exception("Tab name already exists!"); } TabConfig tabConfig = new TabConfig(); tabConfig.Name = para.NewName; var tabs = configuration.Tabs.ToList(); tabs.Add(tabConfig); configuration.Tabs = tabs.ToArray(); DataValue newConfig = ConfigToIndentedDataValue(configuration); await Context.SaveViewConfiguration(newConfig); var tabState = new TabState() { TabName = para.NewName, LastTimeRange = para.TimeRange }; tabStates.Add(tabState); return(ReqResult.OK(new { NewTab = new TabRes() { Name = tabConfig.Name, MaxDataPoints = tabConfig.PlotConfig.MaxDataPoints, Options = MakeGraphOptions(tabConfig), Configuration = tabConfig }, WindowLeft = windowLeft.JavaTicks, WindowRight = windowRight.JavaTicks })); } case "MoveLeft": { var para = parameters.Object <MoveTabParams>() ?? throw new Exception("MoveTabParams is null"); int i = configuration.Tabs.ToList().FindIndex(t => t.Name == para.TabName); if (i <= 0) { throw new Exception("Can't move left"); } var tmp = configuration.Tabs[i]; configuration.Tabs[i] = configuration.Tabs[i - 1]; configuration.Tabs[i - 1] = tmp; DataValue newConfig = ConfigToIndentedDataValue(configuration); await Context.SaveViewConfiguration(newConfig); return(ReqResult.OK("")); } case "MoveRight": { var para = parameters.Object <MoveTabParams>() ?? throw new Exception("MoveTabParams is null"); int i = configuration.Tabs.ToList().FindIndex(t => t.Name == para.TabName); if (i >= configuration.Tabs.Length - 1) { throw new Exception("Can't move right"); } var tmp = configuration.Tabs[i]; configuration.Tabs[i] = configuration.Tabs[i + 1]; configuration.Tabs[i + 1] = tmp; DataValue newConfig = ConfigToIndentedDataValue(configuration); await Context.SaveViewConfiguration(newConfig); return(ReqResult.OK("")); } case "ReadModuleObjects": { var pars = parameters.Object <ReadModuleObjectsParams>() ?? throw new Exception("ReadModuleObjectsParams is null"); ObjectInfos objects; try { objects = await Connection.GetAllObjects(pars.ModuleID); } catch (Exception) { objects = new ObjectInfos(); } return(ReqResult.OK(new { Items = objects.Where(o => o.Variables.Any(IsNumericOrBool)).Select(o => new Obj() { Type = o.ClassName, ID = o.ID.ToEncodedString(), Name = o.Name, Variables = o.Variables.Where(IsNumericOrBool).Select(v => v.Name).ToArray() }).ToArray() })); } default: return(ReqResult.Bad("Unknown command: " + command)); } }
public override Task <long> HistorianCount(VariableRef variable, Timestamp startInclusive, Timestamp endInclusive, QualityFilter filter = QualityFilter.ExcludeNone) { throw new InvalidOperationException("HistorianCount on closed connection"); }