Close

Documentation found

A project log for Opensource HomeLink ecu for VAG

Open-source HomeLink module for VAG cars. Replaces the stock unit, working with standard LIN Bus and supporting various garage doors.

stepan-skopivskiyStepan Skopivskiy 03/11/2025 at 18:030 Comments

Besides the hardware investigation, I continued the Google research.

  1. The CAN bus matrix and Lin Description Files (next LDF) were found: https://openinverter.org/forum/viewtopic.php?t=3152
  2. The VAG BAP discover was to found at all unless the article in the web archive history: https://web.archive.org/web/20241005094756/https://blog.dietmann.org/?p=324
  3. Another document with the name 476121999-BAP-FC-NAV-SD-P30DF48-v2-80-F-pdf was found.

It also sheds some light on the communication protocol. The UGDO_B is the HomeLink buttons. The UGDOe is the HomeLink module. Based on this, half of the shade was eliminated, and information about the buttons' communication was discovered. The BCM drives the LIN bus, it sends the *e_ messages to request information and the *s_ headers to receive the response. The details of the hardware level can be found here: https://www.csselectronics.com/pages/lin-bus-protocol-intro-basics.

  UGDO_Be_01: 4, BCM1, 6 {
    UGDO_SW_LED_Code, 0 ;
    UGDO_SW_Function, 5 ;
    DI_KL_58xs, 8 ;
  }
  UGDO_Bs_01: 3, UGDO_B, 6 {
    UGDO_SW_Buttons, 0 ;
    UGDO_SW_Switchboard, 4 ;
    UGDO_B_ResponseError, 16 ;
  }
  UGDOe_02: 46, BCM1, 6 {
    UGDO_Buttons, 0 ;
    Klemme_15, 6 ;
    ZV_auf_Funk, 7 ;
    ESP_v_Signal_8Bit, 8 ;
    UGDO_Switchboard, 16 ;
    SM_Parken_UGDO_Anf, 24 ;
    SM_Parken_UGDO_Aktion, 28 ;
  }
  UGDOe_BAP: 31, BCM1, 8 {
    BAPe_LSG_ID1, 0 ;
    BAPe_Opcode, 4 ;
    BAPe_Segment, 7 ;
    BAPe_FCT_ID, 8 ;
    BAPe_LSG_ID2, 14 ;
    BAPe_Data_Byte_1, 16 ;
    BAPe_Data_Byte_2, 24 ;
    BAPe_Data_Byte_3, 32 ;
    BAPe_Data_Byte_4, 40 ;
    BAPe_Data_Byte_5, 48 ;
    BAPe_Data_Byte_6, 56 ;
  }
  UGDOs_02: 47, UGDO_S, 8 {
    UGDO_LED_Code_01, 0 ;
    UGDO_Function_01, 5 ;
    UGDO_Failure_Antenne, 8 ;
    UGDO_Failure_Codierung, 9 ;
    UGDO_Failure_Hardware, 10 ;
    UGDO_ResponseError, 16 ;
    UGDO_Channel_1, 24 ;
    UGDO_Channel_2, 25 ;
    UGDO_Channel_3, 26 ;
    UGDO_Channel_4, 27 ;
    UGDO_Channel_5, 28 ;
    UGDO_Channel_6, 29 ;
    UGDO_Channel_7, 30 ;
    UGDO_Channel_8, 31 ;
    UGDO_Channel_9, 32 ;
    UGDO_Channel_10, 33 ;
    UGDO_Channel_11, 34 ;
    UGDO_Channel_12, 35 ;
    UGDO_Channel_13, 36 ;
    UGDO_Channel_14, 37 ;
    UGDO_Channel_15, 38 ;
    UGDO_DoorState, 40 ;
  }
  UGDOs_BAP: 32, UGDO_S, 8 {
    BAPs_LSG_ID1, 0 ;
    BAPs_Opcode, 4 ;
    BAPs_Segment, 7 ;
    BAPs_FCT_ID, 8 ;
    BAPs_LSG_ID2, 14 ;
    BAPs_Data_Byte_1, 16 ;
    BAPs_Data_Byte_2, 24 ;
    BAPs_Data_Byte_3, 32 ;
    BAPs_Data_Byte_4, 40 ;
    BAPs_Data_Byte_5, 48 ;
    BAPs_Data_Byte_6, 56 ;
  }

Also, this file has a bit of info about the diagnosis frames used to work with ODIS. But unfortunately the amount of info was a bit.

