commit 60dfc0a708f5bdb0ae09ecfe542c6e8bab024e41 Author: Finn <65090450+Finnn-glitch@users.noreply.github.com> Date: Fri Dec 26 23:51:29 2025 +0100 Add CAN bus sniffer functionality with filtering diff --git a/dumperwithfilter.ino b/dumperwithfilter.ino new file mode 100644 index 0000000..815ef21 --- /dev/null +++ b/dumperwithfilter.ino @@ -0,0 +1,303 @@ +#include +#include "mcp_can.h" + +const uint8_t CAN_CS_PIN = 8; +const uint8_t CAN_INT_PIN = 2; + +MCP_CAN CAN(CAN_CS_PIN); + +const byte CAN_SPEED = CAN_500KBPS; +const byte OSC = MCP_8MHZ; + +const size_t MAX_IDS = 256; +uint32_t seenIds[MAX_IDS]; +uint16_t seenCounts[MAX_IDS]; +size_t idsUsed = 0; + +const size_t MAX_FILTER_IDS = 16; +unsigned long filterIds[MAX_FILTER_IDS]; +size_t filterCount = 0; +bool filterEnabled = false; // set true to enable filtering at startup + +char lineBuf[64]; +size_t lineLen = 0; + +inline uint32_t packId(unsigned long id, bool extended) { + return (extended ? 0x80000000UL : 0) | (id & 0x1FFFFFFFUL); +} +inline bool isExtendedPacked(uint32_t packed) { return (packed & 0x80000000UL) != 0; } +inline unsigned long unpackId(uint32_t packed) { return packed & 0x1FFFFFFFUL; } + +int16_t findSeenIndex(uint32_t packed) { + for (size_t i = 0; i < idsUsed; i++) { + if (seenIds[i] == packed) return (int16_t)i; + } + return -1; +} + +bool rememberId(uint32_t packed) { + int16_t idx = findSeenIndex(packed); + if (idx >= 0) { + if (seenCounts[idx] != 0xFFFF) seenCounts[idx]++; + return false; + } + if (idsUsed < MAX_IDS) { + seenIds[idsUsed] = packed; + seenCounts[idsUsed] = 1; + idsUsed++; + } + return true; +} + +void printSummary() { + Serial.print(F("\nSummary: ")); + Serial.print(idsUsed); + Serial.println(F(" unique IDs seen.")); + for (size_t i = 0; i < idsUsed; i++) { + bool ext = isExtendedPacked(seenIds[i]); + unsigned long id = unpackId(seenIds[i]); + Serial.print(F(" ID:0x")); + Serial.print(id, HEX); + if (ext) Serial.print(F(" (ext)")); + Serial.print(F(" count:")); + Serial.println(seenCounts[i]); + } + Serial.println(); +} + +bool idInFilter(unsigned long id) { + if (!filterEnabled || filterCount == 0) return true; + for (size_t i = 0; i < filterCount; i++) { + if (filterIds[i] == id) return true; + } + return false; +} + +bool addFilterId(unsigned long id) { + for (size_t i = 0; i < filterCount; i++) { + if (filterIds[i] == id) return false; + } + if (filterCount >= MAX_FILTER_IDS) return false; + filterIds[filterCount++] = id; + return true; +} + +void clearFilter() { + filterCount = 0; +} + +void listFilter() { + Serial.print(F("Filter ")); + Serial.print(filterEnabled ? F("ENABLED") : F("DISABLED")); + Serial.print(F(". IDs (")); + Serial.print(filterCount); + Serial.println(F("):")); + for (size_t i = 0; i < filterCount; i++) { + Serial.print(F(" 0x")); + Serial.println(filterIds[i], HEX); + } + if (filterCount == 0) Serial.println(F(" ")); +} + +static void trimInPlace(char* s) { + while (*s == ' ' || *s == '\t') { memmove(s, s+1, strlen(s)); } + size_t n = strlen(s); + while (n && (s[n-1] == ' ' || s[n-1] == '\t')) { s[--n] = 0; } +} + +static char* nextToken(char* &p) { + while (*p == ' ' || *p == '\t') p++; + if (*p == 0) return nullptr; + char* start = p; + while (*p && *p != ' ' && *p != '\t') p++; + if (*p) { *p = 0; p++; } + return start; +} + +bool parseHexToken(const char* tok, unsigned long &out) { + if (!tok || !*tok) return false; + // optional 0x/0X + if ((tok[0] == '0') && (tok[1] == 'x' || tok[1] == 'X')) tok += 2; + char* endp = nullptr; + unsigned long val = strtoul(tok, &endp, 16); + if (endp == tok) return false; + out = val; + return true; +} + +void printHelp() { + Serial.println(F("\nCommands:")); + Serial.println(F(" s -> summary of all IDs seen")); + Serial.println(F(" c -> clear remembered IDs")); + Serial.println(F(" fe -> enable show-only filter")); + Serial.println(F(" fd -> disable show-only filter")); + Serial.println(F(" fl -> list filter IDs")); + Serial.println(F(" fx -> clear filter IDs")); + Serial.println(F(" f -> set single filter to (replaces list)")); + Serial.println(F(" a -> add to filter list")); + Serial.println(F("Examples: f 100 (only show 0x100), a 203 (also show 0x203), fe")); +} + +void handleCommandLine(char* line) { + trimInPlace(line); + if (line[0] == 0) return; + + Serial.print(F("> ")); + Serial.println(line); + + char* p = line; + char* cmd = nextToken(p); + + // Single-letter quick commands + if (strcmp(cmd, "s") == 0 || strcmp(cmd, "S") == 0) { + printSummary(); + return; + } + if (strcmp(cmd, "c") == 0 || strcmp(cmd, "C") == 0) { + idsUsed = 0; + Serial.println(F("Cleared remembered IDs.")); + return; + } + + if (strcasecmp(cmd, "fe") == 0) { + filterEnabled = true; + Serial.println(F("Filter ENABLED.")); + listFilter(); + return; + } + if (strcasecmp(cmd, "fd") == 0) { + filterEnabled = false; + Serial.println(F("Filter DISABLED. All IDs will be shown.")); + return; + } + if (strcasecmp(cmd, "fl") == 0) { + listFilter(); + return; + } + if (strcasecmp(cmd, "fx") == 0) { + clearFilter(); + Serial.println(F("Filter list cleared.")); + return; + } + + // Commands with arguments + if (strcasecmp(cmd, "f") == 0) { + // set single filter ID + char* tok = nextToken(p); + unsigned long id; + if (parseHexToken(tok, id)) { + clearFilter(); + addFilterId(id); + filterEnabled = true; + Serial.print(F("Filter set to only show ID 0x")); + Serial.println(id, HEX); + return; + } + Serial.println(F("Invalid hex after 'f'. Example: f 100")); + return; + } + + if (strcasecmp(cmd, "a") == 0) { + char* tok = nextToken(p); + unsigned long id; + if (parseHexToken(tok, id)) { + if (addFilterId(id)) { + Serial.print(F("Added 0x")); + Serial.print(id, HEX); + Serial.println(F(" to filter list.")); + } else { + Serial.println(F("Failed to add (duplicate or list full).")); + } + filterEnabled = true; + return; + } + Serial.println(F("Invalid hex after 'a'. Example: a 203")); + return; + } + printHelp(); +} + +void setup() { + Serial.begin(115200); + delay(50); + + pinMode(CAN_INT_PIN, INPUT); + + if (CAN.begin(MCP_ANY, CAN_SPEED, OSC) != CAN_OK) { + Serial.println(F("CAN init failed. Check wiring, bitrate, and 8 MHz oscillator.")); + while (1) { delay(1000); } + } + + CAN.setMode(MCP_LISTENONLY); // sniffer mode + Serial.println(F("CAN listen-only started.")); + printHelp(); + listFilter(); +} + +void loop() { + while (Serial.available() > 0) { + char ch = (char)Serial.read(); + if (ch == '\r') { + if (lineLen > 0) { + lineBuf[lineLen] = 0; + handleCommandLine(lineBuf); + lineLen = 0; + } + } else if (ch == '\n') { + lineBuf[lineLen] = 0; + handleCommandLine(lineBuf); + lineLen = 0; + } else { + if (lineLen < sizeof(lineBuf) - 1) { + lineBuf[lineLen++] = ch; + } else { + lineBuf[lineLen] = 0; + handleCommandLine(lineBuf); + lineLen = 0; + } + } + } + + while (CAN_MSGAVAIL == CAN.checkReceive()) { + unsigned long id = 0; + byte ext = 0; + byte len = 0; + byte buf[8]; + if (CAN.readMsgBuf(&id, &ext, &len, buf) != CAN_OK) { + continue; + } + bool extended = (ext != 0); + + uint32_t packed = packId(id, extended); + bool isNew = rememberId(packed); + + if (!idInFilter(id)) { + continue; + } + + // Dump the frame + Serial.print(millis()); + Serial.print(F("ms ")); + Serial.print(F("ID:0x")); + Serial.print(id, HEX); + if (extended) Serial.print(F(" (ext)")); + Serial.print(F(" DLC:")); + Serial.print(len); + Serial.print(F(" DATA:")); + for (byte i = 0; i < len; i++) { + if (buf[i] < 16) Serial.print('0'); + Serial.print(buf[i], HEX); + Serial.print(' '); + } + Serial.println(); + + // Optional: notify when an ID is seen for the first time + if (isNew) { + //Serial.print(F(" New ID observed -> 0x")); + //Serial.print(id, HEX); + //if (extended) Serial.print(F(" (ext)")); + //Serial.print(F(" | total unique: ")); + //Serial.println(idsUsed); + } + } +}