728x90

QTreeWidget에 Data를 추가 한 후 Json 형태로 Save 및 Load 하는 방법입니다.
 
먼저 좌측에 간단한 데이터가 입력된 Tree가 있고 Save로 Data를 Json 형태로 저장 후 Load 시 우측의 Tree에 Json Data를 출력하는 간단한 예제입니다.
 
아래는 코드 입니다. Save 버튼을 클릭했을 때  Tree의 정보를 Json형태로 Save 하고 Load 버튼을 클릭했을 때 Json을 읽어서 Tree에 Item을 생성하여 넣어줍니다.

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    connect ( ui->btnSave, &QPushButton::clicked, this, [&] {
        QJsonObject saveData;           // 전체 Json 저장할 Object 객체

        QFile saveFile("SaveFile.json");
        if ( !saveFile.open(QIODevice::WriteOnly) ) {
            return;
        }

        QJsonArray TopArray;        // Top Item을 저장할 Array 객체
        for ( int i = 0; i < ui->saveTree->topLevelItemCount(); i++ ) {
            QJsonObject TopObject;

            auto TopItem = ui->saveTree->topLevelItem(i);
            TopObject.insert("Name", TopItem->text(0));

            QJsonArray ChildArray;      // Child Item을 저장할 Array 객체
            for ( int c = 0; c < TopItem->childCount(); c++ ) {
                QJsonObject ChildObject;

                auto ChildItem = TopItem->child(c);
                ChildObject.insert("Name", ChildItem->text(0));
                ChildObject.insert("Value", ChildItem->text(1));
                ChildArray.append(ChildObject);
            }

            TopObject.insert("Child", ChildArray);
            TopArray.append(TopObject);
        }

        saveData.insert("Top", TopArray);

        saveFile.write(QJsonDocument(saveData).toJson());
        saveFile.close();
    });

    connect ( ui->btnLoad, &QPushButton::clicked, this, [&] {
        QFile loadFile("SaveFile.json");
        if ( !loadFile.open(QIODevice::ReadOnly) ) {
            return;
        }

        QJsonDocument loadDocument = QJsonDocument::fromJson(loadFile.readAll());

        auto TopData = loadDocument.object().value("Top").toArray();        // Top Array 정보를 불러옴
        for ( auto topIt = TopData.cbegin(); topIt != TopData.cend(); ++topIt ) {
            auto top = (*topIt).toObject();
            auto makeItem = [&](QJsonObject& topJson) -> QTreeWidgetItem* {
                QTreeWidgetItem* topItem = new QTreeWidgetItem;

                topItem->setText(0, topJson.value("Name").toString());

                auto Child = topJson.value("Child").toArray();      // Top 하위의 Child Array 정보를 불러옴
                for ( auto childIt = Child.cbegin(); childIt != Child.cend(); ++childIt ) {
                    auto childObject = (*childIt).toObject();

                    QTreeWidgetItem* childItem = new QTreeWidgetItem(topItem);
                    childItem->setText(0, childObject.value("Name").toString());
                    childItem->setText(1, childObject.value("Value").toString());
                }

                return topItem;
            }; // Top 정보를 가지고 Item 생성해주는 람다 함수

            ui->loadTree->addTopLevelItem(makeItem(top));
        }

        loadFile.close();
    });
}
{
    "Top": [
        {
            "Child": [
                {
                    "Name": "Ten",
                    "Value": "10"
                },
                {
                    "Name": "Eleven",
                    "Value": "11"
                }
            ],
            "Name": "One"
        },
        {
            "Child": [
            ],
            "Name": "Two"
        }
    ]
}

 

728x90
728x90

기본적으로 QTimer를 만들고 timeout Singal을 connect 해준다면 Main Thread에서 Slot이 처리됩니다. 이럴 때 QThread를 생성 후 Timer를 그 쓰레드로 이동시켜 사용한다면 Main Thread가 아닌 생성한 Thread에서 처리가 됩니다.

class timerMgr : public QObject {
    Q_OBJECT
public:
    timerMgr() {
        for ( int i=0; i < 3 ; i++ ) {
            QTimer* t = new QTimer;
            t->setObjectName("Timer" + QString::number(i));
            t->setInterval(QRandomGenerator::global()->bounded(100, 2000));
            connect ( t, &QTimer::timeout, this, &timerMgr::printTimer);
            timerList.append(t);
        } // 랜덤 주기의 Timer를 생성하고 Connect
    }

    void startTimer() {
        for ( auto t : timerList ) {
            t->start();
        }
    }

private slots:
    void printTimer() {
        qDebug() << "Timeout Thread = " << QThread::currentThreadId();
    } // Thread ID 출력

private:
    QList<QTimer*> timerList;
};


QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    qDebug() << "Main Thread = " << QThread::currentThreadId();
    QThread* TimerThread = new QThread;		// Thread 생성
    timerMgr* t = new timerMgr;	
    t->moveToThread(TimerThread);			// Timer Manager 클래스를 TimerThread에서 돌게
    TimerThread->start();					// Timer Thread Start

    t->startTimer();
}

MainWindow::~MainWindow()
{
    delete ui;
}

위 코드를 실행하면 아래와 같이 Timeout에 대한 Slot 함수가 다른 Thread에서 발생했다는 것을 볼 수 있습니다.

더보기

실행결과

 

Main Thread =  0x52f8
Timeout Thread =  0x638c
Timeout Thread =  0x638c
Timeout Thread =  0x638c
Timeout Thread =  0x638c
Timeout Thread =  0x638c
Timeout Thread =  0x638c
Timeout Thread =  0x638c
Timeout Thread =  0x638c

728x90
728x90
void clearOnlyItem(QTableWidget* table)
{
	table->setRowCount(0);
}

 

728x90
728x90

Windows / PCAN USB를 가지고 ISOTP 혹은 CAN-FD로 간단하게 채팅할 수 있는 프로그램을 제작해 보았습니다. PCAN API C++ 관련 자료가 많이 없어 이 API를 가지고 개발하시려는 분들께 도움이 됐으면 좋겠습니다.

 

 

GitHub - psy1064/PCAN-Chat

Contribute to psy1064/PCAN-Chat development by creating an account on GitHub.

github.com

 

728x90
728x90

QString의 toInt는 정수형태의 문자열을 정수로 변환시켜주는 함수입니다. 그런데 이 호출하는 함수에 맞지 않는 문자열 값을 가지고 있으면 제대로 처리가 되지 않습니다.

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString sUInt   = "123";
    QString sInt    = "-123";
    QString sFloat  = "123.12";

    bool ok;

    qDebug() << sUInt.toUInt(&ok) << ok;
    qDebug() << sInt.toUInt(&ok) << ok;
    qDebug() << sFloat.toUInt(&ok) << ok;

    qDebug() << sUInt.toInt(&ok) << ok;
    qDebug() << sInt.toInt(&ok) << ok;
    qDebug() << sFloat.toInt(&ok) << ok;

    return a.exec();
}

/* 실행결과
123 true
0 false
0 false
123 true
-123 true
0 false
*/

위 실행결과를 보면 알 수 있듯이 해당 함수를 통해 얻고 싶은 자료형이 아닌 문자열이 호출되면 0이 반환되고 인자로 넘겨준 flag에는 false 가 나오게 됩니다.

 

이를 방지하기 위해서는 toInt(), toFloat가 아닌 atoi, atof를 사용하는 것이 좋습니다.

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString sUInt   = "123";
    QString sInt    = "-123";
    QString sFloat  = "123.12";

    bool ok;

    qDebug() << sUInt.toUInt(&ok) << ok;
    qDebug() << sInt.toUInt(&ok) << ok;
    qDebug() << sFloat.toUInt(&ok) << ok;

    qDebug() << sUInt.toInt(&ok) << ok;
    qDebug() << sInt.toInt(&ok) << ok;
    qDebug() << sFloat.toInt(&ok) << ok;

    qDebug() << atoi(sUInt.toLocal8Bit().constData());
    qDebug() << atoi(sInt.toLocal8Bit().constData());
    qDebug() << atoi(sFloat.toLocal8Bit().constData());

    return a.exec();
}
/* 실행결과
123 true
0 false
0 false
123 true
-123 true
0 false
123
-123
123
*/

 

728x90
728x90

Qt에서는 기본적으로 Drag&Drop Action을 지원합니다. 아래의 코드를 사용하면 QTreeWidget에 기본적으로 구현된 내부 Drag & Drop Action이 실행됩니다.

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    ui->originTree->setSelectionMode(QAbstractItemView::SingleSelection);
    ui->originTree->setDragEnabled(true);
    ui->originTree->viewport()->setAcceptDrops(true);
    ui->originTree->setDropIndicatorShown(true);
}

 

기존에 제공하는 로직이 아닌 다른 동을 하기 위해서는 dropEvent를 overriding 처리를 통해 직접 구현할 수 있습니다.