Diagnostic_frames {
  MasterReq: 0x3c {
    MasterReqB0, 0 ;
    MasterReqB1, 8 ;
    MasterReqB2, 16 ;
    MasterReqB3, 24 ;
    MasterReqB4, 32 ;
    MasterReqB5, 40 ;
    MasterReqB6, 48 ;
    MasterReqB7, 56 ;
  }
  SlaveResp: 0x3d {
    SlaveRespB0, 0 ;
    SlaveRespB1, 8 ;
    SlaveRespB2, 16 ;
    SlaveRespB3, 24 ;
    SlaveRespB4, 32 ;
    SlaveRespB5, 40 ;
    SlaveRespB6, 48 ;
    SlaveRespB7, 56 ;
  }
}

After capturing the LIN messages during the ODIS block identification procedure, I got a list of frames. It will be attached as a file. In that mess of frames, I was able to see the interesting frames that looked pretty known.

What if we change the coding? And here we go:

Let's make the messages clearer. Based on the LDF, we can get the info about the bus. It is the LIN 2.0 version and works at 19.2 kbps.

...
LIN_description_file;
LIN_protocol_version = "2.0";
LIN_language_version = "2.0";
LIN_speed = 19.2 kbps;
...

Also, based on the specification of the LIN, the first byte is the header, and the last one is the checksum. To get the checksum, https://linchecksumcalculator.machsystems.cz/ can be used.

And it looks true. 0x 3C01062E600421000041, the frame ID matches with the LFD, and the checksum is the same. So, now the message looked less terrible. After sniffing a few more messages, they became more and more logical but still not clear.

0x3C0C03226205FFFFFF67
0x3D0C100E626205344D8A
0x3D0C2130393539373192
0x3D0C22394120FFFFFF37
0x3C0C03226C05FFFFFF5D
0x3D0C1010626C05475464
0x3D0C214F20424620209A
0x3D0C222020202020FF31
0x3C0C03226605FFFFFF63
0x3D0C100E626605344D86
0x3D0C2130393539373192
0x3D0C22394120FFFFFF37
0x3C0C03226405FFFFFF65
0x3D0C10076264053030B0
0x3D0C213130FFFFFFFF71
0x3C0C03226805FFFFFF61
0x3D0C0662680548303174
0x3C0C03226A05FFFFFF5F
0x3D0C1017626A05323098
0x3D0C213137303232389D
0x3D0C223132343435319F
0x3D0C23203030363031B8
0x3C0C03226005FFFFFF69
0x3D0C037F2231FFFFFF1E
0x3C0C03226E05FFFFFF5B
0x3D0C101A626E0554524D
0x3D0C21572D59534A3225
0x3D0C22382E30322E31A9
0x3D0C23373030303130A7
0x3D0C24363031FFFFFF38
0x3C0103226204FFFFFF73
0x3D01100E626204344D96
0x3D0121303930373431A7
0x3D0122304120FFFFFF4B
0x3C0103226C04FFFFFF69
0x3D011010626C04555363
0x3D0121484C35202020B3
0x3D01222020202020FF3C
0x3C0103226604FFFFFF6F
0x3D01100E626604344D92
0x3D0121303930373431A7
0x3D0122304120FFFFFF4B
0x3C0103226404FFFFFF71
0x3D0110076264043030BC
0x3D01213530FFFFFFFF78
0x3C0103226804FFFFFF6D
0x3D01066268044830347D
0x3C0103226A04FFFFFF6B
0x3D011017626A043030A6
0x3D0121303030303936AD
0x3D012230303333355A86
0x3D012333434935303284
0x3C0103226004FFFFFF75
0x3D01066260042500000D
0x3C0103226E04FFFFFF67
0x3D01101A626E04475860
0x3D0121312D475831317D
0x3D0122332E30332E31B8
0x3D0123373030303133AF
0x3D0124434935FFFFFF19

 The list has a strict repeat, and its first part almost always was 0x3C0C0322 and 0x3C010322. As the VAG use UDS for diagnostic i decide to compare the messages with the UDS messages structure. Details here: https://www.csselectronics.com/pages/uds-protocol-tutorial-unified-diagnostic-services. After exposing the capture by the UDS message structure, it starts looks next:

ID    PCI SID SubFunction DataID Empty  Checksum
0x3C  0C  03  22          6205   FFFFFF 67
0x3C  0C  03  22          6C05   FFFFFF 5D
0x3C  01  03  22          6204   FFFFFF 73
0x3C  01  03  22          6C04   FFFFFF 69

But this structure was described for the CAN bus, and the PCI (Protocol Contriol Info) should not change its value. Unless for requesting simple data. Thankfully, the ODIS provides the possibility to force read the identification of the block. And after doing it just for the buttons, I got the next sniff:

