# ENET - CAN Diagnostic Messages for Bench Coding



## bill57p9 (May 3, 2006)

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)

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

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


----------



## shawnsheridan (Jan 10, 2009)

Nice work!


----------



## bill57p9 (May 3, 2006)

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.


----------



## bill57p9 (May 3, 2006)

The write up of my ICAM installation together with code for a ZGW proxy and a CAN gateway are here: https://github.com/bill57p9/BMW_ZGW_emulator/wiki


----------



## Tuerkay (Sep 26, 2020)

Hey bill57p9, nice job 

can you by any chance share the CAN_gateway_arduino.io?

thanks in advance

Edit: Nevermind, i´m just blind...


----------



## MuffinFlavored (May 18, 2021)

What CAN arbitration ID does the DME receive over the CAN bus for UDS diagnostics? 0x7E0/0x7E8 or no?


----------



## bill57p9 (May 3, 2006)

MuffinFlavored said:


> What CAN arbitration ID does the DME receive over the CAN bus for UDS diagnostics? 0x7E0/0x7E8 or no?


@MuffinFlavored looking at the files I would expect the arbitration ID for DME UDS messages to be 0x618 and/or 0x619 but may be wrong.


----------



## MuffinFlavored (May 18, 2021)

@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


```
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.


----------



## MuffinFlavored (May 18, 2021)

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:


```
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.


----------



## rusefi (Nov 25, 2013)

I know it has been 18 months but google remembers 

First frame/continuation of the message/0x30 ack reminds me of ISO-TP ISO 15765-2 - Wikipedia which is a building block for UDS

Thank you for the great info!

My interest is currently around transmission communications.
On my E65 N73 first ECU/DME seems to be 0x612 and TCU/EGS seems to be 0x618.


----------



## rusefi (Nov 25, 2013)

PS: we play with this at E65 N73 CAN bus - rusefi.com


----------



## bill57p9 (May 3, 2006)

I didn't know about the ISO standards when I started reverse engineering this stuff, but have subsequently been enlightened by MuffinFlavoured.
I have been hacking again, this time for a new project which needed a clock. I have written up what I found here


----------



## VadisSoftware (10 mo ago)

Hi. Have You maybe more experience about ENET messages ? More details ? What IP/Port ? is on FEM Enet Gateway? What IP should be on PC network card ? Can You explain littlest more maybe ?


----------



## bill57p9 (May 3, 2006)

VadisSoftware said:


> Hi. Have You maybe more experience about ENET messages ? More details ? What IP/Port ? is on FEM Enet Gateway? What IP should be on PC network card ? Can You explain littlest more maybe ?


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


----------



## kameleon8888 (8 mo ago)

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?


----------



## kameleon8888 (8 mo ago)

After two weeks I found time to work with this little bit..
Result You can see below ( test iOS app )
Used tool was modBMW
Used hardware : BDC from X5 2015









Always after some time FEM/BDC go sleep. Then power consumption going down from 0.80A to 0.18A ( still some circuits inside working ).


----------

