Преглед изворни кода

改用tdms格式保存数据

luo пре 1 месец
родитељ
комит
cdf567944d

+ 2 - 0
Avalonia/ShakerApp/ShakerApp.csproj

@@ -68,4 +68,6 @@
     <ProjectReference Include="..\Avalonia.Xaml.Behaviors\Avalonia.Xaml.Behaviors.csproj" />
     <ProjectReference Include="..\SukiUI\SukiUI.csproj" />
   </ItemGroup>
+
+  <Import Project="..\..\TdmsFile\TdmsFile.projitems" Label="Shared" />
 </Project>

+ 20 - 0
Avalonia/ShakerApp/ViewModels/ShakerDataViewModel.cs

@@ -10,6 +10,7 @@ namespace ShakerApp.ViewModels
 {
     internal class ShakerDataViewModel:ViewModelBase<IModel>
     {
+        private NationalInstruments.Tdms.File file;
         private float[,] _Data = new float[0,0];
         private object _datalocker = new object();
         private Dictionary<Shaker.Models.AnalogType, List<(List<DataPoint>,Models.StatisticsModel)>> AnalogDataCache = new Dictionary<AnalogType, List<(List<DataPoint>, Models.StatisticsModel)>>();
@@ -22,6 +23,25 @@ namespace ShakerApp.ViewModels
                     if(args.Data.Length>0 && args.Data[0] is float[,] v)
                     {
                         _Data = v;
+                        switch(ViewModels.ShakerStatusViewModel.Instance.RTStatus)
+                        {
+                            case RTStatus.SignalGen:
+                                {
+                                    if(file ==null)
+                                    {
+                                        file = new NationalInstruments.Tdms.File(System.IO.Path.Combine(ViewModels.ShakerSettingViewModel.Instance.DataDirectory, $"{DateTime.Now:yyyy-MM-dd HH-mm-ss}.sdat")).Open();
+                                    }
+                                    //file.
+                                }
+                                break;
+                            default:
+                                if(file!=null)
+                                {
+                                    file?.Dispose();
+                                    file = null;
+                                }
+                                break;
+                        }
                         CalcAnalog();
                     }
                 });

+ 4 - 0
ShakerControl.sln

@@ -115,6 +115,8 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "FFTW", "Calc\FFTW\FFTW.shpr
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModBus", "PLCConnect\ModBus\ModBus.csproj", "{ED35D74A-E634-4CE3-A57A-DA1B3E1840A7}"
 EndProject
+Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "TdmsFile", "TdmsFile\TdmsFile.shproj", "{6A45FECC-B176-47B8-89D1-D3BCC4FBF12E}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -316,6 +318,8 @@ Global
 		Communication\ActiveMQ\ActiveMQ\Apache.NMS\Apache.NMS.projitems*{4bc05ed0-2cea-4726-a0dc-e86810ad3fc3}*SharedItemsImports = 5
 		Communication\ActiveMQ\EasyMQ.Common\EasyMQ.Common.projitems*{4bc05ed0-2cea-4726-a0dc-e86810ad3fc3}*SharedItemsImports = 5
 		OxyPlot\OxyPlot\OxyPlot.projitems*{5c686af2-4400-492e-a4d9-bfdc1e77f332}*SharedItemsImports = 5
+		TdmsFile\TdmsFile.projitems*{6a45fecc-b176-47b8-89d1-d3bcc4fbf12e}*SharedItemsImports = 13
+		TdmsFile\TdmsFile.projitems*{851f11b0-7e93-4c27-8225-88a1f6a2e9be}*SharedItemsImports = 5
 		Communication\ActiveMQ\ActiveMQ\Apache.NMS\Apache.NMS.projitems*{85bc49ac-7e23-4f13-894e-1669253f00bf}*SharedItemsImports = 13
 		Communication\ActiveMQ\ActiveMQ\Apache.NMS.ActiveMQ\Apache.NMS.ActiveMQ.projitems*{99cb1122-4d3a-434b-a11f-f997c940abc0}*SharedItemsImports = 13
 		Communication\ActiveMQ\ActiveMQ\Apache.NMS.ActiveMQ\Apache.NMS.ActiveMQ.projitems*{9ecbf78d-6e8e-4d13-88fd-27a8c8652dbf}*SharedItemsImports = 5

+ 16 - 0
ShakerFile/ShakerFile/FileConstant.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ShakerFile
+{
+    public class FileConstant
+    {
+        public const UInt32 FILEID = 0x11223344;
+        public const UInt16 SHAKERINFOLENGTH = 1024 * 2;
+        public const UInt16 CHANNELNAMESLENGTH = 256;
+        public const UInt16 TESTCONFIGLEHGTH = 1024;
+        public const UInt16 SNLENGTH = 100;
+        public const UInt16 NAMELENGTH = 50;
+    }
+}

+ 29 - 0
ShakerFile/ShakerFile/FileHeader.cs

@@ -0,0 +1,29 @@
+using System.Runtime.InteropServices;
+
+namespace ShakerFile
+{
+    [StructLayout(LayoutKind.Sequential, Pack = 1)]
+    public struct FileHeader
+    {
+        public FileHeader()
+        {
+
+        }
+        public uint FileID = FileConstant.FILEID;
+        public FileVersion Version = FileVersion.Version1_0;
+        public long CreateTime = 0;
+        public long LastTime = 0;
+        public ushort ShakerInfoLength = 0;
+    }
+    [StructLayout(LayoutKind.Sequential, Pack = 1)]
+    public struct ShakerInfo
+    {
+        public ShakerInfo()
+        {
+
+        }
+        public uint SampleRate;
+        public uint ChannelCount;
+        public ulong FrameCount;
+    }
+}

+ 14 - 0
ShakerFile/ShakerFile/FileVersion.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ShakerFile
+{
+    public enum FileVersion : byte
+    {
+        /// <summary>
+        /// 1.0
+        /// </summary>
+        Version1_0,
+    }
+}

+ 10 - 0
ShakerFile/ShakerFile/IFileInfo.cs

@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ShakerFile
+{
+    public interface IFileInfo
+    {
+    }
+}

+ 18 - 0
ShakerFile/ShakerFile/IShakerFile.cs

@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ShakerFile
+{
+    public interface IShakerFile : IDisposable
+    {
+        public ReadOnlyMemory<byte> ShakerOtherConfig { get; }
+        public bool IsOpen { get; }
+        public string Path { get; }
+        public FileHeader FileHeader { get; }
+        public ShakerStringInfo ShakerStringInfo { get; }
+        public void WriteInfo(ShakerInfo shakerInfo, ShakerStringInfo stringInfo,ref byte shakerOtherConfig,ushort shakerInfoLength);
+        public void Close();
+        public void WriteData(ref float value, int count);
+    }
+}

+ 21 - 0
ShakerFile/ShakerFile/ShakerFile.projitems

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
+    <HasSharedItems>true</HasSharedItems>
+    <SharedGUID>1b007f83-8d00-4c26-ad5b-27634a5cccfc</SharedGUID>
+  </PropertyGroup>
+  <PropertyGroup Label="Configuration">
+    <Import_RootNamespace>ShakerFile</Import_RootNamespace>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildThisFileDirectory)FileConstant.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)FileHeader.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)FileVersion.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)IFileInfo.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)IShakerFile.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)ShakerStringInfo.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)V1_0\ShakerFile.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)WinFile.cs" />
+  </ItemGroup>
+</Project>

+ 13 - 0
ShakerFile/ShakerFile/ShakerFile.shproj

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>1b007f83-8d00-4c26-ad5b-27634a5cccfc</ProjectGuid>
+    <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
+  </PropertyGroup>
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
+  <PropertyGroup />
+  <Import Project="ShakerFile.projitems" Label="Shared" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
+</Project>

+ 76 - 0
ShakerFile/ShakerFile/ShakerStringInfo.cs

