Add T31 CAN fold-on-lock Arduino firmware.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
kykaevv 2026-09-01 13:26:49 +03:00
commit f182df290f
9 changed files with 704 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.pio/
.vscode/
*.elf
*.hex
*.bin
*.map
build/
*.bak
Thumbs.db

80
README.md Normal file
View File

@ -0,0 +1,80 @@
# AndruinoXTrail — Nissan X-Trail T31
Складывание зеркал при закрытии машины. Arduino + MCP2515, шина CAN COMM 500 кбит/с.
## Кузов T31
T31 (20072014, в том числе 2014 рестайл) — та же CAN-семья, что Qashqai J10: одна основная шина **CAN COMM**, 500 кбит/с, 11-битные ID. Закрытие дверей BCM публикует в циклических кадрах. Команды «сложить зеркала» на этой шине нет: штатная кнопка в двери меняет полярность моторов.
Схема:
1. Слушаем **закрытие** по CAN (`0x60D` / `0x358`).
2. Складываем зеркала **реле**, параллельно штатной кнопке.
3. Replay кадра кнопки по CAN на T31 почти никогда не нужен: кнопки на OBD нет.
Колодка кнопки складывания у дорестайла и рестайла T31 разная. Реле ставить параллельно **вашей** кнопке.
## Кадры CAN COMM
| ID | Назначение | Полезные байты |
|---|---|---|
| `0x60D` | BCM: двери, зажигание, **замок** | `data[2] & 0x18` — закрыто. `data[1] & 0x0C`: `00` OFF, `04` ACC, `0C` ON, `08` START |
| `0x358` | Детализация замка | `data[5]` bit0 водитель, bit1 остальные двери |
| `0x745` / `0x765` | Диагностика BCM | Не используется |
Прошивка **не** шлёт команды закрытия/открытия дверей.
## Куда врезаться
- OBD: pin 6 CAN-H, pin 14 CAN-L
- или BCM за бардачком, зелёный 40-пин: **pin 20** CAN-H (синий), **pin 40** CAN-L (розовый)
На MCP2515 **снять** перемычку 120 Ом.
## Железо
- Arduino Nano или Uno
- MCP2515 (часто кварц **8 МГц**)
- Два реле 5 В на контакты штатной кнопки складывания
```
Arduino MCP2515
D10 -> CS
D11 -> SI (MOSI)
D12 -> SO (MISO)
D13 -> SCK
D2 -> INT
5V/GND -> VCC/GND
MCP2515 CAN-H -> OBD 6 (или BCM pin 20)
MCP2515 CAN-L -> OBD 14 (или BCM pin 40)
общий GND с кузовом
D7 -> реле СЛОЖИТЬ
D8 -> реле РАЗЛОЖИТЬ
```
Питание Arduino — OBD pin 16 (+12 В) через стабилизатор 5 В, либо постоянная 12 В с предохранителем.
Реле **параллельно** штатной кнопке, не вместо неё. Моторы зеркал с Arduino не питать.
## Прошивки
Библиотека: [coryjfowler/MCP_CAN_lib](https://github.com/coryjfowler/MCP_CAN_lib)
1. `firmware/CanSniffer/CanSniffer.ino` — Serial 115200. Закрыли машину → `60D lock=1`, открыли → `lock=0`. Клавиша `f` + кнопка зеркал: если `DIFF: (none)`, оставляйте реле.
2. `firmware/XTrailMirrors/XTrailMirrors.ino` — в `config.h` по умолчанию `FOLD_VIA_RELAY`.
Если кристалл MCP2515 16 МГц, прошивка сама пробует второй вариант.
## Поведение
- Старт: текущий замок запоминается, зеркала не трогаем.
- Замок 0→1 и зажигание OFF → складывание ~8 с.
- Замок 1→0 → раскладывание ~8 с.
- При ON/START автоматика молчит.
- Пауза 12 с между циклами.
## Безопасность
Подключайтесь на заглушенном авто, с предохранителем. Сначала только сниффер. Реле не держать дольше ~810 с.

View File

@ -0,0 +1,230 @@
/*
* 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);
}
}

View File

@ -0,0 +1,8 @@
#pragma once
// Nissan X-Trail T31 — IDs highlighted by the sniffer.
static const unsigned long CAN_ID_BCM_60D = 0x60D;
static const unsigned long CAN_ID_LOCK_358 = 0x358;
static const uint8_t BCM60D_B1_IGN_MASK = 0x0C;
static const uint8_t BCM60D_B2_LOCKED = 0x18;

View File

@ -0,0 +1,5 @@
#pragma once
static const uint8_t MCP_CS_PIN = 10;
static const unsigned long SERIAL_BAUD = 115200;
static const uint8_t MAX_TRACKED_IDS = 48;

View File

@ -0,0 +1,247 @@
/*
* Nissan X-Trail T31 fold mirrors on lock.
*
* Listens on CAN COMM (500 kbit/s, Qashqai J10 family) for BCM lock status
* (0x60D / 0x358). On a stable lock-with-ignition-off it folds the mirrors.
* Unlock unfolds them.
*
* T31 has no cyclic "fold mirrors" CAN frame. The door fold switch reverses
* motor polarity. Default actuator is a relay across that switch.
*
* Library: https://github.com/coryjfowler/MCP_CAN_lib
*
* Wiring:
* MCP2515 CS -> D10
* MCP2515 INT -> D2
* MCP2515 SI -> D11
* MCP2515 SO -> D12
* MCP2515 SCK -> D13
* MCP2515 VCC -> 5V, GND -> GND
* MCP2515 CAN-H / CAN-L -> OBD pin 6 / 14
* or BCM green 40-pin: pin 20 CAN-H (blue), pin 40 CAN-L (pink)
* Remove the 120 ohm jumper on the MCP2515 board.
*
* Relay FOLD -> D7 (parallels factory fold switch, fold direction)
* Relay UNFOLD -> D8 (unfold direction)
*/
#include <SPI.h>
#include <string.h>
#include <mcp_can.h>
#include "config.h"
static const uint8_t MCP_CS_PIN = 10;
MCP_CAN CAN(MCP_CS_PIN);
enum MirrorAction {
MIRROR_IDLE = 0,
MIRROR_FOLD,
MIRROR_UNFOLD
};
static bool saw60d = false;
static bool saw358 = false;
static bool locked60d = false;
static bool locked358 = false;
static bool ignOn = false;
static bool stableLocked = false;
static bool prevStableLocked = false;
static bool lockArmed = false;
static unsigned long lockEdgeMs = 0;
static MirrorAction action = MIRROR_IDLE;
static unsigned long actionStartMs = 0;
static unsigned long lastActionMs = 0;
static uint8_t canBurstLeft = 0;
static unsigned long nextCanMs = 0;
static void relayWrite(uint8_t pin, bool on) {
if (RELAY_ACTIVE_LOW) {
digitalWrite(pin, on ? LOW : HIGH);
} else {
digitalWrite(pin, on ? HIGH : LOW);
}
}
static void relaysOff() {
relayWrite(PIN_RELAY_FOLD, false);
relayWrite(PIN_RELAY_UNFOLD, false);
}
static bool canStartAction(MirrorAction next) {
unsigned long now = millis();
if (action != MIRROR_IDLE) {
return false;
}
if (now - lastActionMs < ACTION_COOLDOWN_MS && lastActionMs != 0) {
return false;
}
action = next;
actionStartMs = now;
lastActionMs = now;
#if FOLD_ACTUATOR == FOLD_VIA_RELAY
relaysOff();
if (next == MIRROR_FOLD) {
relayWrite(PIN_RELAY_FOLD, true);
} else {
relayWrite(PIN_RELAY_UNFOLD, true);
}
Serial.println(next == MIRROR_FOLD ? F("RELAY fold") : F("RELAY unfold"));
#else
if (FOLD_CAN_FRAME_COUNT == 0 || FOLD_CAN_ID == 0) {
Serial.println(F("CAN replay not configured — capture frames first"));
action = MIRROR_IDLE;
return false;
}
canBurstLeft = FOLD_CAN_FRAME_COUNT;
nextCanMs = now;
Serial.println(next == MIRROR_FOLD ? F("CAN fold") : F("CAN unfold"));
#endif
return true;
}
static void serviceAction() {
if (action == MIRROR_IDLE) {
return;
}
unsigned long now = millis();
#if FOLD_ACTUATOR == FOLD_VIA_RELAY
if (now - actionStartMs >= MIRROR_PULSE_MS) {
relaysOff();
action = MIRROR_IDLE;
Serial.println(F("RELAY done"));
}
#else
if (canBurstLeft > 0 && now >= nextCanMs) {
const unsigned long id = (action == MIRROR_FOLD) ? FOLD_CAN_ID : UNFOLD_CAN_ID;
const uint8_t len = (action == MIRROR_FOLD) ? FOLD_CAN_LEN : UNFOLD_CAN_LEN;
uint8_t buf[8];
const uint8_t *src = (action == MIRROR_FOLD) ? FOLD_CAN_DATA : UNFOLD_CAN_DATA;
memcpy(buf, src, 8);
CAN.sendMsgBuf(id, 0, len, buf);
canBurstLeft--;
nextCanMs = now + FOLD_CAN_INTERVAL_MS;
}
if (canBurstLeft == 0 && now - actionStartMs >= 500) {
action = MIRROR_IDLE;
Serial.println(F("CAN replay done"));
}
#endif
}
static bool currentlyLocked() {
if (saw60d && saw358) {
return locked60d && locked358;
}
if (saw60d) {
return locked60d;
}
if (saw358) {
return locked358;
}
return false;
}
static void onLockState(bool locked) {
unsigned long now = millis();
if (!lockArmed) {
stableLocked = locked;
prevStableLocked = locked;
lockArmed = true;
Serial.print(F("baseline LOCK="));
Serial.println(stableLocked ? 1 : 0);
return;
}
if (locked != stableLocked) {
if (lockEdgeMs == 0) {
lockEdgeMs = now;
}
if (now - lockEdgeMs >= LOCK_STABLE_MS) {
prevStableLocked = stableLocked;
stableLocked = locked;
lockEdgeMs = 0;
Serial.print(F("LOCK="));
Serial.print(stableLocked ? 1 : 0);
Serial.print(F(" IGN="));
Serial.println(ignOn ? 1 : 0);
if (REQUIRE_IGN_OFF && ignOn) {
return;
}
if (stableLocked && !prevStableLocked) {
canStartAction(MIRROR_FOLD);
} else if (!stableLocked && prevStableLocked) {
canStartAction(MIRROR_UNFOLD);
}
}
} else {
lockEdgeMs = 0;
}
}
static void parseFrame(unsigned long id, uint8_t len, const uint8_t *data) {
if (id == CAN_ID_BCM_60D && len >= 3) {
saw60d = true;
locked60d = bcm60dLocked(data[2]);
ignOn = bcm60dIgnOn(data[1]);
onLockState(currentlyLocked());
} else if (id == CAN_ID_LOCK_358 && len >= 6) {
saw358 = true;
locked358 = lock358FullyLocked(data[5]);
onLockState(currentlyLocked());
}
}
void setup() {
pinMode(PIN_RELAY_FOLD, OUTPUT);
pinMode(PIN_RELAY_UNFOLD, OUTPUT);
relaysOff();
Serial.begin(SERIAL_BAUD);
while (!Serial && millis() < 2000) {
}
Serial.println(F("XTrail T31 fold-on-lock"));
// If init fails, try MCP_16MHZ — some MCP2515 boards use a 16 MHz crystal.
if (CAN.begin(MCP_STDEXT, CAN_500KBPS, MCP_8MHZ) != CAN_OK) {
Serial.println(F("MCP2515 8 MHz failed, trying 16 MHz"));
if (CAN.begin(MCP_STDEXT, CAN_500KBPS, MCP_16MHZ) != CAN_OK) {
Serial.println(F("MCP2515 init failed"));
while (true) {
delay(1000);
}
}
}
CAN.init_Mask(0, 0, 0x07FF);
CAN.init_Filt(0, 0, CAN_ID_BCM_60D);
CAN.init_Filt(1, 0, CAN_ID_LOCK_358);
CAN.init_Mask(1, 0, 0x07FF);
CAN.init_Filt(2, 0, CAN_ID_BCM_60D);
CAN.init_Filt(3, 0, CAN_ID_LOCK_358);
CAN.init_Filt(4, 0, CAN_ID_BCM_60D);
CAN.init_Filt(5, 0, CAN_ID_LOCK_358);
CAN.setMode(MCP_NORMAL);
Serial.println(F("CAN 500k OK, waiting for 0x60D / 0x358"));
}
void loop() {
unsigned long id = 0;
uint8_t len = 0;
uint8_t data[8];
if (CAN.checkReceive() == CAN_MSGAVAIL) {
CAN.readMsgBuf(&len, data);
id = CAN.getCanId();
parseFrame(id, len, data);
}
serviceAction();
}

View File

@ -0,0 +1,45 @@
#pragma once
#include <Arduino.h>
// Nissan X-Trail T31 (20072014, including 2014 facelift).
// Same CAN COMM family as Qashqai J10: 500 kbit/s, 11-bit IDs.
// Tap: OBD pin 6/14, or BCM behind the glovebox (green 40-pin:
// pin 20 CAN-H blue, pin 40 CAN-L pink).
//
// Byte 0 = first data byte. Bit 0 = LSB of that byte.
static const unsigned long CAN_ID_BCM_60D = 0x60D;
static const uint8_t BCM60D_B0_DRIVER_OPEN = 0x10;
static const uint8_t BCM60D_B0_PASSENGER_OPEN = 0x20;
static const uint8_t BCM60D_B0_REAR_LH_OPEN = 0x40;
static const uint8_t BCM60D_B0_REAR_RH_OPEN = 0x80;
// data[1] bits 23: 00 OFF, 01 ACC, 11 ON, 10 START
static const uint8_t BCM60D_B1_IGN_MASK = 0x0C;
static const uint8_t BCM60D_B1_IGN_OFF = 0x00;
static const uint8_t BCM60D_B1_IGN_ACC = 0x04;
static const uint8_t BCM60D_B1_IGN_START = 0x08;
static const uint8_t BCM60D_B1_IGN_ON = 0x0C;
// data[2] C.4 / C.5 — any door locked
static const uint8_t BCM60D_B2_LOCKED = 0x18;
static const unsigned long CAN_ID_LOCK_358 = 0x358;
static const uint8_t LOCK358_B5_DRIVER_LOCKED = 0x01;
static const uint8_t LOCK358_B5_OTHER_LOCKED = 0x02;
inline bool bcm60dIgnOn(uint8_t b1) {
uint8_t ign = b1 & BCM60D_B1_IGN_MASK;
return ign == BCM60D_B1_IGN_ON || ign == BCM60D_B1_IGN_START;
}
inline bool bcm60dLocked(uint8_t b2) {
return (b2 & BCM60D_B2_LOCKED) != 0;
}
inline bool lock358FullyLocked(uint8_t b5) {
return (b5 & (LOCK358_B5_DRIVER_LOCKED | LOCK358_B5_OTHER_LOCKED)) ==
(LOCK358_B5_DRIVER_LOCKED | LOCK358_B5_OTHER_LOCKED);
}

View File

@ -0,0 +1,39 @@
#pragma once
#include "can_ids.h"
// Fold actuator:
// FOLD_VIA_RELAY — emulate the factory fold switch (T31: analog in the door)
// FOLD_VIA_CAN — replay frames captured with CanSniffer while pressing
// the door fold button. On T31 that button is not on OBD CAN.
#define FOLD_VIA_RELAY 1
#define FOLD_VIA_CAN 2
#define FOLD_ACTUATOR FOLD_VIA_RELAY
// Relay pins. Typical Arduino relay module is active LOW.
static const uint8_t PIN_RELAY_FOLD = 7;
static const uint8_t PIN_RELAY_UNFOLD = 8;
static const bool RELAY_ACTIVE_LOW = true;
// Mirror motors need ~8 s. Do not overlap fold/unfold.
static const uint16_t MIRROR_PULSE_MS = 8000;
static const uint16_t LOCK_STABLE_MS = 400;
static const uint16_t ACTION_COOLDOWN_MS = 12000;
// Fold only after ignition is off (do not fold while driving).
static const bool REQUIRE_IGN_OFF = true;
static const unsigned long SERIAL_BAUD = 115200;
// Fill these after a successful CanSniffer capture of the fold button.
// Leave FOLD_CAN_FRAME_COUNT = 0 until you have real payloads from THIS car.
static const unsigned long FOLD_CAN_ID = 0x000;
static const uint8_t FOLD_CAN_LEN = 8;
static const uint8_t FOLD_CAN_DATA[8] = {0, 0, 0, 0, 0, 0, 0, 0};
static const unsigned long UNFOLD_CAN_ID = 0x000;
static const uint8_t UNFOLD_CAN_LEN = 8;
static const uint8_t UNFOLD_CAN_DATA[8] = {0, 0, 0, 0, 0, 0, 0, 0};
static const uint8_t FOLD_CAN_FRAME_COUNT = 0; // set to 10+ after capture
static const uint16_t FOLD_CAN_INTERVAL_MS = 40;

41
firmware/shared/can_ids.h Normal file
View File

@ -0,0 +1,41 @@
/*
* Nissan X-Trail T31 CAN map lock, doors, ignition, mirrors.
*
* Platform: T31 (20072014). Same CAN COMM as Qashqai J10.
* Bus: 500 kbit/s, 11-bit IDs.
*
* Tap:
* OBD-II pin 6 CAN-H, pin 14 CAN-L
* or BCM (behind glovebox), green 40-pin: pin 20 CAN-H blue, pin 40 CAN-L pink
* Do not fit extra 120 ohm the bus is already terminated.
*
* T31 has electric folding mirrors on some trims, but BCM does not publish
* a cyclic "fold mirrors" frame. The door switch reverses motor polarity.
*/
#pragma once
#define CAN_XTRAIL_BCM_STATUS 0x60D
/*
* 0x60D data[0]: door-open bits (driver bit4 rear RH bit7)
* 0x60D data[1] bits 2-3 ignition: 00 OFF, 01 ACC, 11 ON, 10 START
* 0x60D data[2] bits 3-4 locked (mask 0x18)
*/
#define CAN_XTRAIL_LOCK_DETAIL 0x358
/*
* 0x358 data[5]: bit0 driver locked, bit1 other doors locked
*/
#define CAN_XTRAIL_BCM_DIAG_REQ 0x745
#define CAN_XTRAIL_BCM_DIAG_RES 0x765
/*
* CONSULT ISO-TP. Not used for lock/unlock or fold in this project.
*/
/*
* Mirror fold/unfold is analog on T31 (switch in the door).
* Facelift T31 (20102014) uses a different fold-switch connector
* than pre-facelift. Parallel the switch with relays, do not drive
* motors from the Arduino.
*/