void dragDropTree::dropEvent(QDropEvent *event)
{
    QByteArray encoded = event->mimeData()->data("application/x-qabstractitemmodeldatalist");
    // 드래그 해온 아이템들에 대한 정보
    QDataStream stream(&encoded, QIODevice::ReadOnly);

    while (!stream.atEnd())
    {
        int row, col;
        QMap<int,  QVariant> roleDataMap;
        QTreeWidgetItem* pCloneItem = new QTreeWidgetItem;

        stream >> row >> col >> roleDataMap;
        for ( auto [role, value] : roleDataMap.asKeyValueRange() ) {
            pCloneItem->setData(col, role, value);
        }

        QModelIndex index = indexAt(event->position().toPoint());
        // 복사할 위치
        auto pItem = itemAt(event->position().toPoint());

        auto dropIndicator = dropIndicatorPosition();       // 아이템이 삽입되는 위치

        switch ( dropIndicator ) {
        case OnItem:            // 아이템으로
            pItem->addChild(pCloneItem);
            break;
        case AboveItem:         // 아이템 상단으로
            if ( !pItem->parent()) {
                insertTopLevelItem(index.row(), pCloneItem);
            } else {
                auto parent = pItem->parent();
                parent->insertChild(index.row(), pCloneItem);
            }
            break;
        case BelowItem:         // 아이템 하단으로
            if ( !pItem->parent()) {
                insertTopLevelItem(index.row()+1, pCloneItem);
            } else {
                auto parent = pItem->parent();
                parent->insertChild(index.row()+1, pCloneItem);
            }
            break;
        case OnViewport:        // 그냥 트리 위젯 공간 안
            insertTopLevelItem(topLevelItemCount(), pCloneItem);
            break;
        }

    } // https://stackoverflow.com/questions/1723989/how-to-decode-application-x-qabstractitemmodeldatalist-in-qt-for-drag-and-drop
}
class dragDropTree : public QTreeWidget {
    Q_OBJECT

protected:
    void dropEvent(QDropEvent *event);
};

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

    dragDropTree* pOrigin;
};

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    pOrigin = new dragDropTree;
    pOrigin->setObjectName("Origin");

    pOrigin->setSelectionMode(QAbstractItemView::SingleSelection);
    pOrigin->setDragEnabled(true);
    pOrigin->viewport()->setAcceptDrops(true);
    pOrigin->setDropIndicatorShown(true);

    auto item = new QTreeWidgetItem(pOrigin);
    item->setText(0, "text");

    item = new QTreeWidgetItem(pOrigin);
    item->setText(0, "text2");

    ui->horizontalLayout->addWidget(pOrigin);
}

MainWindow::~MainWindow()
{
    delete ui;
}
728x90
728x90

PCAN ISOTP API Write 호출 시 비동기 처리가 되기 때문에 loop back을 이용해서 제대로 모든 데이터가 처리가 되었는지 확인할 수 있습니다.

void PCANISOTP::Write(const char *pData, int nSize)
{
    cantp_msg message = {};
    cantp_can_msgtype msgtype = PCANTP_CAN_MSGTYPE_EXTENDED | PCANTP_CAN_MSGTYPE_FD | PCANTP_CAN_MSGTYPE_BRS;

    cantp_netaddrinfo isotp_nai = {};
    isotp_nai.source_addr = N_SA;
    isotp_nai.target_addr = N_TA_PHYS;
    isotp_nai.target_type = PCANTP_ISOTP_ADDRESSING_PHYSICAL;
    isotp_nai.msgtype = PCANTP_ISOTP_MSGTYPE_DIAGNOSTIC;
    isotp_nai.format = PCANTP_ISOTP_FORMAT_NORMAL;

    status = CANTP_MsgDataAlloc_2016(&message, PCANTP_MSGTYPE_ISOTP);
    if (CANTP_StatusIsOk_2016(status)) {
        // initialize ISOTP message
        status = CANTP_MsgDataInit_2016(&message, PCANTP_MAPPING_FLOW_CTRL_NONE, msgtype, nSize, pData, &isotp_nai);
        if (CANTP_StatusIsOk_2016(status)) {
            // write message
            do {
                status = CANTP_Write_2016(channel, &message);
                if (CANTP_StatusIsOk_2016(status)) {
                    debug(QString::asprintf("Successfully queued ISOTP message: Length %i (sts=0x%04X).", nSize, (int)status));
                }
                else {
                    debug(QString::asprintf("Failed to write ISOTP message: Length %i (sts=0x%04X).", nSize, (int)status));
                    return;
                }

            } while ( !checkWriteState(message) );
            emit emit_sendISOTPPacketData(enTx, message);
        }
        else {
            debug(QString::asprintf("Failed to initialize ISOTP message: Length %i (sts=0x%04X).", nSize, (int)status));
            return;
        }
        // release message

        if (!CANTP_StatusIsOk_2016(status)) {
            debug(QString::asprintf("Failed to deallocate message (sts=0x%04X).", status));
            return;
        }
    }
    else {
        debug(QString::asprintf("Failed to allocate message (sts=0x%04X).", status));
        return;
    }
}