@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace ShakerFile
+{
+    public class ShakerStringInfo
+    {
+        public string ShakerName = string.Empty;
+        public string ShakerSN = string.Empty;
+        public byte[] TestConfig = new byte[0];
+        public string ChannelNames = string.Empty;
+
+        public void Read(IntPtr file)
+        {
+            int count = 0;
+            int len = 0;
+            WinFile.ReadFile(file, ref Unsafe.As<int, byte>(ref len), 4, out _, 0);
+            count += 4;
+            if (len > 0)
+            {
+                byte[] temp = new byte[len];
+                WinFile.ReadFile(file, ref temp[0], (uint)len, out _, 0);
+                ShakerName = Encoding.Default.GetString(temp);
+            }
+            count += len;
+            len = 0;
+            WinFile.ReadFile(file, ref Unsafe.As<int, byte>(ref len), 4, out _, 0);
+            count += 4;
+            if (len > 0)
+            {
+                byte[] temp = new byte[len];
+                WinFile.ReadFile(file, ref temp[0], (uint)len, out _, 0);
+                ShakerSN = Encoding.Default.GetString(temp);
+            }
+            count += len;
+            len = 0;
+            WinFile.ReadFile(file, ref Unsafe.As<int, byte>(ref len), 4, out _, 0); if (len > 0)
+            {
+                TestConfig = new byte[len];
+                WinFile.ReadFile(file, ref TestConfig[0], (uint)len, out _, 0);
+            }
+            count += len;
+            len = 0;
+            WinFile.ReadFile(file, ref Unsafe.As<int, byte>(ref len), 4, out _, 0);
+            if (len > 0)
+            {
+                byte[] temp = new byte[len];
+                WinFile.ReadFile(file, ref temp[0], (uint)len, out _, 0);
+                ChannelNames = Encoding.Default.GetString(temp);
+            }
+            count += len;
+        }
+        public void Write(IntPtr file, ref int count)
+        {
+            List<byte[]> bytes = new List<byte[]>();
+            bytes.Add(Encoding.Default.GetBytes(ShakerName));
+            bytes.Add(Encoding.Default.GetBytes(ShakerSN));
+            bytes.Add(TestConfig);
+            bytes.Add(Encoding.Default.GetBytes(ChannelNames));
+            count = 0;
+            for (int i = 0; i < bytes.Count; i++)
+            {
+                int len = bytes[i].Length;
+                WinFile.WriteFile(file, ref Unsafe.As<int, byte>(ref len), 4, out _, 0);
+                count += 4;
+                if (len > 0)
+                {
+                    WinFile.WriteFile(file, ref bytes[i][0], (uint)len, out _, 0);
+                }
+                count += len;
+            }
+        }
+    }
+}

+ 149 - 0
ShakerFile/ShakerFile/V1_0/ShakerFile.cs

@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace ShakerFile.V1_0
+{
+    internal class ShakerFile : IShakerFile
+    {
+        private ShakerFile()
+        {
+
+        }
+        private ShakerFile(string path)
+        {
+            Path = path;
+        }
+        private ShakerFile(ushort shakerInfoLength)
+        {
+            fileHeader.ShakerInfoLength = shakerInfoLength;
+        }
+        static ShakerFile()
+        {
+
+        }
+        public ReadOnlyMemory<byte> ShakerOtherConfig { get; private set; } = new ReadOnlyMemory<byte>();
+        public bool IsOpen => file > 0;
+        private nint file;
+        private FileHeader fileHeader = new FileHeader();
+        private ShakerInfo shakerInfo = new ShakerInfo();
+        private ShakerStringInfo shakerStringInfo = new ShakerStringInfo();
+        public string Path { get; private set; } = string.Empty;
+
+        public FileHeader FileHeader => fileHeader;
+        public ShakerStringInfo ShakerStringInfo => shakerStringInfo;
+
+        public ShakerInfo ShakerInfo => shakerInfo;
+
+        public void Close()
+        {
+            WinFile.CloseHandle(file);
+            file = 0;
+        }
+        private unsafe void CreateFile(string path)
+        {
+            if (fileHeader.ShakerInfoLength == 0) throw new ArgumentException("ShakerInfoLength can not be zero");
+            if (System.IO.File.Exists(path)) System.IO.File.Delete(path);
+            ShakerFile shakerFile = new ShakerFile();
+
+            file = WinFile.CreateFile(path, WinFile.DesiredAccess.ReadAndWrite, WinFile.ShareMode.None, 0, WinFile.CreationDisposition.CreateAlways, WinFile.FileAttributes.FILE_ATTRIBUTE_NORMAL, 0);
+            Path = path;
+            fileHeader.CreateTime = DateTime.Now.Ticks;
+            fileHeader.LastTime = fileHeader.CreateTime;
+            ShakerOtherConfig = new ReadOnlyMemory<byte>(new byte[fileHeader.ShakerInfoLength]);
+            uint count = 0;
+            WinFile.SetFilePointer(file, 0, 0, WinFile.MoveMethod.Begin);
+            WinFile.WriteFile(file, ref Unsafe.As<FileHeader, byte>(ref fileHeader), (uint)Marshal.SizeOf<FileHeader>(), out count, 0);
+            WinFile.WriteFile(file, ref Unsafe.As<ShakerInfo, byte>(ref shakerInfo), (uint)Marshal.SizeOf<ShakerInfo>(), out count, 0);
+            WinFile.WriteFile(file, ref Unsafe.AsRef<byte>(ShakerOtherConfig.Pin().Pointer), fileHeader.ShakerInfoLength, out count, 0);
+        }
+        public static unsafe ShakerFile CreateFile(string path,ushort shakerInfoLength)
+        {
+            if(shakerInfoLength ==0) throw new ArgumentException("ShakerInfoLength can not be zero");
+            if (System.IO.File.Exists(path)) System.IO.File.Delete(path);
+            ShakerFile shakerFile = new ShakerFile(shakerInfoLength);
+            shakerFile.CreateFile(path);
+            return shakerFile;
+        }
+        private unsafe void UpdateLastTime(DateTime time)
+        {
+            fileHeader.LastTime = time.Ticks;
+            int startaddr = (int)((ulong)(Unsafe.AsPointer(ref fileHeader.LastTime)) - (ulong)(Unsafe.AsPointer(ref fileHeader)));
+            WinFile.SetFilePointer(file, startaddr, 0, WinFile.MoveMethod.Begin);
+            uint count = 0;
+            WinFile.WriteFile(file, ref Unsafe.As<long, byte>(ref fileHeader.LastTime), (uint)Marshal.SizeOf(fileHeader.LastTime), out count, 0);
+        }
+        private unsafe void UpdateFrameCount()
+        {
+            UpdateLastTime(DateTime.Now);
+            uint count = 0;
+            int startaddr = (int)((ulong)(Unsafe.AsPointer(ref shakerInfo.FrameCount)) - (ulong)(Unsafe.AsPointer(ref shakerInfo))) + Marshal.SizeOf<FileHeader>();
+            shakerInfo.FrameCount++;
+            WinFile.SetFilePointer(file, startaddr, 0, WinFile.MoveMethod.Begin);
+            WinFile.WriteFile(file, ref Unsafe.As<ulong, byte>(ref shakerInfo.FrameCount), (uint)Unsafe.SizeOf<ulong>(), out count, 0);
+        }
+        public void Dispose()
+        {
+            WinFile.CloseHandle(file);
+            file = 0;
+        }
+
+        private unsafe void OpenFile()
+        {
+            file = WinFile.CreateFile(Path, WinFile.DesiredAccess.Read, WinFile.ShareMode.None, 0, WinFile.CreationDisposition.OpenExisting, WinFile.FileAttributes.FILE_ATTRIBUTE_NORMAL, 0);
+            uint count = 0;
+            WinFile.ReadFile(file, ref Unsafe.As<FileHeader, byte>(ref fileHeader), (uint)Marshal.SizeOf<FileHeader>(), out count, 0);
+            if (fileHeader.FileID != FileConstant.FILEID)
+            {
+                throw new Exception("File Format Error");
+            }
+            if (!Enum.GetValues<FileVersion>().Contains(fileHeader.Version))
+            {
+                throw new Exception("File Version Not Support");
+            }
+            WinFile.ReadFile(file, ref Unsafe.As<ShakerInfo, byte>(ref shakerInfo), (uint)Marshal.SizeOf<ShakerInfo>(), out count, 0);
+            WinFile.ReadFile(file, ref Unsafe.AsRef<byte>(ShakerOtherConfig.Pin().Pointer), fileHeader.ShakerInfoLength, out count, 0);
+            shakerStringInfo.Read(file);
+        }
+        public static ShakerFile OpenFile(string path)
+        {
+            ShakerFile file = new ShakerFile(path);
+            file.OpenFile();
+            return file;
+        }
+
+        public unsafe void WriteInfo(ShakerInfo shakerInfo, ShakerStringInfo info,ref byte shakerOtherConfig,ushort shakerInfoLength)
+        {
+            uint count = 0;
+            UpdateLastTime(DateTime.Now);
+            WinFile.SetFilePointer(file, Marshal.SizeOf<FileHeader>(), 0, WinFile.MoveMethod.Begin);
+            byte[] data = new byte[Marshal.SizeOf(shakerInfo)];
+            Unsafe.CopyBlock(ref data[0], ref Unsafe.As<ShakerInfo, byte>(ref shakerInfo), (uint)data.Length);
+            var d = Unsafe.As<byte, ShakerInfo>(ref data[0]);
+            WinFile.WriteFile(file, ref data[0], (uint)Marshal.SizeOf(shakerInfo), out count, 0);
+            if(shakerInfoLength>0)
+            {
+                WinFile.WriteFile(file, ref shakerOtherConfig,Math.Min(fileHeader.ShakerInfoLength, shakerInfoLength), out count, 0);
+            }
+            WinFile.SetFilePointer(file, Marshal.SizeOf<FileHeader>() +Marshal.SizeOf<ShakerInfo>()+fileHeader.ShakerInfoLength, 0, WinFile.MoveMethod.Begin);
+            this.shakerInfo = shakerInfo;
+            shakerStringInfo.ChannelNames = info.ChannelNames;
+            shakerStringInfo.ShakerName = info.ShakerName;
+            shakerStringInfo.ShakerSN = info.ShakerSN;
+            shakerStringInfo.TestConfig = info.TestConfig;
+            int cnt = 0;
+            shakerStringInfo.Write(file, ref cnt);
+
+        }
+        public void WriteData(ref float value, int count)
+        {
+            UpdateFrameCount();
+            WinFile.SetFilePointer(file, 0, 0, WinFile.MoveMethod.End);
+            uint cnt = 0;
+            WinFile.WriteFile(file, ref Unsafe.As<float, byte>(ref value), (uint)(Unsafe.SizeOf<float>() * count), out cnt, 0);
+        }
+    }
+}

