├── README.md └── shiftlight_with_CAN-BUS_for_MQB.ino /README.md: -------------------------------------------------------------------------------- 1 | # MQB-sniffer 2 | CAN-BUS sniffing for Volkswagen Golf MK7 (and most likely other MQB platform cars). 3 | Currently this is more a work log for future reference. 4 | 5 | ## Hardware used (not everything is required) ## 6 | * Car: VW Golf MK7, 2016, 2.0TDI (110kW, CRLB) + DSG (DQ250) 7 | * OBDeleven (GEN2) OBD2 adapter + iOS device + OBDeleven account with PRO license 8 | * ELM327 v1.5 (Carista) 9 | * MCP2515 + Arduino Nano + bunch of cables + PC/Mac/Linux 10 | * OBD2 cable splitter (to connect two OBD2 devices at the same time to the car) 11 | 12 | ## Sniffing ## 13 | ### Cables and adapters ### 14 | * OBD2 Splitter connected to the car 15 | * OBDeleven connected to one of the ports 16 | * Carista ELM327 connected to the other port 17 | * iOS device connected with OBDeleven adapter 18 | * Mac/PC/Linux connected with Carsita ELM327 dongle 19 | * serial monitor app running on Mac/PC, Carista ELM327 serial port connected at high baudrate (115200bps) 20 | 21 | ### ELM327 config ### 22 | Commands to send to Carista ELM327 to prepare it for data sniffing: 23 | * `at z` - reset ELM327 chip 24 | * `at d` - defaults 25 | * `at sp 6` - set CAN-BUS to 11bit, 500kbps (Golf MK7 is using ISO 15765-4 CAN, 11 bit ID, 500 kbaud standard) 26 | * `at cfc1` - flowcontrol 27 | * `at d1` - show DLC 28 | * `at al` - enable long messages 29 | * `at h1` - show headers 30 | * `at caf0` - disable formating 31 | * `at l0` - disable linefeeds 32 | * `at s1` - enable whitespaces (easier to view data) 33 | 34 | ### Data gathering ### 35 | * `at ma` - start sniffing 36 | * At the same time use OBDeleven to connect with car, go to list of modules, select module, select live data, select data type, view. 37 | * Carista ELM327 will sniff the communication between OBDeleven and car. 38 | 39 | ## Injecting ## 40 | ### ELM327 config ### 41 | * `at z` - reset ELM327 chip 42 | * `at sp 6` - set CAN-BUS to 11bit, 500kbps 43 | * `at al` - enable long messages 44 | * `at caf0` - disable formating 45 | * `at v1` - enable frames shorter than 8 bytes (no padding) 46 | * `at bi` - bypass the initiation step 47 | 48 | ### Sending frame ### 49 | * `at sh 777` - set frame header to 777 50 | * `11 22 33 44 55` - send 0x11 0x22 0x33 0x44 0x55
51 | This will send this frame:
52 | `Standard ID: 0x777 DLC: 5 Data: 0x11 0x22 0x33 0x44 0x55` 53 | 54 | 55 | ## Request headers and modules ## 56 | * `714` - instrument cluster (module 0x17) 57 | * `7E1` - gearbox (module 0x02) 58 | * More to check: https://github.com/commaai/openpilot/issues/1238#issuecomment-602155568 59 | 60 | ## Example data sniffed from OBDeleven-Car communication ## 61 | ### Starting and maintaining communication with instrument cluster ### 62 | `714 8 02 10 03 55 55 55 55 55` - Accessing instrument cluster
63 | `77E 8 06 50 03 00 32 00 C8 AA` - Cluster reply
64 |
65 | `714 8 03 19 02 AE 55 55 55 55`
66 | `77E 8 03 59 02 19 AA AA AA AA`
67 |
68 | `714 8 02 3E 00 55 55 55 55 55` - I'm here, don't sleep
69 | `77E 8 02 7E 00 AA AA AA AA AA` - not sleeping!
70 |
71 | `714 8 02 3E 00 55 55 55 55 55`
72 | `77E 8 02 7E 00 AA AA AA AA AA`
73 | `...`
74 | 75 | ### RPM from instrument cluster ### 76 | `714 8 03 22 22 D1 55 55 55 55` - request
77 | `77E 8 05 62 22 D1 1F 98 AA AA` - reply (0x1F98 = 8088 DEC; 8808 / 4 = 2022RPM)
78 | 79 | ### Phototransistor instrument cluster (ambient light sensor) ### 80 | `714 8 03 22 22 4D 55 55 55 55` - request
81 | `77E 8 04 62 22 4D FA AA AA AA` - reply (0xFA = 250 DEC; 250 out of 0-255 range; higher=brighter)
82 | 83 | ### Enagaged gear from DSG ### 84 | `7E1 8 03 22 38 16 55 55 55 55` - request
85 | `7E9 8 04 62 38 16 00 AA AA AA` - gear not engaged
86 | `7E9 8 04 62 38 16 0C AA AA AA` - reverse
87 | `7E9 8 04 62 38 16 02 AA AA AA` - 1st
88 | 89 | ### Gearbox mode from DSG ### 90 | `7E1 8 03 22 38 15 55 55 55 55` - request
91 | `7E9 8 04 62 38 15 00 AA AA AA` - P
92 | `7E9 8 04 62 38 15 01 AA AA AA` - R
93 | `7E9 8 04 62 38 15 02 AA AA AA` - N
94 | `7E9 8 04 62 38 15 03 AA AA AA` - D
95 | `7E9 8 04 62 38 15 04 AA AA AA` - S
96 | `7E9 8 04 62 38 15 05 AA AA AA` - M
97 | 98 | ## Warnings ## 99 | * Cheap ELM327 adapters will not work. Buffer will overflow with date before you can read the data from it. 100 | * For some reasons, when trying to use MCP2515 to sniff, experiencing communication problems between OBDeleven and the car. Tried both with and without EOL termination resistor with no luck. 101 | 102 | ## Shopping list ## 103 | * OBDeleven adapter - http://mr-fix.info/redirect/obdeleven 104 | * Carista adapter 105 | * Arduino Nano 106 | * MCP2515 107 | * OBD2 splitter 108 | * WS2818 LED panel 109 | -------------------------------------------------------------------------------- /shiftlight_with_CAN-BUS_for_MQB.ino: -------------------------------------------------------------------------------- 1 | /* Shiftlight with CAN-BUS for MQB */ 2 | /* Board: Arduino Nano 3 | * Processor: ATmega328P (Old Bootloader) 4 | * Port: /dev/cu.usbserial-1410 5 | */ 6 | 7 | 8 | #include 9 | #include 10 | #include 11 | #ifdef __AVR__ 12 | #include // Required for 16 MHz Adafruit Trinket 13 | #endif 14 | 15 | //pin config 16 | #define PINPIXELS 3 // pin D3 to data-in WS2812 module 17 | #define NUMPIXELS 8 // number of LEDs in WS2812 module 18 | #define PINBUZZER 4 // pin D2 to pizo speaker 19 | #define GNDBUZZER 5 // ground for pizo speaker 20 | #define CAN0_INT 2 // MCP2515 INT to pin 2 21 | 22 | //MCP2515 config 23 | MCP_CAN CAN0(10); // MCP2515 CS to pin 10 24 | long unsigned int rxId; 25 | unsigned char len = 0; 26 | unsigned char rxBuf[8]; 27 | char msgString[128]; 28 | 29 | //initial config 30 | Adafruit_NeoPixel pixels(NUMPIXELS, PINPIXELS, NEO_GRB + NEO_KHZ800); 31 | unsigned int initialBrightness = 15; // 0-255 32 | unsigned int currentBrightness = initialBrightness; 33 | 34 | unsigned int currentFrequency = 500; 35 | 36 | //user config 37 | unsigned int currentRPM; 38 | unsigned int maxRPM = 8000; 39 | unsigned int perfectRPM = 6500; 40 | unsigned int welcomeMode = 2; 41 | bool mute = true; 42 | 43 | unsigned long nextLoopTime=0; 44 | 45 | //run once 46 | void setup() { 47 | //Serial communication setup 48 | Serial.begin(115200); 49 | 50 | //First data output over serial 51 | printInitialDetails(); 52 | 53 | ///MCP2515 setup 54 | mcpSetup(); 55 | 56 | //WS2812 setup 57 | pixels.begin(); 58 | pixels.clear(); 59 | pixels.show(); 60 | pixels.setBrightness(currentBrightness); 61 | 62 | //Buzzer setup 63 | pinMode(PINBUZZER, OUTPUT); 64 | pinMode(GNDBUZZER, OUTPUT); 65 | digitalWrite(GNDBUZZER, LOW); 66 | 67 | //Run initial animation 68 | welcomeCeremony(welcomeMode); 69 | } 70 | 71 | //run all the time 72 | void loop() { 73 | mcpRead(); 74 | //mcpSearch(); 75 | 76 | currentRPM = getRPM(false); //false = simulation 77 | 78 | pixels.clear(); 79 | pixels.setBrightness(currentBrightness); 80 | 81 | updateAlerts(); 82 | 83 | pixels.show(); 84 | } 85 | 86 | void welcomeCeremony(int type) { 87 | if(type==1){ 88 | for (int i = 0; i < NUMPIXELS; i++) { 89 | pixels.setPixelColor(i, pixels.Color(255, 0, 0)); 90 | pixels.show(); 91 | tone(PINBUZZER, (currentFrequency*(i+1))); 92 | delay(25); 93 | noTone(PINBUZZER); 94 | delay(25); 95 | } 96 | } 97 | 98 | 99 | if(type==2){ 100 | for (int i = 0; i < NUMPIXELS; i++) { 101 | pixels.clear(); 102 | pixels.setPixelColor(i, pixels.Color(0, 0, 255)); 103 | pixels.show(); 104 | tone(PINBUZZER, (currentFrequency*(i+1))); 105 | delay(25); 106 | } 107 | for (int i=7; i>=0; i--) { 108 | pixels.clear(); 109 | pixels.setPixelColor(i, pixels.Color(0, 0, 255)); 110 | pixels.show(); 111 | tone(PINBUZZER, (currentFrequency*(i+1))); 112 | delay(25); 113 | } 114 | noTone(PINBUZZER); 115 | 116 | } 117 | } 118 | 119 | int getRPM(bool real){ 120 | if(real){ 121 | //read RPM from CAN-BUS 122 | //TODO 123 | }else{ 124 | //simulate RPMs for development and debug 125 | return millis()%maxRPM; 126 | } 127 | } 128 | 129 | void updateAlerts(){ 130 | if(currentRPM<(perfectRPM-1000)){ 131 | for (int i = 0; i < NUMPIXELS; i++) { 132 | pixels.setPixelColor(i, pixels.Color(0, 0, 0)); 133 | } 134 | } 135 | 136 | if(currentRPM>=(perfectRPM-1000) && currentRPM<(perfectRPM-800)){ 137 | pixels.setPixelColor(0, pixels.Color(0, 255, 0)); 138 | pixels.setPixelColor(7, pixels.Color(0, 255, 0)); 139 | } 140 | 141 | if(currentRPM>=(perfectRPM-800) && currentRPM<(perfectRPM-600)){ 142 | pixels.setPixelColor(0, pixels.Color(0, 255, 0)); 143 | pixels.setPixelColor(1, pixels.Color(0, 255, 0)); 144 | pixels.setPixelColor(6, pixels.Color(0, 255, 0)); 145 | pixels.setPixelColor(7, pixels.Color(0, 255, 0)); 146 | } 147 | 148 | if(currentRPM>=(perfectRPM-600) && currentRPM<(perfectRPM-400)){ 149 | pixels.setPixelColor(0, pixels.Color(0, 255, 0)); 150 | pixels.setPixelColor(1, pixels.Color(0, 255, 0)); 151 | pixels.setPixelColor(2, pixels.Color(0, 255, 0)); 152 | pixels.setPixelColor(5, pixels.Color(0, 255, 0)); 153 | pixels.setPixelColor(6, pixels.Color(0, 255, 0)); 154 | pixels.setPixelColor(7, pixels.Color(0, 255, 0)); 155 | } 156 | 157 | if(currentRPM>=(perfectRPM-400) && currentRPM<(perfectRPM-200)){ 158 | for (int i = 0; i < NUMPIXELS; i++) { 159 | pixels.setPixelColor(i, pixels.Color(0, 255, 0)); 160 | } 161 | } 162 | 163 | if(currentRPM>=(perfectRPM-200) && currentRPM<(perfectRPM+200)){ 164 | for (int i = 0; i < NUMPIXELS; i++) { 165 | pixels.setPixelColor(i, pixels.Color(0, 0, 255)); 166 | } 167 | } 168 | 169 | if(currentRPM>=(perfectRPM+200) && currentRPM<(perfectRPM+500)){ 170 | for (int i = 0; i < NUMPIXELS; i++) { 171 | pixels.setPixelColor(i, pixels.Color(255, 0, 0)); 172 | } 173 | } 174 | 175 | if(currentRPM>=(perfectRPM+500)){ 176 | for (int i = 0; i < NUMPIXELS; i++) { 177 | pixels.setPixelColor(i, pixels.Color(255, 0, 0)); 178 | } 179 | pixels.show(); 180 | if(!mute){ 181 | tone(PINBUZZER, currentFrequency*8); 182 | } 183 | delay(50); 184 | pixels.clear(); 185 | pixels.show(); 186 | noTone(PINBUZZER); 187 | delay(50); 188 | } 189 | } 190 | 191 | void mcpSetup(){ 192 | if(CAN0.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ) == CAN_OK){ 193 | Serial.println("MCP2515 Initialized Successfully!"); 194 | }else{ 195 | Serial.println("Error Initializing MCP2515..."); 196 | } 197 | CAN0.setMode(MCP_NORMAL); // Set operation mode to normal so the MCP2515 sends acks to received data. 198 | pinMode(CAN0_INT, INPUT); // Configuring pin for /INT input 199 | printSpacer(); 200 | } 201 | 202 | void mcpRead(){ 203 | if(!digitalRead(CAN0_INT)) // If CAN0_INT pin is low, read receive buffer 204 | { 205 | CAN0.readMsgBuf(&rxId, &len, rxBuf); // Read data: len = data length, buf = data byte(s) 206 | 207 | if((rxId & 0x80000000) == 0x80000000) // Determine if ID is standard (11 bits) or extended (29 bits) 208 | sprintf(msgString, "Extended ID: 0x%.8lX DLC: %1d Data:", (rxId & 0x1FFFFFFF), len); 209 | else 210 | sprintf(msgString, "Standard ID: 0x%.3lX DLC: %1d Data:", rxId, len); 211 | 212 | Serial.print(msgString); 213 | 214 | if((rxId & 0x40000000) == 0x40000000){ // Determine if message is a remote request frame. 215 | sprintf(msgString, " REMOTE REQUEST FRAME"); 216 | Serial.print(msgString); 217 | } else { 218 | for(byte i = 0; i