TDMS文件:
TDMS文件是NI主推的一种二进制记录文件,TDMS文件由三个层次结构级别组成:文件、组、通道。在NI的LabVIEW软件中,可通过许多接口直接访问NI TDMS文件,但使用LABVIEW过于局限,NI提供了TDM C DLL,该DLL包含必要的函数,可在能灵活启用DLL通信的任意应用程序开发环境中读写TDMS文件。
TDM C DLL下载地址:
https://www.ni.com/content/dam/web/product-documentation/c_dll_tdm.zip
下载后
其中包含了头文件以及静态库等,由于我使用QT开发软件,故使用C++对其二次封装,实现动态添加组以及动态写入指定通道数据功能。
封装后的头文件:
其实说到低,对TDMS文件的操作,其实就是对文件指针的操作,我们打开NI提供的头文件nilibddc.h,如下图:
通过DDCFileHandle对文件进行操作,通过DDCChannelGroupHandle对组进行操作,通过DDCChannelHandle对通道进行操作。所以使用C++对其二次封装(QList是Qt提供的,如果使用c++请使用STL的List),便于操作:
#ifndef TDMSDATATYPEDEF_H
#define TDMSDATATYPEDEF_H
// Qt VS 中文兼容(UTF-8)
#ifdef _MSC_VER
#pragma execution_character_set("utf-8")
#endif
#include <QString>
#include <QList>
#include <QDateTime>
#include "QNiTdms/include/nilibddc.h"
// 定义:通道
struct TdmsStructChannel
{
QString name;
QString description;
QString unit;
DDCDataType dataType;
DDCChannelHandle pChannel;
};
// 定义:组
struct TdmsStructGroup
{
QString name;
QString description;
DDCChannelGroupHandle pGroup;
QList<TdmsStructChannel> channels;
};
// 定义:文件
struct TdmsStructFile
{
QString name;
QString description;
QString title;
QString author;
DDCFileHandle pFile;
QList<TdmsStructGroup> groups;
};
// 定义:通道 ---- 不含指针
struct TdmsInfoChannel
{
QString name;
QString description;
QString unit;
DDCDataType dataType;
};
// 定义:组 ---- 不含指针
struct TdmsInfoGroup
{
QString name;
QString description;
QList<TdmsInfoChannel> channels;
};
// 定义:文件 ---- 不含指针
struct TdmsInfoFile
{
QString name;
QString description;
QString title;
QString author;
QList<TdmsInfoGroup> groups;
};
#endif // TDMSDATATYPEDEF_H
写入类封装:
#ifndef QTDMSWRITER_H
#define QTDMSWRITER_H
// Qt VS 中文兼容(UTF-8)
#ifdef _MSC_VER
#pragma execution_character_set("utf-8")
#endif
#include <QObject>
#include <QFileInfo>
#include <QDir>
#include <QVector>
#include "tdmsdatatypedef.h"
class QTdmsWriter : public QObject
{
Q_OBJECT
public:
explicit QTdmsWriter(QObject * parent = nullptr);
explicit QTdmsWriter(const QString & filepath,QObject * parent = nullptr);
explicit QTdmsWriter(const QString & filepath, const TdmsInfoFile & tdmsinfo, QObject * parent = nullptr);
~QTdmsWriter();
private:
bool flagOpen; // 状态指示:文件是否已经打开,true表示打开
bool flagErrorOccurred; // 状态指示:是否出现了错误,true表示出错
QString errorMsg; // 出错信息描述
TdmsStructFile tdmsStruct;
private:
bool createFile(const QString & filePath, const QString & fileName, const QString & fileDescription, const QString & fileTitle, const QString & fileAuthor, DDCFileHandle & file); // 创建文件
bool addChannelGroup(DDCFileHandle file, const QString & groupName, const QString & groupDescription, DDCChannelGroupHandle & channelGroup); // 添加组
bool addChannel(DDCChannelGroupHandle channelGroup, DDCDataType dataType, const QString & channelName, const QString & channelDescription, const QString & channelUnit, DDCChannelHandle & channel); // 添加通道
bool appendChannelDataValuesUInt8(DDCChannelHandle channel, const QByteArray & ba, int & numValues); // 添加通道数据:UInt8
bool appendChannelDataValuesInt16(DDCChannelHandle channel, const QByteArray & ba, int & numValues); // 添加通道数据:Int16
bool appendChannelDataValuesInt32(DDCChannelHandle channel, const QByteArray & ba, int & numValues); // 添加通道数据:Int32
bool appendChannelDataValuesFloat(DDCChannelHandle channel, const QByteArray & ba, int & numValues); // 添加通道数据:Float
bool appendChannelDataValuesDouble(DDCChannelHandle channel, const QByteArray & ba, int & numValues); // 添加通道数据:Double
bool appendChannelDataValuesString(DDCChannelHandle channel, const QByteArray & ba, int & numValues); // 添加通道数据:String
bool appendChannelDataValuesTimestamp(DDCChannelHandle channel, const QByteArray & ba, int & numValues); // 添加通道数据:Timestamp
public:
bool isOpen() const;
bool isErrorOccurred() const;
QString errorDescription();
bool appendDataValuesDoubleOfChannelGroup(int indexOfChannelGroup, const QList<std::vector<double>> & groupData); // 追加保存:某“组”中所有通道的数据 ---- float
bool appendDataValuesOfChannelGroup(int indexOfChannelGroup, const QList<QByteArray> & groupData); // 追加保存:某“组”中所有通道的数据
//动态添加组
bool addGroup(TdmsStructGroup groupInfo);
//完成所有操作后保存
bool saveFile();
//向某组的某通道追加保存
bool appendDataValuesDoubleOfGroupChannel(int indexOfChannelGroup,int indexOfChannel,QVector<double> &channelData);
};
#endif // QTDMSWRITER_H
其中私有成员中包含TdmsStructFile这个结构体,整个类的成员函数都是通过对这个结构体的操作来进行文件、组、通道的创建、写入。
创建TDMS文件:
// 创建文件
bool QTdmsWriter::createFile(const QString & filePath, const QString & fileName, const QString & fileDescription, const QString & fileTitle, const QString & fileAuthor, DDCFileHandle & file)
{
// 1. 查看文件名,以tdm或tdms为后缀,自动判断
if (QFile::exists(filePath)) // 文件已存在,删除之
{
bool flag = QFile::remove(filePath);
if (!flag)
{
flagErrorOccurred = true;
errorMsg = tr("同名文件已存在,将其删除时出错");
return false;
}
}
QFileInfo tmpfile(filePath);
QString strSuffix = tmpfile.suffix().toUpper(); // 后缀名(注意不含“.”)
bool tdmsflag = ((strSuffix == "TDM") || (strSuffix == "TDMS"));
if (!tdmsflag)
{
flagErrorOccurred = true;
errorMsg = tr("文件后缀名不是TDM或TDMS");
return false;
}
// 2. 创建文件
QString tdms_filepath = QDir::toNativeSeparators(filePath);
std::string str_filepath = tdms_filepath.toLocal8Bit().toStdString();
const char * ch_filepath = str_filepath.c_str();
std::string str_file_name = fileName.toLocal8Bit().toStdString();
const char * ch_file_name = str_file_name.c_str();
std::string str_file_description = fileDescription.toLocal8Bit().toStdString();
const char * ch_file_description = str_file_description.c_str();
std::string str_file_title = fileTitle.toLocal8Bit().toStdString();
const char * ch_file_title = str_file_title.c_str();
std::string str_file_author = fileAuthor.toLocal8Bit().toStdString();
const char * ch_file_author = str_file_author.c_str();
int errorCode;
if ((strSuffix == "TDM"))
{
errorCode = DDC_CreateFile(ch_filepath, DDC_FILE_TYPE_TDM, ch_file_name, ch_file_description, ch_file_title, ch_file_author, &file);
}
else
{
errorCode = DDC_CreateFile(ch_filepath, DDC_FILE_TYPE_TDM_STREAMING, ch_file_name, ch_file_description, ch_file_title, ch_file_author, &file);
}
flagErrorOccurred = (errorCode != DDC_NoError);
errorMsg = QString(DDC_GetLibraryErrorDescription(errorCode));
if (flagErrorOccurred)
{
return false;
}
if (file == nullptr) // 进一步检查确认
{
flagErrorOccurred = true;
errorMsg = tr("文件创建失败");
return false;
}
return true;
}
动态添加组addGroup函数
bool QTdmsWriter::addGroup(TdmsStructGroup groupInfo)
{
groupInfo.pGroup = nullptr;
for(int i = 0 ; i < groupInfo.channels.size() ; i++)
{
groupInfo.channels[i].pChannel = nullptr;
}
int errorCode;
tdmsStruct.groups.append(groupInfo);
int index = tdmsStruct.groups.size()-1;
bool flag = false;
bool tdmsFlag = false;
flag = addChannelGroup(tdmsStruct.pFile,tdmsStruct.groups.at(index).name,tdmsStruct.groups.at(index).description,tdmsStruct.groups[index].pGroup);
if(!flag)
{
tdmsFlag = true;
}
for(int i = 0;i < tdmsStruct.groups.at(index).channels.size() ; i++)
{
flag = addChannel(tdmsStruct.groups.at(index).pGroup,tdmsStruct.groups.at(index).channels.at(i).dataType,
tdmsStruct.groups.at(index).channels.at(i).name,tdmsStruct.groups.at(index).channels.at(i).description,
tdmsStruct.groups.at(index).channels.at(i).unit,tdmsStruct.groups[index].channels[i].pChannel);
if(!flag)
{
tdmsFlag = true;
continue;
}
}
if (tdmsFlag) // 如果出错,关闭文件
{
int errcode = DDC_CloseFile(tdmsStruct.pFile);
if (errcode == DDC_NoError)
{
flagOpen = false; // 已经顺利关闭
}
flagErrorOccurred = true;
errorMsg = tr("TDM(S)文件中增添'组'或'通道'时出错,部分未成功");
return false;
}
return true;
}
向某组的某通道写入数据appendDataValuesDoubleOfGroupChannel函数
bool QTdmsWriter::appendDataValuesDoubleOfGroupChannel(int indexOfChannelGroup, int indexOfChannel, QVector<double> &channelData)
{
if (!flagOpen)
{
return false;
}
int numGroups = tdmsStruct.groups.size();
if ((indexOfChannelGroup < 0) || (indexOfChannelGroup > numGroups))
{
flagErrorOccurred = true;
errorMsg = tr("输入的 ChannelGroup 索引号有误,超出了有效范围");
return false;
}
int numChannels = tdmsStruct.groups.at(indexOfChannelGroup).channels.size();
if ((indexOfChannel < 0) || (indexOfChannel > numChannels))
{
flagErrorOccurred = true;
errorMsg = tr("输入的通道数据有误,与TDM(S)文件中该组的通道数量不一致");
return false;
}
// 遍历所有通道
bool tdmsflag = false;
unsigned long long len = channelData.size();
const double * pVals = channelData.data();
double * ptr = const_cast<double *>(pVals);
int errorCode = DDC_AppendDataValuesDouble(tdmsStruct.groups.at(indexOfChannelGroup).channels.at(indexOfChannel).pChannel, ptr, len);
flagErrorOccurred = (errorCode != DDC_NoError);
errorMsg = QString(DDC_GetLibraryErrorDescription(errorCode));
if (flagErrorOccurred)
{
tdmsflag = true;
}
if (tdmsflag)
{
flagErrorOccurred = true;
errorMsg = tr("TDM(S)文件中添加保存'通道'数据时出错,部分未成功");
return false;
}
return true;
}
注意最后要保存 saveFile函数
bool QTdmsWriter::saveFile()
{
int errorCode;
errorCode = DDC_SaveFile(tdmsStruct.pFile);
flagErrorOccurred = (errorCode != DDC_NoError);
errorMsg = QString(DDC_GetLibraryErrorDescription(errorCode));
if (flagErrorOccurred)
{
return false;
}
return true;
}
写的Demo测试:
int main(int argc, char *argv[])
{
QTdmsWriter *writer;
//要写入通道的数据
QVector<double> arr;
for(int i = 0 ; i < 100 ; i++)
{
arr.push_back(i);
}
//创建TDMS文件
QDateTime startTime = QDateTime::currentDateTime();
QString timeStr = startTime.toString("yyyyMMddhhmmss");
//注意修改路径
QString charaFilePathName = "E:/Demo/"
+timeStr+".tdms";
//TDMS文件信息
TdmsInfoFile tdmsinfo;
tdmsinfo.name = "ADQ";
tdmsinfo.description = "save acquisition data";
tdmsinfo.title = "data";
tdmsinfo.author = "zx";
//创建
writer = new QTdmsWriter(charaFilePathName,tdmsinfo);
//这里创建8个组,每个组8个通道
for(int cnt = 0 ; cnt < 8 ; cnt++)
{
//组名使用时间命名
QDateTime curDateTime = QDateTime::currentDateTime();
QString timeStr = curDateTime.toString("yyyyMMddhhmmss")+QString::number(cnt);
QString groupName = timeStr ;
TdmsStructGroup groupInfo ;
groupInfo.name = groupName;
//创建通道
TdmsStructChannel channel;
channel.name = "HP";
channel.dataType = DDC_Double;
channel.unit = "MPa";
//添加到组中
groupInfo.channels.append(channel);
//通道名CP,物理量兆帕,类型为Double
channel.name = "CP";
channel.dataType = DDC_Double;
channel.unit = "MPa";
groupInfo.channels.append(channel);
channel.name = "GP";
channel.dataType = DDC_Double;
channel.unit = "MPa";
groupInfo.channels.append(channel);
channel.name = "PT";
channel.dataType = DDC_Double;
channel.unit = "℃";
groupInfo.channels.append(channel);
channel.name = "HN";
channel.dataType = DDC_Double;
channel.unit = "MPa";
groupInfo.channels.append(channel);
channel.name = "CN";
channel.dataType = DDC_Double;
channel.unit = "MPa";
groupInfo.channels.append(channel);
channel.name = "GN";
channel.dataType = DDC_Double;
channel.unit = "MPa";
groupInfo.channels.append(channel);
channel.name = "NT";
channel.dataType = DDC_Double;
channel.unit = "℃";
groupInfo.channels.append(channel);
//添加组
if(!writer->addGroup(groupInfo))
{
qDebug()<<"add group error";
qDebug()<<writer->errorDescription();
}
}
//向0组的1号通道写入arr
writer->appendDataValuesDoubleOfGroupChannel(0,1,arr);
//向0组的2号通道写入arr
writer->appendDataValuesDoubleOfGroupChannel(0,2,arr);
//注意最后一定要保存
writer->saveFile();
delete writer;
return 0;
}
测试效果:
总结:
数据确实已经写入了进去,注意保存操作应该放到最后。
通过EXC打开TDMS插件下载地址:
TDM Excel Add-In for Microsoft Excel Download - NI
本文整个项目下载地址(包含读的操作):
注意编译完成后,请将bin目录下的32-bit或64-bit的动态库全部加入编译的目录下,否则无法运行!