+ 182 - 0
ShakerFile/ShakerFile/WinFile.cs

@@ -0,0 +1,182 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace ShakerFile
+{
+    public static class WinFile
+    {
+        public enum DesiredAccess : uint
+        {
+            Read = 0x80000000,
+            Write = 0x40000000,
+            ReadAndWrite = Read | Write,
+        }
+        public enum ShareMode : uint
+        {
+            None=0,
+            Read = 0x00000001,
+            Write = 0x00000002,
+            Delete = 0x04,
+
+        }
+        public enum CreationDisposition : uint
+        {
+            CreateNew = 1,
+            CreateAlways = 2,
+            OpenExisting = 3,
+            OpenAlways = 4,
+            Truncate_Existing = 5,
+        }
+
+        [Flags]
+        public enum FileAttributes
+        {
+            /// <summary>
+            ///     A file or directory that is an archive file or directory. Applications typically use this attribute to mark
+            ///     files for backup or removal .
+            /// </summary>
+            FILE_ATTRIBUTE_ARCHIVE = 0x20,
+
+            /// <summary>
+            ///     A file or directory that is compressed. For a file, all of the data in the file is compressed. For a
+            ///     directory, compression is the default for newly created files and subdirectories.
+            /// </summary>
+            FILE_ATTRIBUTE_COMPRESSED = 0x800,
+
+            /// <summary>
+            ///     This value is reserved for system use.
+            /// </summary>
+            FILE_ATTRIBUTE_DEVICE = 0x40,
+
+            /// <summary>
+            ///     The handle that identifies a directory.
+            /// </summary>
+            FILE_ATTRIBUTE_DIRECTORY = 0x10,
+
+            /// <summary>
+            ///     A file or directory that is encrypted. For a file, all data streams in the file are encrypted. For a
+            ///     directory, encryption is the default for newly created files and subdirectories.
+            /// </summary>
+            FILE_ATTRIBUTE_ENCRYPTED = 0x4000,
+
+            /// <summary>
+            ///     The file or directory is hidden. It is not included in an ordinary directory listing.
+            /// </summary>
+            FILE_ATTRIBUTE_HIDDEN = 0x2,
+
+            /// <summary>
+            ///     The directory or user data stream is configured with integrity (only supported on ReFS volumes). It is not
+            ///     included in an ordinary directory listing. The integrity setting persists with the file if it's renamed. If a
+            ///     file is copied the destination file will have integrity set if either the source file or destination directory
+            ///     have integrity set.
+            ///     Windows Server 2008 R2, Windows 7, Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP:  This
+            ///     flag is not supported until Windows Server 2012.
+            /// </summary>
+            FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x8000,
+
+            /// <summary>
+            ///     A file that does not have other attributes set. This attribute is valid only when used alone.
+            /// </summary>
+            FILE_ATTRIBUTE_NORMAL = 0x80,
+
+            /// <summary>
+            ///     The file or directory is not to be indexed by the content indexing service.
+            /// </summary>
+            FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000,
+
+            /// <summary>
+            ///     The user data stream not to be read by the background data integrity scanner (AKA scrubber). When set on a
+            ///     directory it only provides inheritance. This
+            ///     flag is only supported on Storage Spaces and ReFS volumes. It is not included in an ordinary directory
+            ///     listing.
+            ///     Windows Server 2008 R2, Windows 7, Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP:  This
+            ///     flag is not supported until Windows 8 and Windows Server 2012.
+            /// </summary>
+            FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x20000,
+
+            /// <summary>
+            ///     The data of a file is not available immediately. This attribute indicates that the file data is physically
+            ///     moved to offline storage. This attribute is used by Remote Storage, which is the hierarchical storage management
+            ///     software. Applications should not arbitrarily change this attribute.
+            /// </summary>
+            FILE_ATTRIBUTE_OFFLINE = 0x1000,
+
+            /// <summary>
+            ///     A file that is read-only. Applications can read the file, but cannot write to it or delete it. This
+            ///     attribute is not honored on directories. For more information, see
+            ///     You cannot view or change the Read-only or the System attributes of folders in Windows Server 2003, in Windows XP,
+            ///     in Windows Vista or in Windows 7.
+            /// </summary>
+            FILE_ATTRIBUTE_READONLY = 0x1,
+
+            /// <summary>
+            ///     A file or directory that has an associated reparse point, or a file that is a symbolic link.
+            /// </summary>
+            FILE_ATTRIBUTE_REPARSE_POINT = 0x400,
+
+            /// <summary>
+            ///     A file that is a sparse file.
+            /// </summary>
+            FILE_ATTRIBUTE_SPARSE_FILE = 0x200,
+
+            /// <summary>
+            ///     A file or directory that the operating system uses a part of, or uses exclusively.
+            /// </summary>
+            FILE_ATTRIBUTE_SYSTEM = 0x4,
+
+            /// <summary>
+            ///     A file that is being used for temporary storage. File systems avoid writing data back to mass storage if
+            ///     sufficient cache memory is available, because typically, an application deletes a temporary file after the handle
+            ///     is closed. In that scenario, the system can entirely avoid writing the data. Otherwise, the data is written after
+            ///     the handle is closed.
+            /// </summary>
+            FILE_ATTRIBUTE_TEMPORARY = 0x100,
+
+            /// <summary>
+            ///     This value is reserved for system use.
+            /// </summary>
+            FILE_ATTRIBUTE_VIRTUAL = 0x10000
+        }
+        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+        public static extern nint CreateFile(
+             string lpFileName,
+            DesiredAccess dwDesiredAccess,
+            ShareMode dwShareMode,
+            nint lpSecurityAttributes,
+            CreationDisposition dwCreationDisposition,
+            FileAttributes dwFlagsAndAttributes,
+            nint hTemplateFile);
+
+        [DllImport("kernel32.dll", SetLastError = true)]
+        public static extern bool ReadFile(
+            nint hFile,
+            ref byte lpBuffer,
+            uint nNumberOfBytesToRead,
+            out uint lpNumberOfBytesRead,
+            nint lpOverlapped);
+
+        [DllImport("kernel32.dll", SetLastError = true)]
+        public static extern bool WriteFile(
+            nint hFile,
+            ref byte lpBuffer,
+            uint nNumberOfBytesToWrite,
+            out uint lpNumberOfBytesWritten,
+            nint lpOverlapped);
+
+        public enum MoveMethod : uint
+        {
+            Begin = 0,
+            Current = 1,
+            End = 2,
+        }
+        [DllImport("kernel32.dll", SetLastError = true)]
+        public static extern bool CloseHandle(nint hObject);
+        [DllImport("kernel32.dll", EntryPoint = "SetFilePointer")]
+        public static extern uint SetFilePointer([In()] nint hFile, int lDistanceToMove, [In()] nint lpDistanceToMoveHigh, MoveMethod dwMoveMethod);
+
+        [DllImport("kernel32.dll", SetLastError = true)]
+        public static extern bool DeleteFile(string lpFileName);
+    }
+}