bool PCANISOTP::checkWriteState(cantp_msg& rx_msg)
{
    cantp_msg loopback_msg;

    bool bRes = false;
    cantp_msgprogress progress;
    cantp_status result;
    memset(&loopback_msg, 0, sizeof(loopback_msg));
    memset(&progress, 0, sizeof(progress));

    HANDLE receive_event = NULL;

    status = CANTP_GetValue_2016(channel, PCANTP_PARAMETER_RECEIVE_EVENT, &receive_event, sizeof(receive_event));
    if (!CANTP_StatusIsOk_2016(status) || receive_event == NULL) {
        debug(QString("Failed to make loop back receive event"));
        return false;
    }

    do {
        int nResult = WaitForSingleObject(receive_event, 10);
        if (nResult == WAIT_OBJECT_0) {
            status = CANTP_MsgDataAlloc_2016(&loopback_msg, PCANTP_MSGTYPE_NONE);
            // Read transmission confirmation.

            result = CANTP_Read_2016(channel, &loopback_msg, 0, PCANTP_MSGTYPE_ANY);
            if (CANTP_StatusIsOk_2016(result, PCANTP_STATUS_OK, false)) {
                if (((PCANTP_MSGTYPE_ISOTP & loopback_msg.type) == PCANTP_MSGTYPE_ISOTP) && (loopback_msg.msgdata.isotp->flags == PCANTP_MSGFLAG_LOOPBACK)
                    && ((loopback_msg.msgdata.isotp->netaddrinfo.msgtype & PCANTP_ISOTP_MSGTYPE_FLAG_INDICATION_TX) == PCANTP_ISOTP_MSGTYPE_FLAG_INDICATION_TX)) {
                    // The message is being received, wait and show progress
                    do {
                        result = CANTP_GetMsgProgress_2016(channel, &loopback_msg, PCANTP_MSGDIRECTION_RX, &progress);
                    } while (progress.state == PCANTP_MSGPROGRESS_STATE_PROCESSING);

                    if ( CANTP_MsgEqual_2016(&rx_msg, &loopback_msg, true) ) {
                        qDebug() << "Equal";
                        bRes = true;
                    }
                }
            }
        }
    } while ( !bRes );
    CANTP_MsgDataFree_2016(&loopback_msg);

    return bRes;
}

checkWriteState 함수를 통해 loop back read를 진행 후 전송한 cantp_msg를 CANTP_MsgEqual_2016 호출로 비교해서 같다면 전송이 성공했다는 방식으로 처리를 할 수 있습니다.

728x90
728x90

PCAN ISOTP API를 이용해서 데이터를 수신할 때 단순히 Read하는 것이 아니라 ISOTP Read 과정이 모두 완료 되었는지 확인하고 Read를 진행할 수 있는 방법이 있습니다.

int PCANISOTP::read_segmented_message(cantp_handle channel, int &nbMsgRead)
{
    cantp_msg rx_msg;
    cantp_msgprogress progress;     // ISO-TP 메시지가 다 들어왔는지 체크하기 위함

    memset(&progress, 0, sizeof(cantp_msgprogress));
    status = CANTP_Read_2016(channel, &rx_msg, NULL, PCANTP_MSGTYPE_NONE);

    if (CANTP_StatusIsOk_2016(status, PCANTP_STATUS_OK, false) ) {
        if (rx_msg.msgdata.any->flags & PCANTP_MSGFLAG_LOOPBACK) {
            return -1;
        } // loopback message의 경우 pass

        if (((PCANTP_MSGTYPE_ISOTP & rx_msg.type) == PCANTP_MSGTYPE_ISOTP)
            && ((rx_msg.msgdata.isotp->netaddrinfo.msgtype & PCANTP_ISOTP_MSGTYPE_FLAG_INDICATION_RX) == PCANTP_ISOTP_MSGTYPE_FLAG_INDICATION_RX)) {
            // The message is being received, wait and show progress
            do {
                status = CANTP_GetMsgProgress_2016(channel, &rx_msg, PCANTP_MSGDIRECTION_RX, &progress);
            } while (progress.state == PCANTP_MSGPROGRESS_STATE_PROCESSING);

            // The message is received, read it
            status = CANTP_Read_2016(channel, &rx_msg, NULL, PCANTP_MSGTYPE_NONE);
        } // 잔여 데이터가 있을 경우 계속 체크 후 다 들어왔다면 다시 한번 Read
        emit emit_sendISOTPPacketData(enRx, rx_msg);
    }

    return 1;
}

위 코드에서 cantp_msgprogress 구조체를 사용하는데 메시지 처리 상태를 확인할 수 있습니다. 진행 상태, 진행률 등을 볼 수 있으며 이 상태를 체크하면서 while로 state가 PCANTP_MSGPROGRESS_STATE_COMPLETED이 될 때 까지 기다립니다.

728x90

+ Recent posts