0x3C0C03226205FFFFFF67
0x3D0C100E626205344D8A
0x3D0C2130393539373192
0x3D0C22394120FFFFFF37
0x3C0C03226C05FFFFFF5D
0x3D0C1010626C05475464
0x3D0C214F20424620209A
0x3D0C222020202020FF31
0x3C0C03226605FFFFFF63
0x3D0C100E626605344D86
0x3D0C2130393539373192
0x3D0C22394120FFFFFF37
0x3C0C03226405FFFFFF65
0x3D0C10076264053030B0
0x3D0C213130FFFFFFFF71
0x3C0C03226805FFFFFF61
0x3D0C0662680548303174
0x3C0C03226A05FFFFFF5F
0x3D0C1017626A05323098
0x3D0C213137303232389D
0x3D0C223132343435319F
0x3D0C23203030363031B8
0x3C0C03226005FFFFFF69
0x3D0C037F2231FFFFFF1E
0x3C0C03226E05FFFFFF5B
0x3D0C101A626E0554524D
0x3D0C21572D59534A3225
0x3D0C22382E30322E31A9
0x3D0C23373030303130A7
0x3D0C24363031FFFFFF38

This was interesting because the LDF has the next data:

UGDO_B{
    LIN_protocol = "2.0" ;
    configured_NAD = 0xC ;
    product_id = 0x0, 0x0, 0 ;
    response_error = UGDO_B_ResponseError ;
    P2_min = 10 ms ;
    ST_min = 10 ms ;
    configurable_frames {
      UGDO_Be_01 = 0x2004 ;
      UGDO_Bs_01 = 0x2003 ;
      ISPe_01 = 0x0 ;
      TCHe_01 = 0x0 ;
    }
  }
  UGDO_S{
    LIN_protocol = "2.0" ;
    configured_NAD = 0x1 ;
    product_id = 0x0, 0x0, 0 ;
    response_error = UGDO_ResponseError ;
    P2_min = 10 ms ;
    ST_min = 10 ms ;
    configurable_frames {
      UGDOe_02 = 0x112E ;
      UGDOe_BAP = 0x111F ;
      UGDOs_02 = 0x112F ;
      UGDOs_BAP = 0x1120 ;
      ISPe_01 = 0x0 ;
      LIMe_02 = 0x0 ;
    }
  }

 So, looks like the first byte of data of the LIN frame is not a PCI but the NAD (Node Address).

ID    NAD PCI SID SubFunction DataID Empty  Checksum
0x3C  0C  03  22  62          05FF   FFFF   67
0x3C  0C  03  22  6C          05FF   FFFF   5D
0x3C  01  03  22  62          04FF   FFFF   73
0x3C  01  03  22  6C          04FF   FFFF   69

And here we get four columns looking true. But no info about the SubFunction and Data ID. And here, the ODIS will help a bit. After running the measurement from ODIS. A part of the report is attached.

The LIN frames were the next:

0x3C0103223AF7FFFFFFA7
0x3D0104623AF700FFFF66
0x3C0103223AFFFFFFFF9F
0x3D0105623AFF0477FFE1
0x3C010322500CFFFFFF7D
0x3D010462500C05FFFF37
0x3C0103223AE5FFFFFFB9
0x3D01100A623AE5000062
0x3D01210000000000FFDD
0x3C0103223AE6FFFFFFB8
0x3D01100A623AE6000061
0x3D01210000000000FFDD
0x3C0103223AE7FFFFFFB7
0x3D01100A623AE7000060
0x3D01210000000000FFDD
0x3C0103223AE8FFFFFFB6
0x3D01037F2278FFFFFFE1
0x3D011008623AE8000061
0x3D0121000000FFFFFFDD
0x3C0103223AE9FFFFFFB5
0x3D01037F2278FFFFFFE1
0x3D011008623AE9000060
0x3D0121000000FFFFFFDD

 Still a mess? - For sure. But what if we divide the bytes based on our knowledge? Now it looks not so weird?

B0 is always 0x22, 0x62, and 0x7F. Let's check the UDS. And looks like they are a SID request (0x22) and SID response (0x62) for Read Data by Identifier. What about the 0x7F? Of course, it looks like the SID negative response.

What if we split B1 and B2 together? Looks familiar? Take a look at the ODIS report. It is data ID for sure. Based on discovery, the LIN bus has no SubFunction ID; at least, it looks so. So, now we can put the information together. And even decode a bit.

Discussions