#include "../core/module.h" #include "../core/ui.h" #include "../core/pins.h" #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 log; 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 (log) log.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 && !log) { SD.mkdir("/logs"); log = SD.open("/logs/can.txt", FILE_APPEND); } if (log) log.printf("%08lX %d [", (unsigned long)id, dlc); for (int i = 0; i < dlc; i++) { if (log) log.printf("%02X ", data[i]); } if (log) log.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); // Reset + config for passive listening (stub; real impl uses full MCP2515 init) say("CAN init %s", speed < 3 ? "ok" : "fail"); } bool readFrame(uint32_t& id, uint8_t& dlc, uint8_t* data) { // Stub: real impl reads MCP2515 RXn buffers via SPI // For now, return false (no frames) return false; } }; Module* makeCan() { return new CanSniff(); }