840 lines
27 KiB
C++
840 lines
27 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-vwtp";
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <algorithm>
|
||
|
#include "vehicle.h"
|
||
|
|
||
|
|
||
|
/**
|
||
|
* PollerVWTPStart: start next VW-TP request (internal method)
|
||
|
*/
|
||
|
void OvmsVehicle::PollerVWTPStart(bool fromTicker)
|
||
|
{
|
||
|
m_poll_vwtp.lastused = monotonictime;
|
||
|
|
||
|
// Check connection state:
|
||
|
if (m_poll_vwtp.bus != m_poll_bus ||
|
||
|
m_poll_vwtp.baseid != m_poll_entry.txmoduleid ||
|
||
|
m_poll_vwtp.moduleid != m_poll_entry.rxmoduleid)
|
||
|
{
|
||
|
// close or reconnect channel:
|
||
|
if (m_poll_vwtp.state != VWTP_Closed)
|
||
|
PollerVWTPEnter(VWTP_ChannelClose);
|
||
|
else if (m_poll_entry.rxmoduleid != 0)
|
||
|
PollerVWTPEnter(VWTP_ChannelSetup);
|
||
|
else if (m_poll_single_rxbuf)
|
||
|
{
|
||
|
m_poll_single_rxbuf->clear();
|
||
|
m_poll_single_rxbuf = NULL;
|
||
|
m_poll_single_rxerr = POLLSINGLE_OK;
|
||
|
m_poll_single_rxdone.Give();
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Check communication state:
|
||
|
if (m_poll_vwtp.state < VWTP_Idle)
|
||
|
{
|
||
|
// previous channel setup/shutdown hasn't finished, start over:
|
||
|
PollerVWTPEnter(VWTP_ChannelSetup);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// abort transfer in progress if any:
|
||
|
if (m_poll_vwtp.state == VWTP_Transmit)
|
||
|
{
|
||
|
PollerVWTPEnter(VWTP_AbortXfer);
|
||
|
}
|
||
|
else if (m_poll_vwtp.state == VWTP_Receive)
|
||
|
{
|
||
|
PollerVWTPEnter(VWTP_AbortXfer);
|
||
|
usleep(m_poll_vwtp.septime * m_poll_vwtp.blocksize);
|
||
|
}
|
||
|
// start new poll:
|
||
|
PollerVWTPEnter(VWTP_StartPoll);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* PollerVWTPEnter: channel state transition (internal method)
|
||
|
*/
|
||
|
void OvmsVehicle::PollerVWTPEnter(vwtp_channelstate_t state)
|
||
|
{
|
||
|
CAN_frame_t txframe = {};
|
||
|
txframe.callback = &m_poll_txcallback;
|
||
|
txframe.FIR.B.FF = CAN_frame_std;
|
||
|
|
||
|
if (m_poll_vwtp.state == state) return;
|
||
|
|
||
|
switch (state)
|
||
|
{
|
||
|
case VWTP_ChannelSetup:
|
||
|
{
|
||
|
// Open TP20 channel for current polling entry:
|
||
|
if (m_poll_protocol != VWTP_20)
|
||
|
{
|
||
|
ESP_LOGD(TAG, "PollerVWTPEnter/ChannelSetup: m_poll_protocol mismatch, abort");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_poll_vwtp.bus = m_poll_bus;
|
||
|
m_poll_vwtp.baseid = m_poll_entry.txmoduleid;
|
||
|
m_poll_vwtp.moduleid = m_poll_entry.rxmoduleid;
|
||
|
m_poll_vwtp.txid = m_poll_vwtp.baseid;
|
||
|
m_poll_vwtp.rxid = m_poll_vwtp.baseid + m_poll_vwtp.moduleid;
|
||
|
|
||
|
// txid & rxid will be changed by the setup response frame, we offer a standard rxid
|
||
|
// matching observed client behaviour with baseid=0x200 → rxid=0x300:
|
||
|
uint16_t offer_rxid = m_poll_vwtp.baseid + 0x100;
|
||
|
|
||
|
ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: channel setup request bus=%d txid=%03X rxid=%03X",
|
||
|
m_poll_vwtp.moduleid, m_poll_entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid);
|
||
|
|
||
|
txframe.MsgID = m_poll_vwtp.txid;
|
||
|
txframe.FIR.B.DLC = 7;
|
||
|
txframe.data.u8[0] = m_poll_vwtp.moduleid;
|
||
|
txframe.data.u8[1] = 0xC0; // setup request
|
||
|
txframe.data.u8[2] = 0x00;
|
||
|
txframe.data.u8[3] = 0x10;
|
||
|
txframe.data.u8[4] = offer_rxid & 0xff;
|
||
|
txframe.data.u8[5] = offer_rxid >> 8;
|
||
|
txframe.data.u8[6] = 0x01;
|
||
|
|
||
|
m_poll_txmsgid = m_poll_vwtp.txid;
|
||
|
m_poll_moduleid_sent = m_poll_vwtp.txid;
|
||
|
m_poll_moduleid_low = m_poll_vwtp.rxid;
|
||
|
m_poll_moduleid_high = m_poll_vwtp.rxid;
|
||
|
|
||
|
m_poll_wait = 2;
|
||
|
m_poll_vwtp.state = VWTP_ChannelSetup;
|
||
|
m_poll_vwtp.lastused = monotonictime;
|
||
|
m_poll_vwtp.bus->Write(&txframe);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case VWTP_ChannelParams:
|
||
|
{
|
||
|
ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: channel params request bus=%d txid=%03X rxid=%03X",
|
||
|
m_poll_vwtp.moduleid, m_poll_entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid);
|
||
|
|
||
|
txframe.MsgID = m_poll_vwtp.txid;
|
||
|
txframe.FIR.B.DLC = 6;
|
||
|
txframe.data.u8[0] = 0xA0; // params request
|
||
|
txframe.data.u8[1] = 0x0F; // block size: 15 frames
|
||
|
txframe.data.u8[2] = 0x8A; // time to wait for ACK: 10ms x 10 = 100 ms
|
||
|
txframe.data.u8[3] = 0xFF; // always ff
|
||
|
txframe.data.u8[4] = 0x0A; // interval between two packets: 0.1ms x 10 = 1 ms
|
||
|
txframe.data.u8[5] = 0xFF; // always ff
|
||
|
|
||
|
m_poll_txmsgid = m_poll_vwtp.txid;
|
||
|
m_poll_moduleid_sent = m_poll_vwtp.txid;
|
||
|
m_poll_moduleid_low = m_poll_vwtp.rxid;
|
||
|
m_poll_moduleid_high = m_poll_vwtp.rxid;
|
||
|
m_poll_wait = 2;
|
||
|
|
||
|
m_poll_vwtp.state = VWTP_ChannelParams;
|
||
|
m_poll_vwtp.bus->Write(&txframe);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case VWTP_ChannelClose:
|
||
|
{
|
||
|
ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: close channel bus=%d txid=%03X rxid=%03X",
|
||
|
m_poll_vwtp.moduleid, m_poll_entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid);
|
||
|
|
||
|
txframe.MsgID = m_poll_vwtp.txid;
|
||
|
txframe.FIR.B.DLC = 1;
|
||
|
txframe.data.u8[0] = 0xA8; // close request
|
||
|
|
||
|
m_poll_txmsgid = m_poll_vwtp.txid;
|
||
|
m_poll_moduleid_sent = m_poll_vwtp.txid;
|
||
|
m_poll_moduleid_low = m_poll_vwtp.rxid;
|
||
|
m_poll_moduleid_high = m_poll_vwtp.rxid;
|
||
|
m_poll_wait = 2;
|
||
|
|
||
|
m_poll_vwtp.state = VWTP_ChannelClose;
|
||
|
m_poll_vwtp.bus->Write(&txframe);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case VWTP_Closed:
|
||
|
{
|
||
|
ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: channel closed bus=%d txid=%03X rxid=%03X",
|
||
|
m_poll_vwtp.moduleid, m_poll_entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid);
|
||
|
m_poll_vwtp = {};
|
||
|
m_poll_vwtp.state = VWTP_Closed;
|
||
|
m_poll_wait = 0;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case VWTP_Idle:
|
||
|
{
|
||
|
ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: idle bus=%d txid=%03X rxid=%03X",
|
||
|
m_poll_vwtp.moduleid, m_poll_entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid);
|
||
|
m_poll_vwtp.state = VWTP_Idle;
|
||
|
m_poll_vwtp.lastused = monotonictime;
|
||
|
m_poll_wait = 0;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case VWTP_StartPoll:
|
||
|
{
|
||
|
ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: start poll type=%02X pid=%X",
|
||
|
m_poll_vwtp.moduleid, m_poll_entry.type, m_poll_entry.pid);
|
||
|
|
||
|
if (m_poll_entry.xargs.tag == POLL_TXDATA)
|
||
|
{
|
||
|
m_poll_tx_data = m_poll_entry.xargs.data;
|
||
|
m_poll_tx_remain = m_poll_entry.xargs.datalen;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_poll_tx_data = m_poll_entry.args.data;
|
||
|
m_poll_tx_remain = m_poll_entry.args.datalen;
|
||
|
}
|
||
|
m_poll_tx_frame = 0;
|
||
|
m_poll_tx_offset = 0;
|
||
|
|
||
|
m_poll_ml_frame = 0;
|
||
|
m_poll_ml_offset = 0;
|
||
|
m_poll_ml_remain = 0;
|
||
|
|
||
|
// fall through to VWTP_Transmit
|
||
|
}
|
||
|
case VWTP_Transmit:
|
||
|
{
|
||
|
ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: transmit frame=%u remain=%u",
|
||
|
m_poll_vwtp.moduleid, m_poll_tx_frame, m_poll_tx_remain);
|
||
|
|
||
|
// Transmit next block of frames:
|
||
|
txframe.MsgID = m_poll_vwtp.txid;
|
||
|
int i;
|
||
|
uint8_t opcode;
|
||
|
for (int block = 1; block <= m_poll_vwtp.blocksize; block++)
|
||
|
{
|
||
|
i = 1;
|
||
|
if (m_poll_tx_frame == 0)
|
||
|
{
|
||
|
// First frame:
|
||
|
uint16_t txlen = m_poll_tx_remain + 1;
|
||
|
txframe.data.u8[3] = m_poll_entry.type;
|
||
|
if (POLL_TYPE_HAS_16BIT_PID(m_poll_entry.type))
|
||
|
{
|
||
|
txframe.data.u8[4] = m_poll_entry.pid >> 8;
|
||
|
txframe.data.u8[5] = m_poll_entry.pid & 0xff;
|
||
|
txlen += 2;
|
||
|
i = 6;
|
||
|
}
|
||
|
else if (POLL_TYPE_HAS_8BIT_PID(m_poll_entry.type))
|
||
|
{
|
||
|
txframe.data.u8[4] = m_poll_entry.pid & 0xff;
|
||
|
txlen += 1;
|
||
|
i = 5;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
i = 4;
|
||
|
}
|
||
|
txframe.data.u8[1] = txlen >> 8;
|
||
|
txframe.data.u8[2] = txlen & 0xff;
|
||
|
}
|
||
|
|
||
|
while (i < 8 && m_poll_tx_remain > 0)
|
||
|
{
|
||
|
txframe.data.u8[i++] = m_poll_tx_data[m_poll_tx_offset++];
|
||
|
m_poll_tx_remain--;
|
||
|
}
|
||
|
txframe.FIR.B.DLC = i;
|
||
|
|
||
|
if (m_poll_tx_remain == 0)
|
||
|
opcode = 0x10; // last packet, waiting for ACK
|
||
|
else if (block < m_poll_vwtp.blocksize)
|
||
|
opcode = 0x20; // more packets following in this block
|
||
|
else
|
||
|
opcode = 0x00; // more packets following, waiting for ACK
|
||
|
txframe.data.u8[0] = opcode | (m_poll_vwtp.txseqnr & 0x0f);
|
||
|
|
||
|
if (m_poll_vwtp.txseqnr > 0)
|
||
|
usleep(m_poll_vwtp.septime);
|
||
|
|
||
|
m_poll_vwtp.txseqnr++;
|
||
|
m_poll_tx_frame++;
|
||
|
m_poll_vwtp.bus->Write(&txframe);
|
||
|
|
||
|
if (m_poll_tx_remain == 0)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
m_poll_txmsgid = m_poll_vwtp.txid;
|
||
|
m_poll_moduleid_sent = m_poll_vwtp.txid;
|
||
|
m_poll_moduleid_low = m_poll_vwtp.rxid;
|
||
|
m_poll_moduleid_high = m_poll_vwtp.rxid;
|
||
|
m_poll_wait = 2;
|
||
|
m_poll_vwtp.state = VWTP_Transmit;
|
||
|
m_poll_vwtp.lastused = monotonictime;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case VWTP_Receive:
|
||
|
{
|
||
|
ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: receive frame=%u remain=%u",
|
||
|
m_poll_vwtp.moduleid, m_poll_ml_frame, m_poll_ml_remain);
|
||
|
m_poll_vwtp.state = VWTP_Receive;
|
||
|
m_poll_vwtp.lastused = monotonictime;
|
||
|
m_poll_wait = 2;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case VWTP_AbortXfer:
|
||
|
{
|
||
|
ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: abort xfer",
|
||
|
m_poll_vwtp.moduleid);
|
||
|
|
||
|
txframe.MsgID = m_poll_vwtp.txid;
|
||
|
txframe.FIR.B.DLC = 1;
|
||
|
if (m_poll_vwtp.state == VWTP_Receive)
|
||
|
{
|
||
|
txframe.data.u8[0] = 0x90 | (m_poll_vwtp.rxseqnr & 0x0f); // ACK, stop sending
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
txframe.data.u8[0] = 0x30 | (m_poll_vwtp.txseqnr & 0x0f); // last frame, abort
|
||
|
m_poll_vwtp.txseqnr++;
|
||
|
}
|
||
|
|
||
|
m_poll_txmsgid = m_poll_vwtp.txid;
|
||
|
m_poll_moduleid_sent = m_poll_vwtp.txid;
|
||
|
m_poll_moduleid_low = m_poll_vwtp.rxid;
|
||
|
m_poll_moduleid_high = m_poll_vwtp.rxid;
|
||
|
|
||
|
m_poll_tx_remain = 0;
|
||
|
m_poll_ml_remain = 0;
|
||
|
m_poll_vwtp.state = VWTP_Idle;
|
||
|
m_poll_vwtp.lastused = monotonictime;
|
||
|
m_poll_wait = 0;
|
||
|
m_poll_vwtp.bus->Write(&txframe);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: idle bus=%d txid=%03X rxid=%03X",
|
||
|
m_poll_vwtp.moduleid, m_poll_entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid);
|
||
|
m_poll_vwtp.state = VWTP_Idle;
|
||
|
m_poll_wait = 0;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* PollerVWTPTxCallback: CAN transmission result (internal)
|
||
|
*/
|
||
|
void OvmsVehicle::PollerVWTPTxCallback(const CAN_frame_t* frame, bool success)
|
||
|
{
|
||
|
if (!success)
|
||
|
{
|
||
|
// Todo: try to recover by repeating the transmission (unless CAN is offline)
|
||
|
// -- this needs a higher frequency ticker, e.g. 100 ms
|
||
|
// For now assume channel is terminated:
|
||
|
PollerVWTPEnter(VWTP_Closed);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* PollerVWTPReceive: process VW-TP frame received (internal)
|
||
|
*/
|
||
|
bool OvmsVehicle::PollerVWTPReceive(CAN_frame_t* frame, uint32_t msgid)
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&m_poll_mutex);
|
||
|
|
||
|
// Log utility:
|
||
|
auto logFrameDump = [=](const char* msg)
|
||
|
{
|
||
|
char *hexdump = NULL;
|
||
|
FormatHexDump(&hexdump, (const char*)frame->data.u8, frame->FIR.B.DLC, frame->FIR.B.DLC);
|
||
|
ESP_LOGW(TAG, "PollerVWTPReceive[%02X]: %s: %s", m_poll_vwtp.moduleid, msg, hexdump ? hexdump : "-");
|
||
|
if (hexdump) free(hexdump);
|
||
|
};
|
||
|
|
||
|
// Send channel keepalive response (0xA3 response):
|
||
|
auto sendPong = [=]()
|
||
|
{
|
||
|
CAN_frame_t txframe = {};
|
||
|
txframe.callback = &m_poll_txcallback;
|
||
|
txframe.FIR.B.FF = CAN_frame_std;
|
||
|
txframe.MsgID = m_poll_vwtp.txid;
|
||
|
txframe.FIR.B.DLC = 6;
|
||
|
txframe.data.u8[0] = 0xA1; // params response
|
||
|
txframe.data.u8[1] = 0x0F; // block size: 15 frames
|
||
|
txframe.data.u8[2] = 0x8A; // time to wait for ACK: 10ms x 10 = 100 ms
|
||
|
txframe.data.u8[3] = 0xFF; // always ff
|
||
|
txframe.data.u8[4] = 0x0A; // interval between two packets: 0.1ms x 10 = 1 ms
|
||
|
txframe.data.u8[5] = 0xFF; // always ff
|
||
|
m_poll_vwtp.bus->Write(&txframe);
|
||
|
};
|
||
|
|
||
|
// Send ACK:
|
||
|
auto sendAck = [=]()
|
||
|
{
|
||
|
CAN_frame_t txframe = {};
|
||
|
txframe.callback = &m_poll_txcallback;
|
||
|
txframe.FIR.B.FF = CAN_frame_std;
|
||
|
txframe.MsgID = m_poll_vwtp.txid;
|
||
|
txframe.FIR.B.DLC = 1;
|
||
|
txframe.data.u8[0] = 0xB0 | (m_poll_vwtp.rxseqnr & 0x0f); // ACK, continue
|
||
|
m_poll_vwtp.bus->Write(&txframe);
|
||
|
};
|
||
|
|
||
|
|
||
|
// After locking the mutex, check again for poll expectance match:
|
||
|
if (m_poll_vwtp.state == VWTP_Closed || !m_poll_vwtp.bus || msgid != m_poll_vwtp.rxid)
|
||
|
{
|
||
|
logFrameDump("dropping unexpected frame");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
switch (m_poll_vwtp.state)
|
||
|
{
|
||
|
case VWTP_ChannelSetup:
|
||
|
{
|
||
|
// Expecting setup response, check if the frame fits:
|
||
|
uint8_t opcode = frame->data.u8[1];
|
||
|
if (frame->FIR.B.DLC != 7 || opcode < 0xD0 || opcode > 0xD8)
|
||
|
{
|
||
|
logFrameDump("channel setup: invalid/unexpected frame");
|
||
|
return false;
|
||
|
}
|
||
|
else if (opcode != 0xD0)
|
||
|
{
|
||
|
// Setup failed:
|
||
|
ESP_LOGD(TAG, "PollerVWTPReceive[%02X]: channel setup failed opcode=%02X", m_poll_vwtp.moduleid, opcode);
|
||
|
m_poll_vwtp.bus = NULL;
|
||
|
m_poll_vwtp.state = VWTP_Closed;
|
||
|
m_poll_wait = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Setup success, configure txid & rxid:
|
||
|
m_poll_vwtp.rxid = (uint16_t)frame->data.u8[3] << 8 | frame->data.u8[2];
|
||
|
m_poll_vwtp.txid = (uint16_t)frame->data.u8[5] << 8 | frame->data.u8[4];
|
||
|
ESP_LOGD(TAG, "PollerVWTPReceive[%02X]: channel setup OK, assigned txid=%03X rxid=%03X",
|
||
|
m_poll_vwtp.moduleid, m_poll_vwtp.txid, m_poll_vwtp.rxid);
|
||
|
// …and send channel parameters:
|
||
|
PollerVWTPEnter(VWTP_ChannelParams);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case VWTP_ChannelParams:
|
||
|
{
|
||
|
// Expecting params response or channel abort:
|
||
|
uint8_t opcode = (frame->FIR.B.DLC > 0) ? frame->data.u8[0] : 0;
|
||
|
if (opcode == 0xA1)
|
||
|
{
|
||
|
// Params received, configure block size & timing:
|
||
|
const int timeunit[4] = { 100, 1000, 10000, 100000 };
|
||
|
m_poll_vwtp.blocksize = frame->data.u8[1];
|
||
|
m_poll_vwtp.acktime = (frame->data.u8[2] & 0b00111111) * timeunit[(frame->data.u8[2] & 0b11000000) >> 6];
|
||
|
m_poll_vwtp.septime = (frame->data.u8[4] & 0b00111111) * timeunit[(frame->data.u8[4] & 0b11000000) >> 6];
|
||
|
ESP_LOGD(TAG, "PollerVWTPReceive[%02X]: channel params OK: bs=%d acktime=%uus septime=%uus",
|
||
|
m_poll_vwtp.moduleid, m_poll_vwtp.blocksize, m_poll_vwtp.acktime, m_poll_vwtp.septime);
|
||
|
// …and proceed to data transmission:
|
||
|
PollerVWTPEnter(VWTP_StartPoll);
|
||
|
}
|
||
|
else if (opcode == 0xA8)
|
||
|
{
|
||
|
// Channel abort received, send ACK & set closed state:
|
||
|
PollerVWTPEnter(VWTP_ChannelClose);
|
||
|
PollerVWTPEnter(VWTP_Closed);
|
||
|
// TODO: application error callback
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
logFrameDump("channel params: invalid/unexpected frame");
|
||
|
return false;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case VWTP_ChannelClose:
|
||
|
{
|
||
|
// Expecting channel close ACK:
|
||
|
uint8_t opcode = (frame->FIR.B.DLC == 1) ? frame->data.u8[0] : 0;
|
||
|
if (opcode == 0xA8)
|
||
|
{
|
||
|
// Close ACK received:
|
||
|
PollerVWTPEnter(VWTP_Closed);
|
||
|
// Check if we shall open another channel:
|
||
|
if (m_poll_protocol == VWTP_20 && m_poll_entry.rxmoduleid != 0)
|
||
|
PollerVWTPEnter(VWTP_ChannelSetup);
|
||
|
else if (m_poll_single_rxbuf)
|
||
|
{
|
||
|
m_poll_single_rxbuf->clear();
|
||
|
m_poll_single_rxbuf = NULL;
|
||
|
m_poll_single_rxerr = POLLSINGLE_OK;
|
||
|
m_poll_single_rxdone.Give();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
logFrameDump("channel params: invalid/unexpected frame");
|
||
|
return false;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case VWTP_Idle:
|
||
|
{
|
||
|
uint8_t opcode = frame->data.u8[0];
|
||
|
if (opcode == 0xA3)
|
||
|
{
|
||
|
// Channel ping received:
|
||
|
sendPong();
|
||
|
}
|
||
|
else if (opcode == 0xA8)
|
||
|
{
|
||
|
// Channel abort received, send ACK & set closed state:
|
||
|
PollerVWTPEnter(VWTP_ChannelClose);
|
||
|
PollerVWTPEnter(VWTP_Closed);
|
||
|
}
|
||
|
else if ((opcode & 0xf0) <= 0x30)
|
||
|
{
|
||
|
// Out of band data frame received in idle state, send ACK/abort:
|
||
|
m_poll_vwtp.rxseqnr++;
|
||
|
CAN_frame_t txframe = {};
|
||
|
txframe.callback = &m_poll_txcallback;
|
||
|
txframe.FIR.B.FF = CAN_frame_std;
|
||
|
txframe.MsgID = m_poll_vwtp.txid;
|
||
|
txframe.FIR.B.DLC = 1;
|
||
|
txframe.data.u8[0] = 0x90 | (m_poll_vwtp.rxseqnr & 0x0f); // ACK, abort
|
||
|
m_poll_vwtp.bus->Write(&txframe);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case VWTP_Transmit:
|
||
|
{
|
||
|
uint8_t opcode = (frame->FIR.B.DLC == 1) ? frame->data.u8[0] : 0;
|
||
|
if ((opcode & 0xf0) == 0xB0)
|
||
|
{
|
||
|
// ACK, continue:
|
||
|
if (m_poll_tx_remain > 0)
|
||
|
PollerVWTPEnter(VWTP_Transmit);
|
||
|
else
|
||
|
PollerVWTPEnter(VWTP_Receive);
|
||
|
}
|
||
|
else if ((opcode & 0xf0) == 0x90)
|
||
|
{
|
||
|
// ACK, abort:
|
||
|
ESP_LOGD(TAG, "PollerVWTPReceive[%02X]: got abort request during transmission at frame=%u remain=%u",
|
||
|
m_poll_vwtp.moduleid, m_poll_tx_frame, m_poll_tx_remain);
|
||
|
m_poll_tx_remain = 0;
|
||
|
PollerVWTPEnter(VWTP_Idle);
|
||
|
// TODO: application error callback
|
||
|
}
|
||
|
else if (opcode == 0xA3)
|
||
|
{
|
||
|
// Channel ping received:
|
||
|
sendPong();
|
||
|
}
|
||
|
else if (opcode == 0xA8)
|
||
|
{
|
||
|
// Channel abort received, send ACK & set closed state:
|
||
|
PollerVWTPEnter(VWTP_ChannelClose);
|
||
|
PollerVWTPEnter(VWTP_Closed);
|
||
|
// TODO: application error callback
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Possibly remaining frames from previously aborted xfer:
|
||
|
logFrameDump("unhandled frame during transmission");
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case VWTP_Receive:
|
||
|
{
|
||
|
uint8_t opcode = frame->data.u8[0];
|
||
|
if (opcode == 0xA3)
|
||
|
{
|
||
|
// Channel ping received:
|
||
|
sendPong();
|
||
|
}
|
||
|
else if (opcode == 0xA8)
|
||
|
{
|
||
|
// Channel abort received, send ACK & set closed state:
|
||
|
PollerVWTPEnter(VWTP_ChannelClose);
|
||
|
PollerVWTPEnter(VWTP_Closed);
|
||
|
// TODO: application error callback
|
||
|
}
|
||
|
else if (opcode < 0x40)
|
||
|
{
|
||
|
//
|
||
|
// Data frame received: check sequence number
|
||
|
//
|
||
|
|
||
|
m_poll_vwtp.lastused = monotonictime;
|
||
|
uint32_t rxseqnr = m_poll_vwtp.rxseqnr++;
|
||
|
if ((opcode & 0x0f) != (rxseqnr & 0x0f))
|
||
|
{
|
||
|
logFrameDump("received out of sequence frame, abort");
|
||
|
PollerVWTPEnter(VWTP_AbortXfer);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Get & validate OBD/UDS meta data
|
||
|
//
|
||
|
|
||
|
uint8_t* tp_data; // TP frame data section address
|
||
|
uint8_t tp_datalen; // TP frame data section length (0…7)
|
||
|
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 (m_poll_ml_frame == 0)
|
||
|
{
|
||
|
// First frame: extract & validate meta data
|
||
|
// Note: upper nibble of byte 1 masked out, length assumed to by 12 bit
|
||
|
// and we've seen 0x80 on byte 1 for response type UDS_RESP_NRC_RCRRP
|
||
|
m_poll_ml_remain = (frame->data.u8[1] & 0x0f) << 8 | frame->data.u8[2];
|
||
|
tp_data = &frame->data.u8[3];
|
||
|
tp_datalen = frame->FIR.B.DLC - 3;
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Consecutive frame:
|
||
|
tp_data = &frame->data.u8[1];
|
||
|
tp_datalen = frame->FIR.B.DLC - 1;
|
||
|
response_type = 0x40+m_poll_type;
|
||
|
response_pid = m_poll_pid;
|
||
|
response_data = tp_data;
|
||
|
response_datalen = tp_datalen;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Process OBD/UDS payload
|
||
|
//
|
||
|
|
||
|
if (response_type == UDS_RESP_TYPE_NRC && error_type == m_poll_type)
|
||
|
{
|
||
|
// Send ACK?
|
||
|
if ((opcode & 0xf0) <= 0x10)
|
||
|
sendAck();
|
||
|
|
||
|
// Negative Response Code:
|
||
|
if (error_code == UDS_RESP_NRC_RCRRP)
|
||
|
{
|
||
|
// Info: requestCorrectlyReceived-ResponsePending (server busy processing the request)
|
||
|
ESP_LOGD(TAG, "PollerVWTPReceive[%02X]: got OBD/UDS info %02X(%X) code=%02X (pending)",
|
||
|
m_poll_vwtp.moduleid, m_poll_type, m_poll_pid, error_code);
|
||
|
// reset wait time:
|
||
|
m_poll_wait = 2;
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Error: forward to application:
|
||
|
ESP_LOGD(TAG, "PollerVWTPReceive[%02X]: process OBD/UDS error %02X(%X) code=%02X",
|
||
|
m_poll_vwtp.moduleid, 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 receive:
|
||
|
m_poll_ml_remain = 0;
|
||
|
PollerVWTPEnter(VWTP_Idle);
|
||
|
}
|
||
|
}
|
||
|
else if (response_type == 0x40+m_poll_type && response_pid == m_poll_pid)
|
||
|
{
|
||
|
// Send ACK?
|
||
|
if ((opcode & 0xf0) <= 0x10)
|
||
|
sendAck();
|
||
|
|
||
|
// Normal matching poll response, forward to application:
|
||
|
m_poll_ml_remain -= tp_datalen;
|
||
|
ESP_LOGD(TAG, "PollerVWTPReceive[%02X]: process OBD/UDS response %02X(%X) frm=%u len=%u off=%u rem=%u",
|
||
|
m_poll_vwtp.moduleid, 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
|
||
|
{
|
||
|
// Response type / PID mismatch: log & abort
|
||
|
char *hexdump = NULL;
|
||
|
FormatHexDump(&hexdump, (const char*)frame->data.u8, frame->FIR.B.DLC, frame->FIR.B.DLC);
|
||
|
ESP_LOGW(TAG, "PollerVWTPReceive[%02X]: OBD/UDS response type/PID mismatch, got %02X(%X) vs %02X(%X) => ignoring: %s",
|
||
|
m_poll_vwtp.moduleid, response_type, response_pid, 0x40+m_poll_type, m_poll_pid, hexdump ? hexdump : "-");
|
||
|
if (hexdump) free(hexdump);
|
||
|
PollerVWTPEnter(VWTP_AbortXfer);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Do we expect more data?
|
||
|
if (m_poll_ml_remain)
|
||
|
{
|
||
|
m_poll_ml_frame++;
|
||
|
m_poll_ml_offset += response_datalen;
|
||
|
m_poll_wait = 2;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Request response complete:
|
||
|
PollerVWTPEnter(VWTP_Idle);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
logFrameDump("unhandled frame during reception");
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
// Ignore all frames received in other states
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Immediately send the next poll for this tick if…
|
||
|
// - we are not waiting for another frame
|
||
|
// - poll throttling is unlimited or limit isn't reached yet
|
||
|
if (m_poll_wait == 0 &&
|
||
|
(!m_poll_sequence_max || m_poll_sequence_cnt < m_poll_sequence_max))
|
||
|
{
|
||
|
PollerSend(false);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* PollerVWTPTicker: per second channel maintenance (internal)
|
||
|
*/
|
||
|
void OvmsVehicle::PollerVWTPTicker()
|
||
|
{
|
||
|
if (m_poll_wait > 0)
|
||
|
{
|
||
|
// State timeout?
|
||
|
if (m_poll_vwtp.state == VWTP_ChannelSetup || m_poll_vwtp.state == VWTP_ChannelParams)
|
||
|
{
|
||
|
ESP_LOGD(TAG, "PollerVWTPTicker[%02X]: setup/params timeout", m_poll_vwtp.moduleid);
|
||
|
PollerVWTPEnter(VWTP_Closed);
|
||
|
}
|
||
|
else if (m_poll_vwtp.state == VWTP_ChannelClose)
|
||
|
{
|
||
|
ESP_LOGD(TAG, "PollerVWTPTicker[%02X]: close timeout", m_poll_vwtp.moduleid);
|
||
|
PollerVWTPEnter(VWTP_Closed);
|
||
|
if (m_poll_protocol == VWTP_20)
|
||
|
PollerVWTPStart(true);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Poll timeout:
|
||
|
if (m_poll_vwtp.state != VWTP_Closed && m_poll_vwtp.state != VWTP_Idle)
|
||
|
{
|
||
|
ESP_LOGD(TAG, "PollerVWTPTicker[%02X]: poll timeout", m_poll_vwtp.moduleid);
|
||
|
PollerVWTPEnter(VWTP_ChannelClose);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Keepalive timeout?
|
||
|
if (m_poll_vwtp.state == VWTP_Idle && m_poll_ch_keepalive > 0 &&
|
||
|
m_poll_vwtp.lastused + m_poll_ch_keepalive < monotonictime)
|
||
|
{
|
||
|
ESP_LOGD(TAG, "PollerVWTPTicker[%02X]: channel inactivity timeout", m_poll_vwtp.moduleid);
|
||
|
PollerVWTPEnter(VWTP_ChannelClose);
|
||
|
}
|
||
|
}
|