OVMS3/OVMS.V3/components/vehicle/vehicle_poller_isotp.cpp

572 lines
17 KiB
C++

/*
; Project: Open Vehicle Monitor System
; Date: 14th March 2017
;
; Changes:
; 1.0 Initial release
;
; (C) 2011 Michael Stegen / Stegen Electronics
; (C) 2011-2017 Mark Webb-Johnson
; (C) 2011 Sonny Chen @ EPRO/DX
; (C) 2021 Michael Balzer <dexter@dexters-web.de>
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
; THE SOFTWARE.
*/
#include "ovms_log.h"
static const char *TAG = "vehicle-isotp";
#include <stdio.h>
#include <algorithm>
#include "vehicle.h"
/**
* PollerISOTPStart: start ISO-TP request
*/
void OvmsVehicle::PollerISOTPStart(bool fromTicker)
{
if (m_poll_plcur->rxmoduleid != 0)
{
// send to <moduleid>, listen to response from <rmoduleid>:
m_poll_moduleid_sent = m_poll_plcur->txmoduleid;
m_poll_moduleid_low = m_poll_plcur->rxmoduleid;
m_poll_moduleid_high = m_poll_plcur->rxmoduleid;
}
else
{
// broadcast: send to 0x7df, listen to all responses:
m_poll_moduleid_sent = 0x7df;
m_poll_moduleid_low = 0x7e8;
m_poll_moduleid_high = 0x7ef;
}
ESP_LOGD(TAG, "PollerISOTPStart(%d): send [bus=%d, type=%02X, pid=%X], expecting %03x/%03x-%03x",
fromTicker, m_poll_plcur->pollbus, m_poll_type, m_poll_pid, m_poll_moduleid_sent,
m_poll_moduleid_low, m_poll_moduleid_high);
//
// Assemble ISO-TP single/first frame
//
uint8_t* fr_data; // Frame data address
uint8_t fr_maxlen; // Frame data max length
uint16_t tp_len; // TP payload length including this frame (0…4095)
uint8_t* tp_data; // TP frame data section address
uint8_t tp_datalen; // TP frame data section length (0…7)
const uint8_t* tx_data; // Payload data
uint16_t tx_datalen; // Payload data length
uint16_t tx_datasent; // Payload data length sent with this frame
if (m_poll_plcur->xargs.tag == POLL_TXDATA)
{
tx_data = m_poll_plcur->xargs.data;
tx_datalen = m_poll_plcur->xargs.datalen;
}
else
{
tx_data = m_poll_plcur->args.data;
tx_datalen = m_poll_plcur->args.datalen;
}
CAN_frame_t txframe = {};
txframe.origin = m_poll_bus;
txframe.callback = &m_poll_txcallback;
txframe.FIR.B.DLC = 8;
std::fill_n(txframe.data.u8, sizeof_array(txframe.data.u8), 0x55);
if (m_poll_protocol == ISOTP_EXTFRAME)
txframe.FIR.B.FF = CAN_frame_ext;
else
txframe.FIR.B.FF = CAN_frame_std;
if (m_poll_protocol == ISOTP_EXTADR)
{
txframe.MsgID = m_poll_moduleid_sent >> 8;
txframe.data.u8[0] = m_poll_moduleid_sent & 0xff;
fr_data = &txframe.data.u8[1];
fr_maxlen = 7;
}
else
{
txframe.MsgID = m_poll_moduleid_sent;
fr_data = &txframe.data.u8[0];
fr_maxlen = 8;
}
// Do we need to split this request into multiple frames?
if (POLL_TYPE_HAS_16BIT_PID(m_poll_plcur->type))
tp_len = 3 + tx_datalen;
else if (POLL_TYPE_HAS_8BIT_PID(m_poll_plcur->type))
tp_len = 2 + tx_datalen;
else
tp_len = 1 + tx_datalen;
if (tp_len <= fr_maxlen - 1)
{
fr_data[0] = (ISOTP_FT_SINGLE << 4) + tp_len;
tp_data = &fr_data[1];
tp_datalen = fr_maxlen - 1;
}
else
{
fr_data[0] = (ISOTP_FT_FIRST << 4) + ((tp_len & 0x0f00) >> 8);
fr_data[1] = tp_len & 0xff;
tp_data = &fr_data[2];
tp_datalen = fr_maxlen - 2;
}
// Add TP data:
if (POLL_TYPE_HAS_16BIT_PID(m_poll_plcur->type))
{
tp_data[0] = m_poll_type;
tp_data[1] = m_poll_pid >> 8;
tp_data[2] = m_poll_pid & 0xff;
tx_datasent = LIMIT_MAX(tx_datalen, tp_datalen - 3);
memcpy(&tp_data[3], tx_data, tx_datasent);
}
else if (POLL_TYPE_HAS_8BIT_PID(m_poll_plcur->type))
{
tp_data[0] = m_poll_type;
tp_data[1] = m_poll_pid;
tx_datasent = LIMIT_MAX(tx_datalen, tp_datalen - 2);
memcpy(&tp_data[2], tx_data, tx_datasent);
}
else
{
tp_data[0] = m_poll_type;
tx_datasent = LIMIT_MAX(tx_datalen, tp_datalen - 1);
memcpy(&tp_data[1], tx_data, tx_datasent);
}
m_poll_txmsgid = txframe.MsgID;
m_poll_tx_frame = 0;
m_poll_tx_data = tx_data;
m_poll_tx_offset = tx_datasent;
m_poll_tx_remain = tx_datalen - tx_datasent;
m_poll_ml_frame = 0;
m_poll_ml_offset = 0;
m_poll_ml_remain = 0;
m_poll_wait = 2;
m_poll_bus->Write(&txframe);
}
/**
* PollerISOTPReceive: process ISO-TP poll response frame
*/
bool OvmsVehicle::PollerISOTPReceive(CAN_frame_t* frame, uint32_t msgid)
{
OvmsRecMutexLock lock(&m_poll_mutex);
char *hexdump = NULL;
// After locking the mutex, check again for poll expectance match:
if (!m_poll_wait || !m_poll_plist || frame->origin != m_poll_bus ||
msgid < m_poll_moduleid_low || msgid > m_poll_moduleid_high)
{
ESP_LOGD(TAG, "PollerISOTPReceive[%03X]: dropping expired poll response", msgid);
return false;
}
//
// Get & validate ISO-TP meta data
//
uint8_t* fr_data; // Frame data address
uint8_t fr_maxlen; // Frame data max length
uint8_t tp_frametype; // ISO-TP frame type (0…3)
uint8_t tp_frameindex; // TP cyclic frame index (0…15)
uint16_t tp_len; // TP remaining payload length including this frame (0…4095)
uint8_t* tp_data; // TP frame data section address
uint8_t tp_datalen; // TP frame data section length (0…7)
uint8_t tp_fc_command; // Flow control command (0 = continue, 1 = wait, 2 = abort)
uint8_t tp_fc_framecnt; // Flow control max frame count (0 = unlimited)
uint8_t tp_fc_septime; // Flow control frame separation time
if (m_poll_protocol == ISOTP_EXTADR)
{
fr_data = &frame->data.u8[1];
fr_maxlen = 7;
}
else
{
fr_data = &frame->data.u8[0];
fr_maxlen = 8;
}
tp_frametype = fr_data[0] >> 4;
switch (tp_frametype)
{
case ISOTP_FT_SINGLE:
tp_frameindex = 0;
tp_len = fr_data[0] & 0x0f;
tp_data = &fr_data[1];
tp_datalen = tp_len;
break;
case ISOTP_FT_FIRST:
tp_frameindex = 0;
tp_len = (fr_data[0] & 0x0f) << 8 | fr_data[1];
tp_data = &fr_data[2];
tp_datalen = (tp_len > fr_maxlen-2) ? fr_maxlen-2 : tp_len;
break;
case ISOTP_FT_CONSECUTIVE:
tp_frameindex = fr_data[0] & 0x0f;
tp_len = m_poll_ml_remain;
tp_data = &fr_data[1];
tp_datalen = (tp_len > fr_maxlen-1) ? fr_maxlen-1 : tp_len;
break;
case ISOTP_FT_FLOWCTRL:
tp_fc_command = fr_data[0] & 0x0f;
tp_fc_framecnt = fr_data[1];
tp_fc_septime = fr_data[2];
break;
default:
{
// This is most likely an indication there is a non ISO-TP device sending
// in our expected RX ID range, so we log the frame and abort:
FormatHexDump(&hexdump, (const char*)frame->data.u8, 8, 8);
ESP_LOGW(TAG, "PollerISOTPReceive[%03X]: ignoring unknown/invalid ISO TP frame: %s",
msgid, hexdump ? hexdump : "-");
if (hexdump) free(hexdump);
return false;
}
}
// Handle TX flow control:
if (tp_frametype == ISOTP_FT_FLOWCTRL)
{
if (tp_fc_command > 2 || m_poll_tx_remain == 0)
{
FormatHexDump(&hexdump, (const char*)frame->data.u8, 8, 8);
ESP_LOGW(TAG, "PollerISOTPReceive[%03X]: ignoring unexpected/invalid ISO TP flow control frame: %s",
msgid, hexdump ? hexdump : "-");
if (hexdump) free(hexdump);
return false;
}
if (tp_fc_command == 1)
{
// add some wait time:
m_poll_wait++;
}
else if (tp_fc_command == 2)
{
// abort TX:
m_poll_tx_remain = 0;
// (but still wait for response)
}
else
{
// continue TX:
CAN_frame_t tx_frame = {};
uint8_t* tx_data;
uint8_t tx_datalen;
uint8_t tx_datasent;
uint32_t txid;
tx_frame.origin = frame->origin;
tx_frame.FIR.B.DLC = 8;
if (m_poll_protocol == ISOTP_EXTFRAME)
tx_frame.FIR.B.FF = CAN_frame_ext;
else
tx_frame.FIR.B.FF = CAN_frame_std;
if (m_poll_moduleid_sent == 0x7df)
{
// broadcast request: derive module ID from response ID:
// (Note: this only works for the SAE standard ID scheme)
txid = frame->MsgID - 8;
}
else
{
// use known module ID:
txid = m_poll_moduleid_sent;
}
if (m_poll_protocol == ISOTP_EXTADR)
{
tx_frame.MsgID = txid >> 8;
tx_frame.data.u8[0] = txid & 0xff;
tx_data = &tx_frame.data.u8[1];
tx_datalen = 6;
}
else
{
tx_frame.MsgID = txid;
tx_data = &tx_frame.data.u8[0];
tx_datalen = 7;
}
// Send next chunk of frames:
while (m_poll_tx_remain > 0)
{
++m_poll_tx_frame;
tx_data[0] = (ISOTP_FT_CONSECUTIVE << 4) + (m_poll_tx_frame & 0x0f);
tx_datasent = LIMIT_MAX(m_poll_tx_remain, tx_datalen);
memcpy(&tx_data[1], m_poll_tx_data+m_poll_tx_offset, tx_datasent);
if (tx_datasent < tx_datalen)
memset(&tx_data[1+tx_datasent], 0x55, tx_datalen-tx_datasent);
tx_frame.Write();
m_poll_tx_offset += tx_datasent;
m_poll_tx_remain -= tx_datasent;
if (m_poll_tx_remain == 0)
break;
if (tp_fc_framecnt > 0 && --tp_fc_framecnt == 0)
break;
if (tp_fc_septime > 0)
{
if (tp_fc_septime <= 127)
usleep(tp_fc_septime * 1000);
else if (tp_fc_septime > 240)
usleep((tp_fc_septime - 240) * 100);
}
}
if (m_poll_tx_remain > 0)
m_poll_wait = 2;
}
return true;
} // if (tp_frametype == ISOTP_FT_FLOWCTRL)
// Check frame index:
if (tp_frametype == ISOTP_FT_CONSECUTIVE)
{
// Note: we tolerate an index less than the expected one, as some devices
// begin counting at the first consecutive frame
if (m_poll_ml_remain == 0 || tp_frameindex > (m_poll_ml_frame & 0x0f))
{
FormatHexDump(&hexdump, (const char*)frame->data.u8, 8, 8);
ESP_LOGW(TAG, "PollerISOTPReceive[%03X]: unexpected/out of sequence ISO TP frame (%d vs %d), aborting poll %02X(%X): %s",
msgid, tp_frameindex, m_poll_ml_frame & 0x0f, m_poll_type, m_poll_pid,
hexdump ? hexdump : "-");
if (hexdump) free(hexdump);
m_poll_moduleid_low = m_poll_moduleid_high = 0; // ignore further frames
m_poll_wait = 2; // give the bus time to let remaining frames pass
return true;
}
}
//
// Get & validate OBD/UDS meta data
//
uint8_t response_type = 0; // OBD/UDS response type tag (expected: 0x40 + request type)
uint16_t response_pid = 0; // OBD/UDS response PID (expected: request PID)
uint8_t* response_data = NULL; // OBD/UDS frame payload address
uint16_t response_datalen = 0; // OBD/UDS frame payload length (0…7)
uint8_t error_type = 0; // OBD/UDS error response service type (expected: request type)
uint8_t error_code = 0; // OBD/UDS error response code (see ISO 14229 Annex A.1)
if (tp_frametype == ISOTP_FT_CONSECUTIVE)
{
response_type = 0x40+m_poll_type;
response_pid = m_poll_pid;
response_data = tp_data;
response_datalen = tp_datalen;
}
else // ISOTP_FT_FIRST || ISOTP_FT_SINGLE
{
response_type = tp_data[0];
if (response_type == UDS_RESP_TYPE_NRC)
{
error_type = tp_data[1];
error_code = tp_data[2];
}
else if (POLL_TYPE_HAS_16BIT_PID(response_type-0x40))
{
response_pid = tp_data[1] << 8 | tp_data[2];
response_data = &tp_data[3];
response_datalen = tp_datalen - 3;
}
else if (POLL_TYPE_HAS_8BIT_PID(response_type-0x40))
{
response_pid = tp_data[1];
response_data = &tp_data[2];
response_datalen = tp_datalen - 2;
}
else
{
response_pid = m_poll_pid;
response_data = &tp_data[1];
response_datalen = tp_datalen - 1;
}
}
//
// Process OBD/UDS payload
//
if (response_type == UDS_RESP_TYPE_NRC && error_type == m_poll_type)
{
// Negative Response Code:
if (error_code == UDS_RESP_NRC_RCRRP)
{
// Info: requestCorrectlyReceived-ResponsePending (server busy processing the request)
ESP_LOGD(TAG, "PollerISOTPReceive[%03X]: got OBD/UDS info %02X(%X) code=%02X (pending)",
msgid, m_poll_type, m_poll_pid, error_code);
// add some wait time:
m_poll_wait++;
return true;
}
else
{
// Error: forward to application:
ESP_LOGD(TAG, "PollerISOTPReceive[%03X]: process OBD/UDS error %02X(%X) code=%02X",
msgid, m_poll_type, m_poll_pid, error_code);
// Running single poll?
if (m_poll_single_rxbuf)
{
m_poll_single_rxerr = error_code;
m_poll_single_rxbuf = NULL;
m_poll_single_rxdone.Give();
}
else
{
IncomingPollError(frame->origin, m_poll_type, m_poll_pid, error_code);
}
// abort:
m_poll_ml_remain = 0;
}
}
else if (response_type == 0x40+m_poll_type && response_pid == m_poll_pid)
{
// Normal matching poll response, forward to application:
m_poll_ml_remain = tp_len - tp_datalen;
ESP_LOGD(TAG, "PollerISOTPReceive[%03X]: process OBD/UDS response %02X(%X) frm=%u len=%u off=%u rem=%u",
msgid, m_poll_type, m_poll_pid,
m_poll_ml_frame, response_datalen, m_poll_ml_offset, m_poll_ml_remain);
// Running single poll?
if (m_poll_single_rxbuf)
{
if (m_poll_ml_frame == 0)
{
m_poll_single_rxbuf->clear();
m_poll_single_rxbuf->reserve(response_datalen + m_poll_ml_remain);
}
m_poll_single_rxbuf->append((char*)response_data, response_datalen);
if (m_poll_ml_remain == 0)
{
m_poll_single_rxerr = 0;
m_poll_single_rxbuf = NULL;
m_poll_single_rxdone.Give();
}
}
else
{
IncomingPollReply(frame->origin, m_poll_type, m_poll_pid, response_data, response_datalen, m_poll_ml_remain);
}
}
else
{
// This is most likely a late response to a previous poll, log & skip:
FormatHexDump(&hexdump, (const char*)frame->data.u8, 8, 8);
ESP_LOGW(TAG, "PollerISOTPReceive[%03X]: OBD/UDS response type/PID mismatch, got %02X(%X) vs %02X(%X) => ignoring: %s",
msgid, response_type, response_pid, 0x40+m_poll_type, m_poll_pid, hexdump ? hexdump : "-");
if (hexdump) free(hexdump);
return false;
}
// Do we expect more data?
if (m_poll_ml_remain)
{
if (tp_frametype == ISOTP_FT_FIRST)
{
// First frame; send flow control frame:
CAN_frame_t txframe;
uint8_t* txdata;
uint32_t txid;
memset(&txframe,0,sizeof(txframe));
txframe.origin = frame->origin;
txframe.FIR.B.DLC = 8;
if (m_poll_protocol == ISOTP_EXTFRAME)
txframe.FIR.B.FF = CAN_frame_ext;
else
txframe.FIR.B.FF = CAN_frame_std;
if (m_poll_moduleid_sent == 0x7df)
{
// broadcast request: derive module ID from response ID:
// (Note: this only works for the SAE standard ID scheme)
txid = frame->MsgID - 8;
}
else
{
// use known module ID:
txid = m_poll_moduleid_sent;
}
if (m_poll_protocol == ISOTP_EXTADR)
{
txframe.MsgID = txid >> 8;
txframe.data.u8[0] = txid & 0xff;
txdata = &txframe.data.u8[1];
}
else
{
txframe.MsgID = txid;
txdata = &txframe.data.u8[0];
}
txdata[0] = 0x30; // flow control frame type
txdata[1] = 0x00; // request all frames available
txdata[2] = m_poll_fc_septime; // with configured separation timing (default 25 ms)
txframe.Write();
m_poll_ml_frame = 1;
}
else
{
m_poll_ml_frame++;
}
m_poll_ml_offset += response_datalen; // next frame application payload offset
m_poll_wait = 2;
}
else
{
// Request response complete:
m_poll_wait = 0;
}
// Immediately send the next poll for this tick if…
// - we are not waiting for another frame
// - the poll was no broadcast (with potential further responses from other devices)
// - poll throttling is unlimited or limit isn't reached yet
if (m_poll_wait == 0 &&
m_poll_moduleid_sent != 0x7df &&
(!m_poll_sequence_max || m_poll_sequence_cnt < m_poll_sequence_max))
{
PollerSend(false);
}
return true;
}