Qt封装TDMS文件实现动态添加组以及动态写入通道数据

5 篇文章 1 订阅
1 篇文章 0 订阅

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的动态库全部加入编译的目录下,否则无法运行!

使用Qt-C++对NI-TDMS库封装进行文件组通道创建数据写入读出-其它文档类资源-CSDN文库

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Qt是一个跨平台的应用程序开发框架,用于开发GUI软件,它支持多种数据格式的处理。TDMS是一种二进制文件格式,该格式旨在更有效地存储和传输大量数据,常用于高速数据采集,测试和测量领域。本文将介绍如何在Qt中生成TDMS文件。 首先,需要使用NI提供的TDMS C库。其次,需要使用Qt文件IO类实现TDMS文件的生成。文件IO类是Qt中用于处理文件和目录的类,可以用于读取和写入文件。 在生成TDMS文件之前,需要明确TDMS文件的结构。TDMS文件包含了通道通道数据信息。通道是一个文件中的顶级数据通道通道中的子项,数据是存储在通道中的数据。 利用Qt文件IO类,可以实现如下代码: ``` #include <QFile> #include <QDataStream> 文件名称:QString tdmsName = "test.tdms"; 通道名称:QString rootName = "测试"; 通道名称:QString channelName = "通道1"; //创建文件对象 QFile tdmsFile(tdmsName); //判断文件是否存在 if (!tdmsFile.exists()) { //文件不存在则创建文件 tdmsFile.open(QIODevice::WriteOnly); tdmsFile.close(); } // 打开文件 if (!tdmsFile.open(QIODevice::ReadWrite)) { return; } // 创建数据流 QDataStream out(&tdmsFile); // 写入通道信息 out << static_cast<quint8>(1); //TDMS 文件版本号 out << quint64(2); //文件头大小 out << QString("TDMS"); //文件头标志 out << static_cast<quint8>(1); //通道数量 out << static_cast<quint16>(rootName.size()); //通道名称长度 out.writeRawData(rootName.constData(), rootName.size()); // 通道名称 // 写入通道信息 out << static_cast<quint32>(1); //通道数量 out << static_cast<quint16>(channelName.size()); //通道名称长度 out.writeRawData(channelName.constData(), channelName.size()); //通道名称 // 写入数据 float data[] = {1.23, 2.34, 3.45}; quint32 dataSize = sizeof(data); out << quint32(0x2015C001); //数据类型 TDMS_TYPE_FLOAT64, TDMS_TYPE_FLOAT32, TDMS_TYPE_INT8等 out << quint32(dataSize); //数据大小 out.writeRawData(reinterpret_cast<const char *>(data), dataSize); ``` 通过上述代码可以实现TDMS文件的生成,文件包含一个通道,一个通道和一数据。实际应用中,需要根据数据类型和数据大小通过数据写入文件。 总结起来,Qt生成TDMS文件需要使用NI提供的TDMS C库和Qt文件IO类。在文件写入通道通道数据信息,即可生成TDMS文件

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

1egenda

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值