+ 89 - 0
TdmsFile/BinaryReaderExtensions.cs

@@ -0,0 +1,89 @@
+using System;
+using System.IO;
+using System.Text;
+
+namespace NationalInstruments.Tdms
+{
+    public static class BinaryReaderExtensions
+    {
+        public static string ReadString(this BinaryReader reader, int length)
+        {
+            return Encoding.UTF8.GetString(reader.ReadBytes(length));
+        }
+
+        public static string ReadLengthPrefixedString(this BinaryReader reader)
+        {
+            return Read<string>(reader, DataType.String);
+        }
+
+        public static T Read<T>(this BinaryReader reader, int dataType)
+        {
+            return (T)Read(reader, dataType);
+        }
+
+        public static object Read(this BinaryReader reader, int dataType)
+        {
+            switch (dataType)
+            {
+                case DataType.Empty: return null;
+                case DataType.Void: reader.ReadByte(); return null;
+                case DataType.Integer8: return reader.ReadSByte();
+                case DataType.Integer16: return reader.ReadInt16();
+                case DataType.Integer32: return reader.ReadInt32();
+                case DataType.Integer64: return reader.ReadInt64();
+                case DataType.UnsignedInteger8: return reader.ReadByte();
+                case DataType.UnsignedInteger16: return reader.ReadUInt16();
+                case DataType.UnsignedInteger32: return reader.ReadUInt32();
+                case DataType.UnsignedInteger64: return reader.ReadUInt64();
+                case DataType.SingleFloat:
+                case DataType.SingleFloatWithUnit: return reader.ReadSingle();
+                case DataType.DoubleFloat:
+                case DataType.DoubleFloatWithUnit: return reader.ReadDouble();
+                case DataType.String: return Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadInt32()));
+                case DataType.Boolean: return reader.ReadBoolean();
+                case DataType.TimeStamp:
+                    return new DateTime(1904, 1, 1, 0, 0, 0, DateTimeKind.Utc)
+                        .AddSeconds(reader.ReadUInt64() / (double)ulong.MaxValue)       //fixed truncate error in old code 
+                        .AddSeconds(reader.ReadInt64())
+                        .ToLocalTime(); 
+                default: throw new ArgumentException("Unknown data type " + dataType, "dataType");
+            }
+        }
+    }
+
+    public static class BinaryWriterExtensions
+    {
+        public static void Write(this BinaryWriter writer, int dataType, object data)
+        {
+            switch (dataType)
+            {
+                case DataType.Empty: break;
+                case DataType.Void: writer.Write((byte)0); break;
+                case DataType.Integer8: writer.Write((sbyte)data); break;
+                case DataType.Integer16: writer.Write((Int16)data); break;
+                case DataType.Integer32: writer.Write((Int32)data); break;
+                case DataType.Integer64: writer.Write((Int64)data); break;
+                case DataType.UnsignedInteger8: writer.Write((byte)data); break;
+                case DataType.UnsignedInteger16: writer.Write((UInt16)data); break;
+                case DataType.UnsignedInteger32: writer.Write((UInt32)data); break;
+                case DataType.UnsignedInteger64: writer.Write((UInt16)data); break;
+                case DataType.SingleFloat:
+                case DataType.SingleFloatWithUnit: writer.Write((float)data); break;
+                case DataType.DoubleFloat:
+                case DataType.DoubleFloatWithUnit: writer.Write((double)data); break;
+                case DataType.String:
+                    byte[] bytes = Encoding.UTF8.GetBytes((string)data);
+                    writer.Write((Int32)bytes.Length);
+                    writer.Write(bytes);
+                    break;
+                case DataType.Boolean: writer.Write((bool)data); break;
+                case DataType.TimeStamp:
+                    var t = (((DateTime)data).ToUniversalTime() - new DateTime(1904, 1, 1, 0, 0, 0, DateTimeKind.Utc));
+                    writer.Write((UInt64)((t.TotalSeconds % 1) * ulong.MaxValue));
+                    writer.Write((Int64)t.TotalSeconds);
+                    break;
+                default: throw new ArgumentException("Unknown data type " + dataType, "dataType");
+            }
+        }
+    }
+}

+ 34 - 0
TdmsFile/Channel.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace NationalInstruments.Tdms
+{
+    public class Channel
+    {
+        private readonly IEnumerable<Reader.RawData> _rawData;
+        private readonly Reader _reader;
+
+        public Channel(string name, IDictionary<string, object> properties, IEnumerable<Reader.RawData> rawData, Reader reader)
+        {
+            _rawData = rawData;
+            _reader = reader;
+            Name = name;
+            Properties = properties;
+        }
+
+        public string Name { get; private set; }
+        public bool HasData { get { return _rawData.Any(); } }
+        public long DataCount { get { return _rawData.Sum(x => x.Count); } }
+        public Type DataType { get { return _rawData.Select(x => x.ClrDataType).FirstOrDefault(); } }
+        public IDictionary<string, object> Properties { get; private set; }
+
+        public IEnumerable<Reader.RawData> RawData { get { return _rawData; } }
+
+        public IEnumerable<T> GetData<T>()
+        {
+            return _rawData.SelectMany(_reader.ReadRawData).Select(value => (T)value);
+        }
+    }
+}

+ 100 - 0
TdmsFile/DataType.cs

@@ -0,0 +1,100 @@
+using System;
+
+namespace NationalInstruments.Tdms
+{
+    public class DataType
+    {
+        public const int Empty = 0x0000000F;
+        public const int Void = 0x00000000;
+        public const int Integer8 = 0x00000001;
+        public const int Integer16 = 0x00000002;
+        public const int Integer32 = 0x00000003;
+        public const int Integer64 = 0x00000004;
+        public const int UnsignedInteger8 = 0x00000005;
+        public const int UnsignedInteger16 = 0x00000006;
+        public const int UnsignedInteger32 = 0x00000007;
+        public const int UnsignedInteger64 = 0x00000008;
+        public const int SingleFloat = 0x00000009;
+        public const int DoubleFloat = 0x0000000A;
+        public const int ExtendedFloat = 0x0000000B;
+        public const int SingleFloatWithUnit = 0x00000019;
+        public const int DoubleFloatWithUnit = 0x0000001A;
+        public const int ExtendedFloatWithUnit = 0x0000001B;
+        public const int String = 0x00000020;
+        public const int Boolean = 0x00000021;
+        public const int TimeStamp = 0x00000044;
+
+        public static long GetArrayLength(int dataType, long size)
+        {
+            return GetLength(dataType) * size;
+        }
+
+        public static int GetLength(int dataType)
+        {
+            switch (dataType)
+            {
+                case Empty: return 0;
+                case Void: return 1;
+                case Integer8: return 1;
+                case Integer16: return 2;
+                case Integer32: return 4;
+                case Integer64: return 8;
+                case UnsignedInteger8: return 1;
+                case UnsignedInteger16: return 2;
+                case UnsignedInteger32: return 4;
+                case UnsignedInteger64: return 8;
+                case SingleFloat:
+                case SingleFloatWithUnit: return 4;
+                case DoubleFloat:
+                case DoubleFloatWithUnit: return 8;
+                case Boolean: return 1;
+                case TimeStamp: return 16;
+                default: throw new ArgumentException("Cannot determine size of data type " + dataType, "dataType");
+            }
+        }
+
+        public static Type GetClrType(int dataType)
+        {
+            switch (dataType)
+            {
+                case Empty: return typeof(object);
+                case Void: return typeof(object);
+                case Integer8: return typeof(sbyte);
+                case Integer16: return typeof(short);
+                case Integer32: return typeof(int);
+                case Integer64: return typeof(long);
+                case UnsignedInteger8: return typeof(byte);
+                case UnsignedInteger16: return typeof(ushort);
+                case UnsignedInteger32: return typeof(uint);
+                case UnsignedInteger64: return typeof(ulong);
+                case SingleFloat:
+                case SingleFloatWithUnit: return typeof(float);
+                case DoubleFloat:
+                case DoubleFloatWithUnit: return typeof(double);
+                case Boolean: return typeof(bool);
+                case String: return typeof(string);
+                case TimeStamp: return typeof(DateTime);
+                default: throw new Exception("Unknown data type " + dataType);
+            }
+        }
+
+        public static int GetDataType(object value)
+        {
+            if (value == null) return Void;
+            else if (value.GetType() == typeof(bool)) return Boolean;
+            else if (value.GetType() == typeof(sbyte)) return Integer8;
+            else if (value.GetType() == typeof(Int16)) return Integer16;
+            else if (value.GetType() == typeof(Int32)) return Integer32;
+            else if (value.GetType() == typeof(Int64)) return Integer64;
+            else if (value.GetType() == typeof(byte)) return UnsignedInteger8;
+            else if (value.GetType() == typeof(UInt16)) return UnsignedInteger16;
+            else if (value.GetType() == typeof(UInt32)) return UnsignedInteger32;
+            else if (value.GetType() == typeof(UInt64)) return UnsignedInteger64;
+            else if (value.GetType() == typeof(float)) return SingleFloat;
+            else if (value.GetType() == typeof(double)) return DoubleFloat;
+            else if (value.GetType() == typeof(string)) return String;
+            else if (value.GetType() == typeof(DateTime)) return TimeStamp;
+            else throw new Exception("Unknown data type " + value.GetType());
+        }
+    }
+}

