├── .gitignore ├── README.md └── Samples ├── V23 ├── Data Retrieval │ ├── .NET Client │ │ ├── App.config │ │ ├── ConnectionWrapper.cs │ │ ├── Instructions.txt │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── AssemblyInfo.cs │ │ └── Views API Example.csproj │ ├── Parquet Export │ │ ├── Binaries │ │ │ └── ParquetExport.zip │ │ └── Source │ │ │ ├── App.config │ │ │ ├── ParquetExport.csproj │ │ │ ├── ParquetExport.sln │ │ │ ├── Program.cs │ │ │ ├── Properties │ │ │ └── AssemblyInfo.cs │ │ │ ├── ReferenceAssemblies │ │ │ └── CanaryWebServiceHelper.dll │ │ │ └── packages.config │ ├── Powershell │ │ ├── DiagnosticTags.txt │ │ ├── ExportToCSV.ps1 │ │ ├── ExportToCSV2.ps1 │ │ └── ExportToCSV3.ps1 │ ├── Python 3 │ │ └── ExportToCSV.py │ ├── R │ │ └── ExportToCSV.R │ └── Web API │ │ ├── Javascript │ │ ├── data_retrieval_demo.html │ │ └── jquery.min.js │ │ └── data_retrieval_documentation.html └── Data Storage │ ├── .NET Client │ ├── Documentation │ │ └── Store & Forward API (& Helper).pdf │ ├── HelperClass.cs │ ├── HelperSession.cs │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── RawClient.cs │ ├── References │ │ └── SAF_Helper.dll │ ├── SAF_Examples.csproj │ └── app.config │ ├── Node-Red │ ├── ModbusClient │ │ ├── CanaryModbusStorage.json │ │ ├── CanaryModbusStorage.png │ │ └── README.txt │ └── ModbusClient_StoreAndForward │ │ ├── CanaryModbuStoreAndForward.json │ │ ├── ModbusClientStoreAndForward.png │ │ ├── README.txt │ │ └── buffer.sql │ └── Web API │ ├── Javascript │ ├── data_storage_demo.html │ ├── jquery.min.js │ └── moment.min.js │ └── data_storage_documentation.html └── V24 ├── Data Retrieval └── .NET Client │ ├── Instructions.txt │ ├── Program.cs │ ├── Views API Example.csproj │ └── ViewsClientProvider.cs └── Data Storage └── .NET Client ├── ExtensionMethodsExample.cs ├── Instructions.txt ├── Program.cs ├── RawClientExample.cs └── SAF_Examples.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Canary Labs Sample Code 2 | Company Website 3 | [https://www.canarylabs.com](https://www.canarylabs.com) 4 | 5 | Here you can download code samples for writing custom integrations with the Canary Labs enterprise historian 6 | 7 | ## Version 24 8 | ### Writing data natively (.NET client) 9 | [.NET Client](https://github.com/CanaryLabs/SampleCode/tree/master/Samples/V24/Data%20Storage/.NET%20Client) 10 | 11 | ### Reading data natively (.NET client) 12 | [.NET Client](https://github.com/CanaryLabs/SampleCode/tree/master/Samples/V24/Data%20Retrieval/.NET%20Client) 13 | 14 | ## Version 23 15 | ### Writing data via the web API (http/json) 16 | [Javascript Sample Client](http://htmlpreview.github.io/?https://github.com/CanaryLabs/SampleCode/blob/master/Samples/V23/Data%20Storage/Web%20API/Javascript/data_storage_demo.html)
17 | [API Reference Documentation (write your own client)](https://writeapi.canarylabs.com) 18 | 19 | ### Writing data natively (.NET Client) 20 | [.NET Client](https://github.com/CanaryLabs/SampleCode/tree/master/Samples/V23/Data%20Storage/.NET%20Client) 21 | 22 | ### Reading data via the web API (http/json) 23 | [Javascript Sample Client](http://htmlpreview.github.io/?https://github.com/CanaryLabs/SampleCode/blob/master/Samples/V23/Data%20Retrieval/Web%20API/Javascript/data_retrieval_demo.html)
24 | [Powershell Samples](https://github.com/CanaryLabs/SampleCode/tree/master/Samples/V23/Data%20Retrieval/Powershell)
25 | [Python 3 Sample](https://github.com/CanaryLabs/SampleCode/tree/master/Samples/V23/Data%20Retrieval/Python%203)
26 | [R Sample](https://github.com/CanaryLabs/SampleCode/tree/master/Samples/V23/Data%20Retrieval/R)
27 | [API Reference Documentation (write your own client)](https://readapi.canarylabs.com/) 28 | 29 | ### Reading data natively (.NET client) 30 | [.NET Client](https://github.com/CanaryLabs/SampleCode/tree/master/Samples/V23/Data%20Retrieval/.NET%20Client) 31 | 32 | ## Other Canary Labs help links 33 | Help Center (KnowledgeBase) 34 | [https://helpcenter.canarylabs.com/](https://helpcenter.canarylabs.com/) -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/.NET Client/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/.NET Client/ConnectionWrapper.cs: -------------------------------------------------------------------------------- 1 | using CanaryWebServiceHelper; 2 | using CanaryWebServiceHelper.HistorianWebService; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace ReadData 10 | { 11 | internal class ConnectionException : Exception 12 | { 13 | public ConnectionException(string message = "Unable to connect to endpoint.") : base(message) 14 | { 15 | } 16 | } 17 | 18 | internal class ConnectionWrapper : IDisposable 19 | { 20 | #region Private Members 21 | 22 | private bool _isConnected; 23 | private HistorianWebServiceClient _client; 24 | private HWS_ConnectionType _connectionType; 25 | private string _host; 26 | private int? _port; 27 | private string _appString; 28 | private string _userString; 29 | private string _user; 30 | private string _password; 31 | private int _cci; // client connection id 32 | 33 | #endregion Private Members 34 | 35 | #region Private Methods 36 | 37 | private bool Connect(string identity = null) 38 | { 39 | if (_isConnected) 40 | return true; 41 | 42 | string host = _host; 43 | if (_port.HasValue) 44 | host = $"{host}:{_port.Value}"; 45 | if (!string.IsNullOrEmpty(identity)) 46 | host = $"{host};{identity}"; 47 | 48 | try 49 | { 50 | HWS_ConnectionHelper.WebServiceConnect( 51 | _connectionType, 52 | host, 53 | _appString, 54 | _userString, 55 | _user, 56 | _password, 57 | out _client, 58 | out _cci); 59 | 60 | _isConnected = true; 61 | return true; 62 | } 63 | catch (Exception ex) 64 | { 65 | if (identity == null && _connectionType == HWS_ConnectionType.NetTcp_Username) 66 | { 67 | // try to parse dns identity from exception 68 | // necessary when connecting to username endpoint 69 | if (TryGetIdentity(ex, out identity)) 70 | return Connect(identity); 71 | } 72 | } 73 | 74 | _isConnected = false; 75 | return false; 76 | } 77 | 78 | private bool Reconnect() 79 | { 80 | if (_isConnected) 81 | { 82 | Disconnect(); 83 | Connect(); 84 | return true; 85 | } 86 | return false; 87 | } 88 | 89 | private void Disconnect() 90 | { 91 | try 92 | { 93 | if (_client != null) 94 | _client.ReleaseClientConnectId(_appString, _userString, _cci); 95 | _client.Close(); 96 | } 97 | catch 98 | { 99 | } 100 | finally 101 | { 102 | _isConnected = false; 103 | _client = null; 104 | _cci = 0; 105 | } 106 | } 107 | 108 | private bool TryGetIdentity(Exception ex, out string identity) 109 | { 110 | string message = ex.Message; 111 | string[] split = message.Split('\''); 112 | identity = (split.Length >= 4) ? split[3] : null; 113 | return !string.IsNullOrEmpty(identity); 114 | } 115 | 116 | private void ValidateConnectionParameters() 117 | { 118 | if (!_isConnected) 119 | { 120 | if (_connectionType == HWS_ConnectionType.Https_Username || _connectionType == HWS_ConnectionType.NetTcp_Username) 121 | { 122 | if (string.IsNullOrEmpty(_user) || string.IsNullOrEmpty(_password)) 123 | throw new ConnectionException("Credentials must be set when using a username endpoint."); 124 | } 125 | else if (!string.IsNullOrEmpty(_user) || !string.IsNullOrEmpty(_password)) 126 | throw new ConnectionException("Endpoint does not use credentials. Do not set them."); 127 | } 128 | } 129 | 130 | #endregion Private Methods 131 | 132 | #region Constructor/Destructor 133 | 134 | public ConnectionWrapper(string host, HWS_ConnectionType connectionType, string application = null, string clientDescription = null) 135 | { 136 | _host = host; 137 | _connectionType = connectionType; 138 | _appString = string.IsNullOrEmpty(application) ? "Read Data Example" : application; 139 | _userString = string.IsNullOrEmpty(clientDescription) ? Environment.MachineName : clientDescription; 140 | _user = null; 141 | _password = null; 142 | _cci = 0; 143 | } 144 | 145 | ~ConnectionWrapper() 146 | { 147 | Disconnect(); 148 | } 149 | 150 | #endregion Constructor/Destructor 151 | 152 | #region Public Properties 153 | 154 | public string Username 155 | { 156 | set { _user = value; } 157 | } 158 | 159 | public string Password 160 | { 161 | set { _password = value; } 162 | } 163 | 164 | public int Port 165 | { 166 | set { _port = value; } 167 | } 168 | 169 | #endregion Public Properties 170 | 171 | #region Public Methods 172 | 173 | public void Run(Action action) 174 | { 175 | try 176 | { 177 | ValidateConnectionParameters(); 178 | if (Connect()) 179 | action(_client, _cci); 180 | } 181 | catch 182 | { 183 | if (Reconnect()) 184 | action(_client, _cci); 185 | else 186 | throw; 187 | } 188 | 189 | if (!_isConnected) 190 | throw new ConnectionException(); 191 | } 192 | 193 | public void Dispose() 194 | { 195 | Disconnect(); 196 | } 197 | 198 | #endregion Public Methods 199 | } 200 | } -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/.NET Client/Instructions.txt: -------------------------------------------------------------------------------- 1 | This application requires a reference to the latest version of the CanaryWebServiceHelper.dll. This dll can be found within the Canary program files installation directory. -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/.NET Client/Program.cs: -------------------------------------------------------------------------------- 1 | using CanaryWebServiceHelper; 2 | using CanaryWebServiceHelper.HistorianWebService; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace ReadData 10 | { 11 | internal class Program 12 | { 13 | private static void Main(string[] args) 14 | { 15 | ResizeWindow(); 16 | 17 | string host = "localhost"; 18 | HWS_ConnectionType connectionType = HWS_ConnectionType.NetTcp_Windows; 19 | using (ConnectionWrapper connection = new ConnectionWrapper(host, connectionType) 20 | { 21 | //// use credentials when connecting to username endpoint 22 | //Username = "name", 23 | //Password = "secret" 24 | }) 25 | { 26 | try 27 | { 28 | // handles reconnect logic 29 | connection.Run((client, cci) => 30 | { 31 | // test version 32 | Version(client); 33 | 34 | // browse nodes/tags 35 | BrowseNodes(client, out string nodePath); 36 | BrowseTags(client, nodePath, out List tags); 37 | 38 | if (tags != null && tags.Count > 0) 39 | { 40 | DateTime endTime = DateTime.Now; 41 | DateTime startTime = endTime.Subtract(TimeSpan.FromHours(1)); 42 | List tagSubset = tags.Take(5).ToList(); 43 | 44 | // read data 45 | GetCurrentValue(client, cci, tagSubset); 46 | GetRawData(client, cci, tagSubset, startTime, endTime); 47 | GetAvailableAggregates(client); 48 | GetProcessedData(client, cci, tagSubset, startTime, endTime); 49 | } 50 | }); 51 | } 52 | catch (Exception ex) 53 | { 54 | Console.WriteLine($"Exception: {ex.Message}"); 55 | } 56 | } 57 | 58 | Console.WriteLine(); 59 | Console.WriteLine("Press any key to exit..."); 60 | Console.ReadKey(); 61 | } 62 | 63 | private static void ResizeWindow() 64 | { 65 | try 66 | { 67 | // width in characters 68 | Console.WindowWidth = 150; 69 | Console.WindowHeight = 50; 70 | } 71 | catch 72 | { 73 | // ignore error 74 | } 75 | } 76 | 77 | private static void Version(HistorianWebServiceClient client) 78 | { 79 | string version = client.WebServiceVersion(); 80 | Console.WriteLine($"Version: {version}"); 81 | } 82 | 83 | private static void BrowseNodes(HistorianWebServiceClient client, out string nodePath) 84 | { 85 | Console.WriteLine(); 86 | Console.WriteLine("Browse Nodes..."); 87 | 88 | nodePath = "ROOT"; // base path 89 | while (true) 90 | { 91 | HWSBrowseNode result = client.Browse(nodePath, false); 92 | if (result != null) 93 | { 94 | if (!string.IsNullOrEmpty(result.errMsg)) 95 | Console.WriteLine($"Browse Nodes Error: {result.errMsg}"); 96 | if (result.children == null || result.children.Length == 0) 97 | Console.WriteLine($"\"{nodePath}\" has 0 child nodes..."); 98 | else 99 | { 100 | // browse deeper into first child node 101 | HWSBrowseInfo firstNode = result.children[0]; 102 | Console.WriteLine($"\"{nodePath}\" path has {result.children.Length} child nodes..."); 103 | nodePath = firstNode.idPath; 104 | continue; 105 | } 106 | } 107 | 108 | break; 109 | } 110 | } 111 | 112 | private static void BrowseTags(HistorianWebServiceClient client, string nodePath, out List tags) 113 | { 114 | Console.WriteLine(); 115 | Console.WriteLine("Browse All Tags..."); 116 | 117 | tags = new List(); 118 | string search = ""; 119 | while (true) 120 | { 121 | int maxCount = 1000; 122 | bool includeSubNodes = true; 123 | bool includeProperties = false; 124 | HWSBrowseTags result = client.BrowseTags(nodePath, search, maxCount, includeSubNodes, includeProperties); 125 | if (result != null) 126 | { 127 | if (!string.IsNullOrEmpty(result.errMsg)) 128 | Console.WriteLine($"Browse Tags Error: {result.errMsg}"); 129 | else if (result.tagNames != null) 130 | { 131 | Console.WriteLine($"Retrieved {result.tagNames.Length} tags from path \"{nodePath}\"..."); 132 | 133 | tags.AddRange(result.tagNames.Select((partialTagName) => 134 | { 135 | // build full tag path 136 | return $"{nodePath}.{partialTagName}"; 137 | })); 138 | 139 | if (result.moreDataAvailable) 140 | { 141 | // use search context as continuation point to get more tags 142 | search = result.searchContext; 143 | continue; 144 | } 145 | } 146 | } 147 | 148 | break; 149 | } 150 | } 151 | 152 | private static void GetCurrentValue(HistorianWebServiceClient client, int cci, List tags) 153 | { 154 | Console.WriteLine(); 155 | Console.WriteLine("Getting Current Values..."); 156 | 157 | foreach (string fullTag in tags) 158 | { 159 | string[] split = fullTag.Split('.'); 160 | string historian = split[0]; 161 | string dataset = null; 162 | string partialTag = string.Join(".", split.Skip(1)); 163 | 164 | HWSTagCurrentValue result = client.GetTagCurrentValue(historian, dataset, new string[] { partialTag }, cci)[0]; 165 | if (result != null) 166 | { 167 | Console.WriteLine($"\"{fullTag}\" =>"); 168 | Console.WriteLine($"\tTime = {result.timeStamp}"); 169 | Console.WriteLine($"\tValue = {result.value}"); 170 | Console.WriteLine($"\tQuality = {client.GetReadableQuality(result.quality)}"); 171 | } 172 | } 173 | } 174 | 175 | private static void GetRawData(HistorianWebServiceClient client, int cci, List tags, DateTime startTime, DateTime endTime) 176 | { 177 | Console.WriteLine(); 178 | Console.WriteLine("Getting Raw Data..."); 179 | 180 | foreach (string fullTag in tags) 181 | { 182 | Console.WriteLine($"\"{fullTag}\" =>"); 183 | 184 | string[] split = fullTag.Split('.'); 185 | string historian = split[0]; 186 | string partialTag = string.Join(".", split.Skip(1)); 187 | 188 | byte[] continuationPoint = null; 189 | while (true) 190 | { 191 | HWSTagRequest3[] request = new HWSTagRequest3[] 192 | { 193 | new HWSTagRequest3() 194 | { 195 | endTime = endTime, 196 | startTime = startTime, 197 | tagName = partialTag, 198 | continuationPoint = continuationPoint 199 | } 200 | }; 201 | 202 | int maxCount = 1000; 203 | bool returnBounds = true; 204 | bool returnAnnotation = false; 205 | HWSTagData3 result = client.GetRawTagData3(historian, request, maxCount, returnBounds, returnAnnotation, cci)[0]; 206 | if (result != null) 207 | { 208 | if (!string.IsNullOrEmpty(result.errMsg)) 209 | Console.WriteLine($"\tGet Raw Data Error: {result.errMsg}"); 210 | else if (result.tvqs != null) 211 | { 212 | Console.WriteLine($"\tRetrieved {result.tvqs.Length} raw values..."); 213 | if (result.continuationPoint != null && result.continuationPoint.Length > 0) 214 | { 215 | // get more data 216 | continuationPoint = result.continuationPoint; 217 | continue; 218 | } 219 | } 220 | } 221 | 222 | break; 223 | } 224 | } 225 | } 226 | 227 | private static void GetAvailableAggregates(HistorianWebServiceClient client) 228 | { 229 | Console.WriteLine(); 230 | Console.WriteLine("Getting available aggregates for processed data..."); 231 | 232 | HWSAggregateDefinition[] aggregates = client.GetAggregateList2(); 233 | if (aggregates != null) 234 | { 235 | foreach (HWSAggregateDefinition aggregate in aggregates) 236 | Console.WriteLine($"{aggregate.aggregateName} => {aggregate.aggregateDescription}"); 237 | } 238 | } 239 | 240 | private static void GetProcessedData(HistorianWebServiceClient client, int cci, List tags, DateTime startTime, DateTime endTime) 241 | { 242 | Console.WriteLine(); 243 | Console.WriteLine("Getting Processed Data..."); 244 | 245 | TimeSpan aggregateInterval = TimeSpan.FromMinutes(1); 246 | foreach (string fullTag in tags) 247 | { 248 | Console.WriteLine($"\"{fullTag}\" =>"); 249 | 250 | string[] split = fullTag.Split('.'); 251 | string historian = split[0]; 252 | string partialTag = string.Join(".", split.Skip(1)); 253 | 254 | HWSTagProcessedRequest3[] request = new HWSTagProcessedRequest3[] 255 | { 256 | new HWSTagProcessedRequest3() 257 | { 258 | aggregateName = HWSAggregate.TimeAverage2.ToString(), 259 | tagName = partialTag 260 | } 261 | }; 262 | 263 | bool returnAnnotations = false; 264 | HWSTagProcessedData2 result = client.GetProcessedTagData3(historian, request, startTime, endTime, aggregateInterval, returnAnnotations, cci)[0]; 265 | if (result != null) 266 | { 267 | if (!string.IsNullOrEmpty(result.errMsg)) 268 | Console.WriteLine($"\tGet Processed Data Error: {result.errMsg}"); 269 | else if (result.tvqs != null) 270 | Console.WriteLine($"\tRetrieved {result.tvqs.Length} processed values..."); 271 | } 272 | } 273 | } 274 | } 275 | } -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/.NET Client/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ReadData")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Windows User")] 12 | [assembly: AssemblyProduct("ReadData")] 13 | [assembly: AssemblyCopyright("Copyright © Windows User 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("24e1ff1d-1237-41db-b560-3d311d55e278")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/.NET Client/Views API Example.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {24E1FF1D-1237-41DB-B560-3D311D55E278} 8 | Exe 9 | ReadData 10 | ReadData 11 | v4.7.1 12 | 512 13 | true 14 | 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\..\..\..\..\Dev\Projects\Views Helper\bin\Debug\CanaryWebServiceHelper.dll 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/Parquet Export/Binaries/ParquetExport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CanaryLabs/SampleCode/4c209c62b5e81e4eb9872df4b9d96ccc05b03bd6/Samples/V23/Data Retrieval/Parquet Export/Binaries/ParquetExport.zip -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/Parquet Export/Source/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/Parquet Export/Source/ParquetExport.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {C46DF6C4-6951-44C1-B3E2-79E1373DA8BA} 8 | Exe 9 | ParquetExport 10 | ParquetExport 11 | v4.7.1 12 | 512 13 | true 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | False 38 | ReferenceAssemblies\CanaryWebServiceHelper.dll 39 | 40 | 41 | packages\IronSnappy.1.3.0\lib\netstandard2.0\IronSnappy.dll 42 | 43 | 44 | packages\Parquet.Net.3.9.1\lib\netstandard2.0\Parquet.dll 45 | 46 | 47 | 48 | packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll 49 | 50 | 51 | 52 | packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll 53 | 54 | 55 | 56 | packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll 57 | 58 | 59 | packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/Parquet Export/Source/ParquetExport.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31229.75 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParquetExport", "ParquetExport.csproj", "{C46DF6C4-6951-44C1-B3E2-79E1373DA8BA}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {C46DF6C4-6951-44C1-B3E2-79E1373DA8BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {C46DF6C4-6951-44C1-B3E2-79E1373DA8BA}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {C46DF6C4-6951-44C1-B3E2-79E1373DA8BA}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {C46DF6C4-6951-44C1-B3E2-79E1373DA8BA}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {54B147A9-9E55-4934-9B39-463D2807BD2F} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/Parquet Export/Source/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ParquetExport")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ParquetExport")] 13 | [assembly: AssemblyCopyright("Copyright © 2021")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("c46df6c4-6951-44c1-b3e2-79e1373da8ba")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/Parquet Export/Source/ReferenceAssemblies/CanaryWebServiceHelper.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CanaryLabs/SampleCode/4c209c62b5e81e4eb9872df4b9d96ccc05b03bd6/Samples/V23/Data Retrieval/Parquet Export/Source/ReferenceAssemblies/CanaryWebServiceHelper.dll -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/Parquet Export/Source/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/Powershell/DiagnosticTags.txt: -------------------------------------------------------------------------------- 1 | localhost.{Diagnostics}.AdminRequests/sec 2 | localhost.{Diagnostics}.Reading.HistoryMax-ms 3 | localhost.{Diagnostics}.Reading.HistoryRequests/sec 4 | localhost.{Diagnostics}.Reading.LiveMax-ms 5 | localhost.{Diagnostics}.Reading.LiveTVQs/sec 6 | localhost.{Diagnostics}.Reading.NumClients 7 | localhost.{Diagnostics}.Reading.TagHandles 8 | localhost.{Diagnostics}.Reading.TVQs/sec 9 | localhost.{Diagnostics}.Sys.CPU Usage Historian 10 | localhost.{Diagnostics}.Sys.CPU Usage Total 11 | localhost.{Diagnostics}.Sys.Historian Handle Count 12 | localhost.{Diagnostics}.Sys.Historian Thread Count 13 | localhost.{Diagnostics}.Sys.Historian Working Set (memory) 14 | localhost.{Diagnostics}.Sys.Memory Page 15 | localhost.{Diagnostics}.Sys.Memory Virtual 16 | localhost.{Diagnostics}.Views.APICallCount/min 17 | localhost.{Diagnostics}.Views.AxiomCallCount 18 | localhost.{Diagnostics}.Views.AxiomLiveCallCount 19 | localhost.{Diagnostics}.Views.AxiomLiveMax-ms 20 | localhost.{Diagnostics}.Views.AxiomLivePixelMax-ms 21 | localhost.{Diagnostics}.Views.AxiomTotalTVQS 22 | localhost.{Diagnostics}.Views.CPU Usage 23 | localhost.{Diagnostics}.Views.GetRawCount 24 | localhost.{Diagnostics}.Views.GetRawMax-ms 25 | localhost.{Diagnostics}.Views.TotalConnections/min 26 | localhost.{Diagnostics}.Views.TotalTVQs/min 27 | localhost.{Diagnostics}.Views.Working Set Memory 28 | localhost.{Diagnostics}.Writing.NumClients 29 | localhost.{Diagnostics}.Writing.Requests/sec 30 | localhost.{Diagnostics}.Writing.TagHandles 31 | localhost.{Diagnostics}.Writing.TVQ TimeExtensions/sec 32 | localhost.{Diagnostics}.Writing.TVQs/sec -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/Powershell/ExportToCSV2.ps1: -------------------------------------------------------------------------------- 1 | # Canary Labs Web API Export Data Example 2 | # Tested with Powershell 5.1 and Powershell Core v6. 3 | # See http://localhost:55235/help for documentation regarding Canary api requests/response 4 | # Utilizes arguments for script variables 5 | 6 | # args 7 | # 0: url ex. http://localhost:55235 8 | # 1: taglist file ex. c:\temp\tags.txt 9 | # 2: output file ex. c:\temp\canaryReadDemo.csv 10 | # 3: start time ex. now-1day 11 | # 4: end time ex. now 12 | # 5: aggregate name ex. TimeAverage2 13 | # 6: aggregate interval ex. 5minute 14 | # 7: timezone ex. Eastern Standard Time 15 | # 8: username 16 | # 9: password 17 | # 10: application name ex. Canary Read Demo 18 | # 11: include quality ex. 0 or 1 19 | # 12: numeric precision digits 20 | 21 | $apiURL = $args[0]+"/api/v2/" 22 | $tagsToRead = Get-Content $args[1] | ForEach-Object {"$_"} 23 | $csvFileName = $args[2] 24 | $startTime = $args[3] 25 | $endTime = $args[4] 26 | $aggregateName = $args[5] 27 | $aggregateInterval = $args[6] 28 | $timezone = $args[7] 29 | $userName = $args[8] 30 | $password = $args[9] 31 | $application = $args[10] 32 | $includeQuality = $args[11] -eq 1 33 | $numericPrecision = $args[12] 34 | 35 | 36 | # parameter is always null on first request (this is for paging purposes and will be returned by the first request) 37 | $continuation = $null 38 | 39 | ############################################# 40 | # Script Execution 41 | ############################################# 42 | 43 | # get a user token for authenticating the requests 44 | $reqBody = new-object psobject 45 | $reqBody | Add-Member -membertype NoteProperty -name "username" -value $userName 46 | $reqBody | Add-Member -membertype NoteProperty -name "password" -value $password 47 | $reqBody | Add-Member -membertype NoteProperty -name "timezone" -value $timezone 48 | $reqBody | Add-Member -membertype NoteProperty -name "application" -value $application 49 | $authReqBodyJson = ConvertTo-Json $reqBody -Depth 100 50 | $userTokenResponseRaw = Invoke-WebRequest -Uri ($apiURL+"getUserToken") -Method POST -Body $authReqBodyJson -ContentType "application/json" 51 | $userTokenResponse = $userTokenResponseRaw.Content | ConvertFrom-Json 52 | if ($userTokenResponse.StatusCode -ne "Good") 53 | { 54 | Write-Host "Error retrieving a user token from the Canary api" 55 | Write-Host "Response"+ $userTokenResponseRaw 56 | exit 1 57 | } 58 | 59 | $userToken = $userTokenResponse.userToken 60 | 61 | do 62 | { 63 | # build the request body 64 | $reqBody = new-object psobject 65 | 66 | # required parameters 67 | $reqBody | Add-Member -MemberType NoteProperty -Name 'userToken' -Value $userToken 68 | $reqBody | Add-Member -MemberType NoteProperty -Name 'tags' -Value $tagsToRead 69 | 70 | # optional parameters 71 | if ($null -ne $startTime) {$reqBody | Add-Member -MemberType NoteProperty -Name 'startTime' -Value $startTime} 72 | if ($null -ne $endTime) {$reqBody | Add-Member -MemberType NoteProperty -Name 'endTime' -Value $endTime} 73 | if ($null -ne $aggregateName) {$reqBody | Add-Member -MemberType NoteProperty -Name 'aggregateName' -Value $aggregateName} 74 | if ($null -ne $aggregateInterval) {$reqBody | Add-Member -MemberType NoteProperty -Name 'aggregateInterval' -Value $aggregateInterval} 75 | if ($null -ne $includeQuality) {$reqBody | Add-Member -MemberType NoteProperty -Name 'includeQuality' -Value $includeQuality} 76 | if ($null -ne $continuation) {$reqBody | Add-Member -MemberType NoteProperty -Name 'continuation' -Value $continuation} 77 | 78 | # convert to JSON for sending 79 | $reqBodyJson = ConvertTo-Json $reqBody -Depth 100 80 | 81 | # call the /getTagData2 endpoint to get data for the tags 82 | $tagDataRaw = (Invoke-WebRequest -Uri ($apiURL+"getTagData2") -Method POST -Body $reqBodyJson -ContentType "application/json").Content 83 | $tagData = $tagDataRaw | ConvertFrom-Json 84 | if ($tagData.statusCode -ne "Good") 85 | { 86 | Write-Host "Error retrieving tag data from the Canary api. Exiting" 87 | Write-Host "Response"+ $tagDataRaw 88 | exit 1 89 | } 90 | 91 | # iterate over the response and export to CSV 92 | $continuation = $tagData.continuation 93 | $done = $true 94 | $recIndex = 0 95 | do { 96 | # every iteration of this loop is a csv record with columns being timestamp, tag1, tag2, tag3, etc... 97 | $csvObj = new-object psobject 98 | $csvObj | add-member -membertype NoteProperty -name "Timestamp" -value "timestamp" 99 | $writeRec = $false 100 | 101 | # using aggregate data, all tags will have the same number of records 102 | foreach($tag in $tagData.data.PsObject.Properties) 103 | { 104 | # each tag with its list of tvqs 105 | # $tagKey is the tag name 106 | if ($recIndex -lt $tag.value.Count) 107 | { 108 | $rec = $tag.value[$recIndex] 109 | # each tvq record for the tag 110 | # $rec is an array of values in this order: timestamp, value, quality 111 | $csvObj.Timestamp = $rec[0] 112 | 113 | $csvObj | add-member -membertype NoteProperty -name $tag.Name -value ([math]::Round($rec[1], $numericPrecision)) 114 | if ($includeQuality) 115 | { 116 | # include the quality as an additional column if specified in the request 117 | $csvObj | add-member -membertype NoteProperty -name ($tag.Name + " Quality") -value $rec[2] 118 | } 119 | 120 | $done = $recIndex+1 -ge $tag.value.Count 121 | $writeRec = $true 122 | } 123 | 124 | $tagIndex++ 125 | } 126 | 127 | $recIndex++ 128 | if($writeRec) 129 | { 130 | Export-Csv $csvFileName -InputObject $csvObj -Append -Encoding UTF8 -NoTypeInformation 131 | } 132 | } while (!$done) 133 | }while ($null -ne $continuation) 134 | 135 | # revoke the user token so that the session is released to the pool for other connections to use 136 | $revokeBody = "{ 'userToken':'$userToken' }" 137 | Invoke-WebRequest -Uri ($apiURL+"revokeUserToken") -Method POST -Body $revokeBody -ContentType "application/json" > $null -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/Powershell/ExportToCSV3.ps1: -------------------------------------------------------------------------------- 1 | # Canary Labs Web API Export Data Example 2 | # Tested with Powershell 5.1 and Powershell Core v6. 3 | # See http://localhost:55235/help for documentation regarding Canary api requests/response 4 | # Accepts a path as an argument instead of a tag file 5 | # Dynamically loads all tags from the path and exports the data for those tags 6 | 7 | # args 8 | # 0: url ex. http://localhost:55235 9 | # 1: path to read ex. .{Diagnostics} 10 | # 2: output file ex. c:\temp\canaryReadDemo.csv 11 | # 3: start time ex. now-1day 12 | # 4: end time ex. now 13 | # 5: aggregate name ex. TimeAverage2 14 | # 6: aggregate interval ex. 5minute 15 | # 7: timezone ex. Eastern Standard Time 16 | # 8: username 17 | # 9: password 18 | # 10: application name ex. Canary Read Demo 19 | # 11: include quality ex. 0 or 1 20 | # 12: numeric precision digits 21 | # 13: search text ex. 'cpu' (all tags that match 'cpu' in their name. multiple strings can be added separated by a space like 'flow oil') 22 | 23 | $apiURL = $args[0]+"/api/v2/" 24 | $pathToRead = $args[1] 25 | $csvFileName = $args[2] 26 | $startTime = $args[3] 27 | $endTime = $args[4] 28 | $aggregateName = $args[5] 29 | $aggregateInterval = $args[6] 30 | $timezone = $args[7] 31 | $userName = $args[8] 32 | $password = $args[9] 33 | $application = $args[10] 34 | $includeQuality = $args[11] -eq 1 35 | $numericPrecision = $args[12] 36 | $searchText = $args[13] 37 | 38 | # parameter is always null on first request (this is for paging purposes and will be returned by the first request) 39 | $continuation = $null 40 | 41 | ############################################# 42 | # Script Execution 43 | ############################################# 44 | 45 | # get a user token for authenticating the requests 46 | $reqBody = new-object psobject 47 | $reqBody | Add-Member -membertype NoteProperty -name "username" -value $userName 48 | $reqBody | Add-Member -membertype NoteProperty -name "password" -value $password 49 | $reqBody | Add-Member -membertype NoteProperty -name "timezone" -value $timezone 50 | $reqBody | Add-Member -membertype NoteProperty -name "application" -value $application 51 | $authReqBodyJson = ConvertTo-Json $reqBody -Depth 100 52 | $userTokenResponseRaw = Invoke-WebRequest -Uri ($apiURL+"getUserToken") -Method POST -Body $authReqBodyJson -ContentType "application/json" 53 | $userTokenResponse = $userTokenResponseRaw.Content | ConvertFrom-Json 54 | if ($userTokenResponse.StatusCode -ne "Good") 55 | { 56 | Write-Host "Error retrieving a user token from the Canary api" 57 | Write-Host "Response"+ $userTokenResponseRaw 58 | exit 1 59 | } 60 | 61 | $userToken = $userTokenResponse.userToken 62 | $tagsToRead = [System.Collections.ArrayList]@() 63 | 64 | # get the tags to read from the browse path command (does a deep browse to retrieve all tags under this path) 65 | do 66 | { 67 | $reqBody = new-object psobject 68 | $reqBody | Add-Member -membertype NoteProperty -Name 'userToken' -Value $userToken 69 | $reqBody | Add-Member -MemberType NoteProperty -Name 'path' -Value $pathToRead 70 | $reqBody | Add-Member -MemberType NoteProperty -Name 'deep' -Value $true 71 | if ($null -ne $searchText) {$reqBody | Add-Member -MemberType NoteProperty -Name 'search' -Value $searchText} 72 | if ($null -ne $continuation) {$reqBody | Add-Member -MemberType NoteProperty -Name 'continuation' -Value $continuation} 73 | 74 | # convert to JSON for sending 75 | $reqBodyJson = ConvertTo-Json $reqBody -Depth 100 76 | 77 | # call the /browseTags endpoint to get the tags for this particular node 78 | $browseDataRaw = (Invoke-WebRequest -Uri ($apiURL+"browseTags") -Method POST -Body $reqBodyJson -ContentType "application/json").Content 79 | $browseData = $browseDataRaw | ConvertFrom-Json 80 | if ($browseData.statusCode -ne "Good") 81 | { 82 | Write-Host "Error retrieving browse data from the Canary api. Exiting" 83 | Write-Host "Response: $browseDataRaw" 84 | exit 1 85 | } 86 | 87 | if ($browseData.tags.Length -eq 0) 88 | { 89 | Write-Host "The tag browse returned 0 tags. Please check your path and search settings. Exiting" 90 | Write-Host "Response: $browseDataRaw" 91 | exit 1 92 | } 93 | 94 | # add the tags to the list of tags to get data for 95 | foreach ($tag in $browseData.tags) 96 | { 97 | $tagsToRead.add($tag) > $null 98 | } 99 | 100 | $continuation = $browseData.continuation 101 | 102 | }while($null -ne $continuation) 103 | 104 | # proceed to read the tag data for all of the tags in the path 105 | $continuation = $null 106 | do 107 | { 108 | # build the request body 109 | $reqBody = new-object psobject 110 | 111 | # required parameters 112 | $reqBody | Add-Member -MemberType NoteProperty -Name 'userToken' -Value $userToken 113 | $reqBody | Add-Member -MemberType NoteProperty -Name 'tags' -Value $tagsToRead 114 | 115 | # optional parameters 116 | if ($null -ne $startTime) {$reqBody | Add-Member -MemberType NoteProperty -Name 'startTime' -Value $startTime} 117 | if ($null -ne $endTime) {$reqBody | Add-Member -MemberType NoteProperty -Name 'endTime' -Value $endTime} 118 | if ($null -ne $aggregateName) {$reqBody | Add-Member -MemberType NoteProperty -Name 'aggregateName' -Value $aggregateName} 119 | if ($null -ne $aggregateInterval) {$reqBody | Add-Member -MemberType NoteProperty -Name 'aggregateInterval' -Value $aggregateInterval} 120 | if ($null -ne $includeQuality) {$reqBody | Add-Member -MemberType NoteProperty -Name 'includeQuality' -Value $includeQuality} 121 | if ($null -ne $continuation) {$reqBody | Add-Member -MemberType NoteProperty -Name 'continuation' -Value $continuation} 122 | 123 | # convert to JSON for sending 124 | $reqBodyJson = ConvertTo-Json $reqBody -Depth 100 125 | 126 | # call the /getTagData2 endpoint to get data for the tags 127 | $tagDataRaw = (Invoke-WebRequest -Uri ($apiURL+"getTagData2") -Method POST -Body $reqBodyJson -ContentType "application/json").Content 128 | $tagData = $tagDataRaw | ConvertFrom-Json 129 | if ($tagData.statusCode -ne "Good") 130 | { 131 | Write-Host "Error retrieving tag data from the Canary api. Exiting" 132 | Write-Host "Response"+ $tagDataRaw 133 | exit 1 134 | } 135 | 136 | # iterate over the response and export to CSV 137 | $continuation = $tagData.continuation 138 | $done = $true 139 | $recIndex = 0 140 | do { 141 | # every iteration of this loop is a csv record with columns being timestamp, tag1, tag2, tag3, etc... 142 | $csvObj = new-object psobject 143 | $csvObj | add-member -membertype NoteProperty -name "Timestamp" -value "timestamp" 144 | $writeRec = $false 145 | 146 | # using aggregate data, all tags will have the same number of records 147 | foreach($tag in $tagData.data.PsObject.Properties) 148 | { 149 | # each tag with its list of tvqs 150 | # $tagKey is the tag name 151 | if ($recIndex -lt $tag.value.Count) 152 | { 153 | $rec = $tag.value[$recIndex] 154 | # each tvq record for the tag 155 | # $rec is an array of values in this order: timestamp, value, quality 156 | $csvObj.Timestamp = $rec[0] 157 | 158 | $csvObj | add-member -membertype NoteProperty -name $tag.Name -value ([math]::Round($rec[1], $numericPrecision)) 159 | if ($includeQuality) 160 | { 161 | # include the quality as an additional column if specified in the request 162 | $csvObj | add-member -membertype NoteProperty -name ($tag.Name + " Quality") -value $rec[2] 163 | } 164 | 165 | $done = $recIndex+1 -ge $tag.value.Count 166 | $writeRec = $true 167 | } 168 | 169 | $tagIndex++ 170 | } 171 | 172 | $recIndex++ 173 | if($writeRec) 174 | { 175 | Export-Csv $csvFileName -InputObject $csvObj -Append -Encoding UTF8 -NoTypeInformation 176 | } 177 | } while (!$done) 178 | }while ($null -ne $continuation) 179 | 180 | # revoke the user token so that the session is released to the pool for other connections to use 181 | $revokeBody = "{ 'userToken':'$userToken' }" 182 | Invoke-WebRequest -Uri ($apiURL+"revokeUserToken") -Method POST -Body $revokeBody -ContentType "application/json" > $null -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/Python 3/ExportToCSV.py: -------------------------------------------------------------------------------- 1 | # Canary Labs Web API Export Data Example 2 | # Tested with Python 3.8. 3 | # See http://localhost:55235/help for documentation regarding Canary api requests/response 4 | # By default, this script reads aggregated diagnostic data (1 minute aggregate from the last day) from a local historian and outputs the results to c:\temp\canaryReadDemo.csv 5 | 6 | # default Canary views web api ports are: 7 | # 55235: http 8 | # 55236: https 9 | # ex: http://localhost:55235/api/v2/ 10 | # ex: https://localhost:55236/api/v2/ 11 | 12 | ############################################# 13 | # Canary Views Web API Settings 14 | ############################################# 15 | # api url. must end with slash '/' (the port number is configured through the views tile in the Canary Admin. default = 55235) 16 | apiURL = 'http://localhost:55235/api/v2/' 17 | # timezone is used to convert times to/from the clients' timezone. valid timezone strings are found via the /getTimeZones api endpoint, which returns: 18 | # 'Dateline Standard Time', 19 | # 'UTC-11', 20 | # 'Aleutian Standard Time', 21 | # 'Hawaiian Standard Time', 22 | # 'Marquesas Standard Time', 23 | # 'Alaskan Standard Time', 24 | # 'UTC-09', 25 | # 'Pacific Standard Time (Mexico)', 26 | # 'UTC-08', 27 | # 'Pacific Standard Time', 28 | # 'US Mountain Standard Time', 29 | # 'Mountain Standard Time (Mexico)', 30 | # 'Mountain Standard Time', 31 | # 'Central America Standard Time', 32 | # 'Central Standard Time', 33 | # 'Easter Island Standard Time', 34 | # 'Central Standard Time (Mexico)', 35 | # 'Canada Central Standard Time', 36 | # 'SA Pacific Standard Time', 37 | # 'Eastern Standard Time (Mexico)', 38 | # 'Eastern Standard Time', 39 | # 'Haiti Standard Time', 40 | # 'Cuba Standard Time', 41 | # 'US Eastern Standard Time', 42 | # 'Turks And Caicos Standard Time', 43 | # 'Paraguay Standard Time', 44 | # 'Atlantic Standard Time', 45 | # 'Venezuela Standard Time', 46 | # 'Central Brazilian Standard Time', 47 | # 'SA Western Standard Time', 48 | # 'Pacific SA Standard Time', 49 | # 'Newfoundland Standard Time', 50 | # 'Tocantins Standard Time', 51 | # 'E. South America Standard Time', 52 | # 'SA Eastern Standard Time', 53 | # 'Argentina Standard Time', 54 | # 'Greenland Standard Time', 55 | # 'Montevideo Standard Time', 56 | # 'Magallanes Standard Time', 57 | # 'Saint Pierre Standard Time', 58 | # 'Bahia Standard Time', 59 | # 'UTC-02', 60 | # 'Mid-Atlantic Standard Time', 61 | # 'Azores Standard Time', 62 | # 'Cape Verde Standard Time', 63 | # 'UTC', 64 | # 'GMT Standard Time', 65 | # 'Greenwich Standard Time', 66 | # 'Morocco Standard Time', 67 | # 'W. Europe Standard Time', 68 | # 'Central Europe Standard Time', 69 | # 'Romance Standard Time', 70 | # 'Sao Tome Standard Time', 71 | # 'Central European Standard Time', 72 | # 'W. Central Africa Standard Time', 73 | # 'Jordan Standard Time', 74 | # 'GTB Standard Time', 75 | # 'Middle East Standard Time', 76 | # 'Egypt Standard Time', 77 | # 'E. Europe Standard Time', 78 | # 'Syria Standard Time', 79 | # 'West Bank Standard Time', 80 | # 'South Africa Standard Time', 81 | # 'FLE Standard Time', 82 | # 'Israel Standard Time', 83 | # 'Kaliningrad Standard Time', 84 | # 'Sudan Standard Time', 85 | # 'Libya Standard Time', 86 | # 'Namibia Standard Time', 87 | # 'Arabic Standard Time', 88 | # 'Turkey Standard Time', 89 | # 'Arab Standard Time', 90 | # 'Belarus Standard Time', 91 | # 'Russian Standard Time', 92 | # 'E. Africa Standard Time', 93 | # 'Iran Standard Time', 94 | # 'Arabian Standard Time', 95 | # 'Astrakhan Standard Time', 96 | # 'Azerbaijan Standard Time', 97 | # 'Russia Time Zone 3', 98 | # 'Mauritius Standard Time', 99 | # 'Saratov Standard Time', 100 | # 'Georgian Standard Time', 101 | # 'Volgograd Standard Time', 102 | # 'Caucasus Standard Time', 103 | # 'Afghanistan Standard Time', 104 | # 'West Asia Standard Time', 105 | # 'Ekaterinburg Standard Time', 106 | # 'Pakistan Standard Time', 107 | # 'India Standard Time', 108 | # 'Sri Lanka Standard Time', 109 | # 'Nepal Standard Time', 110 | # 'Central Asia Standard Time', 111 | # 'Bangladesh Standard Time', 112 | # 'Omsk Standard Time', 113 | # 'Myanmar Standard Time', 114 | # 'SE Asia Standard Time', 115 | # 'Altai Standard Time', 116 | # 'W. Mongolia Standard Time', 117 | # 'North Asia Standard Time', 118 | # 'N. Central Asia Standard Time', 119 | # 'Tomsk Standard Time', 120 | # 'China Standard Time', 121 | # 'North Asia East Standard Time', 122 | # 'Singapore Standard Time', 123 | # 'W. Australia Standard Time', 124 | # 'Taipei Standard Time', 125 | # 'Ulaanbaatar Standard Time', 126 | # 'Aus Central W. Standard Time', 127 | # 'Transbaikal Standard Time', 128 | # 'Tokyo Standard Time', 129 | # 'North Korea Standard Time', 130 | # 'Korea Standard Time', 131 | # 'Yakutsk Standard Time', 132 | # 'Cen. Australia Standard Time', 133 | # 'AUS Central Standard Time', 134 | # 'E. Australia Standard Time', 135 | # 'AUS Eastern Standard Time', 136 | # 'West Pacific Standard Time', 137 | # 'Tasmania Standard Time', 138 | # 'Vladivostok Standard Time', 139 | # 'Lord Howe Standard Time', 140 | # 'Bougainville Standard Time', 141 | # 'Russia Time Zone 10', 142 | # 'Magadan Standard Time', 143 | # 'Norfolk Standard Time', 144 | # 'Sakhalin Standard Time', 145 | # 'Central Pacific Standard Time', 146 | # 'Russia Time Zone 11', 147 | # 'New Zealand Standard Time', 148 | # 'UTC+12', 149 | # 'Fiji Standard Time', 150 | # 'Kamchatka Standard Time', 151 | # 'Chatham Islands Standard Time', 152 | # 'UTC+13', 153 | # 'Tonga Standard Time', 154 | # 'Samoa Standard Time', 155 | # 'Line Islands Standard Time' 156 | timeZone = 'Eastern Standard Time' 157 | # can leave empty if the http anonymous endpoint is turned on in views service and http is used (default); required for https requests 158 | userName = '' 159 | # same rules apply as username 160 | password = '' 161 | # identifies this connection in the Canary Administrator. Lets you know who's connected to it for troubleshooting 162 | application = 'Canary Python Read Demo' 163 | # identifies the dataset and any number of paths to prefix to the tag names. If this is empty, each tag in the list needs to be fully qualified with historian and dataset path 164 | dataSetPrefix = 'localhost.{Diagnostics}' 165 | # collection of tags that you want to read from the api. Note: only 1 tag is supported for raw data since timestamps will not be the same across tags (invalid export) 166 | tagsToRead = [ 167 | 'AdminRequests/sec', 168 | 'Reading.HistoryMax-ms', 169 | 'Reading.HistoryRequests/sec', 170 | 'Reading.LiveMax-ms', 171 | 'Reading.LiveTVQs/sec', 172 | 'Reading.NumClients', 173 | 'Reading.TagHandles', 174 | 'Reading.TVQs/sec', 175 | 'Sys.CPU Usage Historian', 176 | 'Sys.CPU Usage Total', 177 | 'Sys.Historian Handle Count', 178 | 'Sys.Historian Thread Count', 179 | 'Sys.Historian Working Set (memory)', 180 | 'Sys.Memory Page', 181 | 'Sys.Memory Virtual', 182 | 'Views.APICallCount/min', 183 | 'Views.AxiomCallCount', 184 | 'Views.AxiomLiveCallCount', 185 | 'Views.AxiomLiveMax-ms', 186 | 'Views.AxiomLivePixelMax-ms', 187 | 'Views.AxiomTotalTVQS', 188 | 'Views.CPU Usage', 189 | 'Views.GetRawCount', 190 | 'Views.GetRawMax-ms', 191 | 'Views.TotalConnections/min', 192 | 'Views.TotalTVQs/min', 193 | 'Views.Working Set Memory', 194 | 'Writing.NumClients', 195 | 'Writing.Requests/sec', 196 | 'Writing.TagHandles', 197 | 'Writing.TVQ TimeExtensions/sec', 198 | 'Writing.TVQs/sec' 199 | ] 200 | 201 | # start time. can be relative or absolute time 202 | # relative time units can be found at http://localhost:55235/help#relativeTime 203 | # absolute time format Ex. '03/12/2019 12:00:00' 204 | startTime = 'now-1day' 205 | # see start time. 206 | endTime = 'now' 207 | # aggregate function to use for aggregated data. 208 | aggregateName = 'TimeAverage2' 209 | #'TimeAverage2': 'Retrieve the time weighted average data over the interval using Simple Bounding Values.', 210 | #'Interpolative': 'At the beginning of each interval, retrieve the calculated value from the data points on either side of the requested timestamp.', 211 | #'Average': 'Retrieve the average value of the data over the interval.', 212 | #'TimeAverage': 'Retrieve the time weighted average data over the interval using Interpolated Bounding Values.', 213 | #'Total': 'Retrieve the total (time integral) of the data over the interval using Interpolated Bounding Values.', 214 | #'Total2': 'Retrieve the total (time integral in seconds) of the data over the interval using Simple Bounding Values.', 215 | #'TotalPerMinute': 'Retrieve the total (time integral in minutes) of the data over the interval using Simple Bounding Values.', 216 | #'TotalPerHour': 'Retrieve the total (time integral in hours) of the data over the interval using Simple Bounding Values.', 217 | #'TotalPer24Hours': 'Retrieve the total (time integral in 24 hours) of the data over the interval using Simple Bounding Values.', 218 | #'Minimum': 'Retrieve the minimum raw value in the interval with the timestamp of the start of the interval.', 219 | #'Maximum': 'Retrieve the maximum raw value in the interval with the timestamp of the start of the interval.', 220 | #'MinimumActualTime': 'Retrieve the minimum value in the interval and the timestamp of the minimum value.', 221 | #'MaximumActualTime': 'Retrieve the maximum value in the interval and the timestamp of the maximum value.', 222 | #'Range': 'Retrieve the difference between the minimum and maximum value over the interval.', 223 | #'Minimum2': 'Retrieve the minimum value in the interval including the Simple Bounding Values.', 224 | #'Maximum2': 'Retrieve the maximum value in the interval including the Simple Bounding Values.', 225 | #'MinimumActualTime2': 'Retrieve the minimum value with the actual timestamp including the Simple Bounding Values.', 226 | #'MaximumActualTime2': 'Retrieve the maximum value with the actual timestamp including the Simple Bounding Values.', 227 | #'Range2': 'Retrieve the difference between the Minimum2 and Maximum2 value over the interval.', 228 | #'Count': 'Retrieve the number of raw values over the interval.', 229 | #'DurationInStateZero': 'Retrieve the time a Boolean or numeric was in a zero state using Simple Bounding Values.', 230 | #'DurationInStateNonZero': 'Retrieve the time a Boolean or numeric was in a non-zero state using Simple Bounding Values.', 231 | #'NumberOfTransitions': 'Retrieve the number of changes between zero and non-zero that a Boolean or numeric value experienced in the interval.', 232 | #'Start': 'Retrieve the first value in the interval.', 233 | #'End': 'Retrieve the last value in the interval.', 234 | #'Delta': 'Retrieve the difference between the Start and End value in the interval.', 235 | #'StartBound': 'Retrieve the value at the beginning of the interval using Simple Bounding Values.', 236 | #'EndBound': 'Retrieve the value at the end of the interval using Simple Bounding Values.', 237 | #'DeltaBounds': 'Retrieve the difference between the StartBound and EndBound value in the interval.', 238 | #'Instant': 'Retrieve the value at the exact beginning of the interval.', 239 | #'DurationGood': 'Retrieve the total duration of time in the interval during which the data is Good.', 240 | #'DurationBad': 'Retrieve the total duration of time in the interval during which the data is Bad.', 241 | #'PercentGood': 'Retrieve the percentage of data (0 to 100) in the interval which has Good StatusCode', 242 | #'PercentBad': 'Retrieve the percentage of data (0 to 100) in the interval which has Bad StatusCode.', 243 | #'WorstQuality': 'Retrieve the worst StatusCode of data in the interval.', 244 | #'WorstQuality2': 'Retrieve the worst StatusCode of data in the interval including the Simple Bounding Values.', 245 | #'StandardDeviationSample': 'Retrieve the standard deviation for the interval for a sample of the population (n-1).', 246 | #'VarianceSample': 'Retrieve the variance for the interval as calculated by the StandardDeviationSample.', 247 | #'StandardDeviationPopulation': 'Retrieve the standard deviation for the interval for a complete population (n) which includes Simple Bounding Values.', 248 | #'VariancePopulation': 'Retrieve the variance for the interval as calculated by the StandardDeviationPopulation which includes Simple Bounding Values.' 249 | 250 | # interval over which to aggregate. can be relative or absolute 251 | # relative time units can be found at http://localhost:55235/help#relativeTime 252 | # absolute interval can be expressed as a timespan 'days.hours:minutes:seconds.subseconds' or '1.00:01:00' = 1 day 1 minute 253 | aggregateInterval = '1minute' 254 | # if True, includes the quality of the data in the output 255 | includeQuality = False 256 | # limits the max return amount (default is 10,000). 257 | maxSize = 10000 258 | # parameter is always None on first request (this is for paging purposes and will be returned by the first request) 259 | continuation = None 260 | 261 | # Parameter examples 262 | # 1: Last Value. Returns the last (most recent) value for the tags 263 | # startTime = None 264 | # endTime = None 265 | 266 | # 2: Raw Data. Returns raw data for the tags 267 | # startTime = 'now-1day' 268 | # endTime = 'now' 269 | # aggregateName = None 270 | # aggregateInterval = None 271 | 272 | # 3: Processed Data 273 | # startTime = 'now-1day' 274 | # endTime = 'now' 275 | # aggregateName = 'TimeAverage2' 276 | # aggregateInterval = '1minute' 277 | 278 | ############################################# 279 | # CSV Output File Settings 280 | ############################################# 281 | # output file name 282 | csvFileName = 'c:\\temp\\canaryReadDemo.csv' 283 | # if True, then deletes an existing output from the directory before writing new output (valid values are True and False) 284 | purgeExistingOutputFile = False 285 | 286 | ############################################# 287 | # Script Execution 288 | ############################################# 289 | 290 | import os, sys, requests, json, csv 291 | 292 | # delete the output file if exists and configured to do so 293 | if os.path.isfile(csvFileName): 294 | if purgeExistingOutputFile: 295 | try: 296 | os.remove(csvFileName) 297 | except OSError: 298 | pass 299 | else: 300 | sys.exit('Output file already exists. Delete the file or set purgeExistingOutputFile = True to automatically delete on next run. Exiting') 301 | 302 | # prepend the dataset and path to the tag names 303 | fullyQualifiedTagsToRead = [ 304 | (dataSetPrefix + '.' if dataSetPrefix else '') + i for i in tagsToRead 305 | ] 306 | 307 | session = requests.Session() 308 | 309 | reqBody = { 310 | 'username':userName, 311 | 'password':password, 312 | 'timezone':timeZone, 313 | 'application':application 314 | } 315 | 316 | # get a user token for authenticating the requests 317 | response = session.post(apiURL + 'getUserToken', data=json.dumps(reqBody)) 318 | responseJson = response.json() 319 | 320 | if responseJson['statusCode'] != 'Good': 321 | sys.exit('Error retrieving a user token from the Canary api\n' + 322 | 'Response' + response.text) 323 | 324 | userToken = responseJson['userToken'] 325 | 326 | headerRowWritten = False 327 | while True: 328 | # required parameters 329 | reqBody = { 330 | 'userToken':userToken, 331 | 'tags':fullyQualifiedTagsToRead 332 | } 333 | 334 | # optional parameters 335 | if startTime: reqBody['startTime'] = startTime 336 | if endTime: reqBody['endTime'] = endTime 337 | if aggregateName: reqBody['aggregateName'] = aggregateName 338 | if aggregateInterval: reqBody['aggregateInterval'] = aggregateInterval 339 | if includeQuality: reqBody['includeQuality'] = includeQuality 340 | if maxSize: reqBody['maxSize'] = maxSize 341 | if continuation: reqBody['continuation'] = continuation 342 | 343 | # call the /getTagData2 endpoint to get data for the tags 344 | response = session.post(apiURL + 'getTagData2', data=json.dumps(reqBody)) 345 | tagData = response.json() 346 | 347 | # check for errors 348 | if tagData['statusCode'] != 'Good': 349 | session.post(apiURL + 'revokeUserToken', data=json.dumps({'userToken':userToken})) 350 | sys.exit('Error retrieving tag data from the Canary api. Exiting\n' + 351 | 'Response' + response.text) 352 | 353 | # iterate over the response and export to CSV 354 | continuation = tagData['continuation'] 355 | done = False 356 | recordIndex = 0 357 | 358 | with open(csvFileName, 'a', newline='') as csvFile: 359 | writer = csv.DictWriter(csvFile, fieldnames=[]) 360 | 361 | while not done: 362 | # every iteration of this loop is a csv record with columns being timestamp, tag1, tag2, tag3, etc... 363 | csvObj = {} 364 | writeRecord = False 365 | 366 | # using aggregate data, all tags will have the same number of records 367 | for tagKey, tagTvqs in tagData['data'].items(): 368 | # each tag with its list of tvqs 369 | # tagKey is the tag name 370 | if recordIndex < len(tagTvqs): 371 | record = tagTvqs[recordIndex] 372 | # each tvq record for the tag 373 | # record is a dict with the keys 't', 'v', and 'q' that correspond to timestamp, value, and quality 374 | csvObj['Timestamp'] = record['t'] 375 | csvObj[tagKey] = record['v'] 376 | if includeQuality: 377 | csvObj[tagKey + ' Quality'] = record['q'] 378 | 379 | done = recordIndex + 1 > len(tagTvqs) 380 | writeRecord = True 381 | 382 | recordIndex += 1 383 | if writeRecord: 384 | writer.fieldnames = csvObj.keys() 385 | 386 | if not headerRowWritten: 387 | writer.writeheader() 388 | headerRowWritten = True 389 | 390 | writer.writerow(csvObj) 391 | else: 392 | break 393 | 394 | # Exit the loop once there is no continuation point. 395 | if not continuation: 396 | break 397 | 398 | # Revoke user token 399 | session.post(apiURL + 'revokeUserToken', data=json.dumps({'userToken':userToken})) 400 | -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/R/ExportToCSV.R: -------------------------------------------------------------------------------- 1 | # Canary Labs Web API Export Data Example 2 | # Tested with R 4.0.1. 3 | # See http://localhost:55235/help for documentation regarding Canary api requests/response 4 | # By default, this script reads aggregated diagnostic data (1 minute aggregate from the last day) from a local historian and outputs the results to c:\temp\canaryReadDemo.csv 5 | 6 | # default Canary views web api ports are: 7 | # 55235: http 8 | # 55236: https 9 | # ex: http://localhost:55235/api/v2/ 10 | # ex: https://localhost:55236/api/v2/ 11 | 12 | ############################################# 13 | # Canary Views Web API Settings 14 | ############################################# 15 | # api url. must end with slash "/" (the port number is configured through the views tile in the Canary Admin. default = 55235) 16 | api_url <- "http://localhost:55235/api/v2/" 17 | # timezone is used to convert times to/from the clients' timezone. valid timezone strings are found via the /getTimeZones api endpoint, which returns: 18 | # "Dateline Standard Time", 19 | # "UTC-11", 20 | # "Aleutian Standard Time", 21 | # "Hawaiian Standard Time", 22 | # "Marquesas Standard Time", 23 | # "Alaskan Standard Time", 24 | # "UTC-09", 25 | # "Pacific Standard Time (Mexico)", 26 | # "UTC-08", 27 | # "Pacific Standard Time", 28 | # "US Mountain Standard Time", 29 | # "Mountain Standard Time (Mexico)", 30 | # "Mountain Standard Time", 31 | # "Central America Standard Time", 32 | # "Central Standard Time", 33 | # "Easter Island Standard Time", 34 | # "Central Standard Time (Mexico)", 35 | # "Canada Central Standard Time", 36 | # "SA Pacific Standard Time", 37 | # "Eastern Standard Time (Mexico)", 38 | # "Eastern Standard Time", 39 | # "Haiti Standard Time", 40 | # "Cuba Standard Time", 41 | # "US Eastern Standard Time", 42 | # "Turks And Caicos Standard Time", 43 | # "Paraguay Standard Time", 44 | # "Atlantic Standard Time", 45 | # "Venezuela Standard Time", 46 | # "Central Brazilian Standard Time", 47 | # "SA Western Standard Time", 48 | # "Pacific SA Standard Time", 49 | # "Newfoundland Standard Time", 50 | # "Tocantins Standard Time", 51 | # "E. South America Standard Time", 52 | # "SA Eastern Standard Time", 53 | # "Argentina Standard Time", 54 | # "Greenland Standard Time", 55 | # "Montevideo Standard Time", 56 | # "Magallanes Standard Time", 57 | # "Saint Pierre Standard Time", 58 | # "Bahia Standard Time", 59 | # "UTC-02", 60 | # "Mid-Atlantic Standard Time", 61 | # "Azores Standard Time", 62 | # "Cape Verde Standard Time", 63 | # "UTC", 64 | # "GMT Standard Time", 65 | # "Greenwich Standard Time", 66 | # "Morocco Standard Time", 67 | # "W. Europe Standard Time", 68 | # "Central Europe Standard Time", 69 | # "Romance Standard Time", 70 | # "Sao Tome Standard Time", 71 | # "Central European Standard Time", 72 | # "W. Central Africa Standard Time", 73 | # "Jordan Standard Time", 74 | # "GTB Standard Time", 75 | # "Middle East Standard Time", 76 | # "Egypt Standard Time", 77 | # "E. Europe Standard Time", 78 | # "Syria Standard Time", 79 | # "West Bank Standard Time", 80 | # "South Africa Standard Time", 81 | # "FLE Standard Time", 82 | # "Israel Standard Time", 83 | # "Kaliningrad Standard Time", 84 | # "Sudan Standard Time", 85 | # "Libya Standard Time", 86 | # "Namibia Standard Time", 87 | # "Arabic Standard Time", 88 | # "Turkey Standard Time", 89 | # "Arab Standard Time", 90 | # "Belarus Standard Time", 91 | # "Russian Standard Time", 92 | # "E. Africa Standard Time", 93 | # "Iran Standard Time", 94 | # "Arabian Standard Time", 95 | # "Astrakhan Standard Time", 96 | # "Azerbaijan Standard Time", 97 | # "Russia Time Zone 3", 98 | # "Mauritius Standard Time", 99 | # "Saratov Standard Time", 100 | # "Georgian Standard Time", 101 | # "Volgograd Standard Time", 102 | # "Caucasus Standard Time", 103 | # "Afghanistan Standard Time", 104 | # "West Asia Standard Time", 105 | # "Ekaterinburg Standard Time", 106 | # "Pakistan Standard Time", 107 | # "India Standard Time", 108 | # "Sri Lanka Standard Time", 109 | # "Nepal Standard Time", 110 | # "Central Asia Standard Time", 111 | # "Bangladesh Standard Time", 112 | # "Omsk Standard Time", 113 | # "Myanmar Standard Time", 114 | # "SE Asia Standard Time", 115 | # "Altai Standard Time", 116 | # "W. Mongolia Standard Time", 117 | # "North Asia Standard Time", 118 | # "N. Central Asia Standard Time", 119 | # "Tomsk Standard Time", 120 | # "China Standard Time", 121 | # "North Asia East Standard Time", 122 | # "Singapore Standard Time", 123 | # "W. Australia Standard Time", 124 | # "Taipei Standard Time", 125 | # "Ulaanbaatar Standard Time", 126 | # "Aus Central W. Standard Time", 127 | # "Transbaikal Standard Time", 128 | # "Tokyo Standard Time", 129 | # "North Korea Standard Time", 130 | # "Korea Standard Time", 131 | # "Yakutsk Standard Time", 132 | # "Cen. Australia Standard Time", 133 | # "AUS Central Standard Time", 134 | # "E. Australia Standard Time", 135 | # "AUS Eastern Standard Time", 136 | # "West Pacific Standard Time", 137 | # "Tasmania Standard Time", 138 | # "Vladivostok Standard Time", 139 | # "Lord Howe Standard Time", 140 | # "Bougainville Standard Time", 141 | # "Russia Time Zone 10", 142 | # "Magadan Standard Time", 143 | # "Norfolk Standard Time", 144 | # "Sakhalin Standard Time", 145 | # "Central Pacific Standard Time", 146 | # "Russia Time Zone 11", 147 | # "New Zealand Standard Time", 148 | # "UTC+12", 149 | # "Fiji Standard Time", 150 | # "Kamchatka Standard Time", 151 | # "Chatham Islands Standard Time", 152 | # "UTC+13", 153 | # "Tonga Standard Time", 154 | # "Samoa Standard Time", 155 | # "Line Islands Standard Time" 156 | time_zone <- "Eastern Standard Time" 157 | # can leave empty if the http anonymous endpoint is turned on in views service and http is used (default); required for https requests 158 | user_name <- "" 159 | # same rules apply as username 160 | password <- "" 161 | # identifies this connection in the Canary Administrator. Lets you know who's connected to it for troubleshooting 162 | application <- "Canary R Read Demo" 163 | # identifies the dataset and any number of paths to prefix to the tag names. If this is empty, each tag in the list needs to be fully qualified with historian and dataset path 164 | data_set_prefix <- "localhost.{Diagnostics}" 165 | # collection of tags that you want to read from the api. Note: only 1 tag is supported for raw data since timestamps will not be the same across tags (invalid export) 166 | tags_to_read <- c( 167 | "AdminRequests/sec", 168 | "Reading.HistoryMax-ms", 169 | "Reading.HistoryRequests/sec", 170 | "Reading.LiveMax-ms", 171 | "Reading.LiveTVQs/sec", 172 | "Reading.NumClients", 173 | "Reading.TagHandles", 174 | "Reading.TVQs/sec", 175 | "Sys.CPU Usage Historian", 176 | "Sys.CPU Usage Total", 177 | "Sys.Historian Handle Count", 178 | "Sys.Historian Thread Count", 179 | "Sys.Historian Working Set (memory)", 180 | "Sys.Memory Page", 181 | "Sys.Memory Virtual", 182 | "Views.APICallCount/min", 183 | "Views.AxiomCallCount", 184 | "Views.AxiomLiveCallCount", 185 | "Views.AxiomLiveMax-ms", 186 | "Views.AxiomLivePixelMax-ms", 187 | "Views.AxiomTotalTVQS", 188 | "Views.CPU Usage", 189 | "Views.GetRawCount", 190 | "Views.GetRawMax-ms", 191 | "Views.TotalConnections/min", 192 | "Views.TotalTVQs/min", 193 | "Views.Working Set Memory", 194 | "Writing.NumClients", 195 | "Writing.Requests/sec", 196 | "Writing.TagHandles", 197 | "Writing.TVQ TimeExtensions/sec", 198 | "Writing.TVQs/sec" 199 | ) 200 | 201 | # start time. can be relative or absolute time 202 | # relative time units can be found at http://localhost:55235/help#relativeTime 203 | # absolute time format Ex. "03/12/2019 12:00:00" 204 | start_time <- "now-1day" 205 | # see start time. 206 | end_time <- "now" 207 | # aggregate function to use for aggregated data. 208 | aggregate_name <- "TimeAverage2" 209 | #"TimeAverage2": "Retrieve the time weighted average data over the interval using Simple Bounding Values.", 210 | #"Interpolative": "At the beginning of each interval, retrieve the calculated value from the data points on either side of the requested timestamp.", 211 | #"Average": "Retrieve the average value of the data over the interval.", 212 | #"TimeAverage": "Retrieve the time weighted average data over the interval using Interpolated Bounding Values.", 213 | #"Total": "Retrieve the total (time integral) of the data over the interval using Interpolated Bounding Values.", 214 | #"Total2": "Retrieve the total (time integral in seconds) of the data over the interval using Simple Bounding Values.", 215 | #"TotalPerMinute": "Retrieve the total (time integral in minutes) of the data over the interval using Simple Bounding Values.", 216 | #"TotalPerHour": "Retrieve the total (time integral in hours) of the data over the interval using Simple Bounding Values.", 217 | #"TotalPer24Hours": "Retrieve the total (time integral in 24 hours) of the data over the interval using Simple Bounding Values.", 218 | #"Minimum": "Retrieve the minimum raw value in the interval with the timestamp of the start of the interval.", 219 | #"Maximum": "Retrieve the maximum raw value in the interval with the timestamp of the start of the interval.", 220 | #"MinimumActualTime": "Retrieve the minimum value in the interval and the timestamp of the minimum value.", 221 | #"MaximumActualTime": "Retrieve the maximum value in the interval and the timestamp of the maximum value.", 222 | #"Range": "Retrieve the difference between the minimum and maximum value over the interval.", 223 | #"Minimum2": "Retrieve the minimum value in the interval including the Simple Bounding Values.", 224 | #"Maximum2": "Retrieve the maximum value in the interval including the Simple Bounding Values.", 225 | #"MinimumActualTime2": "Retrieve the minimum value with the actual timestamp including the Simple Bounding Values.", 226 | #"MaximumActualTime2": "Retrieve the maximum value with the actual timestamp including the Simple Bounding Values.", 227 | #"Range2": "Retrieve the difference between the Minimum2 and Maximum2 value over the interval.", 228 | #"Count": "Retrieve the number of raw values over the interval.", 229 | #"DurationInStateZero": "Retrieve the time a Boolean or numeric was in a zero state using Simple Bounding Values.", 230 | #"DurationInStateNonZero": "Retrieve the time a Boolean or numeric was in a non-zero state using Simple Bounding Values.", 231 | #"NumberOfTransitions": "Retrieve the number of changes between zero and non-zero that a Boolean or numeric value experienced in the interval.", 232 | #"Start": "Retrieve the first value in the interval.", 233 | #"End": "Retrieve the last value in the interval.", 234 | #"Delta": "Retrieve the difference between the Start and End value in the interval.", 235 | #"StartBound": "Retrieve the value at the beginning of the interval using Simple Bounding Values.", 236 | #"EndBound": "Retrieve the value at the end of the interval using Simple Bounding Values.", 237 | #"DeltaBounds": "Retrieve the difference between the StartBound and EndBound value in the interval.", 238 | #"Instant": "Retrieve the value at the exact beginning of the interval.", 239 | #"DurationGood": "Retrieve the total duration of time in the interval during which the data is Good.", 240 | #"DurationBad": "Retrieve the total duration of time in the interval during which the data is Bad.", 241 | #"PercentGood": "Retrieve the percentage of data (0 to 100) in the interval which has Good StatusCode", 242 | #"PercentBad": "Retrieve the percentage of data (0 to 100) in the interval which has Bad StatusCode.", 243 | #"WorstQuality": "Retrieve the worst StatusCode of data in the interval.", 244 | #"WorstQuality2": "Retrieve the worst StatusCode of data in the interval including the Simple Bounding Values.", 245 | #"StandardDeviationSample": "Retrieve the standard deviation for the interval for a sample of the population (n-1).", 246 | #"VarianceSample": "Retrieve the variance for the interval as calculated by the StandardDeviationSample.", 247 | #"StandardDeviationPopulation": "Retrieve the standard deviation for the interval for a complete population (n) which includes Simple Bounding Values.", 248 | #"VariancePopulation": "Retrieve the variance for the interval as calculated by the StandardDeviationPopulation which includes Simple Bounding Values." 249 | 250 | # interval over which to aggregate. can be relative or absolute 251 | # relative time units can be found at http://localhost:55235/help#relativeTime 252 | # absolute interval can be expressed as a timespan "days.hours:minutes:seconds.subseconds" or "1.00:01:00" = 1 day 1 minute 253 | aggregate_interval <- "1minute" 254 | # if True, includes the quality of the data in the output 255 | include_quality <- FALSE 256 | # limits the max return amount (default is 10,000). 257 | max_size <- 10000 258 | # parameter is always NA on first request (this is for paging purposes and will be returned by the first request) 259 | continuation <- NA 260 | 261 | # Parameter example 262 | 263 | # 1: Processed Data 264 | # start_time <- "now-1day" 265 | # end_time <- "now" 266 | # aggregate_name <- "TimeAverage2" 267 | # aggregate_interval <- "1minute" 268 | 269 | ############################################# 270 | # CSV Output File Settings 271 | ############################################# 272 | # output file name 273 | csv_file_name <- "c:\\temp\\canaryReadDemo.csv" 274 | # if True, then deletes an existing output from the directory before writing new output (valid values are TRUE and FALSE) 275 | purge_existing_output_file <- FALSE 276 | 277 | ############################################# 278 | # Script Execution 279 | ############################################# 280 | 281 | library(dplyr) 282 | library(httr) 283 | library(jsonlite) 284 | 285 | # delete the output file if exists and configured to do so 286 | if (purge_existing_output_file) { 287 | invisible(suppressWarnings(file.remove(csv_file_name))) 288 | } else if (file.exists(csv_file_name)) { 289 | cat("Output file already exists. Delete the file or set purge_existing_output_file = TRUE to automatically delete on next run. Exiting") 290 | quit(save = "no") 291 | } 292 | 293 | # prepend the dataset and path to the tag names 294 | fully_qualified_tags_to_read <- paste(data_set_prefix, tags_to_read, sep = ".") 295 | 296 | request_body <- list ( 297 | "username" = user_name, 298 | "password" = password, 299 | "timezone" = time_zone, 300 | "application" = application 301 | ) 302 | 303 | # get a user token for authenticating the requests 304 | response <- POST(paste(api_url, "getUserToken", sep = ""), body = request_body, encode = "json") 305 | response_text <- content(response, as = "text", encoding = "UTF-8") 306 | response_content <- fromJSON(response_text) 307 | 308 | # check for any errors with the request 309 | if (response_content["statusCode"] != "Good") { 310 | cat(paste("Error retrieving a user token from the Canary api:\n", 311 | paste(response_content[["errors"]], collapse = "\n"), 312 | sep = "")) 313 | quit(save = "no") 314 | } 315 | 316 | user_token <- response_content[["userToken"]] 317 | 318 | # required parameters 319 | request_body_base <- list( 320 | "userToken" = user_token, 321 | "tags" = fully_qualified_tags_to_read, 322 | "aggregateName" = aggregate_name, 323 | "aggregateInterval" = aggregate_interval 324 | ) 325 | 326 | # optional parameters 327 | if (exists("start_time") & length(start_time) > 0) { 328 | request_body_base <- c(request_body_base, "startTime" = start_time) 329 | } 330 | if (exists("end_time") & length(end_time) > 0) { 331 | request_body_base <- c(request_body_base, "endTime" = end_time) 332 | } 333 | if (exists("include_quality")) { 334 | request_body_base <- c(request_body_base, "includeQuality" = include_quality) 335 | } 336 | if (exists("max_size")) { 337 | request_body_base <- c(request_body_base, "maxSize" = max_size) 338 | } 339 | 340 | header_row_written <- FALSE 341 | 342 | repeat { 343 | if (exists("continuation") & !is.na(continuation)[1]) { 344 | request_body <- c(request_body_base, "continuation" = list(continuation)) 345 | } else { 346 | request_body <- request_body_base 347 | } 348 | 349 | # call the /getTagData2 endpoint to get data for the tags 350 | response <- POST(paste(api_url, "getTagData2", sep = ""), body = request_body, encode = "json") 351 | response_text <- content(response, as = "text", encoding = "UTF-8") 352 | response_content <- fromJSON(response_text) 353 | 354 | # check for any errors with the request 355 | if (response_content["statusCode"] != "Good") { 356 | cat(paste("Error retrieving tag data from the Canary api:\n", 357 | paste(response_content[["errors"]], collapse = "\n"), 358 | sep = "")) 359 | quit(save = "no") 360 | } 361 | 362 | # store the continuation point so it can be used in the next call to getTagData2 363 | continuation <- response_content[["continuation"]] 364 | 365 | tag_data <- response_content[["data"]] 366 | 367 | # get the timestamp column from the first tag returned. 368 | # since we are getting aggregate data, all of the tags 369 | # will return tvqs with the same timestamps. 370 | common_time_stamps <- list(data.frame(Timestamp = tag_data[[1]][["t"]])) 371 | 372 | # add a new "Timestamp" column to the front of the tag data list 373 | modified_tag_data <- c(common_time_stamps, tag_data) 374 | 375 | # create a dataframe from the tag data list. 376 | result_data_frame <- data.frame(modified_tag_data, check.names = FALSE) 377 | 378 | # remove the timestamp column from each tag. 379 | result_data_frame <- result_data_frame %>% 380 | select(!matches(".*?\\.t$")) 381 | 382 | # rename the columns to be easier to read. 383 | colnames(result_data_frame) <- sub("(.*?)\\.v$", "\\1", colnames(result_data_frame)) 384 | colnames(result_data_frame) <- sub("(.*?)\\.q$", "\\1 Quality", colnames(result_data_frame)) 385 | 386 | # write the dataframe to a csv file. 387 | invisible(write.table(x = result_data_frame, file = csv_file_name, append = TRUE, sep = ",", row.names = FALSE, col.names = !header_row_written)) 388 | header_row_written <- TRUE 389 | 390 | if (is.null(continuation) || is.na(continuation)[1]) { 391 | break 392 | } 393 | } 394 | 395 | # revoke the user token 396 | request_body <- list("userToken" = user_token) 397 | invisible(POST(paste(api_url, "revokeUserToken", sep = ""), body = request_body, encode = "json")) 398 | -------------------------------------------------------------------------------- /Samples/V23/Data Retrieval/Web API/Javascript/data_retrieval_demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Views API Demo 5 | 6 | 80 | 83 | 84 | 85 | 86 | 356 | 381 | 382 | 383 |
384 |

Views API Demo

385 |

Before using the API, the proper endpoints must be turned on to listen for requests. This demo has been configured to read data from a views service and historian running on the local machine.

386 |
    387 |
  1. Open the "Canary Admin" program.
  2. 388 |
  3. Click on the "Views" info box on the home screen.
  4. 389 |
  5. Click on "Configuration" in the bottom tab control.
  6. 390 |
  7. Click on "Endpoints" in the side tab control.
  8. 391 |
  9. Turn on the (Web API) endpoints that you will use. Note the port numbers. These are the ports that will be listening for the Web API requests.
  10. 392 |
393 |

Click on the buttons to execute method calls to an api on the local machine. Hit the F12 key to open the browser console window to view the results of each method call.

394 |
    395 |
  1. Call "GetTimeZones" and keep the "timezone" that should be used when returning timestamps.
  2. 396 |
  3. Call "GetUserToken" to get a "userToken" that is required in subsequent calls.
  4. 397 |
  5. Call "BrowseTags" to load a tag array used to retrieve data in subsequent calls.
  6. 398 |
  7. Call "GetCurrentValue" to get current values of the tag array.
  8. 399 |
  9. Call "GetRawData" to get raw data of the tag array.
  10. 400 |
  11. Call "GetProcessedData" to get processed data of the tag array.
  12. 401 |
  13. Revoke the "userToken" when finished retrieving data.
  14. 402 |
403 |
404 |
405 |

Primary

406 |
407 |
408 |
409 |

Supplemental

410 |
411 |
412 |
413 |
414 |
415 |

Page Source

416 |

This source uses the jQuery library.

417 |
418 |
419 | 420 | -------------------------------------------------------------------------------- /Samples/V23/Data Storage/.NET Client/Documentation/Store & Forward API (& Helper).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CanaryLabs/SampleCode/4c209c62b5e81e4eb9872df4b9d96ccc05b03bd6/Samples/V23/Data Storage/.NET Client/Documentation/Store & Forward API (& Helper).pdf -------------------------------------------------------------------------------- /Samples/V23/Data Storage/.NET Client/HelperClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | // helper 7 | using SAF_Helper; 8 | using SAF_Helper.SAF_SenderService; 9 | 10 | namespace SAF_Examples 11 | { 12 | public class HelperClassExample 13 | { 14 | #region Private Members 15 | 16 | private string _historian = "localhost"; // necessary when using named pipe binding (other bindings could be configured if necessary) 17 | private SAFSenderServiceContractClient _client = null; 18 | private string _sessionId = null; 19 | private Dictionary _tagMap = new Dictionary(); 20 | 21 | #endregion 22 | 23 | #region Public Methods 24 | 25 | public Setting BuildSetting(string name, object value) 26 | { 27 | Setting setting = new Setting(); 28 | setting.name = name; 29 | setting.value = value; 30 | return setting; 31 | } 32 | 33 | public string GetSessionId() 34 | { 35 | Connect(); 36 | 37 | if (_sessionId == null) 38 | { 39 | bool failed; 40 | string clientId = "HelperExample"; 41 | 42 | // arbitrary settings used for example code 43 | List settings = new List(); 44 | settings.Add(BuildSetting(SettingNames.AutoCreateDatasets, true)); 45 | settings.Add(BuildSetting(SettingNames.PacketDelay, 500)); 46 | settings.Add(BuildSetting(SettingNames.TrackErrors, true)); 47 | 48 | string result = _client.GetSessionId(out failed, _historian, clientId, settings.ToArray()); 49 | if (failed) 50 | { 51 | // handle error 52 | string error = result; 53 | } 54 | else 55 | _sessionId = result; 56 | } 57 | 58 | return _sessionId; 59 | } 60 | 61 | public void Connect() 62 | { 63 | if (_client == null) 64 | { 65 | ConnectionType connectionType = ConnectionType.NetPipe_Anonymous; 66 | string host = null; 67 | string usernameCredentials = null; 68 | string passwordCredentials = null; 69 | HelperClass.Connect(connectionType, host, usernameCredentials, passwordCredentials, out _client); 70 | } 71 | } 72 | 73 | public Dictionary GetTagIds() 74 | { 75 | Connect(); 76 | 77 | int tagCount = 4; 78 | if (_tagMap.Count != tagCount) 79 | { 80 | bool failed; 81 | string sessionId = GetSessionId(); 82 | 83 | string dataSet = "HelperExample"; 84 | Tag[] tags = new Tag[tagCount]; 85 | for (int i = 0; i < tagCount; i++) 86 | { 87 | Tag tag = new Tag(); 88 | tag.name = String.Format("{0}.Tag {1:D4}", dataSet, i + 1); 89 | tag.transformEquation = null; 90 | tag.timeExtension = true; 91 | tags[i] = tag; 92 | } 93 | 94 | // no time extension 95 | object[] results = HelperClass.GetTagIds(out failed, _client, sessionId, tags); 96 | 97 | if (failed) 98 | { 99 | for (int i = 0; i < tagCount; i++) 100 | { 101 | object result = results[i]; 102 | if (!(result is int)) 103 | { 104 | // handle error 105 | string error = (string)result; 106 | } 107 | } 108 | return _tagMap; 109 | } 110 | else 111 | { 112 | // create tag mapping to reference id 113 | for (int i = 0; i < tagCount; i++) 114 | { 115 | int id = (int)results[i]; 116 | _tagMap.Add(tags[i].name, id); 117 | } 118 | } 119 | } 120 | 121 | return _tagMap; 122 | } 123 | 124 | public string StoreData() 125 | { 126 | Connect(); 127 | 128 | List tvqsList = new List(); 129 | List propertiesList = new List(); 130 | List annotationsList = new List(); 131 | 132 | // create data to store 133 | DateTime now = DateTime.Now; 134 | string sessionId = GetSessionId(); 135 | Dictionary tagIds = GetTagIds(); 136 | foreach (KeyValuePair pair in tagIds) 137 | { 138 | string tagName = pair.Key; 139 | int id = pair.Value; 140 | 141 | // add tvq data 142 | for (int i = 0; i < 500; i++) 143 | { 144 | TVQ tvq = new TVQ(); 145 | tvq.id = id; 146 | tvq.timestamp = now.AddTicks(i); 147 | tvq.value = i % 100; 148 | tvq.quality = StandardQualities.Good; 149 | tvqsList.Add(tvq); 150 | } 151 | 152 | // add property data 153 | Property highScale = new Property(); 154 | highScale.id = id; 155 | highScale.description = null; 156 | highScale.timestamp = now; 157 | highScale.name = StandardPropertyNames.ScaleHigh; 158 | highScale.value = 100; 159 | highScale.quality = StandardQualities.Good; 160 | propertiesList.Add(highScale); 161 | 162 | // add property data 163 | Property lowScale = new Property(); 164 | lowScale.id = id; 165 | lowScale.description = null; 166 | lowScale.timestamp = now; 167 | lowScale.name = StandardPropertyNames.ScaleLow; 168 | lowScale.value = 0; 169 | lowScale.quality = StandardQualities.Good; 170 | propertiesList.Add(lowScale); 171 | 172 | // add property data 173 | Property sampleInterval = new Property(); 174 | sampleInterval.id = id; 175 | sampleInterval.description = null; 176 | sampleInterval.timestamp = now; 177 | sampleInterval.name = StandardPropertyNames.SampleInterval; 178 | sampleInterval.value = TimeSpan.FromSeconds(1); 179 | sampleInterval.quality = StandardQualities.Good; 180 | propertiesList.Add(sampleInterval); 181 | 182 | // add annotation 183 | Annotation annotation = new Annotation(); 184 | annotation.id = id; 185 | annotation.timestamp = now; 186 | //annotation.createdAt = now; // only necessary if creation time differs from annotation timestamp 187 | annotation.user = "Example User"; 188 | annotation.value = "Example Annotation"; 189 | annotationsList.Add(annotation); 190 | } 191 | 192 | int tvqsStored; 193 | int propertiesStored; 194 | int annotationsStored; 195 | 196 | TVQ[] tvqs = tvqsList.ToArray(); 197 | Property[] properties = propertiesList.ToArray(); 198 | Annotation[] annotations = annotationsList.ToArray(); 199 | 200 | // send only tvqs in this call 201 | //string result = SAF_HelperClass.StoreData(client, sessionId, tvqs, out tvqsStored); 202 | 203 | // send only properties in this call 204 | //string result = SAF_HelperClass.StoreData(client, sessionId, properties, out propertiesStored); 205 | 206 | // send only annotations in this call 207 | //string result = SAF_HelperClass.StoreData(client, sessionId, annotations, out annotationsStored); 208 | 209 | // send tvqs, properties, and annotations in this call 210 | string result = HelperClass.StoreData(_client, sessionId, tvqs, properties, annotations, out tvqsStored, out propertiesStored, out annotationsStored); 211 | 212 | return result; 213 | } 214 | 215 | public bool IsLocalhost() 216 | { 217 | bool result = HelperClass.IsLocalhost(_historian); 218 | return result; 219 | } 220 | 221 | public bool TryParseEndpoint() 222 | { 223 | ConnectionType connectionType; 224 | string host; 225 | int port; 226 | 227 | string endpoint = "net.pipe://localhost/saf/sender/anonymous"; 228 | bool result = HelperClass.TryParseEndpoint(endpoint, out connectionType, out host, out port); 229 | 230 | return result; 231 | } 232 | 233 | public string ReleaseSession() 234 | { 235 | if ((_client != null) && (_sessionId != null)) 236 | { 237 | bool failed; 238 | string result = _client.ReleaseSession(out failed, _sessionId); 239 | if (failed) 240 | { 241 | // handle error 242 | string error = result; 243 | } 244 | 245 | // reset stored variables 246 | _sessionId = null; 247 | _tagMap.Clear(); 248 | 249 | return result; 250 | } 251 | 252 | return null; 253 | } 254 | 255 | #endregion 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /Samples/V23/Data Storage/.NET Client/HelperSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | // helper 7 | using SAF_Helper; 8 | using SAF_Helper.SAF_SenderService; 9 | 10 | namespace SAF_Examples 11 | { 12 | public class HelperSessionExample 13 | { 14 | private string _historian = "localhost"; 15 | private string _dataset = "SessionExample"; 16 | private HelperSession _session = null; 17 | private Dictionary _tagMap = new Dictionary(); 18 | 19 | public Setting BuildSetting(string name, object value) 20 | { 21 | Setting setting = new Setting(); 22 | setting.name = name; 23 | setting.value = value; 24 | return setting; 25 | } 26 | 27 | public void Connect() 28 | { 29 | if (_session != null) 30 | return; 31 | 32 | _session = new HelperSession(); 33 | 34 | // arbitrary settings used for example code 35 | string clientId = "SessionExample"; 36 | List settings = new List(); 37 | settings.Add(BuildSetting("AutoCreateDataSets", true)); 38 | settings.Add(BuildSetting("PacketDelay", 500)); 39 | 40 | string result = _session.Connect(_historian, clientId, settings.ToArray()); 41 | } 42 | 43 | public void GetSessionId() 44 | { 45 | // NOTE: the helper session class will keep track of the session id 46 | // internally so that it is not required when making calls 47 | } 48 | 49 | public Dictionary GetTagIds() 50 | { 51 | Connect(); 52 | 53 | int tagCount = 1; 54 | if (_tagMap.Count != tagCount) 55 | { 56 | Tag[] tags = new Tag[tagCount]; 57 | for (int i = 0; i < tagCount; i++) 58 | { 59 | Tag tag = new Tag(); 60 | tag.name = String.Format("{0}.Tag {1:D4}", _dataset, i + 1); 61 | tag.transformEquation = null; 62 | tag.timeExtension = true; 63 | tags[i] = tag; 64 | } 65 | 66 | int[] tagIds; 67 | string error = _session.GetTagIds(tags, out tagIds); 68 | if (error != null) 69 | { 70 | // handle error 71 | return null; 72 | } 73 | 74 | for (int i = 0; i < tagCount; i++) 75 | _tagMap.Add(tags[i].name, tagIds[i]); 76 | } 77 | 78 | return _tagMap; 79 | } 80 | 81 | public string StoreData() 82 | { 83 | Connect(); 84 | 85 | List tvqs = new List(); 86 | List properties = new List(); 87 | List annotations = new List(); 88 | 89 | // create data to store 90 | DateTime now = DateTime.Now; 91 | Dictionary tagIds = GetTagIds(); 92 | foreach (KeyValuePair pair in tagIds) 93 | { 94 | string tagName = pair.Key; 95 | int id = pair.Value; 96 | 97 | // add tvq data 98 | for (int i = 0; i < 5; i++) 99 | { 100 | TVQ tvq = new TVQ(); 101 | tvq.id = id; 102 | tvq.timestamp = now.AddTicks(i); 103 | tvq.value = i % 100; 104 | tvq.quality = StandardQualities.Good; 105 | tvqs.Add(tvq); 106 | } 107 | 108 | // add property data 109 | Property highScale = new Property(); 110 | highScale.id = id; 111 | highScale.description = null; 112 | highScale.timestamp = now; 113 | highScale.name = StandardPropertyNames.ScaleHigh; 114 | highScale.value = 100; 115 | highScale.quality = StandardQualities.Good; 116 | properties.Add(highScale); 117 | 118 | // add property data 119 | Property lowScale = new Property(); 120 | lowScale.id = id; 121 | lowScale.description = null; 122 | lowScale.timestamp = now; 123 | lowScale.name = StandardPropertyNames.ScaleLow; 124 | lowScale.value = 0; 125 | lowScale.quality = StandardQualities.Good; 126 | properties.Add(lowScale); 127 | 128 | // add property data 129 | Property sampleInterval = new Property(); 130 | sampleInterval.id = id; 131 | sampleInterval.description = null; 132 | sampleInterval.timestamp = now; 133 | sampleInterval.name = StandardPropertyNames.SampleInterval; 134 | sampleInterval.value = TimeSpan.FromSeconds(1); 135 | sampleInterval.quality = StandardQualities.Good; 136 | properties.Add(sampleInterval); 137 | 138 | // add annotation 139 | Annotation annotation = new Annotation(); 140 | annotation.id = id; 141 | annotation.timestamp = now; 142 | //annotation.createdAt = now; // only necessary if creation time differs from annotation timestamp 143 | annotation.user = "Example User"; 144 | annotation.value = "Example Annotation"; 145 | annotations.Add(annotation); 146 | } 147 | 148 | // send only tvqs in this call 149 | //string result = session.StoreTVQs(tvqs.ToArray()); 150 | 151 | // send only properties in this call 152 | //string result = session.StoreProperties(properties.ToArray()); 153 | 154 | // send only annotations in this call 155 | //string result = session.StoreAnnotations(annotations.ToArray()); 156 | 157 | // send tvqs, properties, and annotations in this call 158 | string result = _session.StoreData(tvqs.ToArray(), properties.ToArray(), annotations.ToArray()); 159 | 160 | return result; 161 | } 162 | 163 | // NOTE: this method of writing data will buffer the added items in the 164 | // sessions class and send all items when the FlushData method is called 165 | public string StoreData2() 166 | { 167 | Connect(); 168 | 169 | // create data to store 170 | DateTime now = DateTime.Now; 171 | Dictionary tagIds = GetTagIds(); 172 | foreach (KeyValuePair pair in tagIds) 173 | { 174 | string tagName = pair.Key; 175 | int id = pair.Value; 176 | 177 | // add tvq data 178 | for (int i = 0; i < 500; i++) 179 | { 180 | TVQ tvq = new TVQ(); 181 | tvq.id = id; 182 | tvq.timestamp = now.AddTicks(i); 183 | tvq.value = i % 100; 184 | tvq.quality = StandardQualities.Good; 185 | _session.AddTVQ(tvq); 186 | } 187 | 188 | // add property data 189 | Property highScale = new Property(); 190 | highScale.id = id; 191 | highScale.description = null; 192 | highScale.timestamp = now; 193 | highScale.name = StandardPropertyNames.ScaleHigh; 194 | highScale.value = 100; 195 | highScale.quality = StandardQualities.Good; 196 | _session.AddProperty(highScale); 197 | 198 | // add property data 199 | Property lowScale = new Property(); 200 | lowScale.id = id; 201 | lowScale.description = null; 202 | lowScale.timestamp = now; 203 | lowScale.name = StandardPropertyNames.ScaleLow; 204 | lowScale.value = 0; 205 | lowScale.quality = StandardQualities.Good; 206 | _session.AddProperty(lowScale); 207 | 208 | // add property data 209 | Property sampleInterval = new Property(); 210 | sampleInterval.id = id; 211 | sampleInterval.description = null; 212 | sampleInterval.timestamp = now; 213 | sampleInterval.name = StandardPropertyNames.SampleInterval; 214 | sampleInterval.value = TimeSpan.FromSeconds(1); 215 | sampleInterval.quality = StandardQualities.Good; 216 | _session.AddProperty(sampleInterval); 217 | 218 | // add annotation 219 | Annotation annotation = new Annotation(); 220 | annotation.id = id; 221 | annotation.timestamp = now; 222 | //annotation.createdAt = now; // only necessary if creation time differs from annotation timestamp 223 | annotation.user = "Example User"; 224 | annotation.value = "Example Annotation"; 225 | _session.AddAnnotation(annotation); 226 | } 227 | 228 | int storedTVQs; 229 | int storedProperties; 230 | int storedAnnotations; 231 | return _session.FlushData(out storedTVQs, out storedProperties, out storedAnnotations); 232 | } 233 | 234 | public string Disconnect() 235 | { 236 | return _session.Disconnect(); 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /Samples/V23/Data Storage/.NET Client/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | namespace SAF_Examples 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | // write using raw client 14 | Console.WriteLine("Writing data using raw client..."); 15 | RawClientExample rawClient = new RawClientExample(); 16 | rawClient.StoreData(); 17 | 18 | // write using helper class 19 | Console.WriteLine("Writing data using helper class..."); 20 | HelperClassExample helperClass = new HelperClassExample(); 21 | helperClass.StoreData(); 22 | 23 | // write using helper session 24 | Console.WriteLine("Writing data using helper session..."); 25 | HelperSessionExample helperSession = new HelperSessionExample(); 26 | helperSession.StoreData(); 27 | 28 | Console.WriteLine("Disconnecting..."); 29 | 30 | // disconnect raw client 31 | Thread.Sleep(3000); 32 | rawClient.ReleaseSession(); 33 | 34 | // disconnect helper class 35 | Thread.Sleep(3000); 36 | helperClass.ReleaseSession(); 37 | 38 | // disconnect helper session 39 | Thread.Sleep(3000); 40 | helperSession.Disconnect(); 41 | 42 | Console.WriteLine("Finished."); 43 | Console.ReadLine(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Samples/V23/Data Storage/.NET Client/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SAF_Examples")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Windows User")] 12 | [assembly: AssemblyProduct("SAF_Examples")] 13 | [assembly: AssemblyCopyright("Copyright © Windows User 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a92977e1-dc6d-4fcf-b26a-a93b77e8a322")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Samples/V23/Data Storage/.NET Client/RawClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.ServiceModel; 6 | 7 | // helper 8 | using SAF_Helper.SAF_SenderService; 9 | 10 | namespace SAF_Examples 11 | { 12 | public class RawClientExample 13 | { 14 | #region Private Members 15 | 16 | private string _historian = "localhost"; // necessary when using named pipe binding (other bindings could be configured if necessary) 17 | private string _dataset = "RawExample"; 18 | private SAFSenderServiceContractClient _client = null; 19 | private string _sessionId = null; 20 | private Dictionary _tagMap = new Dictionary(); 21 | 22 | #endregion 23 | 24 | #region Private Methods 25 | 26 | static private NetNamedPipeBinding CreateNamedPipeBindingBase() 27 | { 28 | NetNamedPipeBinding pipeBinding = new NetNamedPipeBinding(); 29 | pipeBinding.MaxBufferPoolSize = 2147483647; 30 | pipeBinding.MaxReceivedMessageSize = 2147483647; 31 | pipeBinding.MaxConnections = 50; 32 | pipeBinding.ReaderQuotas.MaxStringContentLength = 2147483647; 33 | pipeBinding.ReaderQuotas.MaxArrayLength = 2147483647; 34 | pipeBinding.ReaderQuotas.MaxBytesPerRead = 2147483647; 35 | pipeBinding.ReceiveTimeout = TimeSpan.FromHours(1); 36 | 37 | return pipeBinding; 38 | } 39 | 40 | private void Connect() 41 | { 42 | if (_client == null) 43 | { 44 | NetNamedPipeBinding pipeBinding = CreateNamedPipeBindingBase(); 45 | pipeBinding.Security.Mode = NetNamedPipeSecurityMode.None; 46 | System.ServiceModel.Channels.Binding binding = pipeBinding; 47 | 48 | // host and port are ignored because this is always local 49 | string address = "net.pipe://localhost/saf/sender/anonymous"; 50 | EndpointAddress endpoint = new EndpointAddress(new Uri(address)); 51 | _client = new SAFSenderServiceContractClient(binding, endpoint); 52 | } 53 | } 54 | 55 | #endregion 56 | 57 | #region Public Properties 58 | 59 | public string SessionId 60 | { 61 | get { return _sessionId; } 62 | } 63 | 64 | #endregion 65 | 66 | #region Public Methods 67 | 68 | public string InterfaceVersion() 69 | { 70 | Connect(); 71 | 72 | return _client.Version(); 73 | } 74 | 75 | public string[] GetDataSets() 76 | { 77 | Connect(); 78 | 79 | bool failed; 80 | 81 | string[] results = _client.GetDataSets(out failed, _historian); 82 | if (failed) 83 | { 84 | // handle error 85 | string error = results[0]; 86 | } 87 | 88 | return results; 89 | } 90 | 91 | public Setting BuildSetting(string name, object value) 92 | { 93 | Setting setting = new Setting(); 94 | setting.name = name; 95 | setting.value = value; 96 | return setting; 97 | } 98 | 99 | public string GetSessionId() 100 | { 101 | Connect(); 102 | 103 | if (_sessionId == null) 104 | { 105 | bool failed; 106 | string clientId = "RawExample"; 107 | 108 | // arbitrary settings used for example code 109 | List settings = new List(); 110 | settings.Add(BuildSetting("AutoCreateDataSets", true)); 111 | settings.Add(BuildSetting("PacketDelay", 500)); 112 | settings.Add(BuildSetting("TrackErrors", true)); 113 | 114 | string result = _client.GetSessionId(out failed, _historian, clientId, settings.ToArray()); 115 | if (failed) 116 | { 117 | // handle error 118 | string error = result; 119 | } 120 | else 121 | _sessionId = result; 122 | } 123 | 124 | return _sessionId; 125 | } 126 | 127 | public string[] UpdateSettings() 128 | { 129 | Connect(); 130 | 131 | bool failed; 132 | string sessionId = GetSessionId(); 133 | 134 | // arbitrary settings used for example code 135 | List settings = new List(); 136 | settings.Add(BuildSetting("PacketDelay", 0)); 137 | settings.Add(BuildSetting("TrackErrors", false)); 138 | 139 | string[] results = _client.UpdateSettings(out failed, sessionId, settings.ToArray()); 140 | if (failed) 141 | { 142 | foreach (string result in results) 143 | { 144 | if (result != null) 145 | { 146 | // handle error 147 | string error = result; 148 | } 149 | } 150 | } 151 | 152 | return results; 153 | } 154 | 155 | public Dictionary GetTagIds() 156 | { 157 | Connect(); 158 | 159 | int tagCount = 4; 160 | if (_tagMap.Count != tagCount) 161 | { 162 | List tags = new List(); 163 | for (int i = 0; i < tagCount; i++) 164 | { 165 | Tag tag = new Tag(); 166 | tag.name = String.Format("{0}.Tag {1:D4}", _dataset, i + 1); 167 | tag.transformEquation = "Value * 10"; 168 | tag.timeExtension = true; 169 | tags.Add(tag); 170 | } 171 | 172 | bool failed; 173 | string sessionId = GetSessionId(); 174 | object[] results = _client.GetTagIds(out failed, sessionId, tags.ToArray()); 175 | if (failed) 176 | { 177 | for (int i = 0; i < tags.Count; i++) 178 | { 179 | object result = results[i]; 180 | if (!(result is int)) 181 | { 182 | // handle error 183 | string error = (string)result; 184 | } 185 | } 186 | return _tagMap; 187 | } 188 | else 189 | { 190 | // create tag mapping to reference id 191 | for (int i = 0; i < tags.Count; i++) 192 | { 193 | string tagName = tags[i].name; 194 | int id = (int)results[i]; 195 | _tagMap.Add(tagName, id); 196 | } 197 | } 198 | } 199 | 200 | return _tagMap; 201 | } 202 | 203 | public string StoreData() 204 | { 205 | Connect(); 206 | 207 | List tvqsList = new List(); 208 | List propertiesList = new List(); 209 | List annotationsList = new List(); 210 | 211 | // create data to store 212 | DateTime now = DateTime.Now; 213 | string sessionId = GetSessionId(); 214 | Dictionary tagIds = GetTagIds(); 215 | foreach (KeyValuePair pair in tagIds) 216 | { 217 | string tagName = pair.Key; 218 | int id = pair.Value; 219 | 220 | // add tvq data 221 | for (int i = 0; i < 500; i++) 222 | { 223 | TVQ tvq = new TVQ(); 224 | tvq.id = id; 225 | tvq.timestamp = now.AddTicks(i); 226 | tvq.value = i % 100; 227 | tvq.quality = 0xC0; 228 | tvqsList.Add(tvq); 229 | } 230 | 231 | // add property data 232 | Property highScale = new Property(); 233 | highScale.id = id; 234 | highScale.description = null; 235 | highScale.timestamp = now; 236 | highScale.name = "Default High Scale"; 237 | highScale.value = 100; 238 | highScale.quality = 0xC0; 239 | propertiesList.Add(highScale); 240 | 241 | // add property data 242 | Property lowScale = new Property(); 243 | lowScale.id = id; 244 | lowScale.description = null; 245 | lowScale.timestamp = now; 246 | lowScale.name = "Default Low Scale"; 247 | lowScale.value = 0; 248 | lowScale.quality = 0xC0; 249 | propertiesList.Add(lowScale); 250 | 251 | // add annotation 252 | Annotation annotation = new Annotation(); 253 | annotation.id = id; 254 | annotation.timestamp = now; 255 | //annotation.createdAt = now; // only necessary if creation time differs from annotation timestamp 256 | annotation.user = "Example User"; 257 | annotation.value = "Example Annotation"; 258 | annotationsList.Add(annotation); 259 | } 260 | 261 | bool failed; 262 | TVQ[] tvqs = tvqsList.ToArray(); 263 | Property[] properties = propertiesList.ToArray(); 264 | Annotation[] annotations = annotationsList.ToArray(); 265 | 266 | string result = _client.StoreData(out failed, sessionId, tvqs, properties, annotations); 267 | if (failed) 268 | { 269 | // handle error 270 | string error = result; 271 | } 272 | 273 | return result; 274 | } 275 | 276 | public string[] NoData() 277 | { 278 | Connect(); 279 | 280 | bool failed; 281 | string sessionId = GetSessionId(); 282 | Dictionary tagIds = GetTagIds(); 283 | 284 | return _client.NoData(out failed, sessionId, tagIds.Values.ToArray()); 285 | } 286 | 287 | public string CreateNewFile() 288 | { 289 | Connect(); 290 | 291 | bool failed; 292 | string sessionId = GetSessionId(); 293 | HistorianFile newFile = new HistorianFile(); 294 | newFile.dataSet = "RawExample"; 295 | newFile.fileTime = DateTime.MinValue; 296 | 297 | string result = _client.CreateNewFile(out failed, sessionId, newFile); 298 | if (failed) 299 | { 300 | // handle error 301 | string error = result; 302 | } 303 | 304 | return result; 305 | } 306 | 307 | public string FileRollOver() 308 | { 309 | Connect(); 310 | 311 | bool failed; 312 | string sessionId = GetSessionId(); 313 | HistorianFile rollOverFile = new HistorianFile(); 314 | rollOverFile.dataSet = "RawExample"; 315 | rollOverFile.fileTime = DateTime.MinValue; 316 | 317 | string result = _client.FileRollOver(out failed, sessionId, rollOverFile); 318 | if (failed) 319 | { 320 | // handle error 321 | string error = result; 322 | } 323 | 324 | return result; 325 | } 326 | 327 | public string GetErrors() 328 | { 329 | Connect(); 330 | 331 | bool failed; 332 | Errors errors; 333 | string sessionId = GetSessionId(); 334 | 335 | string result = _client.GetErrors(out failed, out errors, sessionId); 336 | if (failed) 337 | { 338 | // handle error 339 | string error = result; 340 | } 341 | 342 | return result; 343 | } 344 | 345 | public string ReleaseSession() 346 | { 347 | if ((_client != null) && (_sessionId != null)) 348 | { 349 | bool failed; 350 | string result = _client.ReleaseSession(out failed, _sessionId); 351 | if (failed) 352 | { 353 | // handle error 354 | string error = result; 355 | } 356 | 357 | // reset stored variables 358 | this._sessionId = null; 359 | this._tagMap.Clear(); 360 | 361 | return result; 362 | } 363 | 364 | return null; 365 | } 366 | 367 | #endregion 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /Samples/V23/Data Storage/.NET Client/References/SAF_Helper.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CanaryLabs/SampleCode/4c209c62b5e81e4eb9872df4b9d96ccc05b03bd6/Samples/V23/Data Storage/.NET Client/References/SAF_Helper.dll -------------------------------------------------------------------------------- /Samples/V23/Data Storage/.NET Client/SAF_Examples.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {45562696-B59F-476D-804C-4B82869ECF0D} 9 | Exe 10 | Properties 11 | SAF_Examples 12 | SAF_Examples 13 | v4.7.1 14 | 15 | 16 | 512 17 | 18 | 19 | x86 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | false 28 | 29 | 30 | x86 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | false 38 | 39 | 40 | 41 | False 42 | References\SAF_Helper.dll 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Always 68 | 69 | 70 | 71 | 78 | -------------------------------------------------------------------------------- /Samples/V23/Data Storage/.NET Client/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Samples/V23/Data Storage/Node-Red/ModbusClient/CanaryModbusStorage.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": "17c20294.37a725", 3 | "type": "tab", 4 | "label": "Canary Modbus Storage", 5 | "disabled": true, 6 | "info": "Reads registers from a modbus device and stores tag data in the Canary historian" 7 | }, 8 | { 9 | "id": "b4c20f6c.f829e8", 10 | "type": "http request", 11 | "z": "17c20294.37a725", 12 | "name": "GetCanaryUserToken", 13 | "method": "POST", 14 | "ret": "obj", 15 | "url": "http://localhost:55253/api/v1/getUserToken", 16 | "tls": "", 17 | "x": 680, 18 | "y": 480, 19 | "wires": [["b4acc8dc.4b039", 20 | "ff3330ab.e8598"]] 21 | }, 22 | { 23 | "id": "2ffc7d75.a63c4a", 24 | "type": "catch", 25 | "z": "17c20294.37a725", 26 | "name": "", 27 | "scope": null, 28 | "x": 120, 29 | "y": 760, 30 | "wires": [["3a8072fd.9ad666"]] 31 | }, 32 | { 33 | "id": "3a8072fd.9ad666", 34 | "type": "debug", 35 | "z": "17c20294.37a725", 36 | "name": "Debug Exceptions", 37 | "active": true, 38 | "tosidebar": true, 39 | "console": false, 40 | "tostatus": false, 41 | "complete": "true", 42 | "x": 318, 43 | "y": 760.25, 44 | "wires": [] 45 | }, 46 | { 47 | "id": "9966877f.6710f8", 48 | "type": "debug", 49 | "z": "17c20294.37a725", 50 | "name": "", 51 | "active": true, 52 | "tosidebar": true, 53 | "console": false, 54 | "tostatus": false, 55 | "complete": "payload", 56 | "x": 410, 57 | "y": 920, 58 | "wires": [] 59 | }, 60 | { 61 | "id": "b56ac976.c8d66", 62 | "type": "inject", 63 | "z": "17c20294.37a725", 64 | "name": "global.canarySessionToken", 65 | "topic": "", 66 | "payload": "canarySessionToken", 67 | "payloadType": "global", 68 | "repeat": "", 69 | "crontab": "", 70 | "once": false, 71 | "onceDelay": 0.1, 72 | "x": 190, 73 | "y": 920, 74 | "wires": [["9966877f.6710f8"]] 75 | }, 76 | { 77 | "id": "b4acc8dc.4b039", 78 | "type": "function", 79 | "z": "17c20294.37a725", 80 | "name": "Set Global UserToken", 81 | "func": "global.set(\"canaryUserToken\", msg.payload.userToken);\nreturn msg;", 82 | "outputs": 1, 83 | "noerr": 0, 84 | "x": 180, 85 | "y": 540, 86 | "wires": [["3fb23d54.3d3812"]] 87 | }, 88 | { 89 | "id": "d920b440.aaade", 90 | "type": "comment", 91 | "z": "17c20294.37a725", 92 | "name": "Retrieve global session token and print to debug", 93 | "info": "", 94 | "x": 240, 95 | "y": 880, 96 | "wires": [] 97 | }, 98 | { 99 | "id": "70c92869.38252", 100 | "type": "http request", 101 | "z": "17c20294.37a725", 102 | "name": "GetCanarySessionToken", 103 | "method": "POST", 104 | "ret": "obj", 105 | "url": "http://localhost:55253/api/v1/getSessionToken", 106 | "tls": "", 107 | "x": 690, 108 | "y": 540, 109 | "wires": [["b7351ee9.73593"]] 110 | }, 111 | { 112 | "id": "3fb23d54.3d3812", 113 | "type": "template", 114 | "z": "17c20294.37a725", 115 | "name": "Format SessionToken JSON", 116 | "field": "payload", 117 | "fieldType": "msg", 118 | "format": "handlebars", 119 | "syntax": "mustache", 120 | "template": "{\n \"userToken\":\"{{global.canaryUserToken}}\",\n \"historians\":[\n \"Josh\"\n ],\n \"clientId\":\"Node-Red Client\",\n \"settings\": {\n \"autoCreateDatasets\": true\n }\n}", 121 | "output": "json", 122 | "x": 440, 123 | "y": 540, 124 | "wires": [["70c92869.38252"]] 125 | }, 126 | { 127 | "id": "b7351ee9.73593", 128 | "type": "function", 129 | "z": "17c20294.37a725", 130 | "name": "Set Global SessionToken", 131 | "func": "global.set(\"canarySessionToken\", msg.payload.sessionToken);\nreturn msg;", 132 | "outputs": 1, 133 | "noerr": 0, 134 | "x": 190, 135 | "y": 600, 136 | "wires": [["d0df7bd7.ef32"]] 137 | }, 138 | { 139 | "id": "67b56a47.716794", 140 | "type": "debug", 141 | "z": "17c20294.37a725", 142 | "name": "", 143 | "active": true, 144 | "tosidebar": true, 145 | "console": false, 146 | "tostatus": false, 147 | "complete": "payload", 148 | "x": 410, 149 | "y": 840, 150 | "wires": [] 151 | }, 152 | { 153 | "id": "69879dff.5d0f7c", 154 | "type": "inject", 155 | "z": "17c20294.37a725", 156 | "name": "", 157 | "topic": "", 158 | "payload": "canaryUserToken", 159 | "payloadType": "global", 160 | "repeat": "", 161 | "crontab": "", 162 | "once": false, 163 | "onceDelay": 0.1, 164 | "x": 180, 165 | "y": 840, 166 | "wires": [["67b56a47.716794"]] 167 | }, 168 | { 169 | "id": "1a7b8eb1.f679c9", 170 | "type": "comment", 171 | "z": "17c20294.37a725", 172 | "name": "Retrieve global user token and print to debug", 173 | "info": "", 174 | "x": 230, 175 | "y": 800, 176 | "wires": [] 177 | }, 178 | { 179 | "id": "7418f793.0710d8", 180 | "type": "function", 181 | "z": "17c20294.37a725", 182 | "name": "Log Coils", 183 | "func": "// takes an array of booleans from a modbus coil read and \n// builds the payload to pass to the canary logging flow\n\nvar date = new Date();\nvar canaryMsg = {};\ncanaryMsg.payload = {};\ncanaryMsg.payload.userToken = global.get(\"canaryUserToken\");\ncanaryMsg.payload.sessionToken = global.get(\"canarySessionToken\");\ncanaryMsg.payload.tvqs = {};\n\n// configure your tag list here. \n// tag name should be fully qualified with what should be stored in the\n// canary historian ex. ..\n// this list needs to be <= the number of coils\n// being read from modbus (configured in the modbus function)\nvar tags = \n[\n \"Node-Red.Coil1\", // this tag is array item 0\n \"Node-Red.Coil2\", // this tag is array item 1\n \"Node-Red.Coil3\", // this tag is array item 2\n \"Node-Red.Coil4\", // ...\n \"Node-Red.Coil5\",\n \"Node-Red.Coil6\",\n \"Node-Red.Coil7\",\n \"Node-Red.Coil8\",\n \"Node-Red.Coil9\",\n \"Node-Red.Coil10\"\n];\n\nfor(var i=0; i..\n// this list needs to be <= the number of coils configured to be\n// read from modbus (configured in the modbus function)\nvar tags = \n[\n \"Node-Red.Input1\", // this tag is array item 0\n \"Node-Red.Input2\", // this tag is array item 1\n \"Node-Red.Input3\", // this tag is array item 2\n \"Node-Red.Input4\", // ...\n \"Node-Red.Input5\",\n \"Node-Red.Input6\",\n \"Node-Red.Input7\",\n \"Node-Red.Input8\",\n \"Node-Red.Input9\",\n \"Node-Red.Input10\"\n];\n\nfor(var i=0; i..\n// this list needs to be <= the number of input registers configured to be\n// be read from modbus (configured in the modbus function)\nvar tags = \n[\n \"Node-Red.HoldingRegister1\", // this tag is array item 0\n \"Node-Red.HoldingRegister2\", // this tag is array item 1\n \"Node-Red.HoldingRegister3\", // this tag is array item 2\n \"Node-Red.HoldingRegister4\", // ...\n \"Node-Red.HoldingRegister5\",\n \"Node-Red.HoldingRegister6\",\n \"Node-Red.HoldingRegister7\",\n \"Node-Red.HoldingRegister8\",\n \"Node-Red.HoldingRegister9\",\n \"Node-Red.HoldingRegister10\"\n];\n\nfor(var i=0; i..\n// this list needs to be <= the number of input registers configured to be\n// read from modbus (configured in the modbus function)\nvar tags = \n[\n \"Node-Red.InputRegister1\", // this tag is array item 0\n \"Node-Red.InputRegister2\", // this tag is array item 1\n \"Node-Red.InputRegister3\", // this tag is array item 2\n \"Node-Red.InputRegister4\", // ...\n \"Node-Red.InputRegister5\",\n \"Node-Red.InputRegister6\",\n \"Node-Red.InputRegister7\",\n \"Node-Red.InputRegister8\",\n \"Node-Red.InputRegister9\",\n \"Node-Red.InputRegister10\"\n];\n\nfor(var i=0; i 2 | 3 | 4 | Sender API Demo 5 | 6 | 80 | 84 | 85 | 86 | 87 | 88 | 89 | 387 | 413 | 414 | 415 |
416 |

Sender API Demo

417 |

Before using the API, the proper endpoints must be turned on to listen for requests.
This demo has been configured to write data to a sender service and historian running on the local machine.

418 |
    419 |
  1. Open the "Canary Admin" program.
  2. 420 |
  3. Click on the "Sender" info box on the home screen.
  4. 421 |
  5. Click on "Configuration" in the bottom tab control.
  6. 422 |
  7. Click on "Endpoints" in the side tab control.
  8. 423 |
  9. Turn on the (Web API) endpoints that you will use. Note the port numbers. These are the ports that will be listening for the Web API requests.
  10. 424 |
425 |

Click on the buttons to execute method calls to an api on the local machine. Hit the F12 key to open the browser console window to view the results of each method call.

426 |
    427 |
  1. Call "GetUserToken" and keep the "userToken" for subsequent calls.
  2. 428 |
  3. Call "GetSessionToken" and keep the "sessionToken" for subsequent calls.
  4. 429 |
  5. Call "StoreData" with a valid "userToken" and "sessionToken" as often as necessary.
  6. 430 |
  7. Call "KeepAlive" every 15-30 seconds for long running tasks to prevent tokens from expiring,
    OR make calls to revoke tokens when finished writing data.
  8. 431 |
432 |
433 |
434 |

Primary

435 |
436 |
437 |
438 |

Supplemental

439 |
440 |
441 |
442 |
443 |
444 |

Page Source

445 |

This source uses jQuery and moment.js libraries.

446 |
447 |
448 | 449 | -------------------------------------------------------------------------------- /Samples/V24/Data Retrieval/.NET Client/Instructions.txt: -------------------------------------------------------------------------------- 1 | This application requires references to the latest version of the following dlls. 2 | 3 | Canary.Utility.CanaryClientHelper.dll 4 | Canary.Utility.GrpcHelper.dll 5 | Canary.Utility.ProtobufSharedTypes.dll 6 | Canary.Views.Grpc.Api.dll 7 | Canary.Views.Grpc.Common.dll 8 | 9 | These dlls can be found within the Views folder of the Canary program files installation directory. The proto files contained within these dlls can be found in API\Protos\Views. -------------------------------------------------------------------------------- /Samples/V24/Data Retrieval/.NET Client/Program.cs: -------------------------------------------------------------------------------- 1 | using Canary.Utility.ProtobufSharedTypes; 2 | using Canary.Views.Grpc.Api; 3 | using Canary.Views.Grpc.Common; 4 | using Google.Protobuf; 5 | using Google.Protobuf.WellKnownTypes; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Diagnostics.CodeAnalysis; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | using static Canary.Views.Grpc.Api.CanaryViewsApiService; 12 | 13 | namespace ReadData 14 | { 15 | internal class Program 16 | { 17 | private static async Task Main(string[] args) 18 | { 19 | ResizeWindow(); 20 | 21 | string host = "localhost"; 22 | string identityApiToken = "11111111-2222-3333-4444-555555555555"; // This is the anonymous api token. If anonymous is not enabled, another api token will need configured 23 | 24 | List tags = []; 25 | 26 | // Example 1: Manually manage connection status/cci 27 | try 28 | { 29 | // Manually call the Connect method 30 | CanaryViewsApiServiceClient serviceClient = await ViewsClientProvider.Connect(host, identityApiToken); 31 | 32 | // browse nodes/tags 33 | string nodePath = await BrowseNodesAsync(serviceClient); 34 | tags = await BrowseTagsAsync(serviceClient, nodePath); 35 | 36 | if (tags != null && tags.Count > 0) 37 | { 38 | DateTime endTime = DateTime.Now; 39 | DateTime startTime = endTime.Subtract(TimeSpan.FromHours(1)); 40 | List tagSubset = tags.Take(5).ToList(); 41 | 42 | // Manually retrieve cci to pass to the data call 43 | int cci = await ViewsClientProvider.GetCciAsync(serviceClient); 44 | 45 | // read data 46 | await GetCurrentValueAsync(serviceClient, cci, tagSubset); 47 | 48 | await serviceClient.ReleaseClientConnectionIdAsync(new ReleaseClientConnectionIdRequest() 49 | { 50 | Cci = cci 51 | }); 52 | } 53 | } 54 | catch (Exception ex) 55 | { 56 | Console.WriteLine($"Exception: {ex.Message}"); 57 | } 58 | 59 | // Example 2: Use helper class MakeRequestAsync method to automatically manage connection status/cci 60 | var clientProvider = new ViewsClientProvider(identityApiToken, host); 61 | 62 | try 63 | { 64 | if (tags != null && tags.Count > 0) 65 | { 66 | DateTime endTime = DateTime.Now; 67 | DateTime startTime = endTime.Subtract(TimeSpan.FromHours(1)); 68 | List tagSubset = tags.Take(5).ToList(); 69 | 70 | await GetRawDataAsync(clientProvider, tagSubset, startTime, endTime); 71 | await GetAvailableAggregatesAsync(clientProvider); 72 | await GetAggregateDataAsync(clientProvider, tagSubset, startTime, endTime); 73 | } 74 | } 75 | catch (Exception ex) 76 | { 77 | Console.WriteLine($"Exception: {ex.Message}"); 78 | } 79 | finally 80 | { 81 | await clientProvider.ReleaseCciAsync(); 82 | } 83 | 84 | Console.WriteLine(); 85 | Console.WriteLine("Press any key to exit..."); 86 | Console.ReadKey(); 87 | } 88 | 89 | private static void ResizeWindow() 90 | { 91 | try 92 | { 93 | // width in characters 94 | Console.WindowWidth = 150; 95 | Console.WindowHeight = 50; 96 | } 97 | catch 98 | { 99 | // ignore error 100 | } 101 | } 102 | 103 | private static async Task GetVersionAsync(ViewsClientProvider client) 104 | { 105 | GetWebServiceVersionResponse response = await client.MakeRequestAsync(async (client, cci) => { 106 | return await client.GetWebServiceVersionAsync(new Empty()); 107 | }); 108 | 109 | if (HasBadStatus(response.Status, nameof(GetVersionAsync), out string? errorMessage)) 110 | Console.WriteLine($"Get Version Error: {errorMessage}"); 111 | else 112 | Console.WriteLine($"Views Version: {response.Version}"); 113 | } 114 | 115 | private static async Task BrowseNodesAsync(CanaryViewsApiServiceClient client) 116 | { 117 | Console.WriteLine(); 118 | Console.WriteLine("Browse Nodes..."); 119 | 120 | string nodePath = "ROOT"; // base path 121 | while (true) 122 | { 123 | BrowseResponse response = await client.BrowseAsync(new BrowseRequest() 124 | { 125 | NodeIdPath = nodePath, 126 | ForceReload = false 127 | }); 128 | 129 | if (HasBadStatus(response.Status, nameof(BrowseNodesAsync), out string? errorMessage)) 130 | Console.WriteLine($"Browse Nodes Error: {errorMessage}"); 131 | if (response.Node.Children.Count == 0) 132 | Console.WriteLine($"\"{nodePath}\" has 0 child nodes..."); 133 | else 134 | { 135 | // browse deeper into first child node 136 | var firstNode = response.Node.Children[0]; 137 | Console.WriteLine($"\"{nodePath}\" path has {response.Node.Children.Count} child nodes..."); 138 | nodePath = firstNode.IdPath; 139 | continue; 140 | } 141 | 142 | break; 143 | } 144 | 145 | return nodePath; 146 | } 147 | 148 | private static async Task> BrowseTagsAsync(CanaryViewsApiServiceClient client, string nodePath) 149 | { 150 | Console.WriteLine(); 151 | Console.WriteLine("Browse All Tags..."); 152 | 153 | List tags = new List(); 154 | string search = ""; 155 | while (true) 156 | { 157 | int maxCount = 1000; 158 | bool includeSubNodes = true; 159 | bool includeProperties = false; 160 | BrowseTagsResponse response = await client.BrowseTagsAsync(new BrowseTagsRequest() 161 | { 162 | NodeIdBrowse = nodePath, 163 | IncludeSubNodes = includeSubNodes, 164 | IncludeProperties = includeProperties, 165 | MaxCount = maxCount, 166 | SearchContext = search, 167 | }); 168 | 169 | if (HasBadStatus(response.Status, nameof(BrowseTagsAsync), out string? errorMessage)) 170 | Console.WriteLine($"Browse Tags Error: {errorMessage}"); 171 | else 172 | { 173 | Console.WriteLine($"Retrieved {response.TagNames.Count} tags from path \"{nodePath}\"..."); 174 | 175 | tags.AddRange(response.TagNames.Select((partialTagName) => 176 | { 177 | // build full tag path 178 | return $"{nodePath}.{partialTagName}"; 179 | })); 180 | 181 | if (response.MoreDataAvailable) 182 | { 183 | // use search context as continuation point to get more tags 184 | search = response.SearchContext; 185 | continue; 186 | } 187 | } 188 | 189 | break; 190 | } 191 | 192 | return tags; 193 | } 194 | 195 | private static async Task GetCurrentValueAsync(CanaryViewsApiServiceClient client, int cci, List tags) 196 | { 197 | Console.WriteLine(); 198 | Console.WriteLine("Getting Current Values..."); 199 | 200 | foreach (string fullTag in tags) 201 | { 202 | string[] split = fullTag.Split('.'); 203 | string view = split[0]; 204 | string partialTag = string.Join(".", split.Skip(1)); 205 | 206 | GetTagCurrentValueResponse response = await client.GetTagCurrentValueAsync(new GetTagCurrentValueRequest() 207 | { 208 | View = view, 209 | TagNames = { partialTag }, 210 | UseTimeExtension = true, 211 | Cci = cci 212 | }); 213 | 214 | if (HasBadStatus(response.Status, nameof(GetCurrentValueAsync), out string? errorMessage)) 215 | Console.WriteLine($"Get Current Value Error: {errorMessage}"); 216 | else if (response.Status.StatusType == ApiCallStatusType.CheckExtendedStatus) 217 | Console.WriteLine($"Get Current Value Error. Status: {response.ExtendedStatus}, Error: {response.Status.StatusErrorMessage}"); 218 | else 219 | { 220 | TagCurrentValue result = response.TagValues[0]; 221 | Console.WriteLine($"\"{fullTag}\" =>"); 222 | Console.WriteLine($"\tTime = {result.Timestamp}"); 223 | Console.WriteLine($"\tValue = {result.Value}"); 224 | Console.WriteLine($"\tQuality = {result.Quality}"); 225 | } 226 | } 227 | } 228 | 229 | private static async Task GetRawDataAsync(ViewsClientProvider client, List tags, DateTime startTime, DateTime endTime) 230 | { 231 | Console.WriteLine(); 232 | Console.WriteLine("Getting Raw Data..."); 233 | 234 | foreach (string fullTag in tags) 235 | { 236 | Console.WriteLine($"\"{fullTag}\" =>"); 237 | 238 | string[] split = fullTag.Split('.'); 239 | string view = split[0]; 240 | string partialTag = string.Join(".", split.Skip(1)); 241 | 242 | byte[]? continuationPoint = null; 243 | while (true) 244 | { 245 | RawTagRequest[] requests = 246 | [ 247 | new() 248 | { 249 | TagName = partialTag, 250 | StartTime = startTime.ToGrpcTimestamp(), 251 | EndTime = endTime.ToGrpcTimestamp(), 252 | ContinuationPoint = continuationPoint != null ? ByteString.CopyFrom(continuationPoint) : ByteString.Empty 253 | } 254 | ]; 255 | 256 | GetRawDataResponse response = await client.MakeRequestAsync(async (client, cci) => { 257 | return await client.GetRawDataAsync(new GetRawDataRequest() 258 | { 259 | Requests = { requests }, 260 | View = view, 261 | MaxCountPerTag = 1000, 262 | ReturnAnnotations = false, 263 | ReturnBounds = true, 264 | Cci = cci 265 | }); 266 | }); 267 | 268 | if (HasBadStatus(response.Status, nameof(GetRawDataAsync), out string? errorMessage)) 269 | Console.WriteLine($"\tGet Raw Data Error: {errorMessage}"); 270 | else if (response.Status.StatusType == ApiCallStatusType.CheckExtendedStatus) 271 | Console.WriteLine($"\tGet Raw Data Error. Status: {response.ExtendedStatus}, Error: {response.Status.StatusErrorMessage}"); 272 | else 273 | { 274 | var result = response.RawData[0]; 275 | Console.WriteLine($"\tRetrieved {result.Tvqs.Count} raw values..."); 276 | if (result.ContinuationPoint != null && result.ContinuationPoint.Length > 0) 277 | { 278 | // get more data 279 | continuationPoint = result.ContinuationPoint.ToByteArray(); 280 | continue; 281 | } 282 | } 283 | 284 | break; 285 | } 286 | } 287 | } 288 | 289 | private static async Task GetAvailableAggregatesAsync(ViewsClientProvider client) 290 | { 291 | Console.WriteLine(); 292 | Console.WriteLine("Getting available aggregates for processed data..."); 293 | 294 | GetAggregateListResponse response = await client.MakeRequestAsync(async (client, cci) => { 295 | return await client.GetAggregateListAsync(new Empty()); 296 | }); 297 | 298 | if (HasBadStatus(response.Status, nameof(GetAvailableAggregatesAsync), out string? errorMessage)) 299 | Console.WriteLine($"Get Available Aggregates Error: {errorMessage}"); 300 | else 301 | { 302 | foreach (AggregateDefinition aggregate in response.Aggregates) 303 | Console.WriteLine($"{aggregate.AggregateName} => {aggregate.AggregateDescription}"); 304 | } 305 | } 306 | 307 | private static async Task GetAggregateDataAsync(ViewsClientProvider client, List tags, DateTime startTime, DateTime endTime) 308 | { 309 | Console.WriteLine(); 310 | Console.WriteLine("Getting Processed Data..."); 311 | 312 | TimeSpan aggregateInterval = TimeSpan.FromMinutes(1); 313 | foreach (string fullTag in tags) 314 | { 315 | Console.WriteLine($"\"{fullTag}\" =>"); 316 | 317 | string[] split = fullTag.Split('.'); 318 | string view = split[0]; 319 | string partialTag = string.Join(".", split.Skip(1)); 320 | 321 | AggregateTagRequest[] requests = 322 | { 323 | new() 324 | { 325 | AggregateName = "TimeAverage2", 326 | TagName = partialTag 327 | } 328 | }; 329 | 330 | GetAggregateDataResponse response = await client.MakeRequestAsync(async (client, cci) => { 331 | return await client.GetAggregateDataAsync(new GetAggregateDataRequest() 332 | { 333 | View = view, 334 | Requests = { requests }, 335 | StartTime = startTime.ToGrpcTimestamp(), 336 | EndTime = endTime.ToGrpcTimestamp(), 337 | Interval = aggregateInterval.ToDuration(), 338 | ReturnAnnotations = false, 339 | Cci = cci 340 | }); 341 | }); 342 | 343 | if (HasBadStatus(response.Status, nameof(GetAggregateDataAsync), out string? errorMessage)) 344 | Console.Write($"Get Aggregate Data Error: {errorMessage}"); 345 | else if (response.Status.StatusType == ApiCallStatusType.CheckExtendedStatus) 346 | Console.WriteLine($"Get Aggregate Data Error. Status {response.ExtendedStatus}, Error: {response.Status.StatusErrorMessage}"); 347 | else 348 | Console.WriteLine($"\tRetrieved {response.AggregatedData[0].Tvqs.Count} processed values..."); 349 | } 350 | } 351 | 352 | private static bool HasBadStatus(ApiCallStatus requestStatus, string method, [NotNullWhen(true)] out string? errorMessage) 353 | { 354 | errorMessage = ""; 355 | switch (requestStatus.StatusType) 356 | { 357 | case ApiCallStatusType.Success: 358 | case ApiCallStatusType.CheckExtendedStatus: 359 | return false; 360 | 361 | case ApiCallStatusType.NoLicense: 362 | default: 363 | errorMessage = $"Error making Views request '{method}'. Status code '{requestStatus.StatusType}'.{(!string.IsNullOrEmpty(errorMessage) ? $" Error: {errorMessage}" : string.Empty)}"; 364 | return true; 365 | }; 366 | } 367 | } 368 | } -------------------------------------------------------------------------------- /Samples/V24/Data Retrieval/.NET Client/Views API Example.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | Exe 5 | ReadData 6 | ReadData 7 | false 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Samples/V24/Data Retrieval/.NET Client/ViewsClientProvider.cs: -------------------------------------------------------------------------------- 1 | using Canary.Utility.CanaryClientHelper; 2 | using Canary.Utility.GrpcHelper.Token; 3 | using Canary.Views.Grpc.Api; 4 | using Grpc.Core; 5 | using System; 6 | using System.Threading.Tasks; 7 | using static Canary.Views.Grpc.Api.CanaryViewsApiService; 8 | 9 | namespace ReadData 10 | { 11 | public class ViewsClientConnectionContext : IGrpcClientContext 12 | { 13 | public ViewsClientConnectionContext(CanaryViewsApiServiceClient client, int cci) 14 | { 15 | Client = client; 16 | Cci = cci; 17 | } 18 | 19 | public int Cci { get; } 20 | public CanaryViewsApiServiceClient Client { get; } 21 | } 22 | 23 | public class ViewsClientProvider : GrpcClientProviderBase 24 | { 25 | private const string APP_ID = "Views API Example"; 26 | private readonly string _identityApiToken; 27 | private readonly string _viewsEndpoint; 28 | 29 | internal ViewsClientProvider(string identityApiToken, string viewsEndpoint) 30 | { 31 | _identityApiToken = identityApiToken; 32 | _viewsEndpoint = viewsEndpoint; 33 | } 34 | 35 | // Takes a function that has the CanaryViewsApiServiceClient and cci as parameters 36 | public async Task MakeRequestAsync(Func> requestAction) 37 | { 38 | return await MakeRequestAsync(async (ViewsClientConnectionContext context) => 39 | { 40 | return await requestAction(context.Client, context.Cci); 41 | }); 42 | } 43 | 44 | // Releases the Client Connection Id in the Views Service 45 | public async Task ReleaseCciAsync() 46 | { 47 | try 48 | { 49 | await MakeRequestAsync(async (ViewsClientConnectionContext context) => 50 | { 51 | ReleaseClientConnectionIdRequest request = new() 52 | { 53 | Cci = context.Cci 54 | }; 55 | 56 | ReleaseClientConnectionIdResponse response = new ReleaseClientConnectionIdResponse() { Status = new() }; 57 | 58 | try 59 | { 60 | response = await context.Client.ReleaseClientConnectionIdAsync(request, deadline: DateTime.UtcNow.AddSeconds(10)); 61 | } 62 | catch (Exception ex) 63 | { 64 | response.Status.StatusType = Canary.Views.Grpc.Common.ApiCallStatusType.CheckExtendedStatus; 65 | response.Status.StatusErrorMessage = ex.Message; 66 | } 67 | finally 68 | { 69 | // Reset the client context so that CreateClientContext is called again on the next MakeRequestAsync call 70 | ResetClientContext(context); 71 | } 72 | 73 | return response; 74 | }); 75 | } 76 | catch { } 77 | } 78 | 79 | public static async Task GetCciAsync(CanaryViewsApiServiceClient client) 80 | { 81 | GetClientConnectionIdResponse getCciResponse = await client.GetClientConnectionIdAsync( 82 | new GetClientConnectionIdRequest() 83 | { 84 | App = APP_ID, 85 | UserId = System.Net.Dns.GetHostName() + ":" + System.Environment.UserName 86 | }); 87 | 88 | 89 | if (getCciResponse.Status.StatusType == Canary.Views.Grpc.Common.ApiCallStatusType.NoLicense) 90 | throw new Exception("Views License is not available at this time."); 91 | else if (getCciResponse.Status.StatusType != Canary.Views.Grpc.Common.ApiCallStatusType.Success) 92 | throw new Exception($"Error getting Views client connection id: {getCciResponse.Status.StatusErrorMessage}"); 93 | 94 | return getCciResponse.Cci; 95 | } 96 | 97 | public static async Task Connect(string viewsEndpoint, string identityToken) 98 | { 99 | return await GrpcClientConnectionHelper.Instance.GetViewsClientAsync( 100 | viewsEndpoint, 101 | (callOptions) => 102 | { 103 | var clientToken = CanaryClientToken.CreateApiToken(identityToken); 104 | if (clientToken is null) 105 | return callOptions; 106 | 107 | return callOptions.WithHeaders(new Metadata 108 | { 109 | new Metadata.Entry( 110 | key: clientToken.GetMetadataKey(), 111 | value: clientToken.TokenValue) 112 | }); 113 | }); 114 | } 115 | 116 | // Called by the base class 117 | // Makes the connection to Views and retrieves the Client Connection Id required for data calls 118 | protected override async Task CreateClientContext() 119 | { 120 | CanaryViewsApiServiceClient client = await Connect(_viewsEndpoint, _identityApiToken); 121 | int cci = await GetCciAsync(client); 122 | 123 | // The CanaryViewsApiServiceClient and cci are saved into the base class. They are made available to the user inside the function parameter of the MakeRequestAsync method 124 | return new ViewsClientConnectionContext(client, cci); 125 | } 126 | 127 | 128 | protected override async Task MakeRequestAsync(Func> requestAction) 129 | { 130 | try 131 | { 132 | // The base method calls CreateClientContext if it has not been done yet or if the context has been reset. It 133 | // then invokes the requestAction with the client context as the parameter 134 | return await base.MakeRequestAsync(requestAction); 135 | } 136 | catch (GrpcFailedRequestException) 137 | { 138 | // Logic can be added here to handle Views Service disconnects 139 | throw; 140 | } 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /Samples/V24/Data Storage/.NET Client/ExtensionMethodsExample.cs: -------------------------------------------------------------------------------- 1 | using Canary.StoreAndForward2.Grpc.Api; 2 | using Canary.StoreAndForward2.Grpc.Api.Helper; 3 | using Canary.Utility.ProtobufSharedTypes; 4 | 5 | namespace SAF_Examples 6 | { 7 | // This class uses extension methods that simplify tag configuration and storing data 8 | public class ExtensionMethodsExample 9 | { 10 | private enum Qualities 11 | { 12 | Good = 192, // 0xC0 13 | NoData = -32768, // 0x8000 14 | } 15 | 16 | private readonly string _dataset = "ExtensionExample"; 17 | private readonly string _historian = "localhost"; 18 | private readonly Dictionary _tagMap = []; 19 | private IGrpcApiSession? _session = null; 20 | 21 | public async Task CreateNewFileAsync() 22 | { 23 | var session = Connect(); 24 | 25 | HistorianFile file = new() 26 | { 27 | DataSet = _dataset, 28 | FileTime = DateTime.Now 29 | }; 30 | 31 | await session.CreateNewFileAsync(file, CancellationToken.None); 32 | } 33 | 34 | public async Task DisconnectAsync() 35 | { 36 | if (_session != null) 37 | { 38 | await _session.CloseAsync(TimeSpan.FromSeconds(30)); 39 | _session = null; 40 | } 41 | } 42 | 43 | public async Task FileRollOverAsync() 44 | { 45 | var session = Connect(); 46 | 47 | HistorianFile file = new() 48 | { 49 | DataSet = _dataset, 50 | FileTime = DateTime.MinValue 51 | }; 52 | 53 | await session.RollOverFileAsync(file, CancellationToken.None); 54 | } 55 | 56 | public Dictionary GetTagIds() 57 | { 58 | var session = Connect(); 59 | 60 | if (_tagMap.Count == 0) 61 | { 62 | int tagCount = 5; 63 | Tag[] tags = new Tag[tagCount]; 64 | for (int i = 0; i < tagCount; i++) 65 | { 66 | tags[i] = new Tag 67 | { 68 | Name = string.Format("{0}.Tag {1:D4}", _dataset, i + 1), 69 | TransformEquation = null, 70 | TimeExtension = true 71 | }; 72 | } 73 | 74 | session.GetTagIds(tags, out int[] tagIds); 75 | 76 | for (int i = 0; i < tagCount; i++) 77 | _tagMap.Add(tags[i].Name, tagIds[i]); 78 | } 79 | 80 | return _tagMap; 81 | } 82 | 83 | public async Task NoDataAsync() 84 | { 85 | var session = Connect(); 86 | 87 | Dictionary tagIds = GetTagIds(); 88 | 89 | await session.StoreNoDataAsync(tagIds.Values, CancellationToken.None); 90 | } 91 | 92 | public async Task StoreDataAsync() 93 | { 94 | var session = Connect(); 95 | 96 | List dataElements = []; 97 | List propertyElements = []; 98 | List annotationElements = []; 99 | 100 | // create data to store 101 | DateTime now = DateTime.Now; 102 | Dictionary tagIds = GetTagIds(); 103 | foreach (KeyValuePair pair in tagIds) 104 | { 105 | string tagName = pair.Key; 106 | int id = pair.Value; 107 | 108 | // add tvq data 109 | for (int i = 0; i < 5; i++) 110 | { 111 | dataElements.Add(new DataElement() 112 | { 113 | TagId = id, 114 | Timestamp = now.AddTicks(i).ToGrpcTimestamp(), 115 | Value = (i % 100).ToVariant(), 116 | Quality = 192 117 | }); 118 | } 119 | 120 | // add property data 121 | propertyElements.Add(new PropertyElement() 122 | { 123 | TagId = id, 124 | NullableDescription = null, 125 | Timestamp = now.ToGrpcTimestamp(), 126 | Name = "ScaleMax", 127 | Value = 100.ToVariant(), 128 | Quality = (int)Qualities.Good 129 | }); 130 | 131 | // add property data 132 | propertyElements.Add(new PropertyElement() 133 | { 134 | TagId = id, 135 | NullableDescription = null, 136 | Timestamp = now.ToGrpcTimestamp(), 137 | Name = "ScaleLow", 138 | Value = 0.ToVariant(), 139 | Quality = (int)Qualities.Good 140 | }); 141 | 142 | // add property data 143 | propertyElements.Add(new PropertyElement() 144 | { 145 | TagId = id, 146 | NullableDescription = null, 147 | Timestamp = now.ToGrpcTimestamp(), 148 | Name = "SampleInterval", 149 | Value = TimeSpan.FromSeconds(1).ToString().ToVariant(), 150 | Quality = (int)Qualities.Good 151 | }); 152 | 153 | // add annotation 154 | annotationElements.Add(new AnnotationElement() 155 | { 156 | CreationTimestamp = now.ToGrpcTimestamp(), // only necessary if creation time differs from annotation timestamp 157 | TagId = id, 158 | Timestamp = now.ToGrpcTimestamp(), 159 | CreationUser = "Example User", 160 | Value = "Example Annotation".ToVariant() 161 | }); 162 | } 163 | 164 | await session.AddTvqsAsync(dataElements, CancellationToken.None); 165 | await session.AddPropertiesAsync(propertyElements, CancellationToken.None); 166 | await session.WriteAsync(annotationElements.Select(p => new StreamElement() { Annotation = p }), CancellationToken.None); // No extension method for annotations 167 | } 168 | 169 | private static IGrpcApiSession CreateSenderSession() 170 | { 171 | List settings = new() 172 | { 173 | new ConfigureSettingRequest { Kind = SettingKind.IsCreateDataSetEnabled, Value = true.ToVariant() }, 174 | new ConfigureSettingRequest { Kind = SettingKind.IsWriteNoDataOnCloseEnabled, Value = false.ToVariant() }, 175 | new ConfigureSettingRequest { Kind = SettingKind.IsTimestampExtensionEnabled, Value = true.ToVariant() }, 176 | new ConfigureSettingRequest { Kind = SettingKind.IsInsertOrReplaceDataEnabled, Value = false.ToVariant() } 177 | }; 178 | 179 | GrpcApiSessionBuilder builder = new GrpcApiSessionBuilder(new LocalEndpointContext()); 180 | var sessionContext = new SessionContext() { CollectorType = "SAF_Example_Collector", Name = "SAF_Example", NullableDestination = "localhost" }; 181 | var session = builder.Build(sessionContext); 182 | session.ConfigureSettings(settings); 183 | return session; 184 | } 185 | 186 | private IGrpcApiSession Connect() 187 | { 188 | _session ??= CreateSenderSession(); 189 | return _session; 190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /Samples/V24/Data Storage/.NET Client/Instructions.txt: -------------------------------------------------------------------------------- 1 | In order for this application to work, the following dlls need copied from the Canary Admin folder of the Canary program files installation directory and referenced by this project. 2 | 3 | Canary.StoreAndForward2.Grpc.Api.dll 4 | Canary.StoreAndForward2.Grpc.Api.Helper.dll 5 | Canary.Utility.CanaryClientHelper.dll 6 | Canary.Utility.CanaryShared.dll 7 | Canary.Utility.GrpcHelper.dll 8 | Canary.Utility.JsonCore.dll 9 | Canary.Utility.LogCore.dll 10 | Canary.Utility.PathCore.dll 11 | Canary.Utility.ProtobufSharedTypes.dll 12 | Canary.Utility.SecurityCore.dll 13 | Canary.Utility.SettingsCore.dll 14 | Canary.Utility.TaskCore.dll 15 | 16 | The proto files contained within these dlls can be found in API\Protos\StoreAndForward. -------------------------------------------------------------------------------- /Samples/V24/Data Storage/.NET Client/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | namespace SAF_Examples 8 | { 9 | class Program 10 | { 11 | static async Task Main(string[] args) 12 | { 13 | // write using raw client 14 | Console.WriteLine("Writing data using raw client..."); 15 | RawClientExample rawClient = new(); 16 | await rawClient.StoreDataAsync(); 17 | 18 | // write using extension methods 19 | Console.WriteLine("Writing data using extension methods..."); 20 | ExtensionMethodsExample extensionMethodsClient = new(); 21 | await extensionMethodsClient.StoreDataAsync(); 22 | 23 | Console.WriteLine("Disconnecting..."); 24 | 25 | // disconnect raw client 26 | Thread.Sleep(3000); 27 | await rawClient.DisconnectAsync(); 28 | 29 | // disconnect extension methods client 30 | Thread.Sleep(3000); 31 | await extensionMethodsClient.DisconnectAsync(); 32 | 33 | Console.WriteLine("Finished."); 34 | Console.ReadLine(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Samples/V24/Data Storage/.NET Client/RawClientExample.cs: -------------------------------------------------------------------------------- 1 | using Canary.StoreAndForward2.Grpc.Api; 2 | using Canary.StoreAndForward2.Grpc.Api.Helper; 3 | using Canary.Utility.ProtobufSharedTypes; 4 | 5 | namespace SAF_Examples 6 | { 7 | public class RawClientExample 8 | { 9 | private enum Qualities 10 | { 11 | Good = 192, // 0xC0 12 | NoData = -32768, // 0x8000 13 | } 14 | 15 | private readonly string _dataset = "RawExample"; 16 | private readonly string _historian = "localhost"; 17 | private readonly Dictionary _tagMap = []; 18 | private IGrpcApiSession? _session = null; 19 | 20 | public async Task CreateNewFileAsync() 21 | { 22 | var session = Connect(); 23 | 24 | await session.WriteAsync(new StreamElement() 25 | { 26 | FileNew = new FileNewElement() 27 | { 28 | DataSet = _dataset, 29 | FileTime = DateTime.MinValue.ToGrpcTimestamp() 30 | } 31 | }, CancellationToken.None); 32 | } 33 | 34 | public async Task DisconnectAsync() 35 | { 36 | if (_session != null) 37 | { 38 | await _session.CloseAsync(TimeSpan.FromSeconds(30)); 39 | _session = null; 40 | } 41 | } 42 | 43 | public async Task FileRollOverAsync() 44 | { 45 | var session = Connect(); 46 | 47 | await session.WriteAsync(new StreamElement() 48 | { 49 | FileRollover = new FileRolloverElement() 50 | { 51 | DataSet = _dataset, 52 | FileTime = DateTime.MinValue.ToGrpcTimestamp() 53 | } 54 | }, CancellationToken.None); 55 | } 56 | 57 | public Dictionary GetTagIds() 58 | { 59 | var session = Connect(); 60 | 61 | if (_tagMap.Count == 0) 62 | { 63 | int tagCount = 5; 64 | ConfigureTagRequest[] requests = new ConfigureTagRequest[tagCount]; 65 | 66 | for (int i = 0; i < tagCount; i++) 67 | { 68 | requests[i] = new ConfigureTagRequest() 69 | { 70 | TagPath = string.Format("{0}.Tag {1:D4}", _dataset, i + 1), 71 | NullableTransformEquation = null, 72 | NullableTimestampNormalizationMilliseconds = null, 73 | IsTimestampExtensionEnabled = true 74 | }; 75 | } 76 | 77 | session.ConfigureTags(requests); 78 | 79 | for (int i = 0; i < requests.Length; i++) 80 | { 81 | _tagMap.Add(requests[i].TagPath, session.GetConfiguredTagId(requests[i].TagPath)); 82 | } 83 | } 84 | 85 | return _tagMap; 86 | } 87 | 88 | public async Task NoDataAsync() 89 | { 90 | var session = Connect(); 91 | 92 | Dictionary tagIds = GetTagIds(); 93 | 94 | IEnumerable streamElements = tagIds.Values.Select(p => new StreamElement() 95 | { 96 | NoData = new NoDataElement() 97 | { 98 | TagId = p 99 | } 100 | }); 101 | 102 | await session.WriteAsync(streamElements, CancellationToken.None); 103 | } 104 | 105 | public async Task StoreDataAsync() 106 | { 107 | var session = Connect(); 108 | 109 | List streamElements = []; 110 | 111 | // create data to store 112 | DateTime now = DateTime.Now; 113 | Dictionary tagIds = GetTagIds(); 114 | foreach (KeyValuePair pair in tagIds) 115 | { 116 | string tagName = pair.Key; 117 | int id = pair.Value; 118 | 119 | // add tvq data 120 | for (int i = 0; i < 5; i++) 121 | { 122 | streamElements.Add(new StreamElement() 123 | { 124 | Data = new DataElement() 125 | { 126 | TagId = id, 127 | Timestamp = now.AddTicks(i).ToGrpcTimestamp(), 128 | Value = (i % 100).ToVariant(), 129 | Quality = 192 130 | } 131 | }); 132 | } 133 | 134 | // add property data 135 | streamElements.Add(new StreamElement() 136 | { 137 | Property = new PropertyElement() 138 | { 139 | TagId = id, 140 | NullableDescription = null, 141 | Timestamp = now.ToGrpcTimestamp(), 142 | Name = "ScaleMax", 143 | Value = 100.ToVariant(), 144 | Quality = (int)Qualities.Good 145 | } 146 | }); 147 | 148 | // add property data 149 | streamElements.Add(new StreamElement() 150 | { 151 | Property = new PropertyElement() 152 | { 153 | TagId = id, 154 | NullableDescription = null, 155 | Timestamp = now.ToGrpcTimestamp(), 156 | Name = "ScaleLow", 157 | Value = 0.ToVariant(), 158 | Quality = (int)Qualities.Good 159 | } 160 | }); 161 | 162 | // add property data 163 | streamElements.Add(new StreamElement() 164 | { 165 | Property = new PropertyElement() 166 | { 167 | TagId = id, 168 | NullableDescription = null, 169 | Timestamp = now.ToGrpcTimestamp(), 170 | Name = "SampleInterval", 171 | Value = TimeSpan.FromSeconds(1).ToString().ToVariant(), 172 | Quality = (int)Qualities.Good 173 | } 174 | }); 175 | 176 | // add annotation 177 | streamElements.Add(new StreamElement() 178 | { 179 | Annotation = new AnnotationElement() 180 | { 181 | CreationTimestamp = now.ToGrpcTimestamp(), // only necessary if creation time differs from annotation timestamp 182 | TagId = id, 183 | Timestamp = now.ToGrpcTimestamp(), 184 | CreationUser = "Example User", 185 | Value = "Example Annotation".ToVariant() 186 | } 187 | }); 188 | } 189 | 190 | await session.WriteAsync(streamElements, CancellationToken.None); 191 | } 192 | 193 | private static IGrpcApiSession CreateSenderSession() 194 | { 195 | List settings = new List() 196 | { 197 | new ConfigureSettingRequest { Kind = SettingKind.IsCreateDataSetEnabled, Value = true.ToVariant() }, 198 | new ConfigureSettingRequest { Kind = SettingKind.IsWriteNoDataOnCloseEnabled, Value = false.ToVariant() }, 199 | new ConfigureSettingRequest { Kind = SettingKind.IsTimestampExtensionEnabled, Value = true.ToVariant() }, 200 | new ConfigureSettingRequest { Kind = SettingKind.IsInsertOrReplaceDataEnabled, Value = false.ToVariant() } 201 | }; 202 | 203 | GrpcApiSessionBuilder builder = new GrpcApiSessionBuilder(new LocalEndpointContext()); 204 | var sessionContext = new SessionContext() { CollectorType = "SAF_Example_Collector", Name = "SAF_Example", NullableDestination = "localhost" }; 205 | var session = builder.Build(sessionContext); 206 | session.ConfigureSettings(settings); 207 | return session; 208 | } 209 | 210 | private IGrpcApiSession Connect() 211 | { 212 | _session ??= CreateSenderSession(); 213 | return _session; 214 | } 215 | } 216 | } -------------------------------------------------------------------------------- /Samples/V24/Data Storage/.NET Client/SAF_Examples.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------