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이 될 때 까지 기다립니다.
PEAK CAN ISO-TP API를 사용하면 PC에서도 USB 등을 이용하여 ISO-TP 통신을 사용할 수 있습니다.
이 API를 사용하면서 가장 헷갈리는 부분 중 하나가 Mapping 함수인데 간단히 설명하려고 합니다.
먼저 설명에 앞서 ISO-TP의 사양을 간단히 설명하면 CAN 혹은 CAN-FD의 메시지를 사용해서 통신을 하는 구조입니다. 메시지를 보낼 때 사이즈가 DLC(한번에 보내기로 결정한 데이터 사이즈)보다 크다면 여러 메시지를 주고 받으면서 통신을 진행합니다. 이때 First Frame, ConsecutiveFrame, FlowControl이라는 메시지를 사용하는데 FirstFrame과 Consecutive Frame은 송신 측에서 데이터를 보낼 때 사용하고 FlowControl은 수신측에서 수신 상태를 알려줄 때 사용합니다.
이 CAN Message를 보낼 때 ID를 사용하게 되는데 이 때 Mapping으로 ID를 미리 지정해두게 됩니다.
만약 내가 이 프로그램에서 ISOTP 메시지를 보낼 때 CAN ID를 0x01로 그리고 상대쪽에서 이 메시지에 대한 FlowControl ID가 0x02라면 아래와 같이 등록합니다. format, msgtype, target_type은 상황에 맞게 설정합니다. Target, Source Address는 Normal Format에서는 크게 중요치 않습니다.
m_SendCANID = 0x01;
m_SendFlowID = 0x02;
status = initSendMsgMappings(channel, m_SendCANID, m_SendFlowID);
if ( !CANTP_StatusIsOk_2016(status) ) {
debug(QString::asprintf("Failed to Send initialize mapping"));
return bResult;
} // ISO-TP 주소 매핑
// CAN ID = 내가 쓸 때의 ISOTP CAN ID
// FlowID = 상대방(GUI)에서 응답하는 FlowControl ID
// PCAN-ISO-TP API Usermanual p.265 / ISO-TP Document p.40 참고
cantp_status PCANISOTP::initSendMsgMappings(cantp_handle channel, uint32_t can_id, uint32_t can_id_flow_ctrl)
{
cantp_mapping mapping_phys_tx;
// clean variables (it is common to leave can_tx_dl uninitialized which can lead to invalid 0xCC values)
memset(&mapping_phys_tx, 0, sizeof(mapping_phys_tx));
// configure a mapping to transmit physical message
mapping_phys_tx.can_id = can_id;
mapping_phys_tx.can_id_flow_ctrl = can_id_flow_ctrl;
mapping_phys_tx.can_msgtype = PCANTP_CAN_MSGTYPE_EXTENDED;
mapping_phys_tx.netaddrinfo.format = PCANTP_ISOTP_FORMAT_NORMAL;
mapping_phys_tx.netaddrinfo.msgtype = PCANTP_ISOTP_MSGTYPE_DIAGNOSTIC;
mapping_phys_tx.netaddrinfo.target_type = PCANTP_ISOTP_ADDRESSING_PHYSICAL;
mapping_phys_tx.netaddrinfo.source_addr = N_SA;
mapping_phys_tx.netaddrinfo.target_addr = N_TA_PHYS;
status = CANTP_AddMapping_2016(channel, &mapping_phys_tx);
if (!CANTP_StatusIsOk_2016(status)) {
return status;
}
return status;
}
Write 할 때 msgDataAlloc 후 MsgDataInit을 하는데 이 때 Mapping 할 때 등록했던 각종 정보를 그대로 전달합니다. 전달받은 netaddrinfo 정보를 토대로 Mapping 된 정보에서 CAN, Flow ID를 가져옵니다.
//! [8]
QFuture<QByteArray> Images::download(const QList<QUrl> &urls)
{
//! [8]
//! [9]
DebugMsg("DonwloadStart");
QSharedPointer<QPromise<QByteArray>> promise(new QPromise<QByteArray>());
promise->start();
//! [9]
//! [10]
for (const auto &url : urls) {
DebugMsg("Donwload " + url.toString());
QSharedPointer<QNetworkReply> reply(qnam.get(QNetworkRequest(url)));
replies.push_back(reply);
//! [10]
//! [11]
QtFuture::connect(reply.get(), &QNetworkReply::finished).then([=] {
if (promise->isCanceled()) {
if (!promise->future().isFinished())
promise->finish();
return;
}
if (reply->error() != QNetworkReply::NoError) {
if (!promise->future().isFinished())
throw reply->error();
}
//! [12]
promise->addResult(reply->readAll());
DebugMsg("Get Data");
// Report finished on the last download
if (promise->future().resultCount() == urls.size()) {
DebugMsg("Finish promis");
promise->finish();
}
//! [12]
}).onFailed([promise] (QNetworkReply::NetworkError error) {
promise->setException(std::make_exception_ptr(error));
promise->finish();
}).onFailed([promise] {
const auto ex = std::make_exception_ptr(
std::runtime_error("Unknown error occurred while downloading."));
promise->setException(ex);
promise->finish();
});
}
//! [11]
//! [13]
DebugMsg("Return");
return promise->future();
}
//! [13]
여기서 QPromise와 QFuture을 사용하게 됩니다. C++에서 Promise와 Furture은 같이 사용하게 되는데 Furture(미래)에 데이터를 돌려주겠다는 Promise(약속)이라고 생각하면 됩니다. QPromise의 비동기 처리를 QFurture에 전달하는 방식입니다. QFurtuer의 Connect를 통해 Signal 처리를 하게 되고 혹은 QFurture::waitforFinish를 통해 비동기 처리가 완료될 때까지 기다릴 수도 있습니다.
for문으로 전달받은 Url들을 요청하게 되는데 이에 대한 결과는 QNetworkReply::Finish Signal을 QtFurture::connect로 묶어 처리가 됩니다. 아래 로그를 확인하면 Download 후 바로 Promise가 가지고 있는 QFurture을 Return하게 됩니다. 그 후 요청한 Url에 대한 결과가 도착하고 Url 개수만큼의 완료가 진행 됐다면 QPromise는 종료합니다.
여기서 QFurtherWatcher라는 객체에 QtConcurrent::run의 Return을 넘겨주게 됩니다. QFurtherWatcher는 QFurture에 대한 상태를 체크하고 QFurture가 Singal 상태가 됐을 때 실행할 Slot을 connect 시켜주었습니다.