+ 267 - 0
TdmsFile/File.cs

@@ -0,0 +1,267 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace NationalInstruments.Tdms
+{
+    public class File : IEnumerable<Group>, IDisposable
+    {
+        private readonly Lazy<Stream> _stream;
+
+        private File()
+        {
+            Properties = new Dictionary<string, object>();
+            Groups = new Dictionary<string, Group>();
+        }
+
+        public File(Stream stream) : this()
+        {
+            if (!stream.CanSeek) throw new ArgumentException("Only seekable streams supported.", "stream");
+            _stream = new Lazy<Stream>(() => stream);
+        }
+
+        public File(string path) : this()
+        {
+            _stream = new Lazy<Stream>(() => new FileStream(path, FileMode.Open, FileAccess.Read));
+        }
+
+        public IDictionary<string, object> Properties { get; private set; }
+        public IDictionary<string, Group> Groups { get; private set; }
+
+        public File Open()
+        {
+            var reader = new Reader(_stream.Value);
+            var metadata = LoadMetadata(reader).ToList();
+            LoadFile(metadata);
+            LoadGroups(Groups, metadata);
+            LoadChannels(Groups, metadata, reader);
+            return this;
+        }
+
+        /// <summary>
+        /// This will re-write the TDMS file. Mostly used for write demonstration. Although, this will also defragment the file. 
+        /// </summary>
+        /// <param name="path"></param>
+        public void ReWrite(string path)
+        {
+            using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write))
+                ReWrite(stream);
+        }
+
+        /// <summary>
+        /// This will re-write the TDMS file. Mostly used for write demonstration. Although, this will also defragment the file. 
+        /// </summary>
+        /// <param name="stream"></param>
+        public void ReWrite(Stream stream)
+        {
+            WriteSegment segment = new WriteSegment(stream);
+            segment.Header.TableOfContents.HasRawData = Groups.SelectMany(g => g.Value.Channels.Values, (g, c) => c.HasData).Any();
+            //when we re-write the file, no data shall be interleaved. (It's an all or nothing situation, with only 1 segment)
+            //segment.Header.TableOfContents.RawDataIsInterleaved = Groups.SelectMany(g => g.Value.Channels.Values, (g, c) => c.RawData.First().IsInterleaved).Any();
+
+            //Top level
+            Reader.Metadata m = new Reader.Metadata();
+            m.Path = new string[0];
+            m.Properties = Properties;
+            segment.MetaData.Add(m);
+
+            //Groups
+            foreach(KeyValuePair<string, Group> group in Groups)
+            {
+                m = new Reader.Metadata();
+                m.Path = new string[] { group.Key };
+                m.Properties = group.Value.Properties;
+                segment.MetaData.Add(m);
+
+                //Channels
+                foreach (KeyValuePair<string, Channel> channel in group.Value.Channels)
+                {
+                    Reader.RawData[] raws = channel.Value.RawData.ToArray();
+
+                    //Add first part
+                    m = new Reader.Metadata();
+                    m.Path = new string[] { group.Key, channel.Key };
+                    m.Properties = channel.Value.Properties;
+                    m.RawData = raws?[0];
+                    segment.MetaData.Add(m);
+
+                    //Add the other parts (if any)
+                    for(int i = 1; i < raws?.Length; i++)
+                    {
+                        m = new Reader.Metadata();
+                        m.Path = new string[] { group.Key, channel.Key };
+                        m.RawData = raws[i];
+                        segment.MetaData.Add(m);
+                    }
+                }
+            }
+
+            //Write all raw data
+            Writer writer = segment.Open();
+            var reader = new Reader(_stream.Value);
+            foreach (KeyValuePair<string, Group> group in Groups)
+                foreach (KeyValuePair<string, Channel> channel in group.Value.Channels)
+                    foreach (Reader.RawData raw in channel.Value.RawData)
+                    {
+                        var data = reader.ReadRawData(raw);
+                        raw.IsInterleaved = false;  //when we re-write the file, no data shall be interleaved
+                        writer.WriteRawData(raw, data);
+                    }
+
+            //close up
+            segment.Close();
+        }
+
+        private void LoadFile(IEnumerable<Reader.Metadata> metadata)
+        {
+            metadata.Where(x => x.Path.Length == 0)
+                .SelectMany(m => m.Properties).ToList()
+                .ForEach(x => Properties[x.Key] = x.Value);
+        }
+
+        private static void LoadGroups(IDictionary<string, Group> groups, IEnumerable<Reader.Metadata> metadata)
+        {
+            var groupMetadata = metadata.Where(x => x.Path.Length == 1).
+                                         GroupBy(x => x.Path[0], (k, r) => r.OrderByDescending(y => y.Properties.Count).First());
+            foreach (var group in groupMetadata)
+                groups.Add(group.Path[0], new Group(group.Path[0], group.Properties));
+
+            // add implicit groups
+            foreach (var groupName in metadata.Where(x => x.Path.Length > 1 && !groups.ContainsKey(x.Path[0])).Select(x => x.Path[0]))
+                groups.Add(groupName, new Group(groupName, new Dictionary<string, object>()));
+        }
+
+        private static void LoadChannels(IDictionary<string, Group> groups, IEnumerable<Reader.Metadata> metadata, Reader reader)
+        {
+            var channelMetadata = metadata
+                .Where(x => x.Path.Length == 2)
+                .GroupBy(x => Tuple.Create(x.Path[0], x.Path[1]))
+                .Join(groups, x => x.First().Path[0], x => x.Key, (c, g) => Tuple.Create(g.Value, c));
+            foreach (var channel in channelMetadata)
+                channel.Item1.Channels.Add(channel.Item2.First().Path[1], new Channel(channel.Item2.First().Path[1],
+                    channel.Item2.OrderByDescending(y => y.Properties.Count).First().Properties,
+                    channel.Item2.Where(x => x.RawData.Count > 0).Select(x => x.RawData), reader));
+        }
+
+        private static IEnumerable<Reader.Metadata> LoadMetadata(Reader reader)
+        {
+            var segments = GetSegments(reader).ToList();
+
+            var prevMetaDataLookup = new Dictionary<string, Dictionary<string, Reader.Metadata>>();
+            foreach (var segment in segments)
+            {
+                if (!(segment.TableOfContents.ContainsNewObjects || 
+                    segment.TableOfContents.HasDaqMxData || 
+                    segment.TableOfContents.HasMetaData || 
+                    segment.TableOfContents.HasRawData)) {
+                    continue;
+                }
+                var metadatas = reader.ReadMetadata(segment);
+                long rawDataSize = 0;
+                long nextOffset = segment.RawDataOffset;
+
+                foreach (var m in metadatas)
+                {
+                    if (m.RawData.Count == 0 && m.Path.Length > 1)
+                    {
+                        // apply previous metadata if available
+                        if (prevMetaDataLookup.ContainsKey(m.Path[0]) && prevMetaDataLookup[m.Path[0]].ContainsKey(m.Path[1]))
+                        {
+                            var prevMetaData = prevMetaDataLookup[m.Path[0]][m.Path[1]];
+                            if (prevMetaData != null)
+                            {
+                                m.RawData.Count = segment.TableOfContents.HasRawData ? prevMetaData.RawData.Count : 0;
+                                m.RawData.DataType = prevMetaData.RawData.DataType;
+                                m.RawData.ClrDataType = prevMetaData.RawData.ClrDataType;
+                                m.RawData.Offset = segment.RawDataOffset + rawDataSize;
+                                m.RawData.IsInterleaved = prevMetaData.RawData.IsInterleaved;
+                                m.RawData.InterleaveStride = prevMetaData.RawData.InterleaveStride;
+                                m.RawData.Size = prevMetaData.RawData.Size;
+                                m.RawData.Dimension = prevMetaData.RawData.Dimension;
+                            }
+                        }
+                    }
+                    if (m.RawData.IsInterleaved && segment.NextSegmentOffset <= 0)
+                    {
+                        m.RawData.Count = segment.NextSegmentOffset > 0
+                            ? (segment.NextSegmentOffset - m.RawData.Offset + m.RawData.InterleaveStride - 1)/
+                              m.RawData.InterleaveStride
+                            : (reader.FileSize - m.RawData.Offset + m.RawData.InterleaveStride - 1)/
+                              m.RawData.InterleaveStride;
+                    }
+                    if (m.Path.Length > 1)
+                    {
+                        rawDataSize += m.RawData.Size;
+                        nextOffset += m.RawData.Size;
+                    }
+                }
+
+                var implicitMetadatas = new List<Reader.Metadata>();
+                if (metadatas.All(m => !m.RawData.IsInterleaved && m.RawData.Size > 0))
+                {
+                    while (nextOffset < segment.NextSegmentOffset ||
+                           (segment.NextSegmentOffset == -1 && nextOffset < reader.FileSize))
+                    {
+                        // Incremental Meta Data see http://www.ni.com/white-paper/5696/en/#toc1
+                        foreach (var m in metadatas)
+                        {
+                            if (m.Path.Length > 1)
+                            {
+                                var implicitMetadata = new Reader.Metadata()
+                                {
+                                    Path = m.Path,
+                                    RawData = new Reader.RawData()
+                                    {
+                                        Count = m.RawData.Count,
+                                        DataType = m.RawData.DataType,
+                                        ClrDataType = m.RawData.ClrDataType,
+                                        Offset = nextOffset,
+                                        IsInterleaved = m.RawData.IsInterleaved,
+                                        Size = m.RawData.Size,
+                                        Dimension = m.RawData.Dimension
+                                    },
+                                    Properties = m.Properties
+                                };
+                                implicitMetadatas.Add(implicitMetadata);
+                                nextOffset += implicitMetadata.RawData.Size;
+                            }
+                        }
+                    }
+                }
+                var metadataWithImplicit = metadatas.Concat(implicitMetadatas).ToList();
+                foreach (var metadata in metadataWithImplicit)
+                {
+                    if (metadata.Path.Length == 2)
+                    {
+                        if (!prevMetaDataLookup.ContainsKey(metadata.Path[0]))
+                        {
+                            prevMetaDataLookup[metadata.Path[0]] = new Dictionary<string, Reader.Metadata>();
+                        }
+                        prevMetaDataLookup[metadata.Path[0]][metadata.Path[1]] = metadata;
+                    }
+                    yield return metadata;
+                }
+            }
+        }
+
+        private static IEnumerable<Reader.Segment> GetSegments(Reader reader)
+        {
+            var segment = reader.ReadFirstSegment();
+            while (segment != null)
+            {
+                yield return segment;
+                segment = reader.ReadSegment(segment.NextSegmentOffset);
+            }
+        }
+
+        public IEnumerator<Group> GetEnumerator() { return Groups.Values.GetEnumerator(); }
+        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
+
+        public void Dispose()
+        {
+            _stream.Value.Dispose();
+        }
+    }
+}

