#include "../core/module.h" #include "../core/ui.h" #include "../core/pins.h" #include #include // CAN Bus sniffer. Listen on MCP2515 over SPI for OBD/automotive frames. // Passive only; displays frame ID, DLC, data; logs to SD. class CanSniff : public Module { static constexpr int CS = pins::PROBE[0]; bool running = false, sdOk = false; uint32_t fcount = 0, lastMs = 0; uint8_t speed = 0; // 0=500k, 1=250k, 2=125k char msg[3][40] = {{0},{0},{0}}; File logfile; public: const char* name() const override { return "CAN Bus"; } const char* blurb() const override { return "OBD/automotive sniffer"; } void onEnter() override { running = false; fcount = 0; sdOk = SD.begin(); initMcp2515(); } void onExit() override { running = false; if (logfile) logfile.close(); } bool onKey(char c) override { if (c == ' ') { running = !running; if (running) fcount = 0; return true; } if (c == 's') { speed = (speed + 1) % 3; initMcp2515(); return true; } if (c == 'c') { fcount = 0; say("cleared"); return true; } return false; } void tick() override { if (!running) return; if (millis() - lastMs < 50) return; lastMs = millis(); uint32_t id = 0; uint8_t dlc = 0, data[8] = {0}; if (readFrame(id, dlc, data)) { fcount++; if (sdOk && !logfile) { SD.mkdir("/logs"); logfile = SD.open("/logs/can.txt", FILE_APPEND); } if (logfile) logfile.printf("%08lX %d [", (unsigned long)id, dlc); for (int i = 0; i < dlc; i++) { if (logfile) logfile.printf("%02X ", data[i]); } if (logfile) logfile.println("]"); } } void draw() override { const char* sp[] = {"500k", "250k", "125k"}; ui::lineC(0, ui::accent(), "CAN %s %s", sp[speed], running ? "LISTEN" : "idle"); ui::line(1, "frames: %lu", (unsigned long)fcount); for (int i = 0; i < 3; i++) ui::line(3 + i, "%s", msg[i]); if (running) ui::spinner(228, ui::BODY_Y + 1, ui::glow()); ui::hintBar("[s]peed [c]lear [space]go [`]back"); } private: void say(const char* fmt, ...) { for (int i = 2; i > 0; i--) strncpy(msg[i], msg[i-1], 39); va_list ap; va_start(ap, fmt); vsnprintf(msg[0], 40, fmt, ap); va_end(ap); } void initMcp2515() { SPI.begin(pins::PROBE[3], pins::PROBE[4], pins::PROBE[5], CS); pinMode(CS, OUTPUT); digitalWrite(CS, HIGH); digitalWrite(CS, LOW); SPI.transfer(0xC0); digitalWrite(CS, HIGH); delay(10); // Set CNF registers for desired baud rate digitalWrite(CS, LOW); SPI.transfer(0x80); // Write instruction SPI.transfer(0x2A); // CNF1 address if (speed == 0) { SPI.transfer(0x00); SPI.transfer(0xA3); SPI.transfer(0x13); } // 500k else if (speed == 1) { SPI.transfer(0x00); SPI.transfer(0xA3); SPI.transfer(0x25); } // 250k else { SPI.transfer(0x00); SPI.transfer(0xA3); SPI.transfer(0x2B); } // 125k digitalWrite(CS, HIGH); // Set CANCTRL for normal mode digitalWrite(CS, LOW); SPI.transfer(0x80); SPI.transfer(0x0F); // CANCTRL address SPI.transfer(0x00); // Normal mode digitalWrite(CS, HIGH); say("CAN %s mode ok", speed == 0 ? "500k" : speed == 1 ? "250k" : "125k"); } bool readFrame(uint32_t& id, uint8_t& dlc, uint8_t* data) { digitalWrite(CS, LOW); SPI.transfer(0x03); // Read RX0 buffer status/id uint8_t sidh = SPI.transfer(0); uint8_t sidl = SPI.transfer(0); uint8_t eid8 = SPI.transfer(0); uint8_t eid0 = SPI.transfer(0); dlc = SPI.transfer(0) & 0x0F; for (int i = 0; i < dlc && i < 8; i++) data[i] = SPI.transfer(0); digitalWrite(CS, HIGH); id = ((uint32_t)sidh << 3) | ((sidl >> 5) & 0x07); return dlc > 0; } }; Module* makeCan() { return new CanSniff(); }