231 lines
6.2 KiB
C++
231 lines
6.2 KiB
C++
/*
|
|
* Nissan X-Trail T31 — CAN sniffer for lock and mirror-fold capture.
|
|
*
|
|
* Use this first. Park, ignition OFF, serial 115200.
|
|
*
|
|
* Commands:
|
|
* l — mark LOCK snapshot (press after locking the car)
|
|
* u — mark UNLOCK snapshot
|
|
* f — mark FOLD snapshot (press after tapping the door fold switch)
|
|
* o — mark UNFOLD / open-mirrors snapshot
|
|
* d — dump last frames of all tracked IDs
|
|
* s — silent (only print 0x60D / 0x358)
|
|
* a — print all frames (verbose)
|
|
*
|
|
* Capture method for fold:
|
|
* 1. Type 'd' to see live IDs.
|
|
* 2. Type 'f', then immediately press the factory fold button.
|
|
* 3. Frames that changed vs the previous dump are printed as DIFF.
|
|
* Copy any new ID+payload into XTrailMirrors/config.h (FOLD_CAN_*).
|
|
*
|
|
* If DIFF is empty, the fold switch is not on this CAN. Use relay mode.
|
|
*
|
|
* Library: https://github.com/coryjfowler/MCP_CAN_lib
|
|
* MCP2515: CS=D10 INT=D2, 8 MHz crystal, 120 ohm jumper OFF.
|
|
* OBD: CAN-H pin 6, CAN-L pin 14.
|
|
* BCM (glovebox, green 40-pin): pin 20 CAN-H blue, pin 40 CAN-L pink.
|
|
*/
|
|
|
|
#include <SPI.h>
|
|
#include <string.h>
|
|
#include <mcp_can.h>
|
|
#include "config.h"
|
|
#include "can_ids.h"
|
|
|
|
MCP_CAN CAN(MCP_CS_PIN);
|
|
|
|
struct Tracked {
|
|
unsigned long id;
|
|
uint8_t len;
|
|
uint8_t data[8];
|
|
bool used;
|
|
};
|
|
|
|
static Tracked tracked[MAX_TRACKED_IDS];
|
|
static bool verbose = false;
|
|
static bool sawLockPrint = false;
|
|
static bool lastLocked = false;
|
|
static uint8_t last358 = 0xFF;
|
|
|
|
static Tracked *findOrAlloc(unsigned long id) {
|
|
Tracked *empty = 0;
|
|
for (uint8_t i = 0; i < MAX_TRACKED_IDS; i++) {
|
|
if (tracked[i].used && tracked[i].id == id) {
|
|
return &tracked[i];
|
|
}
|
|
if (!tracked[i].used && empty == 0) {
|
|
empty = &tracked[i];
|
|
}
|
|
}
|
|
return empty;
|
|
}
|
|
|
|
static void printFrame(unsigned long id, uint8_t len, const uint8_t *data) {
|
|
char line[80];
|
|
snprintf(line, sizeof(line), "%03lX [%u]", id, len);
|
|
Serial.print(line);
|
|
for (uint8_t i = 0; i < len && i < 8; i++) {
|
|
snprintf(line, sizeof(line), " %02X", data[i]);
|
|
Serial.print(line);
|
|
}
|
|
Serial.println();
|
|
}
|
|
|
|
static void printLockDecode(unsigned long id, uint8_t len, const uint8_t *data) {
|
|
if (id == CAN_ID_BCM_60D && len >= 3) {
|
|
bool locked = (data[2] & BCM60D_B2_LOCKED) != 0;
|
|
uint8_t ign = data[1] & BCM60D_B1_IGN_MASK;
|
|
if (locked != lastLocked || !sawLockPrint) {
|
|
Serial.print(F("60D lock="));
|
|
Serial.print(locked ? 1 : 0);
|
|
Serial.print(F(" ign="));
|
|
Serial.print(ign, HEX);
|
|
Serial.print(F(" b0="));
|
|
Serial.println(data[0], HEX);
|
|
lastLocked = locked;
|
|
sawLockPrint = true;
|
|
}
|
|
} else if (id == CAN_ID_LOCK_358 && len >= 6) {
|
|
if (data[5] != last358) {
|
|
last358 = data[5];
|
|
Serial.print(F("358 b5="));
|
|
Serial.println(data[5], HEX);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void dumpAll(const __FlashStringHelper *tag) {
|
|
Serial.println(tag);
|
|
for (uint8_t i = 0; i < MAX_TRACKED_IDS; i++) {
|
|
if (tracked[i].used) {
|
|
printFrame(tracked[i].id, tracked[i].len, tracked[i].data);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void snapshotAndDiff(const __FlashStringHelper *tag) {
|
|
Serial.println(tag);
|
|
Serial.println(F("Press the control now. Changed IDs follow (2s window)."));
|
|
|
|
Tracked before[MAX_TRACKED_IDS];
|
|
memcpy(before, tracked, sizeof(tracked));
|
|
|
|
unsigned long t0 = millis();
|
|
while (millis() - t0 < 2000) {
|
|
unsigned long id = 0;
|
|
uint8_t len = 0;
|
|
uint8_t data[8];
|
|
if (CAN.checkReceive() == CAN_MSGAVAIL) {
|
|
CAN.readMsgBuf(&len, data);
|
|
id = CAN.getCanId();
|
|
Tracked *slot = findOrAlloc(id);
|
|
if (slot) {
|
|
slot->id = id;
|
|
slot->len = len;
|
|
memset(slot->data, 0, 8);
|
|
memcpy(slot->data, data, len > 8 ? 8 : len);
|
|
slot->used = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
Serial.println(F("DIFF:"));
|
|
bool any = false;
|
|
for (uint8_t i = 0; i < MAX_TRACKED_IDS; i++) {
|
|
if (!tracked[i].used) {
|
|
continue;
|
|
}
|
|
Tracked *old = 0;
|
|
for (uint8_t j = 0; j < MAX_TRACKED_IDS; j++) {
|
|
if (before[j].used && before[j].id == tracked[i].id) {
|
|
old = &before[j];
|
|
break;
|
|
}
|
|
}
|
|
bool changed = false;
|
|
if (old == 0) {
|
|
changed = true;
|
|
} else if (old->len != tracked[i].len ||
|
|
memcmp(old->data, tracked[i].data, 8) != 0) {
|
|
changed = true;
|
|
}
|
|
if (changed) {
|
|
any = true;
|
|
Serial.print(F(" "));
|
|
printFrame(tracked[i].id, tracked[i].len, tracked[i].data);
|
|
}
|
|
}
|
|
if (!any) {
|
|
Serial.println(F(" (none — this action is probably not on OBD CAN)"));
|
|
}
|
|
}
|
|
|
|
void setup() {
|
|
Serial.begin(SERIAL_BAUD);
|
|
while (!Serial && millis() < 2000) {
|
|
}
|
|
memset(tracked, 0, sizeof(tracked));
|
|
|
|
Serial.println(F("XTrail T31 CAN sniffer"));
|
|
Serial.println(F("keys: l=lock u=unlock f=fold o=unfold d=dump s=quiet a=all"));
|
|
|
|
if (CAN.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ) != CAN_OK) {
|
|
Serial.println(F("MCP2515 8 MHz failed, trying 16 MHz"));
|
|
if (CAN.begin(MCP_ANY, CAN_500KBPS, MCP_16MHZ) != CAN_OK) {
|
|
Serial.println(F("MCP2515 init failed"));
|
|
while (true) {
|
|
delay(1000);
|
|
}
|
|
}
|
|
}
|
|
CAN.setMode(MCP_NORMAL);
|
|
Serial.println(F("CAN 500k listening"));
|
|
}
|
|
|
|
void loop() {
|
|
if (Serial.available()) {
|
|
char c = (char)Serial.read();
|
|
if (c == 'l' || c == 'L') {
|
|
snapshotAndDiff(F("CAPTURE LOCK"));
|
|
} else if (c == 'u' || c == 'U') {
|
|
snapshotAndDiff(F("CAPTURE UNLOCK"));
|
|
} else if (c == 'f' || c == 'F') {
|
|
snapshotAndDiff(F("CAPTURE FOLD BUTTON"));
|
|
} else if (c == 'o' || c == 'O') {
|
|
snapshotAndDiff(F("CAPTURE UNFOLD BUTTON"));
|
|
} else if (c == 'd' || c == 'D') {
|
|
dumpAll(F("DUMP"));
|
|
} else if (c == 's' || c == 'S') {
|
|
verbose = false;
|
|
Serial.println(F("quiet: 60D/358 only"));
|
|
} else if (c == 'a' || c == 'A') {
|
|
verbose = true;
|
|
Serial.println(F("verbose: all frames"));
|
|
}
|
|
}
|
|
|
|
unsigned long id = 0;
|
|
uint8_t len = 0;
|
|
uint8_t data[8];
|
|
if (CAN.checkReceive() != CAN_MSGAVAIL) {
|
|
return;
|
|
}
|
|
CAN.readMsgBuf(&len, data);
|
|
id = CAN.getCanId();
|
|
|
|
Tracked *slot = findOrAlloc(id);
|
|
if (slot) {
|
|
slot->id = id;
|
|
slot->len = len;
|
|
memset(slot->data, 0, 8);
|
|
memcpy(slot->data, data, len > 8 ? 8 : len);
|
|
slot->used = true;
|
|
}
|
|
|
|
if (verbose) {
|
|
printFrame(id, len, data);
|
|
} else if (id == CAN_ID_BCM_60D || id == CAN_ID_LOCK_358) {
|
|
printLockDecode(id, len, data);
|
|
}
|
|
}
|