基于nilibddc.dll,在QtC++中实现tdmsReader和tdmsWriter
TDMS文件将数据划分为三个不同层次的对象。最顶层由单个对象组成,其中包含了文件本身特有的信息,如作者或标题。每个文件可包含任意数量的组,每组又可包含任意数量的通道。
下图是利用本文tdmsWriter的程序生成的文件结构:
本文代码脱胎于以下文章中的代码,并做了改进与完善,但本文并未下载该文章的资源代码,仅参考了其文章中的代码。
“Qt封装TDMS文件实现动态添加组以及动态写入通道数据”
tdms writer
首先需要确保写出的文件能够被labview、DIAdem等程序读取,因此,先参考上述文章写了文件写入类。
定义文件、组、通道的头文件如下:
#include <QString>
#include <QList>
#include <QDateTime>
#include "nilibddc.h"
#define Wf_XName "wf_xname"
#define Wf_XUnit "wf_xunit_string"
#define Wf_Start_Time "wf_start_time"
#define Wf_Offset "wf_start_offset"
#define Wf_Increment "wf_increment"
// 定义:通道
struct TdmsChannel
{
QString name;
QString description;
QString unit;//Y轴数字的单位
DDCDataType dataType;
QString wf_xName;//X轴数字的名称
QString wf_xUnit;//X轴数字的单位
QDateTime wf_StartTime;//时间轴的初始日期时间值
int wf_Offset;//第一个值距离开始时间有几个Increment
double wf_Increment;//X轴的增量值,采样率的倒数
DDCChannelHandle pChannel;
long long dataLength = -1;//read的时候获取的本通道数据长度
};
// 定义:组
struct TdmsGroup
{
QString name;
QString description;
DDCChannelGroupHandle pGroup;
QList<TdmsChannel> channels;
};
// 定义:文件
struct TdmsFile
{
QString name;
QString description;
QString title;
QString author;
DDCFileHandle pFile;
void appendGroup(TdmsGroup& g)
{
int counter = 1;
while (groupsNames.contains(g.name)) {
g.name = QString("%1_%2").arg(g.name).arg(counter);
counter++;
}
groups<<g;
groupsNames<<g.name;
}
const QList<TdmsGroup> &getGroups()
{return groups;}
private:
QList<TdmsGroup> groups;
QList<QString> groupsNames;
};
本文中将一个writer包装为一个类,对应于一个tdms文件的引用,并通过各种函数实现组的创建、通道的创建、数据的添加、属性的添加、文件的保存等操作。
这里要重点描述一下文件保存操作,实际使用该类的过程中,如流式数据的保存,主程序可以无数次调用DDC_SaveFile,只要不关闭文件,可以一直调用,将已有的数据写入文件中。
下面是tdms writer的头文件定义:
#include <QObject>
#include <QFileInfo>
#include <QDir>
#include <QVector>
#include <QThread>
#include "TdmsDataTypeDef.h"
class QTdmsWriter : public QObject
{
Q_OBJECT
public:
explicit QTdmsWriter(const QString & filePath, TdmsFile & tdmsInfo, QObject * parent = nullptr);
~QTdmsWriter();
private:
bool flagOpen = false; // 状态指示:文件是否已经打开,true表示打开
bool flagErrorOccurred = false; // 状态指示:是否出现了错误,true表示出错
QString errorMsg = "No ERROR"; // 出错信息描述
QString fileFullPath;
TdmsFile &tdmsStruct;
private:
void setErrorMsg(QString msg);
bool checkErrAndReturn(int errcode);
// 创建文件,返回处理后的文件名,或null
QString createFile(const QString & filePath,
const QString & fileDescription, const QString & fileTitle,
const QString & fileAuthor, DDCFileHandle & file);
bool addGroup2File(DDCFileHandle file, TdmsGroup &group); // 添加组
bool addChannel2Group(DDCChannelGroupHandle channelGroup, TdmsChannel &channel); // 添加通道
public:
bool isOpen() const;
bool isErrorOccurred() const;
QString errorDescription();
//动态添加组
bool addGroup(TdmsGroup &groupInfo);
//完成所有操作后保存
bool saveFile();
//关闭文件
bool closeFile();
//向某组的某通道追加保存
bool appendDouble2ChannelofGroup(int indexOfChannelGroup,int indexOfChannel,QVector<double> &channelData);
bool appendString2ChannelofGroup(int indexOfChannelGroup,int indexOfChannel,QVector<QString> &channelData);
};
利用该类即可保存出labVIEW、DIAdem、tdmsAddon均可打开的文件。
tdms reader
后续本文又写了一个reader类,将上述文件打开,并读取的所有定义的属性、以及数据。目前包含了double、float、string类型的数据,其他的可以以此类推。
头文件如下:
#include <QObject>
#include <QFileInfo>
#include <QDir>
#include "TdmsDataTypeDef.h"
class QTdmsReader : public QObject
{
Q_OBJECT
public:
explicit QTdmsReader(const QString & filePath,QObject *parent = nullptr);
TdmsFile readFile;
bool flagErrorOccurred = false; // 状态指示:是否出现了错误,true表示出错
QString errorMsg = "No ERROR";
QVector<char> readChar(TdmsChannel &ch, int index,int len);
QVector<short> readShort(TdmsChannel &ch, int index,int len);
QVector<long> readLong(TdmsChannel &ch, int index,int len);
QVector<float> readFloat(TdmsChannel &ch, int index,int len);
QVector<double> readDouble(TdmsChannel &ch, int index,int len);
QVector<QString> readString(TdmsChannel &ch, int index,int len);
signals:
private:
void setErrorMsg(QString msg);
///
/// \brief checkErrAndReturn
/// \param errcode
/// true:一切正常
/// false:出现错误
/// \return
///
bool checkErrAndReturn(int errcode);
///
/// \brief this_GetFilePropertyString
/// \param file
/// \param property // File property constants
/// #define DDC_FILE_NAME "name" // Name
/// #define DDC_FILE_DESCRIPTION "description" // Description
/// #define DDC_FILE_TITLE "title" // Title
/// #define DDC_FILE_AUTHOR "author" // Author
/// #define DDC_FILE_DATETIME "datetime" // Date/Time
/// \return
///
QString this_GetFilePropertyString(DDCFileHandle file, const char *property);
///
/// \brief this_GetGroupPropertyString
/// \param group
/// \param property // ChannelGroup property constants
/// #define DDC_CHANNELGROUP_NAME "name" // Name
/// #define DDC_CHANNELGROUP_DESCRIPTION "description" // Description
/// \return
///
QString this_GetGroupPropertyString(DDCChannelGroupHandle group, const char *property);
///
/// \brief this_GetChannelPropertyString
/// \param channelH
/// \param property // Channel property constants
/// #define DDC_CHANNEL_NAME "name" // Name
/// #define DDC_CHANNEL_DESCRIPTION "description" // Description
/// #define DDC_CHANNEL_UNIT_STRING "unit_string" // Unit String
///
/// #define Wf_XName "wf_xname"
/// #define Wf_XUnit "wf_xunit_string"
/// #define Wf_Start_Time "wf_start_time"
/// #define Wf_Offset "wf_start_offset"
/// #define Wf_Increment "wf_increment"
/// \return
///
QString this_GetChannelPropertyString(DDCChannelHandle channelH, const char *property);
void init_Channel(DDCChannelHandle channelH, TdmsChannel &ch);
};
测试
最后在main函数中做了测试
writer测试
void testwriter()
{
QTdmsWriter *writer;
//要写入通道的数据
QVector<double> arr;
for(int i = 0 ; i < 20480 ; i++)
{
arr<<i;
}
QVector<QString> arrStr;
arrStr<<"1-8899aassdd测试测试"<<"2-测试"<<"3-ssdd测"<<"4-aassdd99测"<<"5-aassdd"<<"6-aassdd"<<"7-\n"
<<"8-8899aassdd测试测试"<<"9-测试"<<"10-ssdd测"<<"11-aassdd99测"<<"12-aassdd"<<"13-aassdd"<<"\n"
<<"8899aassdd测试测试"<<"测试"<<"ssdd测"<<"aassdd99测"<<"aassdd"<<"aassdd"<<"\n";
//注意修改路径
QString charaFilePathName = "E:/tdmsFileDemo/ttt/test.tdms";
//TDMS文件信息
TdmsFile tdmsinfo;
tdmsinfo.name = "daq";
tdmsinfo.description = "save acquisition data";
tdmsinfo.title = "underwater acousi...";
tdmsinfo.author = "peter";
int gCNT =14;
//创建
writer = new QTdmsWriter(charaFilePathName,tdmsinfo);
//这里创建8个组,每个组8个通道
for(int cnt = 0 ; cnt < gCNT ; cnt++)
{
//组名使用时间命名
QDateTime curDateTime = QDateTime::currentDateTime();
QString timeStr = curDateTime.toString("yyyyMMddhhmmss")+"_第"+QString::number(cnt)+"组";
QString groupName = timeStr ;
TdmsGroup groupInfo ;
groupInfo.name = groupName;
groupInfo.description = "?///>>><<<**&&^^%$#@@~";
int rate=204800;
groupInfo.channels<<TdmsChannel{"HP","description0","MPa",DDC_Double,
"时间","s",QDateTime::currentDateTime(),0,1.0/rate,nullptr}
<<TdmsChannel{"压力瞬态响应6688ssqq","description0","MPa",DDC_Double,
"时间","s",QDateTime::currentDateTime(),0,1.0/rate,nullptr}
<<TdmsChannel{"GP","description0","MPa",DDC_Double,
"时间","s",QDateTime::currentDateTime(),0,1.0/rate,nullptr}
<<TdmsChannel{"出口段温度测量","description0","℃",DDC_Double,
"时间","s",QDateTime::currentDateTime(),0,1.0/rate,nullptr}
<<TdmsChannel{"HN","description0","MPa",DDC_Double,
"时间","s",QDateTime::currentDateTime(),0,1.0/rate,nullptr}
<<TdmsChannel{"电阻测量","description0","Ω",DDC_Double,
"时间","s",QDateTime::currentDateTime(),0,1.0/rate,nullptr}
<<TdmsChannel{"NT","description0","℃",DDC_Double,
"时间","s",QDateTime::currentDateTime(),0,1.0/rate,nullptr}
<<TdmsChannel{"QTSTRINGs","test 文本","℃",DDC_String,
"文本","s",QDateTime::currentDateTime(),0,1.0/rate,nullptr};
//添加组
if(!writer->addGroup(groupInfo))
{
qDebug()<<"add group error:"<<writer->errorDescription();
}
}
for(int appendIndex = 0;appendIndex<8;appendIndex++){//模拟采样8次,可随便调整
for(int gIndex = 0;gIndex<gCNT;gIndex++)
{
for(int cIndex = 0;cIndex<7;cIndex++)
{
writer->appendDouble2ChannelofGroup(gIndex,cIndex,arr);
}
writer->appendString2ChannelofGroup(gIndex,7,arrStr);
}
writer->saveFile();
}
//注意最后一定要保存
writer->closeFile();
delete writer;
}
reader测试
void testreader()
{
QTextStream cout(stdout);
QTdmsReader reader("E:/tdmsFileDemo/ttt/test_7.tdms");
TdmsFile &tdmsinfo = reader.readFile;
const QList<TdmsGroup> & Gs = tdmsinfo.getGroups();
//getdata from index with len
//遍历打印每个通道前12个值和最后12个值
int firstLen = 12,lastLen = 12;
for(int groupi = 0;groupi<Gs.count();groupi++)
{
QList<TdmsChannel> chans = Gs[groupi].channels;
cout <<"group name: "<<Gs[groupi].name<<";group des: "<<Gs[groupi].description<<"\n";
for(int channeli=0;channeli<chans.count();channeli++)
{
cout<<"ch name:"<<chans[channeli].name<<"ch description:"<<chans[channeli].description
<<"ch wf_xName:"<<chans[channeli].wf_xName
<<"ch dataType:"<<chans[channeli].dataType
<<"ch unit:"<<chans[channeli].unit<<"\n";
//打印数据
switch(chans[channeli].dataType)
{
case DDC_Double:{
QVector<double> data1 = reader.readDouble(chans[channeli],0,firstLen);
cout<<"first "<<firstLen<<"values\n";
for(double i : data1)
cout<<i<<";";
cout<<"\n";
QVector<double> dataLast1 = reader.readDouble(chans[channeli],
chans[channeli].dataLength-lastLen,
lastLen);
cout<<"last "<<lastLen<<"values\n";
for(double i : dataLast1)
cout<<i<<";";
cout<<"\n";}
break;
case DDC_Float:{
QVector<float> data2 = reader.readFloat(chans[channeli],0,firstLen);
cout<<"first "<<firstLen<<"values\n";
for(float i : data2)
cout<<i<<";";
cout<<"\n";
QVector<float> dataLast2 = reader.readFloat(chans[channeli],
chans[channeli].dataLength-lastLen,
lastLen);
cout<<"last "<<lastLen<<"values\n";
for(float i : dataLast2)
cout<<i<<";";
cout<<"\n";}
break;
case DDC_String:{
QVector<QString> data3 = reader.readString(chans[channeli],0,firstLen);
cout<<"first "<<firstLen<<"values\n";
for(QString i : data3)
cout<<i<<";";
cout<<"\n";
QVector<QString> dataLast3 = reader.readString(chans[channeli],
chans[channeli].dataLength-lastLen,
lastLen);
cout<<"last "<<lastLen<<"values\n";
for(QString i : dataLast3)
cout<<i<<";";
cout<<"\n";}
break;
case DDC_UInt8:{
}break;
case DDC_Int16:{
}break;
case DDC_Int32:{
}break;
case DDC_Timestamp:{
}break;
}
}
}
}
资源分享
基于原来文章中的资源太贵了,这里直接分享本文修改的writer和reader的实现如下
引用
[1]: NI TDMS File Format - What is a TDMS File?
[2]: 写入可数据管理的TDMS文件
[3]: TDMS文件格式内部结构
[4]: Qt封装TDMS文件实现动态添加组以及动态写入通道数据