+ 22 - 0
TdmsFile/Group.cs

@@ -0,0 +1,22 @@
+using System.Collections;
+using System.Collections.Generic;
+
+namespace NationalInstruments.Tdms
+{
+    public class Group : IEnumerable<Channel>
+    {
+        public Group(string name, IDictionary<string, object> properties)
+        {
+            Name = name;
+            Properties = properties;
+            Channels = new Dictionary<string, Channel>();
+        }
+
+        public string Name { get; private set; }
+        public IDictionary<string, object> Properties { get; private set; } 
+        public IDictionary<string, Channel> Channels { get; private set; }
+
+        public IEnumerator<Channel> GetEnumerator() { return Channels.Values.GetEnumerator(); }
+        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
+    }
+}

+ 198 - 0
TdmsFile/Reader.cs

@@ -0,0 +1,198 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace NationalInstruments.Tdms
+{
+    public class Reader
+    {
+        private readonly BinaryReader _reader;
+
+        public Reader(Stream stream)
+        {
+            _reader = new BinaryReader(stream);
+        }
+
+        public Segment ReadFirstSegment()
+        {
+            return ReadSegment(0);
+        }
+
+        public Segment ReadSegment(long offset)
+        {
+            if (offset < 0 || offset >= _reader.BaseStream.Length) return null;
+            _reader.BaseStream.Seek(offset, SeekOrigin.Begin);
+            var leadin = new Segment { Offset = offset, MetadataOffset = offset + Segment.Length };
+            leadin.Identifier = Encoding.ASCII.GetString(_reader.ReadBytes(4));
+            var tableOfContentsMask = _reader.ReadInt32();
+            leadin.TableOfContents = new TableOfContents
+                {
+                    ContainsNewObjects = ((tableOfContentsMask >> 2) & 1) == 1,
+                    HasDaqMxData = ((tableOfContentsMask >> 7) & 1) == 1,
+                    HasMetaData = ((tableOfContentsMask >> 1) & 1) == 1,
+                    HasRawData = ((tableOfContentsMask >> 3) & 1) == 1,
+                    NumbersAreBigEndian = ((tableOfContentsMask >> 6) & 1) == 1,
+                    RawDataIsInterleaved = ((tableOfContentsMask >> 5) & 1) == 1
+                };
+            leadin.Version = _reader.ReadInt32();
+            Func<long, long> resetWhenEol = x => x < _reader.BaseStream.Length ? x : -1;
+            leadin.NextSegmentOffset = resetWhenEol(_reader.ReadInt64() + offset + Segment.Length);
+            leadin.RawDataOffset = _reader.ReadInt64() + offset + Segment.Length;
+            return leadin;
+        }
+
+        public IList<Metadata> ReadMetadata(Segment segment)
+        {
+            _reader.BaseStream.Seek(segment.MetadataOffset, SeekOrigin.Begin);
+            var objectCount = _reader.ReadInt32();
+            var metadatas = new List<Metadata>();
+            var rawDataOffset = segment.RawDataOffset;
+            bool isInterleaved = segment.TableOfContents.RawDataIsInterleaved;
+            int interleaveStride = 0;
+            for (var x = 0; x < objectCount; x++)
+            {
+                var metadata = new Metadata();
+                metadata.Path = Regex.Matches(Encoding.UTF8.GetString(_reader.ReadBytes(_reader.ReadInt32())), "'(.*?)'").Cast<Match>().
+                                SelectMany(y => y.Groups.Cast<System.Text.RegularExpressions.Group>().Skip(1), (m, g) => g.Value).ToArray();
+                metadata.RawData = new RawData();
+                var rawDataIndexLength = _reader.ReadInt32();
+                if (rawDataIndexLength > 0)
+                {
+                    metadata.RawData.Offset = rawDataOffset;
+                    metadata.RawData.IsInterleaved = segment.TableOfContents.RawDataIsInterleaved;
+                    metadata.RawData.DataType = _reader.ReadInt32();
+                    metadata.RawData.ClrDataType = DataType.GetClrType(metadata.RawData.DataType);
+                    metadata.RawData.Dimension = _reader.ReadInt32();
+                    metadata.RawData.Count = _reader.ReadInt64();
+                    metadata.RawData.Size = rawDataIndexLength == 28 ? _reader.ReadInt64() :
+                                                DataType.GetArrayLength(metadata.RawData.DataType, metadata.RawData.Count);
+                    if (isInterleaved)
+                    {
+                        //fixed error. The interleave stride is the sum of all channel (type) dataSizes
+                        rawDataOffset += DataType.GetLength(metadata.RawData.DataType);
+                        interleaveStride += DataType.GetLength(metadata.RawData.DataType);
+                    }
+                    else
+                        rawDataOffset += metadata.RawData.Size;
+                }
+                var propertyCount = _reader.ReadInt32();
+                metadata.Properties = new Dictionary<string, object>();
+                for (var y = 0; y < propertyCount; y++)
+                {
+                    var key = _reader.ReadLengthPrefixedString();
+                    var value = _reader.Read(_reader.ReadInt32());
+                    metadata.Properties[key] = value;
+                }
+                metadatas.Add(metadata);
+            }
+            if (isInterleaved)
+            {
+                foreach (var metadata in metadatas)
+                {
+                    if (metadata.RawData.ClrDataType != null)
+                    {
+                        metadata.RawData.InterleaveStride = interleaveStride;
+
+                        metadata.RawData.Count = segment.NextSegmentOffset > 0
+                            ? (segment.NextSegmentOffset - metadata.RawData.Offset + interleaveStride - 1) / interleaveStride
+                            : (_reader.BaseStream.Length - metadata.RawData.Offset + interleaveStride - 1) / interleaveStride;
+                    }
+                }
+            }
+            return metadatas;
+        }
+
+        public IEnumerable<object> ReadRawData(RawData rawData)
+        {
+            if (rawData.IsInterleaved)
+                return ReadRawInterleaved(rawData.Offset, rawData.Count, rawData.DataType, rawData.InterleaveStride - DataType.GetLength(rawData.DataType));    //fixed error
+            return rawData.DataType == DataType.String ? ReadRawStrings(rawData.Offset, rawData.Count) :
+                                                         ReadRawFixed(rawData.Offset, rawData.Count, rawData.DataType);
+        }
+
+        private IEnumerable<object> ReadRawFixed(long offset, long count, int dataType)
+        {
+            _reader.BaseStream.Seek(offset, SeekOrigin.Begin);
+            for (var x = 0; x < count; x++) yield return _reader.Read(dataType);
+        }
+
+        private IEnumerable<object> ReadRawInterleaved(long offset, long count, int dataType, int interleaveSkip)
+        {
+            _reader.BaseStream.Seek(offset, SeekOrigin.Begin);
+            for (var x = 0; x < count; x++)
+            {
+                var value = _reader.Read(dataType);
+                _reader.BaseStream.Seek(interleaveSkip, SeekOrigin.Current);
+                yield return value;
+            }
+        }
+
+        private IEnumerable<object> ReadRawStrings(long offset, long count)
+        {
+            _reader.BaseStream.Seek(offset, SeekOrigin.Begin);
+            var dataOffset = offset + (count * 4);
+            var indexPosition = _reader.BaseStream.Position;
+            var dataPosition = dataOffset;
+            for (var x = 0; x < count; x++)
+            {
+                var endOfString = _reader.ReadInt32();
+                indexPosition = _reader.BaseStream.Position;
+
+                _reader.BaseStream.Seek(dataPosition, SeekOrigin.Begin);
+                yield return _reader.ReadString((int)((dataOffset + endOfString) - dataPosition));
+
+                dataPosition = dataOffset + endOfString;
+                _reader.BaseStream.Seek(indexPosition, SeekOrigin.Begin);
+            }
+        }
+
+        public long FileSize
+        {
+            get { return _reader.BaseStream.Length; }
+        }
+
+        public class Segment
+        {
+            public const long Length = 28;
+            public long Offset { get; set; }
+            public long MetadataOffset { get; set; }
+            public long RawDataOffset { get; set; }
+            public long NextSegmentOffset { get; set; }
+            public string Identifier { get; set; }
+            public TableOfContents TableOfContents { get; set; }
+            public int Version { get; set; }
+        }
+
+        public class TableOfContents
+        {
+            public bool HasMetaData { get; set; }
+            public bool HasRawData { get; set; }
+            public bool HasDaqMxData { get; set; }
+            public bool RawDataIsInterleaved { get; set; }
+            public bool NumbersAreBigEndian { get; set; }
+            public bool ContainsNewObjects { get; set; }
+        }
+
+        public class Metadata
+        {
+            public string[] Path { get; set; }
+            public RawData RawData { get; set; }
+            public IDictionary<string, object> Properties { get; set; }
+        }
+
+        public class RawData
+        {
+            public long Offset { get; set; }
+            public int DataType { get; set; }
+            public Type ClrDataType { get; set; }
+            public int Dimension { get; set; }
+            public long Count { get; set; }
+            public long Size { get; set; }
+            public bool IsInterleaved { get; set; }
+            public int InterleaveStride { get; set; }
+        }
+    }
+}

