Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 198 additions & 0 deletions resources/examples/can_tp/can_tp.ecb
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
{
"data": {
"devices": {
"c07c3701-6a7a-4a23-9848-dd86bbc38061": {
"type": "can",
"canDevice": {
"id": "c07c3701-6a7a-4a23-9848-dd86bbc38061",
"name": "SIMULATE_0",
"handle": 0,
"vendor": "simulate",
"canfd": false,
"database": "",
"bitrate": {
"sjw": 1,
"timeSeg1": 13,
"timeSeg2": 2,
"preScaler": 10,
"freq": 500000,
"clock": "80",
"_X_ROW_KEY": "row_80"
}
}
}
},
"ia": {},
"tester": {},
"subFunction": {},
"nodes": {
"f67340dd-e027-454b-aaba-d0521350993c": {
"name": "Client",
"id": "f67340dd-e027-454b-aaba-d0521350993c",
"channel": [
"c07c3701-6a7a-4a23-9848-dd86bbc38061"
],
"workNode": "",
"script": "tester.ts"
},
"99806fc5-64a9-4c84-9f83-710c938b28a9": {
"name": "Server",
"id": "99806fc5-64a9-4c84-9f83-710c938b28a9",
"channel": [
"c07c3701-6a7a-4a23-9848-dd86bbc38061"
],
"script": "ecu.ts",
"workNode": ""
}
},
"database": {
"lin": {},
"can": {},
"orti": {}
},
"graphs": {},
"guages": {},
"vars": {},
"datas": {},
"panels": {},
"logs": {},
"traces": {
"trace": {
"id": "trace",
"name": "Trace",
"filter": [
"canBase",
"ipBase",
"linBase",
"uds",
"someipBase",
"osTrace"
],
"filterDevice": [
"SIMULATE_0"
],
"filterId": [],
"columnConfig": [
{
"key": "seqIndex",
"visible": true,
"width": 80
},
{
"key": "ts",
"visible": true,
"width": 200
},
{
"key": "deltaTs",
"visible": true,
"width": 120
},
{
"key": "absTimeStr",
"visible": true,
"width": 200
},
{
"key": "name",
"visible": true,
"width": 200
},
{
"key": "data",
"visible": true,
"width": 300
},
{
"key": "dir",
"visible": true,
"width": 50
},
{
"key": "id",
"visible": true,
"width": 100
},
{
"key": "dlc",
"visible": true,
"width": 100
},
{
"key": "len",
"visible": true,
"width": 100
},
{
"key": "msgType",
"visible": true,
"width": 100
},
{
"key": "channel",
"visible": true,
"width": 100
},
{
"key": "device",
"visible": true,
"width": 200
}
]
}
},
"pluginData": {},
"replays": {}
},
"project": {
"wins": {
"message": {
"pos": {
"x": 383,
"y": 219,
"w": 1280,
"h": 200
},
"options": {
"params": {}
},
"title": "message",
"label": "Message",
"id": "message",
"layoutType": "bottom"
},
"network": {
"pos": {
"x": 1141,
"y": 0,
"w": 845,
"h": 559
},
"title": "network",
"label": "Network",
"id": "network",
"options": {}
},
"trace": {
"pos": {
"x": 8,
"y": 0,
"w": 1111,
"h": 952
},
"title": "trace",
"label": "Trace",
"id": "trace",
"options": {
"params": {
"edit-index": "trace"
},
"name": "Trace"
}
}
},
"example": {
"catalog": "CAN"
}
}
}
97 changes: 97 additions & 0 deletions resources/examples/can_tp/ecu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* Simulated ECU — receives CAN-TP requests and replies with a positive response.
*
* Uses Util.OnCan + output() to implement ISO 15765-2 without CanTpCreateConnection.
*
* RX = 0x7E0 (Tester → ECU)
* TX = 0x7E8 (ECU → Tester)
*
* Response rule: first byte = service_id + 0x40, remaining bytes echoed back.
*/
import { output, CAN_ID_TYPE } from 'ECB'
import type { CanMessage } from 'ECB'

const RX_ID = 0x7e0
const TX_ID = 0x7e8

let rxState: { totalLen: number; buf: number[]; expectedSN: number } | null = null
let txState: { data: number[]; offset: number; sn: number } | null = null

function sendRaw(bytes: number[]) {
const padded = [...bytes, ...Array(Math.max(0, 8 - bytes.length)).fill(0x00)].slice(0, 8)
output({
id: TX_ID,
data: Buffer.from(padded),
msgType: { idType: CAN_ID_TYPE.STANDARD, brs: false, canfd: false, remote: false }
} as CanMessage)
}

function respond(payload: number[]) {
const resp = [payload[0] + 0x40, ...payload.slice(1)]
const preview = resp
.slice(0, 8)
.map((b) => b.toString(16).padStart(2, '0'))
.join(' ')
console.log(`[ECU] TX ${resp.length}B: ${preview}${resp.length > 8 ? ' ...' : ''}`)

if (resp.length <= 7) {
sendRaw([resp.length, ...resp])
} else {
txState = { data: resp, offset: 6, sn: 1 }
sendRaw([0x10 | ((resp.length >> 8) & 0x0f), resp.length & 0xff, ...resp.slice(0, 6)])
}
}

Util.OnCan(RX_ID, (msg) => {
const d = Array.from(msg.data)
if (!d.length) return
const frameType = (d[0] >> 4) & 0x0f

if (frameType === 0) {
// Single Frame
const len = d[0] & 0x0f
if (len < 1 || len > d.length - 1) return
rxState = null
respond(d.slice(1, 1 + len))
} else if (frameType === 1) {
// First Frame — send FC immediately
const totalLen = ((d[0] & 0x0f) << 8) | d[1]
if (totalLen < 8) return
rxState = { totalLen, buf: [...d.slice(2)], expectedSN: 1 }
sendRaw([0x30, 0x00, 0x00]) // FC: ContinueToSend, BS=0, STmin=0
} else if (frameType === 2) {
// Consecutive Frame
if (!rxState) return
const sn = d[0] & 0x0f
if (sn !== rxState.expectedSN % 16) {
rxState = null
return
}
rxState.expectedSN++
rxState.buf.push(...d.slice(1))
if (rxState.buf.length >= rxState.totalLen) {
const payload = rxState.buf.slice(0, rxState.totalLen)
rxState = null
const preview = payload
.slice(0, 8)
.map((b) => b.toString(16).padStart(2, '0'))
.join(' ')
console.log(`[ECU] RX ${payload.length}B: ${preview}${payload.length > 8 ? ' ...' : ''}`)
respond(payload)
}
} else if (frameType === 3) {
// Flow Control (tester acking ECU's FF)
if (!txState || (d[0] & 0x0f) !== 0) return
while (txState.offset < txState.data.length) {
const chunk = txState.data.slice(txState.offset, txState.offset + 7)
sendRaw([0x20 | (txState.sn & 0x0f), ...chunk])
txState.sn++
txState.offset += 7
}
txState = null
}
})

Util.Init(() => {
console.log('[ECU] Ready RX=0x7E0 TX=0x7E8')
})
Loading
Loading