BimmerFest BMW Forum banner

ENET - CAN Diagnostic Messages for Bench Coding

9K views 16 replies 8 participants last post by  0MMM 
#1 ·
Long story, however I have been reverse engineering the diagnostic/E-Sys messages on an F46 (same as F45/48/56 and probably common for F series) and am writing this up here as a reference in case it's useful to someone else.

I used it to write a bench programming rig using a Raspberry Pi & a couple of cheap CAN interface modules. The code isn't pretty however I can make it available if anyone is interested.

In the description below, 0x indicates a hex number.

ENET Messages
These are exchanged between ISTA/E-Sys and the ZGW over TCP/IP (ZGW TCP server port 6801)
Each message has an 8 byte header followed by the message payload.
  • Bytes 1-4: Message length INCLUDING source & destination ECU bytes, i.e. payload length + 2
  • Bytes 5-6: Not sure what it is - See below
  • Byte 7: Source ECU ID
  • Byte 8: Destination ECU ID
Bytes 5 & 6 are usually 0x0001, however whenever ISTA/E-Sys sends a message the ZGW sends an acknowledgement back which is 0x0002.
ISTA/E-Sys use ECU ID 0xF4. The ECU IDs are shown in E-Sys next to the ECU name.

Remember that TCP is a stream rather than datagram protocol, so you can get multiple and/or split messages within a packet.

Corresponding CAN BUS Messages
The CAN BUS packets all appear to have 11 bit identifiers (i.e. 0x000 to 0x7FF) and a maximum payload of 8 bytes.
The identifier is always 0x6nn where nn is the source ECU ID. So for example, anything from ECU 0x0D will have a CAN packet ident of 0x60D.
Some packets with less than 8 bytes of actual data are padded to an 8 byte payload. Others have shorter payloads.

The first byte of the payload (for a CAN bus message ID 0x6nn) is the destination ECU ID.
The second byte of the payload (for a CAN bus message ID 0x6nn) is:
  • 0x0n
    A short message of a single CAN frame, length "n" (max 6)
  • 0x1n
    First frame in a longer message. Message length is 0xnyy where yy is the third byte in the payload. So for example 0x11 followed by 0x02 would indicate a 0x101 = 258 byte message, which will require 44 CAN frames (the first frame will have the first 5 bytes of the message, the remaining 6 bytes each)
  • 0x2n
    A continuation of the message.
    n is the frame order number. n wraps as it has a max of 0xF = 15.
    In a multiframe message, the first frame will be 0x1n and the second frame 0x21 the 0x22 and so on up to 0x2F then 0x20
  • 0x30
    Message 0x30 0x00 0x02 is always sent in response to a multiframe initial frame (0x1n).
    The sending ECU will/must not send the continuation frames until this has been received.
    This message is not relayed over ENET. It is managed by the ZGW.
Examples (All data is hex)
  1. E-Sys sends to ZGW over ENET: 00 00 00 05 00 01 F4 06 22 30 00
    ZGW responds to E-Sys: 00 00 00 05 00 02 06 F4 22 30 00
    ZGW sends on CANBUS with ECU 06: ID 6F4 Data 06 03 22 30 00
    Note that the length stated in the ENET message (05) is 2 bytes more than that in the CANBUS data because it includes the source & destination ECU IDs
  2. CANBUS message sent to ECU: ID 606 Data F4 10 07 10 3C 62 F1 01
    This indicates a multiframe message of 07 bytes from ECU 06 to E-Sys/ISTA
    ZGW responds on CANBUS: ID 6F4 Data 06 30 00 02
    ECU continues with only remaining frame. ID 606 Data F4 21 01 01
    ZGW sends to ISTA/E-Sys over ENET 00 00 00 09 00 01 06 F4 10 3C 62 F1 01 01 01
 
See less See more
#3 ·
One more detail: ECU ID DF appears to be a broadcast to which every ECU responds. This is used when E-Sys searches for which ECUs are present.

I originally used a Raspberry Pi with a (cheap) CAN bus module running software I wrote myself in JavaScript (node.js). E-Sys/ISTA connects to the Pi which forwards all ENET messages to the real ZGW ENET but also sends messages for the target ECU (06 = ICAM in my case - I was programming & coding on an F46 without KCAN3). It also forwards any CAN diagnostic messages from the target ECU (CAN ID 606 in my case) to E-Sys/ISTA.
 
#8 ·
@bill57p9 I tried to find a way to contact you (private messaging, github, email, etc.)

Not having a ton of luck. Can you reach out? my username at gmail please

Code:
const dgram = require('dgram')
const net = require('net')

// 00000046 0001 12f4 62f1010101000620050689ffff0100000000111401 00003164 03 13 05 0200003d62ffffff06 00004295 01 0e 01 0800005d5532a0050d 000063a532a00 705 0000274200ab0e
/*
  HWEL: 00003164-003.019.005
  BTLD: 00004295-001.014.001
  SWFL: 00005d55-050.160.005
  SWFL: 000063a5-050.160.007
  CAFD: 00002742-000.171.014
*/

