From 5671959051849a4b7de388a9c9c124bebc65171c Mon Sep 17 00:00:00 2001 From: "phares@iscn5cg20977xq" Date: Mon, 18 Aug 2025 12:42:27 -0700 Subject: [PATCH] national-instruments-helper --- .editorconfig | 2 +- .vscode/launch.json | 2 +- .vscode/settings.json | 1 + .vscode/tasks.json | 18 +- File-Watcher.csproj | 13 +- Helpers/DAQmx/CSVTime.cs | 12 + Helpers/DAQmx/CVIAbsoluteTime.cs | 17 + Helpers/DAQmx/DAQmx.cs | 13 + Helpers/DAQmx/DAQmxActiveEdge.cs | 7 + Helpers/DAQmx/DAQmxDataLayout.cs | 7 + .../DAQmx/DAQmxDigitalPatternTriggerWhen.cs | 7 + Helpers/DAQmx/DAQmxEdge.cs | 7 + Helpers/DAQmx/DAQmxException.cs | 14 + Helpers/DAQmx/DAQmxFillMode.cs | 7 + .../DAQmx/DAQmxInputTerminalConfiguration.cs | 10 + Helpers/DAQmx/DAQmxLevel.cs | 7 + Helpers/DAQmx/DAQmxLineGrouping.cs | 7 + Helpers/DAQmx/DAQmxLoggingMode.cs | 8 + Helpers/DAQmx/DAQmxLoggingTDMSOperation.cs | 9 + Helpers/DAQmx/DAQmxMemoryException.cs | 5 + Helpers/DAQmx/DAQmxPolarity.cs | 7 + Helpers/DAQmx/DAQmxSampleMode.cs | 8 + Helpers/DAQmx/DAQmxTask.cs | 347 ++++++++++++++ Helpers/DAQmx/DAQmxTimescale.cs | 7 + Helpers/DAQmx/DAQmxUnits.cs | 7 + Helpers/DAQmx/DAQmxWindowTriggerWhen.cs | 7 + Helpers/DAQmx/DAQmxWriteRegenMode.cs | 7 + Helpers/DAQmx/Interop.cs | 431 ++++++++++++++++++ Helpers/DeterministicHashCodeHelper.cs | 27 +- Helpers/NationalInstrumentsHelper.cs | 75 +++ Models/AppSettings.cs | 4 + Models/NationalInstrumentsConfiguration.cs | 27 ++ NancyModules/SyncModule.cs | 6 +- Startup.cs | 6 +- Worker.cs | 3 + 35 files changed, 1118 insertions(+), 24 deletions(-) create mode 100644 Helpers/DAQmx/CSVTime.cs create mode 100644 Helpers/DAQmx/CVIAbsoluteTime.cs create mode 100644 Helpers/DAQmx/DAQmx.cs create mode 100644 Helpers/DAQmx/DAQmxActiveEdge.cs create mode 100644 Helpers/DAQmx/DAQmxDataLayout.cs create mode 100644 Helpers/DAQmx/DAQmxDigitalPatternTriggerWhen.cs create mode 100644 Helpers/DAQmx/DAQmxEdge.cs create mode 100644 Helpers/DAQmx/DAQmxException.cs create mode 100644 Helpers/DAQmx/DAQmxFillMode.cs create mode 100644 Helpers/DAQmx/DAQmxInputTerminalConfiguration.cs create mode 100644 Helpers/DAQmx/DAQmxLevel.cs create mode 100644 Helpers/DAQmx/DAQmxLineGrouping.cs create mode 100644 Helpers/DAQmx/DAQmxLoggingMode.cs create mode 100644 Helpers/DAQmx/DAQmxLoggingTDMSOperation.cs create mode 100644 Helpers/DAQmx/DAQmxMemoryException.cs create mode 100644 Helpers/DAQmx/DAQmxPolarity.cs create mode 100644 Helpers/DAQmx/DAQmxSampleMode.cs create mode 100644 Helpers/DAQmx/DAQmxTask.cs create mode 100644 Helpers/DAQmx/DAQmxTimescale.cs create mode 100644 Helpers/DAQmx/DAQmxUnits.cs create mode 100644 Helpers/DAQmx/DAQmxWindowTriggerWhen.cs create mode 100644 Helpers/DAQmx/DAQmxWriteRegenMode.cs create mode 100644 Helpers/DAQmx/Interop.cs create mode 100644 Helpers/NationalInstrumentsHelper.cs create mode 100644 Models/NationalInstrumentsConfiguration.cs diff --git a/.editorconfig b/.editorconfig index b48850a..73cf788 100644 --- a/.editorconfig +++ b/.editorconfig @@ -118,7 +118,7 @@ dotnet_diagnostic.CA2254.severity = none # CA2254: The logging message template dotnet_diagnostic.IDE0001.severity = warning # IDE0001: Simplify name dotnet_diagnostic.IDE0002.severity = warning # Simplify (member access) - System.Version.Equals("1", "2"); Version.Equals("1", "2"); dotnet_diagnostic.IDE0004.severity = warning # IDE0004: Cast is redundant. -dotnet_diagnostic.IDE0005.severity = warning # Using directive is unnecessary +dotnet_diagnostic.IDE0005.severity = none # Using directive is unnecessary dotnet_diagnostic.IDE0010.severity = none # Add missing cases to switch statement (IDE0010) dotnet_diagnostic.IDE0028.severity = error # IDE0028: Collection initialization can be simplified dotnet_diagnostic.IDE0031.severity = warning # Use null propagation (IDE0031) diff --git a/.vscode/launch.json b/.vscode/launch.json index 775aade..8c66bab 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "name": ".NET Core Launch (console)", "type": "coreclr", "request": "launch", - "preLaunchTask": "build", + "preLaunchTask": "Build", "program": "${workspaceFolder}/bin/Debug/net8.0/win-x64/File-Watcher.dll", "args": [ "s" diff --git a/.vscode/settings.json b/.vscode/settings.json index e010ee0..7a59eab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,6 +42,7 @@ "PDSF", "pged", "Phares", + "Pinnable", "Rijndael", "Serilog", "SUBM", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6b33062..3ff7286 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,8 +7,6 @@ "type": "process", "args": [ "user-secrets", - "-p", - "${workspaceFolder}/File-Watcher.csproj", "init" ], "problemMatcher": "$msCompile" @@ -19,8 +17,6 @@ "type": "process", "args": [ "user-secrets", - "-p", - "${workspaceFolder}/File-Watcher.csproj", "set", "_UserSecretsId", "6516d19d6569" @@ -43,19 +39,16 @@ "problemMatcher": "$msCompile" }, { - "label": "build", + "label": "Build", "command": "dotnet", "type": "process", "args": [ - "build", - "${workspaceFolder}/File-Watcher.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" + "build" ], "problemMatcher": "$msCompile" }, { - "label": "Format-Whitespaces", + "label": "Format Whitespaces", "command": "dotnet", "type": "process", "args": [ @@ -74,10 +67,7 @@ "win-x64", "-c", "Release", - "-p:PublishAot=true", - "${workspaceFolder}/File-Watcher.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" + "-p:PublishAot=true" ], "problemMatcher": "$msCompile" }, diff --git a/File-Watcher.csproj b/File-Watcher.csproj index b96ce9a..96f74e1 100644 --- a/File-Watcher.csproj +++ b/File-Watcher.csproj @@ -12,11 +12,6 @@ - - - - - @@ -34,4 +29,12 @@ + + + + + + + + \ No newline at end of file diff --git a/Helpers/DAQmx/CSVTime.cs b/Helpers/DAQmx/CSVTime.cs new file mode 100644 index 0000000..c24bd5e --- /dev/null +++ b/Helpers/DAQmx/CSVTime.cs @@ -0,0 +1,12 @@ +namespace Helpers.DAQmx; + +#pragma warning disable IDE1006 // Naming Styles +#pragma warning disable IDE0044 // Add readonly modifier +#pragma warning disable IDE0051 // Remove unused private members +#pragma warning disable CS0169 // Field is never assigned to, and will always have its default value + +public struct CSVTime +{ + private ulong lsb; + private long msb; +} \ No newline at end of file diff --git a/Helpers/DAQmx/CVIAbsoluteTime.cs b/Helpers/DAQmx/CVIAbsoluteTime.cs new file mode 100644 index 0000000..16c5a01 --- /dev/null +++ b/Helpers/DAQmx/CVIAbsoluteTime.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace Helpers.DAQmx; + +#pragma warning disable IDE0044 // Add readonly modifier +#pragma warning disable IDE0051 // Remove unused private members +#pragma warning disable IDE1006 // Naming Styles +#pragma warning disable CS0169 // Field is never assigned to, and will always have its default value + +[StructLayout(LayoutKind.Explicit)] +public struct CVIAbsoluteTime +{ + [FieldOffset(0)] + private CSVTime cviTime; + [FieldOffset(0)] + private uint[] u32Data; +} diff --git a/Helpers/DAQmx/DAQmx.cs b/Helpers/DAQmx/DAQmx.cs new file mode 100644 index 0000000..05c5640 --- /dev/null +++ b/Helpers/DAQmx/DAQmx.cs @@ -0,0 +1,13 @@ +using System.Text; + +namespace Helpers.DAQmx; + +public static class DAQmx +{ + public static string GetErrorString(int errorCode) + { + StringBuilder errorString = new(256); + _ = Interop.DAQmxGetErrorString(errorCode, errorString, (uint)(errorString.Capacity + 1)); + return errorString.ToString(); + } +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxActiveEdge.cs b/Helpers/DAQmx/DAQmxActiveEdge.cs new file mode 100644 index 0000000..c164502 --- /dev/null +++ b/Helpers/DAQmx/DAQmxActiveEdge.cs @@ -0,0 +1,7 @@ +namespace Helpers.DAQmx; + +public enum DAQmxActiveEdge +{ + Falling = 10171, // 0x000027BB + Rising = 10280, // 0x00002828 +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxDataLayout.cs b/Helpers/DAQmx/DAQmxDataLayout.cs new file mode 100644 index 0000000..c906d17 --- /dev/null +++ b/Helpers/DAQmx/DAQmxDataLayout.cs @@ -0,0 +1,7 @@ +namespace Helpers.DAQmx; + +public enum DAQmxDataLayout +{ + GroupByChannel, + GroupByScanNumber, +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxDigitalPatternTriggerWhen.cs b/Helpers/DAQmx/DAQmxDigitalPatternTriggerWhen.cs new file mode 100644 index 0000000..156b14c --- /dev/null +++ b/Helpers/DAQmx/DAQmxDigitalPatternTriggerWhen.cs @@ -0,0 +1,7 @@ +namespace Helpers.DAQmx; + +public enum DAQmxDigitalPatternTriggerWhen +{ + PatternDoesNotMatch = 10253, // 0x0000280D + PatternMatches = 10254, // 0x0000280E +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxEdge.cs b/Helpers/DAQmx/DAQmxEdge.cs new file mode 100644 index 0000000..182d753 --- /dev/null +++ b/Helpers/DAQmx/DAQmxEdge.cs @@ -0,0 +1,7 @@ +namespace Helpers.DAQmx; + +public enum DAQmxEdge +{ + Falling = 10171, // 0x000027BB + Rising = 10280, // 0x00002828 +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxException.cs b/Helpers/DAQmx/DAQmxException.cs new file mode 100644 index 0000000..cac872e --- /dev/null +++ b/Helpers/DAQmx/DAQmxException.cs @@ -0,0 +1,14 @@ +namespace Helpers.DAQmx; + +public class DAQmxException : Exception +{ + public DAQmxException(string message) + : base(message) + { + } + + public DAQmxException(int errorCode, string message) + : base(errorCode.ToString() + " - " + message + " (" + DAQmx.GetErrorString(errorCode) + ")") + { + } +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxFillMode.cs b/Helpers/DAQmx/DAQmxFillMode.cs new file mode 100644 index 0000000..d938d76 --- /dev/null +++ b/Helpers/DAQmx/DAQmxFillMode.cs @@ -0,0 +1,7 @@ +namespace Helpers.DAQmx; + +public enum DAQmxFillMode +{ + GroupByChannel, + GroupByScanNumber, +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxInputTerminalConfiguration.cs b/Helpers/DAQmx/DAQmxInputTerminalConfiguration.cs new file mode 100644 index 0000000..25ef331 --- /dev/null +++ b/Helpers/DAQmx/DAQmxInputTerminalConfiguration.cs @@ -0,0 +1,10 @@ +namespace Helpers.DAQmx; + +public enum DAQmxInputTerminalConfiguration +{ + Default = -1, // 0xFFFFFFFF + NonReferencedSingleEnded = 10078, // 0x0000275E + ReferencedSingleEnded = 10083, // 0x00002763 + Differential = 10106, // 0x0000277A + Pseudodifferential = 12529, // 0x000030F1 +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxLevel.cs b/Helpers/DAQmx/DAQmxLevel.cs new file mode 100644 index 0000000..5734d65 --- /dev/null +++ b/Helpers/DAQmx/DAQmxLevel.cs @@ -0,0 +1,7 @@ +namespace Helpers.DAQmx; + +public enum DAQmxLevel +{ + High = 10192, // 0x000027D0 + Low = 10214, // 0x000027E6 +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxLineGrouping.cs b/Helpers/DAQmx/DAQmxLineGrouping.cs new file mode 100644 index 0000000..b2a6f81 --- /dev/null +++ b/Helpers/DAQmx/DAQmxLineGrouping.cs @@ -0,0 +1,7 @@ +namespace Helpers.DAQmx; + +public enum DAQmxLineGrouping +{ + OneChannelForEachLine, + OneChannelForAllLines, +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxLoggingMode.cs b/Helpers/DAQmx/DAQmxLoggingMode.cs new file mode 100644 index 0000000..d17218c --- /dev/null +++ b/Helpers/DAQmx/DAQmxLoggingMode.cs @@ -0,0 +1,8 @@ +namespace Helpers.DAQmx; + +public enum DAQmxLoggingMode +{ + Off = 10231, // 0x000027F7 + LogAndRead = 15842, // 0x00003DE2 + Log = 15844, // 0x00003DE4 +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxLoggingTDMSOperation.cs b/Helpers/DAQmx/DAQmxLoggingTDMSOperation.cs new file mode 100644 index 0000000..c400825 --- /dev/null +++ b/Helpers/DAQmx/DAQmxLoggingTDMSOperation.cs @@ -0,0 +1,9 @@ +namespace Helpers.DAQmx; + +public enum DAQmxLoggingTDMSOperation +{ + Open = 10437, // 0x000028C5 + OpenOrCreate = 15846, // 0x00003DE6 + CreateOrReplace = 15847, // 0x00003DE7 + Create = 15848, // 0x00003DE8 +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxMemoryException.cs b/Helpers/DAQmx/DAQmxMemoryException.cs new file mode 100644 index 0000000..8f503a9 --- /dev/null +++ b/Helpers/DAQmx/DAQmxMemoryException.cs @@ -0,0 +1,5 @@ +namespace Helpers.DAQmx; + +public class DAQmxMemoryException(string message) : Exception(message) +{ +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxPolarity.cs b/Helpers/DAQmx/DAQmxPolarity.cs new file mode 100644 index 0000000..c14d506 --- /dev/null +++ b/Helpers/DAQmx/DAQmxPolarity.cs @@ -0,0 +1,7 @@ +namespace Helpers.DAQmx; + +public enum DAQmxPolarity +{ + ActiveHigh = 10095, // 0x0000276F + ActiveLow = 10096, // 0x00002770 +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxSampleMode.cs b/Helpers/DAQmx/DAQmxSampleMode.cs new file mode 100644 index 0000000..864d219 --- /dev/null +++ b/Helpers/DAQmx/DAQmxSampleMode.cs @@ -0,0 +1,8 @@ +namespace Helpers.DAQmx; + +public enum DAQmxSampleMode +{ + ContinuousSamples = 10123, // 0x0000278B + FiniteSamples = 10178, // 0x000027C2 + HardwareTimedSinglePoint = 12522, // 0x000030EA +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxTask.cs b/Helpers/DAQmx/DAQmxTask.cs new file mode 100644 index 0000000..0bed946 --- /dev/null +++ b/Helpers/DAQmx/DAQmxTask.cs @@ -0,0 +1,347 @@ +namespace Helpers.DAQmx; + +#pragma warning disable IDE1006 // Naming Styles +#pragma warning disable IDE0044 // Add readonly modifier +#pragma warning disable IDE0051 // Remove unused private members +#pragma warning disable CS0169 // Field is never assigned to, and will always have its default value + +public class DAQmxTask +{ + + private IntPtr taskHandle; + + private DAQmxTask() + { + } + + public double DT { get; private set; } + public int Channels { get; private set; } + public ulong TotalSamplesRead { get; private set; } + public DateTime TaskStartedUtc { get; private set; } + public ulong TotalSamplesWrite { get; private set; } + public ulong TotalSamplesPerChanRead { get; private set; } + public ulong TotalSamplesPerChanWrite { get; private set; } + + public static DAQmxTask Create(string taskName) + { + DAQmxTask result; + IntPtr taskHandle; + int task = Interop.DAQmxCreateTask(taskName, out taskHandle); + if (task < 0) + throw new DAQmxException(task, "Could not create Task"); + result = new() { taskHandle = taskHandle, Channels = 0 }; + return result; + } + + public void ConfigureLoggingTechnicalDataManagementStreaming(string filePath, DAQmxLoggingMode loggingMode, string groupName, DAQmxLoggingTDMSOperation operation) + { + int errorCode = Interop.DAQmxConfigureLogging(taskHandle, filePath, (int)loggingMode, groupName, (int)operation); + if (errorCode < 0) + throw new DAQmxException(errorCode, "Could not configure technical data management streaming logging"); + } + + public void CreateAnalogInputVoltageChannel(string physicalChannel, string nameToAssignToChannel, DAQmxInputTerminalConfiguration terminalConfig, double minVal, double maxVal, DAQmxUnits units, string customScaleName) + { + int analogInputVoltageChan = Interop.DAQmxCreateAIVoltageChan(taskHandle, physicalChannel, nameToAssignToChannel, (int)terminalConfig, minVal, maxVal, (int)units, customScaleName); + if (analogInputVoltageChan < 0) + throw new DAQmxException(analogInputVoltageChan, "Could not create analog input voltage channel"); + ++Channels; + } + + public void CreateAnalogOutputVoltageChan(string physicalChannel, string nameToAssignToChannel, double minVal, double maxVal, DAQmxUnits units, string customScaleName) + { + int analogOutputVoltageChan = Interop.DAQmxCreateAOVoltageChan(taskHandle, physicalChannel, nameToAssignToChannel, minVal, maxVal, (int)units, customScaleName); + if (analogOutputVoltageChan < 0) + throw new DAQmxException(analogOutputVoltageChan, "Could not create analog output voltage channel"); + ++Channels; + } + + public void CreateDigitalOutputChanel(string lines, string nameToAssignToLines, DAQmxLineGrouping lineGrouping) + { + int doChan = Interop.DAQmxCreateDOChan(taskHandle, lines, nameToAssignToLines, (int)lineGrouping); + if (doChan < 0) + throw new DAQmxException(doChan, "Could not create digital output channel"); + ++Channels; + } + +#if unsafe + public unsafe void ReadAnalogF64(int numSamplesPerChan, double timeout, DAQmxFillMode fillMode, Span data) + { + int arraySizeInSamples = numSamplesPerChan * Channels; + if (data.Length < arraySizeInSamples) + throw new DAQmxMemoryException("Span length too short. (Span length: " + data.Length.ToString() + ", required length: " + arraySizeInSamples.ToString() + ")"); + fixed (double* readArray = &data.GetPinnableReference()) + { + IntPtr samplesPerChanRead; + int errorCode = Interop.DAQmxReadAnalogF64(taskHandle, numSamplesPerChan, timeout, (int)fillMode, readArray, (uint)arraySizeInSamples, out samplesPerChanRead, IntPtr.Zero); + if (errorCode < 0) + throw new DAQmxException(errorCode, "Could not read samples"); + int int32 = samplesPerChanRead.ToInt32(); + if (int32 != numSamplesPerChan) + throw new DAQmxException("Could not read requested number of samples"); + TotalSamplesPerChanRead += (ulong)int32; + TotalSamplesRead += (ulong)arraySizeInSamples; + } + } +#endif + + public double ReadAnalogScalarF64(double timeout) + { + double result; + int errorCode = Interop.DAQmxReadAnalogScalarF64(taskHandle, timeout, out result, IntPtr.Zero); + if (errorCode < 0) + throw new DAQmxException(errorCode, "Could not read samples"); + ++TotalSamplesRead; + return result; + } + +#if unsafe + public double ReadAnalogF64(double timeout) + { + double result = DAQmxReadAnalogF64(timeout); + ++TotalSamplesRead; + } +#endif + +#if unsafe + private unsafe double DAQmxReadAnalogF64(double timeout) + { + double results; + int numSamplesPerChan = 1; + uint arraySizeInSamples = 1; + IntPtr samplesPerChanRead = IntPtr.Zero; + double* readArray = stackalloc double[1]; + int fillMode = (int)DAQmxFillMode.GroupByChannel; + int errorCode = Interop.DAQmxReadAnalogF64(taskHandle, + numSamplesPerChan, + timeout, + fillMode, + readArray, + arraySizeInSamples, + out samplesPerChanRead, + IntPtr.Zero); + if (errorCode < 0) + throw new DAQmxException(errorCode, "Could not read samples"); + results = *readArray; + return results; + } +#endif + + public void Start() + { + int errorCode = Interop.DAQmxStartTask(taskHandle); + if (errorCode < 0) + throw new DAQmxException(errorCode, "Could not start Task"); + TaskStartedUtc = DateTime.UtcNow; + } + + public void Stop() + { + int errorCode = Interop.DAQmxStopTask(taskHandle); + if (errorCode < 0) + throw new DAQmxException(errorCode, "Could not stop Task"); + } + + public void Clear() + { + int errorCode = Interop.DAQmxClearTask(taskHandle); + if (errorCode < 0) + throw new DAQmxException(errorCode, "Could not clear Task"); + } + + public void CfgSampleClkTiming(string source, double rate, DAQmxActiveEdge activeEdge, DAQmxSampleMode sampleMode, ulong samplesPerChan) + { + int errorCode = Interop.DAQmxCfgSampClkTiming(taskHandle, source, rate, (int)activeEdge, (int)sampleMode, samplesPerChan); + if (errorCode < 0) + throw new DAQmxException(errorCode, "CfgSampleClkTiming failed."); + DT = 1.0 / SampleClockRate; + } + + public void DAQmxCfgHandshakingTiming(DAQmxSampleMode sampleMode, ulong samplesPerChan) + { + int errorCode = Interop.DAQmxCfgHandshakingTiming(taskHandle, (int)sampleMode, samplesPerChan); + if (errorCode < 0) + throw new DAQmxException(errorCode, "DAQmxCfgHandshakingTiming failed."); + } + + public void DAQmxCfgBurstHandshakingTimingImportClock(DAQmxSampleMode sampleMode, ulong samplesPerChan, double sampleClkRate, string sampleClkSrc, DAQmxPolarity sampleClkActiveEdge, DAQmxLevel pauseWhen, DAQmxPolarity readyEventActiveLevel) + { + int errorCode = Interop.DAQmxCfgBurstHandshakingTimingImportClock(taskHandle, (int)sampleMode, samplesPerChan, sampleClkRate, sampleClkSrc, (int)sampleClkActiveEdge, (int)pauseWhen, (int)readyEventActiveLevel); + if (errorCode < 0) + throw new DAQmxException(errorCode, "DAQmxCfgBurstHandshakingTimingImportClock failed."); + } + + public void DAQmxCfgBurstHandshakingTimingExportClock(DAQmxSampleMode sampleMode, ulong samplesPerChan, double sampleClkRate, string sampleClkOutputTerm, DAQmxPolarity sampleClkPulsePolarity, DAQmxLevel pauseWhen, DAQmxPolarity readyEventActiveLevel) + { + int errorCode = Interop.DAQmxCfgBurstHandshakingTimingExportClock(taskHandle, (int)sampleMode, samplesPerChan, sampleClkRate, sampleClkOutputTerm, (int)sampleClkPulsePolarity, (int)pauseWhen, (int)readyEventActiveLevel); + if (errorCode < 0) + throw new DAQmxException(errorCode, "DAQmxCfgBurstHandshakingTimingExportClock failed."); + } + + public void DAQmxCfgChangeDetectionTiming(string risingEdgeChan, string fallingEdgeChan, DAQmxSampleMode sampleMode, ulong samplesPerChan) + { + int errorCode = Interop.DAQmxCfgChangeDetectionTiming(taskHandle, risingEdgeChan, fallingEdgeChan, (int)sampleMode, samplesPerChan); + if (errorCode < 0) + throw new DAQmxException(errorCode, "DAQmxCfgChangeDetectionTiming failed."); + } + + public void DAQmxCfgImplicitTiming(DAQmxSampleMode sampleMode, ulong samplesPerChan) + { + int errorCode = Interop.DAQmxCfgImplicitTiming(taskHandle, (int)sampleMode, samplesPerChan); + if (errorCode < 0) + throw new DAQmxException(errorCode, "DAQmxCfgImplicitTiming failed."); + } + + public void DAQmxCfgPipelinedSampleClkTiming(string source, double rate, DAQmxActiveEdge activeEdge, DAQmxSampleMode sampleMode, ulong samplesPerChan) + { + int errorCode = Interop.DAQmxCfgPipelinedSampClkTiming(taskHandle, source, rate, (int)activeEdge, (int)sampleMode, samplesPerChan); + if (errorCode < 0) + throw new DAQmxException(errorCode, "DAQmxCfgPipelinedSampleClkTiming failed."); + } + + public double SampleClockRate + { + get + { + double result = 0.0; + int sampleClkRate = Interop.DAQmxGetSampClkRate(taskHandle, ref result); + if (sampleClkRate < 0) + throw new DAQmxException(sampleClkRate, "Could not get SampleClockRate"); + return result; + } + set + { + int errorCode = Interop.DAQmxSetSampClkRate(taskHandle, value); + if (errorCode < 0) + throw new DAQmxException(errorCode, "Could not set SampleClockRate"); + } + } + + public double SampleClockMaxRate + { + get + { + double result = 0.0; + int sampleClkMaxRate = Interop.DAQmxGetSampClkMaxRate(taskHandle, ref result); + if (sampleClkMaxRate < 0) + throw new DAQmxException(sampleClkMaxRate, "Could not get SampleClockMaxRate"); + return result; + } + } + + public void DisableStartTrig() + { + int errorCode = Interop.DAQmxDisableStartTrig(taskHandle); + if (errorCode < 0) + throw new DAQmxException(errorCode, "DisableStartTrig failed."); + } + + public void CfgDigEdgeStartTrig(string triggerSource, DAQmxEdge triggerEdge) + { + int errorCode = Interop.DAQmxCfgDigEdgeStartTrig(taskHandle, triggerSource, (int)triggerEdge); + if (errorCode < 0) + throw new DAQmxException(errorCode, "CfgDigEdgeStartTrig failed."); + } + + public void CfgAnalogEdgeStartTrig(string triggerSource, DAQmxEdge triggerSlope, double triggerLevel) + { + int errorCode = Interop.DAQmxCfgAnlgEdgeStartTrig(taskHandle, triggerSource, (int)triggerSlope, triggerLevel); + if (errorCode < 0) + throw new DAQmxException(errorCode, "CfgAnalogEdgeStartTrig failed."); + } + + public void CfgAnalogMultiEdgeStartTrig(string triggerSources, DAQmxEdge[] triggerSlopes, double[] triggerLevels) => + throw new NotImplementedException("CfgAnalogMultiEdgeStartTrig is not implemented in this version."); + + public void CfgAnalogWindowStartTrig(string triggerSource, DAQmxWindowTriggerWhen triggerWhen, double windowTop, double windowBottom) + { + int errorCode = Interop.DAQmxCfgAnlgWindowStartTrig(taskHandle, triggerSource, (int)triggerWhen, windowTop, windowBottom); + if (errorCode < 0) + throw new DAQmxException(errorCode, "CfgAnalogWindowStartTrig failed."); + } + + public void CfgTimeStartTrig(CVIAbsoluteTime when, DAQmxTimescale timescale) + { + int errorCode = Interop.DAQmxCfgTimeStartTrig(taskHandle, when, (int)timescale); + if (errorCode < 0) + throw new DAQmxException(errorCode, "CfgTimeStartTrig failed."); + } + + public void CfgDigPatternStartTrig(string triggerSource, string triggerPattern, DAQmxDigitalPatternTriggerWhen triggerWhen) + { + int errorCode = Interop.DAQmxCfgDigPatternStartTrig(taskHandle, triggerSource, triggerPattern, (int)triggerWhen); + if (errorCode < 0) + throw new DAQmxException(errorCode, "CfgDigPatternStartTrig failed."); + } + + public void DisableRefTrig() + { + int errorCode = Interop.DAQmxDisableRefTrig(taskHandle); + if (errorCode < 0) + throw new DAQmxException(errorCode, "DisableRefTrig failed."); + } + + public void CfgDigEdgeRefTrig(string triggerSource, DAQmxEdge triggerEdge, uint pretriggerSamples) + { + int errorCode = Interop.DAQmxCfgDigEdgeRefTrig(taskHandle, triggerSource, (int)triggerEdge, pretriggerSamples); + if (errorCode < 0) + throw new DAQmxException(errorCode, "CfgDigEdgeRefTrig failed."); + } + + public void CfgAnalogEdgeRefTrig(string triggerSource, DAQmxEdge triggerSlope, double triggerLevel, uint pretriggerSamples) + { + int errorCode = Interop.DAQmxCfgAnlgEdgeRefTrig(taskHandle, triggerSource, (int)triggerSlope, triggerLevel, pretriggerSamples); + if (errorCode < 0) + throw new DAQmxException(errorCode, "CfgAnalogEdgeRefTrig failed."); + } + + public void CfgAnalogMultiEdgeRefTrig(string triggerSources, DAQmxEdge[] triggerSlopes, double[] triggerLevels, uint pretriggerSamples) => + throw new NotImplementedException("CfgAnalogMultiEdgeRefTrig is not implemented in this version."); + + public void CfgAnalogWindowRefTrig(string triggerSource, DAQmxWindowTriggerWhen triggerWhen, double windowTop, double windowBottom, uint pretriggerSamples) + { + int errorCode = Interop.DAQmxCfgAnlgWindowRefTrig(taskHandle, triggerSource, (int)triggerWhen, windowTop, windowBottom, pretriggerSamples); + if (errorCode < 0) + throw new DAQmxException(errorCode, "CfgAnalogWindowRefTrig failed."); + } + + public void CfgDigPatternRefTrig(string triggerSource, string triggerPattern, DAQmxDigitalPatternTriggerWhen triggerWhen, uint pretriggerSamples) + { + int errorCode = Interop.DAQmxCfgDigPatternRefTrig(taskHandle, triggerSource, triggerPattern, (int)triggerWhen, pretriggerSamples); + if (errorCode < 0) + throw new DAQmxException(errorCode, "CfgDigPatternRefTrig failed."); + } + + public void WriteAnalogF64(int numSamplesPerChan, bool autoStart, double timeout, DAQmxDataLayout dataLayout, Span data) + { + int num = numSamplesPerChan * Channels; + if (data.Length < num) + throw new DAQmxMemoryException("Span length too short. (Span length: " + data.Length.ToString() + ", required length: " + num.ToString() + ")"); + IntPtr samplesPerChanWritten; + int errorCode = Interop.DAQmxWriteAnalogF64(taskHandle, numSamplesPerChan, autoStart, timeout, dataLayout > DAQmxDataLayout.GroupByChannel, data.ToArray(), out samplesPerChanWritten, IntPtr.Zero); + if (errorCode < 0) + throw new DAQmxException(errorCode, "Could not write samples"); + int int32 = samplesPerChanWritten.ToInt32(); + if (int32 != numSamplesPerChan) + throw new DAQmxException("Could not write requested number of samples"); + TotalSamplesPerChanWrite += (ulong)int32; + TotalSamplesWrite += (ulong)num; + } + + public void WriteDigitalLines(int numSamplesPerChan, bool autoStart, double timeout, DAQmxDataLayout dataLayout, Span data) + { + int num = numSamplesPerChan * Channels; + if (data.Length < num) + throw new DAQmxMemoryException("Span length too short. (Span length: " + data.Length.ToString() + ", required length: " + num.ToString() + ")"); + IntPtr samplesPerChanWritten; + int errorCode = Interop.DAQmxWriteDigitalLines(taskHandle, numSamplesPerChan, autoStart, timeout, dataLayout > DAQmxDataLayout.GroupByChannel, data.ToArray(), out samplesPerChanWritten, IntPtr.Zero); + if (errorCode < 0) + throw new DAQmxException(errorCode, "Could not write samples"); + int int32 = samplesPerChanWritten.ToInt32(); + if (int32 != numSamplesPerChan) + throw new DAQmxException("Could not write requested number of samples"); + TotalSamplesPerChanWrite += (ulong)int32; + TotalSamplesWrite += (ulong)num; + } + +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxTimescale.cs b/Helpers/DAQmx/DAQmxTimescale.cs new file mode 100644 index 0000000..bb309f9 --- /dev/null +++ b/Helpers/DAQmx/DAQmxTimescale.cs @@ -0,0 +1,7 @@ +namespace Helpers.DAQmx; + +public enum DAQmxTimescale +{ + HostTime = 16126, // 0x00003EFE + IODeviceTime = 16127, // 0x00003EFF +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxUnits.cs b/Helpers/DAQmx/DAQmxUnits.cs new file mode 100644 index 0000000..6470f12 --- /dev/null +++ b/Helpers/DAQmx/DAQmxUnits.cs @@ -0,0 +1,7 @@ +namespace Helpers.DAQmx; + +public enum DAQmxUnits +{ + FromCustomScale = 10065, // 0x00002751 + Volts = 10348, // 0x0000286C +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxWindowTriggerWhen.cs b/Helpers/DAQmx/DAQmxWindowTriggerWhen.cs new file mode 100644 index 0000000..739272b --- /dev/null +++ b/Helpers/DAQmx/DAQmxWindowTriggerWhen.cs @@ -0,0 +1,7 @@ +namespace Helpers.DAQmx; + +public enum DAQmxWindowTriggerWhen +{ + EnteringWindow = 10163, // 0x000027B3 + LeavingWindow = 10208, // 0x000027E0 +} \ No newline at end of file diff --git a/Helpers/DAQmx/DAQmxWriteRegenMode.cs b/Helpers/DAQmx/DAQmxWriteRegenMode.cs new file mode 100644 index 0000000..1eea825 --- /dev/null +++ b/Helpers/DAQmx/DAQmxWriteRegenMode.cs @@ -0,0 +1,7 @@ +namespace Helpers.DAQmx; + +public enum DAQmxWriteRegenMode +{ + AllowRegen = 10097, // 0x00002771 + DoNotAllowRegen = 10158, // 0x000027AE +} \ No newline at end of file diff --git a/Helpers/DAQmx/Interop.cs b/Helpers/DAQmx/Interop.cs new file mode 100644 index 0000000..e53ad85 --- /dev/null +++ b/Helpers/DAQmx/Interop.cs @@ -0,0 +1,431 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; + +#pragma warning disable IDE1006 // Naming Styles +#pragma warning disable SYSLIB1054 // Type or member is obsolete + +namespace Helpers.DAQmx; + +internal class Interop +{ // cSpell:disable + + private const string lib = "DAQmx"; + private static IntPtr libHandle = IntPtr.Zero; + + static Interop() => + NativeLibrary.SetDllImportResolver(typeof(Interop).Assembly, ImportResolver); + + private static IntPtr ImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + if (libraryName == "DAQmx" && !(libHandle != IntPtr.Zero)) + { + bool loaded; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + loaded = NativeLibrary.TryLoad("C:/Windows/System32/nicaiu.dll", assembly, searchPath, out libHandle); + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + loaded = NativeLibrary.TryLoad("/usr/local/lib64/libnidaqmxbase.so", assembly, searchPath, out libHandle); + if (!loaded) + loaded = NativeLibrary.TryLoad("/usr/local/lib/libnidaqmxbase.so", assembly, searchPath, out libHandle); + if (!loaded) + loaded = NativeLibrary.TryLoad("/usr/local/natinst/lib/libnidaqmx.so", assembly, searchPath, out libHandle); + if (!loaded) + loaded = NativeLibrary.TryLoad("/usr/lib/x86_64-linux-gnu/libnidaqmx.so", assembly, searchPath, out libHandle); + } + else + throw new PlatformNotSupportedException("Unsupported platform for DAQmx library."); + if (!loaded) + throw new DllNotFoundException($"Failed to load {lib} library."); + } + return libHandle; + } + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxGetSampClkRate(IntPtr taskHandle, ref double data); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxSetSampClkRate(IntPtr taskHandle, double data); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxGetSampClkMaxRate(IntPtr taskHandle, ref double data); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxSetWriteRegenMode(IntPtr taskHandle, int data); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxCreateAIVoltageChan( + IntPtr taskHandle, + string physicalChannel, + string nameToAssignToChannel, + int terminalConfig, + double minVal, + double maxVal, + int units, + string customScaleName); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxCreateAOVoltageChan( + IntPtr taskHandle, + string physicalChannel, + string nameToAssignToChannel, + double minVal, + double maxVal, + int units, + string customScaleName); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxCreateDOChan( + IntPtr taskHandle, + string lines, + string nameToAssignToLines, + int lineGrouping); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxGetErrorString( + int errorCode, + StringBuilder errorString, + uint buffersize); + +#if unsafe + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern unsafe int DAQmxReadAnalogF64( + IntPtr taskHandle, + int numSampsPerChan, + double timeout, + int fillMode, + double* readArray, + uint arraySizeInSamps, + out IntPtr sampsPerChanRead, + IntPtr reserved); +#endif + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxReadAnalogScalarF64( + IntPtr taskHandle, + double timeout, + out double value, + IntPtr reserved); + +#if unsafe + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern unsafe int DAQmxReadBinaryI16( + IntPtr taskHandle, + int numSampsPerChan, + double timeout, + int fillMode, + bool* readArray, + uint arraySizeInSamps, + out IntPtr sampsPerChanRead, + IntPtr reserved); +#endif + +#if unsafe + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern unsafe int DAQmxReadBinaryU16( + IntPtr taskHandle, + int numSampsPerChan, + double timeout, + int fillMode, + ushort* readArray, + uint arraySizeInSamps, + out IntPtr sampsPerChanRead, + IntPtr reserved); +#endif + +#if unsafe + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern unsafe int DAQmxReadBinaryI32( + IntPtr taskHandle, + int numSampsPerChan, + double timeout, + int fillMode, + int* readArray, + uint arraySizeInSamps, + out IntPtr sampsPerChanRead, + IntPtr reserved); +#endif + +#if unsafe + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern unsafe int DAQmxReadBinaryU32( + IntPtr taskHandle, + int numSampsPerChan, + double timeout, + int fillMode, + uint* readArray, + uint arraySizeInSamps, + out IntPtr sampsPerChanRead, + IntPtr reserved); +#endif + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxConfigureLogging( + IntPtr taskHandle, + string filePath, + int loggingMode, + string groupName, + int operation); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxLoadTask(string taskName, out IntPtr taskHandle); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxCreateTask(string taskName, out IntPtr taskHandle); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxAddGlobalChansToTask(IntPtr taskHandle, string[] channelNames); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxStartTask(IntPtr taskHandle); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxStopTask(IntPtr taskHandle); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxClearTask(IntPtr taskHandle); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxWaitUntilTaskDone(IntPtr taskHandle, double timeToWait); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxWaitForValidTimestamp( + IntPtr taskHandle, + int timestampEvent, + double timeout, + CVIAbsoluteTime timestamp); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxIsTaskDone(IntPtr taskHandle, out uint isTaskDone); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxTaskControl(IntPtr taskHandle); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxGetNthTaskChannel(IntPtr taskHandle); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxGetNthTaskDevice(IntPtr taskHandle); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxGetTaskAttribute(IntPtr taskHandle); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxCfgSampClkTiming( + IntPtr taskHandle, + string source, + double rate, + int activeEdge, + int sampleMode, + ulong sampsPerChan); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxCfgHandshakingTiming( + IntPtr taskHandle, + int sampleMode, + ulong sampsPerChan); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxCfgBurstHandshakingTimingImportClock( + IntPtr taskHandle, + int sampleMode, + ulong sampsPerChan, + double sampleClkRate, + string sampleClkSrc, + int sampleClkActiveEdge, + int pauseWhen, + int readyEventActiveLevel); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxCfgBurstHandshakingTimingExportClock( + IntPtr taskHandle, + int sampleMode, + ulong sampsPerChan, + double sampleClkRate, + string sampleClkOutpTerm, + int sampleClkPulsePolarity, + int pauseWhen, + int readyEventActiveLevel); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxCfgChangeDetectionTiming( + IntPtr taskHandle, + string risingEdgeChan, + string fallingEdgeChan, + int sampleMode, + ulong sampsPerChan); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxCfgImplicitTiming( + IntPtr taskHandle, + int sampleMode, + ulong sampsPerChan); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxCfgPipelinedSampClkTiming( + IntPtr taskHandle, + string source, + double rate, + int activeEdge, + int sampleMode, + ulong sampsPerChan); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxGetTimingAttribute( + IntPtr taskHandle, + int attribute, + out object value); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxSetTimingAttribute( + IntPtr taskHandle, + int attribute, + object value); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxResetTimingAttribute(IntPtr taskHandle, int attribute); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxGetTimingAttributeEx( + IntPtr taskHandle, + string deviceNames, + int attribute, + out object value); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxSetTimingAttributeEx( + IntPtr taskHandle, + string deviceNames, + int attribute, + object value); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + internal static extern int DAQmxResetTimingAttributeEx( + IntPtr taskHandle, + string deviceNames, + int attribute); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxDisableStartTrig(IntPtr taskHandle); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxCfgDigEdgeStartTrig( + IntPtr taskHandle, + string triggerSource, + int triggerEdge); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxCfgAnlgEdgeStartTrig( + IntPtr taskHandle, + string triggerSource, + int triggerSlope, + double triggerLevel); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxCfgAnlgMultiEdgeStartTrig( + IntPtr taskHandle, + string triggerSources, + int[] triggerSlopeArray, + double[] triggerLevelArray, + uint arraySize); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxCfgAnlgWindowStartTrig( + IntPtr taskHandle, + string triggerSource, + int triggerWhen, + double windowTop, + double windowBottom); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxCfgTimeStartTrig( + IntPtr taskHandle, + CVIAbsoluteTime when, + int timescale); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxCfgDigPatternStartTrig( + IntPtr taskHandle, + string triggerSource, + string triggerPattern, + int triggerWhen); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxDisableRefTrig(IntPtr taskHandle); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxCfgDigEdgeRefTrig( + IntPtr taskHandle, + string triggerSource, + int triggerEdge, + uint pretriggerSamples); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxCfgAnlgEdgeRefTrig( + IntPtr taskHandle, + string triggerSource, + int triggerSlope, + double triggerLevel, + uint pretriggerSamples); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxCfgAnlgMultiEdgeRefTrig( + IntPtr taskHandle, + string triggerSources, + int[] triggerSlopeArray, + double[] triggerLevelArray, + uint pretriggerSamples, + uint arraySize); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxCfgAnlgWindowRefTrig( + IntPtr taskHandle, + string triggerSource, + int triggerWhen, + double windowTop, + double windowBottom, + uint pretriggerSamples); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxCfgDigPatternRefTrig( + IntPtr taskHandle, + string triggerSource, + string triggerPattern, + int triggerWhen, + uint pretriggerSamples); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxGetTrigAttribute( + IntPtr taskHandle, + int attribute, + out object value); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxSetTrigAttribute(IntPtr taskHandle, int attribute, object value); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxResetTrigAttribute(IntPtr taskHandle, int attribute); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxWriteAnalogF64( + IntPtr taskHandle, + int numSampsPerChan, + bool autoStart, + double timeout, + bool dataLayout, + double[] writeArray, + out IntPtr sampsPerChanWritten, + IntPtr reserved); + + [DllImport("DAQmx", CallingConvention = CallingConvention.StdCall)] + public static extern int DAQmxWriteDigitalLines( + IntPtr taskHandle, + int numSampsPerChan, + bool autoStart, + double timeout, + bool dataLayout, + byte[] writeArray, + out IntPtr sampsPerChanWritten, + IntPtr reserved); + +} \ No newline at end of file diff --git a/Helpers/DeterministicHashCodeHelper.cs b/Helpers/DeterministicHashCodeHelper.cs index 376ee82..4b99fd2 100644 --- a/Helpers/DeterministicHashCodeHelper.cs +++ b/Helpers/DeterministicHashCodeHelper.cs @@ -1,6 +1,12 @@ using CliWrap; using File_Watcher.Models; + +#if ShellProgressBar + using ShellProgressBar; + +#endif + using System.Collections.ObjectModel; using System.Drawing; using System.Drawing.Imaging; @@ -21,13 +27,24 @@ internal static partial class DeterministicHashCodeHelper { public long Ticks { get; init; } - public int? CurrentTick => _ProgressBar?.CurrentTick; + public int? CurrentTick => +#if ShellProgressBar + _ProgressBar?.CurrentTick; +#else + throw new NotSupportedException("ShellProgressBar is not supported in this context."); +#endif +#if ShellProgressBar private ProgressBar? _ProgressBar; private readonly ProgressBarOptions _ProgressBarOptions; +#endif public Windows() => +#if ShellProgressBar _ProgressBarOptions = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; +#else + throw new NotSupportedException("ShellProgressBar is not supported in this context."); +#endif DeterministicHashCode IWindows.GetDeterministicHashCode(HttpClient httpClient, Uri uri) => GetDeterministicHashCode(httpClient, uri); @@ -98,18 +115,26 @@ internal static partial class DeterministicHashCodeHelper } void IWindows.Tick() => +#if ShellProgressBar _ProgressBar?.Tick(); +#else + throw new NotSupportedException("ShellProgressBar is not supported in this context."); +#endif void IDisposable.Dispose() { +#if ShellProgressBar _ProgressBar?.Dispose(); +#endif GC.SuppressFinalize(this); } void IWindows.ConstructProgressBar(int maxTicks, string message) { +#if ShellProgressBar _ProgressBar?.Dispose(); _ProgressBar = new(maxTicks, message, _ProgressBarOptions); +#endif } ReadOnlyCollection IWindows.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(ResultSettings resultSettings, HttpClient? httpClient, FilePath filePath) diff --git a/Helpers/NationalInstrumentsHelper.cs b/Helpers/NationalInstrumentsHelper.cs new file mode 100644 index 0000000..9a586b3 --- /dev/null +++ b/Helpers/NationalInstrumentsHelper.cs @@ -0,0 +1,75 @@ +using File_Watcher.Models; +using Helpers.DAQmx; + +namespace File_Watcher.Helpers; + +internal static partial class NationalInstrumentsHelper +{ + + private static Dictionary? _DataAcquisitionTasks = null; + + internal static bool WriteData(AppSettings appSettings, ILogger logger) + { + double value; + LogNetToHoursSince(logger); + if (_DataAcquisitionTasks is null) + { + string name; + _DataAcquisitionTasks = []; + DAQmxTask dataAcquisitionTask; + const DAQmxUnits volts = DAQmxUnits.Volts; + NationalInstrumentsConfiguration ni = appSettings.NationalInstrumentsConfiguration; + const DAQmxInputTerminalConfiguration differential = DAQmxInputTerminalConfiguration.Differential; + const DAQmxInputTerminalConfiguration referencedSingleEnded = DAQmxInputTerminalConfiguration.ReferencedSingleEnded; + foreach (string physicalChannel in appSettings.NationalInstrumentsConfiguration.DifferentialPhysicalChannels.Distinct()) + { + name = !physicalChannel.Contains('/') ? physicalChannel : physicalChannel.Split('/')[1]; + dataAcquisitionTask = DAQmxTask.Create(name); + dataAcquisitionTask.CreateAnalogInputVoltageChannel(physicalChannel, name, differential, ni.MiniumValue, ni.MaximumValue, volts, ni.CustomScaleName); + _DataAcquisitionTasks.Add(name, dataAcquisitionTask); + } + foreach (string physicalChannel in appSettings.NationalInstrumentsConfiguration.ReferencedSingleEndedPhysicalChannels.Distinct()) + { + name = !physicalChannel.Contains('/') ? physicalChannel : physicalChannel.Split('/')[1]; + dataAcquisitionTask = DAQmxTask.Create(name); + dataAcquisitionTask.CreateAnalogInputVoltageChannel(physicalChannel, name, referencedSingleEnded, ni.MiniumValue, ni.MaximumValue, volts, ni.CustomScaleName); + _DataAcquisitionTasks.Add(name, dataAcquisitionTask); + } + } + foreach (KeyValuePair keyValuePair in _DataAcquisitionTasks) + { + if (appSettings.NationalInstrumentsConfiguration.UsePointerMethod) + throw new NotSupportedException("Pointer method is not supported in this implementation."); + value = keyValuePair.Value.ReadAnalogScalarF64(appSettings.NationalInstrumentsConfiguration.ReadTimeout); + logger.LogInformation("{key}-{read}: {value}", keyValuePair.Key, keyValuePair.Value.TotalSamplesRead, value); + } + return true; + } + + private static void LogNetToHoursSince(ILogger? logger) + { + double secondsInAHour = 3600f; + long epoch = new DateTime(1970, 1, 1).Ticks; + long net8ReleaseDate = new DateTime(2023, 11, 14).Ticks; + long net9ReleaseDate = new DateTime(2024, 11, 12).Ticks; + long net10ReleaseDate = new DateTime(2026, 01, 01).Ticks; + long framework48ReleaseDate = new DateTime(2019, 04, 18).Ticks; + double net8TotalSeconds = new TimeSpan(net8ReleaseDate - epoch).TotalSeconds; + double net9TotalSeconds = new TimeSpan(net9ReleaseDate - epoch).TotalSeconds; + double net10TotalSeconds = new TimeSpan(net10ReleaseDate - epoch).TotalSeconds; + double framework48TotalSeconds = new TimeSpan(framework48ReleaseDate - epoch).TotalSeconds; + logger?.LogInformation("It has been {net8TotalSeconds} seconds since net8 was released", net8TotalSeconds); + logger?.LogInformation("It has been {net9TotalSeconds} seconds since net9 was released", net9TotalSeconds); + logger?.LogInformation("It has been {net10TotalSeconds} seconds since net10 was released", net10TotalSeconds); + logger?.LogInformation("It has been {framework48TotalSeconds} seconds since framework48 was released", framework48TotalSeconds); + double net8TotalHours = Math.Floor((DateTimeOffset.UtcNow.ToUnixTimeSeconds() - net8TotalSeconds) / secondsInAHour); + double net9TotalHours = Math.Floor((DateTimeOffset.UtcNow.ToUnixTimeSeconds() - net9TotalSeconds) / secondsInAHour); + double net10TotalHours = Math.Floor((DateTimeOffset.UtcNow.ToUnixTimeSeconds() - net10TotalSeconds) / secondsInAHour); + double framework48TotalHours = Math.Floor((DateTimeOffset.UtcNow.ToUnixTimeSeconds() - framework48TotalSeconds) / secondsInAHour); + logger?.LogInformation("It has been {net8TotalHours} hours since net8 was released", net8TotalHours); + logger?.LogInformation("It has been {net9TotalHours} hours since net9 was released", net9TotalHours); + logger?.LogInformation("It has been {net10TotalHours} hours since net10 was released", net10TotalHours); + logger?.LogInformation("It has been {framework48TotalHours} hours since framework48 was released", framework48TotalHours); + } + +} \ No newline at end of file diff --git a/Models/AppSettings.cs b/Models/AppSettings.cs index b8bb17a..4d74f36 100644 --- a/Models/AppSettings.cs +++ b/Models/AppSettings.cs @@ -16,6 +16,7 @@ public record AppSettings(CamstarOracleConfiguration CamstarOracleConfiguration, IsoConfiguration IsoConfiguration, MetadataSettings MetadataSettings, MetrologyConfiguration MetrologyConfiguration, + NationalInstrumentsConfiguration NationalInstrumentsConfiguration, NugetConfiguration NugetConfiguration, ResultSettings ResultSettings, SeleniumConfiguration SeleniumConfiguration, @@ -42,6 +43,7 @@ public record AppSettings(CamstarOracleConfiguration CamstarOracleConfiguration, IsoConfiguration? isoConfiguration = configurationRoot.GetSection(nameof(IsoConfiguration)).Get(); MetadataSettings? metadataSettings = configurationRoot.GetSection(nameof(MetadataSettings)).Get(); MetrologyConfiguration? metrologyConfiguration = configurationRoot.GetSection(nameof(MetrologyConfiguration)).Get(); + NationalInstrumentsConfiguration? nationalInstrumentsConfiguration = configurationRoot.GetSection(nameof(NationalInstrumentsConfiguration)).Get(); NugetConfiguration? nugetConfiguration = configurationRoot.GetSection(nameof(NugetConfiguration)).Get(); ResultSettings? resultSettings = configurationRoot.GetSection(nameof(ResultSettings)).Get(); SeleniumConfiguration? seleniumConfiguration = configurationRoot.GetSection(nameof(SeleniumConfiguration)).Get(); @@ -63,6 +65,7 @@ public record AppSettings(CamstarOracleConfiguration CamstarOracleConfiguration, || isoConfiguration is null || metadataSettings is null || metrologyConfiguration is null + || nationalInstrumentsConfiguration is null || nugetConfiguration is null || resultSettings is null || seleniumConfiguration is null @@ -96,6 +99,7 @@ public record AppSettings(CamstarOracleConfiguration CamstarOracleConfiguration, isoConfiguration, metadataSettings, metrologyConfiguration, + nationalInstrumentsConfiguration, nugetConfiguration, resultSettings, seleniumConfiguration, diff --git a/Models/NationalInstrumentsConfiguration.cs b/Models/NationalInstrumentsConfiguration.cs new file mode 100644 index 0000000..67e2b82 --- /dev/null +++ b/Models/NationalInstrumentsConfiguration.cs @@ -0,0 +1,27 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Watcher.Models; + +public record NationalInstrumentsConfiguration(string CustomScaleName, + string[] DifferentialPhysicalChannels, + int MaximumValue, + int MiniumValue, + string[] ReferencedSingleEndedPhysicalChannels, + int ReadTimeout, + bool UsePointerMethod) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, NationalInstrumentsConfigurationSourceGenerationContext.Default.NationalInstrumentsConfiguration); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(NationalInstrumentsConfiguration))] +internal partial class NationalInstrumentsConfigurationSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/NancyModules/SyncModule.cs b/NancyModules/SyncModule.cs index 6ecffa3..c1830da 100644 --- a/NancyModules/SyncModule.cs +++ b/NancyModules/SyncModule.cs @@ -1,3 +1,5 @@ +#if Nancy + using Nancy; using Nancy.Extensions; @@ -48,4 +50,6 @@ public class SyncModule : NancyModule }); } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/Startup.cs b/Startup.cs index 0d81aff..eb09ac9 100644 --- a/Startup.cs +++ b/Startup.cs @@ -1,3 +1,5 @@ +#if Nancy + using Microsoft.Owin.Cors; using Nancy.Owin; using Owin; @@ -16,4 +18,6 @@ public class Startup _ = app.UseNancy(); } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/Worker.cs b/Worker.cs index 0c2e1b2..dd9ce5e 100644 --- a/Worker.cs +++ b/Worker.cs @@ -24,7 +24,9 @@ public partial class Worker : BackgroundService _IsWindowsService = collection.Contains(nameof(WindowsServiceLifetime)); if (appSettings.FileWatcherConfiguration.Helper == nameof(Helpers.SyncHelper)) { +#if Nancy _ = Microsoft.Owin.Hosting.WebApp.Start(appSettings.SyncConfiguration.UniformResourceLocator); +#endif logger.LogInformation("Server running on {url}", appSettings.SyncConfiguration.UniformResourceLocator); } } @@ -87,6 +89,7 @@ public partial class Worker : BackgroundService nameof(Helpers.HelperWaferCounter) => Helpers.HelperWaferCounter.MoveFile(_AppSettings, _Logger), nameof(Helpers.HelperSerial) => Helpers.HelperSerial.ReadWrite(_AppSettings, _Logger, cancellationToken), nameof(Helpers.HelperMetrologyFiles) => Helpers.HelperMetrologyFiles.SortAndDelete(_AppSettings, _Logger), + nameof(Helpers.NationalInstrumentsHelper) => Helpers.NationalInstrumentsHelper.WriteData(_AppSettings, _Logger), nameof(Helpers.DeterministicHashCodeHelper) => Helpers.DeterministicHashCodeHelper.WindowsWork(_AppSettings, _Logger), #if Selenium nameof(Helpers.SeleniumHelper) => Helpers.SeleniumHelper.HyperTextMarkupLanguageToPortableNetworkGraphics(_AppSettings, _Logger),