+ 20 - 0
TdmsFile/TdmsFile.projitems

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
+    <HasSharedItems>true</HasSharedItems>
+    <SharedGUID>6a45fecc-b176-47b8-89d1-d3bcc4fbf12e</SharedGUID>
+  </PropertyGroup>
+  <PropertyGroup Label="Configuration">
+    <Import_RootNamespace>TdmsFile</Import_RootNamespace>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildThisFileDirectory)BinaryReaderExtensions.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)Channel.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)DataType.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)File.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)Group.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)Reader.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)Writer.cs" />
+  </ItemGroup>
+</Project>

+ 13 - 0
TdmsFile/TdmsFile.shproj

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>6a45fecc-b176-47b8-89d1-d3bcc4fbf12e</ProjectGuid>
+    <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
+  </PropertyGroup>
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
+  <PropertyGroup />
+  <Import Project="TdmsFile.projitems" Label="Shared" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
+</Project>

+ 237 - 0
TdmsFile/Writer.cs

@@ -0,0 +1,237 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace NationalInstruments.Tdms
+{
+    public class Writer
+    {
+        private readonly BinaryWriter _writer;
+
+        public Stream BaseStream { get { return _writer.BaseStream; } }
+
+        public Writer(Stream stream)
+        {
+            _writer = new BinaryWriter(stream);
+        }
+
+        public void WriteSegment(long offset, Reader.Segment leadin)
+        {
+            _writer.BaseStream.Seek(offset, SeekOrigin.Begin);
+            _writer.Write(Encoding.ASCII.GetBytes(leadin.Identifier), 0, 4);
+            Int32 tableOfContentsMask = 0;
+            if (leadin.TableOfContents.ContainsNewObjects) tableOfContentsMask |= 1 << 2;
+            if (leadin.TableOfContents.HasDaqMxData) tableOfContentsMask |= 1 << 7;
+            if (leadin.TableOfContents.HasMetaData) tableOfContentsMask |= 1 << 1;
+            if (leadin.TableOfContents.HasRawData) tableOfContentsMask |= 1 << 3;
+            if (leadin.TableOfContents.NumbersAreBigEndian) tableOfContentsMask |= 1 << 6;
+            if (leadin.TableOfContents.RawDataIsInterleaved) tableOfContentsMask |= 1 << 5;
+            _writer.Write(tableOfContentsMask);
+            _writer.Write(leadin.Version);
+            _writer.Write((Int64)leadin.NextSegmentOffset);
+            _writer.Write((Int64)leadin.RawDataOffset);
+        }
+
+        public static string GetEncodePath(params string[] path)
+        {
+            return path == null || path.Length == 0 ? "/" : path.Length == 1 ? "/'" + path[0] + "'" : "/'" + path[0] + "'/'" + path[1] + "'";
+        }
+
+        public void WriteMetadata(IList<Reader.Metadata> metadatas)
+        {
+            _writer.Write((Int32)metadatas.Count);
+            foreach(var metadata in metadatas)
+            {
+                _writer.Write(DataType.String, GetEncodePath(metadata.Path));
+
+                //DAQmx Format Changing scaler 0x1269, DAQmx Digital Line scalar 0x1369
+                /* If the raw data index of this object in this segment exactly matches the index the same object had in the previous segment, an unsigned 32-bit integer (0x0000000) will be stored instead of the index information. */
+                if (metadata.RawData == null) _writer.Write((Int32)(-1)); 
+                else
+                {
+                    _writer.Write((Int32)20 + (metadata.RawData.DataType == DataType.String ? 8 : 0));
+                    _writer.Write((Int32)metadata.RawData.DataType);
+                    _writer.Write((Int32)metadata.RawData.Dimension);   //must be 1 
+                    _writer.Write((UInt64)metadata.RawData.Count);
+                    if (metadata.RawData.DataType == DataType.String) _writer.Write((UInt64)metadata.RawData.Size);
+                }
+                _writer.Write((Int32)(metadata.Properties?.Count ?? 0));
+                foreach(KeyValuePair<string, object> entry in (metadata.Properties ?? new Dictionary<string, object>()))
+                {
+                    _writer.Write(DataType.String, entry.Key);
+                    int dataType = DataType.GetDataType(entry.Value);
+                    _writer.Write((Int32)dataType);
+                    _writer.Write((Int32)dataType, entry.Value);
+                }
+            }
+        }
+
+        public void WriteRawData(Reader.RawData rawData, IEnumerable<object> data)
+        {
+            if (rawData.IsInterleaved) WriteRawInterleaved(rawData.DataType, rawData.Size, rawData.InterleaveStride, data);
+            else if (rawData.DataType == DataType.String) WriteRawStrings(rawData.Count, data);
+            else WriteRawFixed(rawData.DataType, data);
+        }
+
+        public void WriteRawInterleaved(int dataType, long dataSize, int interleaveStride, IEnumerable<object> data)
+        {
+            long offset = _writer.BaseStream.Position;
+            foreach (object o in data)
+            {
+                _writer.Write(dataType, o);
+                _writer.BaseStream.Seek(interleaveStride - dataSize, SeekOrigin.Current);
+            }
+            _writer.BaseStream.Seek(offset + dataSize, SeekOrigin.Begin);   //move cursor to next channel
+        }
+
+        public void WriteRawFixed(int dataType, IEnumerable<object> data)
+        {
+            foreach(object o in data) _writer.Write(dataType, o);
+        }
+
+        public void WriteRawStrings(long count, IEnumerable<object> data)
+        {
+            long indexPosition = _writer.BaseStream.Position;
+            long dataOffset = indexPosition + (count * 4);
+            long baseOffset = dataOffset;
+            foreach (object o in data)
+            {
+                //write string
+                _writer.BaseStream.Seek(dataOffset, SeekOrigin.Begin);
+                _writer.Write(Encoding.UTF8.GetBytes((string)o));
+                int relative_offset = (int)(_writer.BaseStream.Position - baseOffset);
+                int length = (int)(_writer.BaseStream.Position - dataOffset);
+
+                //write index
+                _writer.BaseStream.Seek(indexPosition, SeekOrigin.Begin);
+                _writer.Write((Int32)relative_offset);
+
+                //increment
+                indexPosition += 4;
+                dataOffset += length;
+            }
+            _writer.BaseStream.Seek(dataOffset, SeekOrigin.Begin);  //move cursor to end
+        }
+    }
+
+    /// <summary>
+    /// Small helper class for the writer. This will serialize the binary segment proper.
+    /// </summary>
+    public class WriteSegment : IDisposable
+    {
+        private Writer _writer;
+        private bool _isOpen = false;
+        private long _startOffset;
+        private bool _ownsStream = false;
+
+        public Stream BaseStream { get { return _writer.BaseStream; } }
+        public Reader.Segment Header { get; set; }
+        public List<Reader.Metadata> MetaData { get; set; }
+
+        public WriteSegment(Stream stream)
+        {
+            _writer = new Writer(stream);
+
+            //default header
+            Header = new Reader.Segment();
+            Header.Identifier = "TDSm";
+            Header.Version = 4713;
+            Header.TableOfContents = new Reader.TableOfContents();
+            Header.TableOfContents.HasMetaData = true;  //it would be rather odd with a TDMS file with no meta data. (Then there would be no raw data either)
+            Header.NextSegmentOffset = -1; //no more segments
+            Header.RawDataOffset = 0;  //for now
+
+            MetaData = new List<Reader.Metadata>();
+        }
+
+        public WriteSegment(string path) : this(new FileStream(path, FileMode.Create, FileAccess.Write))
+        {
+            _ownsStream = true;
+        }
+
+        public Writer Open()
+        {
+            if (_isOpen) throw new Exception("The TDMS segment is already open. It cannot be re-opened");
+            if (MetaData.Count == 0) throw new Exception("First insert some meta data");
+
+            //write header
+            _startOffset = BaseStream.Position;
+            _writer.WriteSegment(_startOffset, Header);
+            _writer.WriteMetadata(MetaData);
+            Header.RawDataOffset = BaseStream.Position - Reader.Segment.Length - _startOffset;
+
+            _isOpen = true;
+            return _writer;
+        }
+
+        public void Close()
+        {
+            if (!_isOpen) return;
+
+            //Re-write raw and next offset
+            long end_offset = BaseStream.Position;
+            Header.NextSegmentOffset = end_offset - _startOffset - Reader.Segment.Length;
+            _writer.WriteSegment(_startOffset, Header);
+            _writer.WriteMetadata(MetaData);     //meta data contains byte count. These should be updated before 'closing'
+            BaseStream.Seek(end_offset, SeekOrigin.Begin);  //set cursor at end
+
+            //close stream 
+            if (_ownsStream) BaseStream.Close();
+
+            _isOpen = false;
+        }
+
+        public void Dispose()
+        {
+            Close();
+        }
+
+        #region " Helper functions for creating new TDMS files (with properties) "
+
+        public static Reader.Metadata GenerateStandardProperties(string description, params string[] path)
+        {
+            Reader.Metadata meta = new Reader.Metadata();
+            meta.Path = path;
+            meta.Properties = new Dictionary<string, object>();
+            if (!string.IsNullOrEmpty(description)) meta.Properties.Add("description", description);
+            return meta;
+        }
+
+        public static Reader.Metadata GenerateStandardRoot(string name, string author, string description, DateTime datetime)
+        {
+            Reader.Metadata meta = GenerateStandardProperties(description, new string[0]);
+            if (!string.IsNullOrEmpty(name)) meta.Properties.Add("name", name);
+            if (!string.IsNullOrEmpty(author)) meta.Properties.Add("author", author);
+            if (datetime != new DateTime(1, 1, 1)) meta.Properties.Add("datetime", datetime);
+            return meta;
+        }
+
+        public static Reader.Metadata GenerateStandardGroup(string name, string description)
+        {
+            Reader.Metadata meta = GenerateStandardProperties( description, name);
+            return meta;
+        }
+
+        public static Reader.Metadata GenerateStandardChannel(string groupName, string name, string description, string yUnitString, string xUnitString, string xName, DateTime startTime, double increment, Type dataType, int dataCount, int stringBlobLength = 0)
+        {
+            Reader.Metadata meta = GenerateStandardProperties( description, groupName, name);
+            if(!string.IsNullOrEmpty(yUnitString)) meta.Properties.Add("unit_string", yUnitString);
+            if(!string.IsNullOrEmpty(xUnitString)) meta.Properties.Add("wf_xunit_string", xUnitString);
+            if(!string.IsNullOrEmpty(xName)) meta.Properties.Add("wf_xname", xName);
+            if(startTime != new DateTime(1,1,1)) meta.Properties.Add("wf_start_time", startTime);
+            if(increment != 0) meta.Properties.Add("wf_increment", increment);
+
+            meta.RawData = new Reader.RawData();
+            meta.RawData.DataType = DataType.GetDataType(Activator.CreateInstance(dataType));
+            meta.RawData.Count = dataCount;
+            meta.RawData.Dimension = 1; //always 1
+            if (dataType == typeof(string)) meta.RawData.Size = stringBlobLength;
+
+            return meta;
+        }
+
+        #endregion  
+    }
+}
+