const DISCOVERY_PORT = 6811
const DISCOVERY_IP = '169.254.255.255'
const DISCOVERY_REQUEST = Buffer.from('000000000011', 'hex')
const ICOM_PORT = 6801

const TESTER_ID = 0xF4
const ZGW_ID = 0x10
const DME_ID = 0x12
const CAS_ID = 0x40

const getIcomAddress = async () => {
  let icomAddress = null
  const client = dgram.createSocket('udp4')
  client.on('message', (msg, rinfo) => {
    icomAddress = rinfo
  })
  await new Promise((resolve, reject) => {
    client.send(DISCOVERY_REQUEST, DISCOVERY_PORT, DISCOVERY_IP, (err) => {
      if (err) {
        return reject(err)
      }
      resolve()
    })
  })
  // wait for response
  for (;;) {
    if (icomAddress !== null) {
      return icomAddress
    }
    await delay(1)
  }
}

const icomWrite = (client, unk1, sourceId, destinationId, payload) => {
  let request = ''
  request += `${unk1.toString(16).padStart(4, '0')}`
  request += `${sourceId.toString(16).padStart(2, '0')}`
  request += `${destinationId.toString(16).padStart(2, '0')}`
  request += payload
  const buffer = Buffer.from(request, 'hex')
  const length = (buffer.length - 2).toString(16).padStart(8, '0')
  client.write(Buffer.from(`${length}`, 'hex'))
  client.write(buffer)
}

export const enet = async () => {
  const icomAddress = await getIcomAddress()
  log(`got icom address address: ${icomAddress.address}`)
  const client = new net.Socket()
  client.on('data', (chunk) => {
    console.log(chunk.toString('hex'))
  })
  await new Promise((resolve, reject) => {
    client.connect(ICOM_PORT, icomAddress.address, (err) => {
      if (err) {
        return reject(err)
      }
      resolve()
    })
  })
  log('connected to icom')
  await delay(1000)
  // something with CAS immboilizer
  await icomWrite(client, 0x0001, TESTER_ID, CAS_ID, '310102050d000063a532a007')
  await delay(250)
  // 22 f1 01
  await icomWrite(client, 0x0001, TESTER_ID, DME_ID, '22f101')
  await delay(250)
  // 22 f1 86
  await icomWrite(client, 0x0001, TESTER_ID, DME_ID, '22f186')
  await delay(250)
  // 22 f1 90
  await icomWrite(client, 0x0001, TESTER_ID, DME_ID, '22f190')
  await delay(250)
}
I came up with a touch more succinct/cleaner way of doing these communications. But I have a very specific question I need to ask you.
 
#9 ·
That's to do it through ZGW the DoIP way. If you have a DME on bench with no ZGW/ZGM/BDC and you want to do it the pure CAN way, it looks a bit like this:

Code:
const dmeWrite = async (connection, buffer, payload) => {
  const testerId = 'F1'
  const ecuId = '12' // DME
  await write(connection, `can_send_ascii(0x6${testerId}, "${ecuId}${payload}")\r`)
}

const dmeRecv = async (connection, buffer) => {
  const ecuId = '12' // DME
  await write(connection, `can_recv(0x6${ecuId})\r`)
}

await dmeWrite(connection, buffer, '0322f190555555')
The ECU needs to be running in ASW. I didn't figure out how to do it from CBOOT yet.
 
#14 ·
I wrote this up based on using an ENET cable to the OBD socket though I have subsequently found that the same message format is used over the DCAN.

Indeed to use ENET you need to know the ZGW/FEM IP address. Unconfigured ethernet devices have a (MAC address based) "link local" IP address in the 169.254.0.0/16 range (i.e. between 169.254.0.0 and 169.254.255.255 - mine is 169.254.5.77). So if you connect your laptop without an IP address configured locally via an ENET cable to the ZGW/FEM then they can communicate using these "link local" 169.254 addresses.

Alternatively in my experience you can connect the ZGW/FEM to a network with a DHCP server and assign it a "real" IP address.

The simplest way I found to find the ZGW/FEM IP address is to use E-Sys which will has discovery.

The ENET stuff uses TCP port 6801 with the ZGW/FEM as the TCP server.

Hope this helps
 
#15 ·
I currently have a modBMW WIFI ENET adapter. This device sets up a WIFI network with the router's address 192.168.16.117. Since the addresses in the vehicle are in the address group between 169.254.0.0 and 169.254.255.255, does that mean my device should be configured to an address in this range?
Maybe this is like Tester is server with DHCP and FEM just subscribe as device IP from address pule?
 
This is an older thread, you may not receive a response, and could be reviving an old thread. Please consider creating a new thread.
Top