diff --git a/dumperwithfilter.ino b/dumperwithfilter.ino index 815ef21..44cb4fc 100644 --- a/dumperwithfilter.ino +++ b/dumperwithfilter.ino @@ -9,21 +9,51 @@ MCP_CAN CAN(CAN_CS_PIN); const byte CAN_SPEED = CAN_500KBPS; const byte OSC = MCP_8MHZ; -const size_t MAX_IDS = 256; +// REDUCED: 64 IDs (saves memory) +const size_t MAX_IDS = 64; uint32_t seenIds[MAX_IDS]; uint16_t seenCounts[MAX_IDS]; +uint16_t changeCounts[MAX_IDS]; // NEW: Track number of changes size_t idsUsed = 0; +// Store last seen data for change detection +byte lastData[MAX_IDS][8]; +byte lastDataLen[MAX_IDS]; + +// REDUCED: 16 filter IDs (saves 64 bytes) 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 +bool filterEnabled = false; +bool filterReverse = false; -char lineBuf[64]; +// REDUCED: 48 bytes linebuf (saves 16 bytes) +char lineBuf[48]; size_t lineLen = 0; +bool showTxOutput = true; +bool showChangeOnly = false; +uint8_t frameTypeFilter = 0; + +// ========== Send Task Structure ========== +struct SendTask { + unsigned long id; + unsigned long interval_ms; + unsigned long last_send_ms; + byte data[8]; + byte len; + bool active; + bool extended; +}; + +const size_t MAX_SEND_TASKS = 4; +SendTask sendTasks[MAX_SEND_TASKS]; +bool canSendMode = false; + +// ========== Functions ========== + inline uint32_t packId(unsigned long id, bool extended) { - return (extended ? 0x80000000UL : 0) | (id & 0x1FFFFFFFUL); + 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; } @@ -35,44 +65,110 @@ int16_t findSeenIndex(uint32_t packed) { return -1; } -bool rememberId(uint32_t packed) { - int16_t idx = findSeenIndex(packed); +// UPDATED: Store OLD data before updating, return change status +// Returns: 0=new ID, 1=no change, 2=data changed +uint8_t checkAndUpdateData(uint32_t packed, byte* newData, byte newLen, int16_t &idx, byte* oldData, byte &oldLen) { + idx = findSeenIndex(packed); + if (idx >= 0) { if (seenCounts[idx] != 0xFFFF) seenCounts[idx]++; - return false; + + // Store old data + oldLen = lastDataLen[idx]; + for (byte i = 0; i < oldLen; i++) { + oldData[i] = lastData[idx][i]; + } + + // Check if data changed + bool changed = false; + if (lastDataLen[idx] != newLen) { + changed = true; + } else { + for (byte i = 0; i < newLen; i++) { + if (lastData[idx][i] != newData[i]) { + changed = true; + break; + } + } + } + + // NEW: Increment change counter if data changed + if (changed && changeCounts[idx] != 0xFFFF) { + changeCounts[idx]++; + } + + // Update stored data + lastDataLen[idx] = newLen; + for (byte i = 0; i < newLen; i++) { + lastData[idx][i] = newData[i]; + } + + return changed ? 2 : 1; } + + // New ID if (idsUsed < MAX_IDS) { + idx = idsUsed; seenIds[idsUsed] = packed; seenCounts[idsUsed] = 1; + changeCounts[idsUsed] = 0; // NEW: Initialize change count to 0 + lastDataLen[idsUsed] = newLen; + for (byte i = 0; i < newLen; i++) { + lastData[idsUsed][i] = newData[i]; + } idsUsed++; + return 0; // New ID } - return true; + + return 1; // Array full } void printSummary() { - Serial.print(F("\nSummary: ")); + Serial.print(F("\nSum: ")); Serial.print(idsUsed); - Serial.println(F(" unique IDs seen.")); + Serial.println(F(" IDs")); 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(F(" 0x")); Serial.print(id, HEX); - if (ext) Serial.print(F(" (ext)")); - Serial.print(F(" count:")); - Serial.println(seenCounts[i]); + if (ext) Serial.print(F("(E)")); + Serial.print(F(" n: ")); + Serial.print(seenCounts[i]); + Serial.print(F(" chg:")); // NEW: Show change count + Serial.print(changeCounts[i]); + Serial.print(F(" [")); + for (byte j = 0; j < lastDataLen[i]; j++) { + if (lastData[i][j] < 16) Serial.print('0'); + Serial.print(lastData[i][j], HEX); + if (j < lastDataLen[i] - 1) Serial.print(' '); + } + Serial.println(F("]")); } Serial.println(); } -bool idInFilter(unsigned long id) { - if (!filterEnabled || filterCount == 0) return true; +bool isIdInList(unsigned long id) { for (size_t i = 0; i < filterCount; i++) { if (filterIds[i] == id) return true; } return false; } +bool shouldDisplayId(unsigned long id) { + if (! filterEnabled) return true; + if (filterCount == 0) return false; + bool inList = isIdInList(id); + return filterReverse ? !inList : inList; +} + +bool shouldDisplayFrameType(bool extended) { + if (frameTypeFilter == 0) return true; + if (frameTypeFilter == 1) return extended; + if (frameTypeFilter == 2) return !extended; + return true; +} + bool addFilterId(unsigned long id) { for (size_t i = 0; i < filterCount; i++) { if (filterIds[i] == id) return false; @@ -87,16 +183,26 @@ void clearFilter() { } void listFilter() { - Serial.print(F("Filter ")); - Serial.print(filterEnabled ? F("ENABLED") : F("DISABLED")); - Serial.print(F(". IDs (")); + Serial.print(F("Filt ")); + Serial.print(filterEnabled ? F("ON") : F("OFF")); + Serial.print(F(" ")); + Serial.print(filterReverse ? F("REV") : F("NOR")); + Serial.print(F(" (")); Serial.print(filterCount); - Serial.println(F("):")); + 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(" ")); + if (filterCount == 0 && filterEnabled) Serial.println(F(" (no output)")); + + Serial.print(F("Frame: ")); + if (frameTypeFilter == 0) Serial.println(F("BOTH")); + else if (frameTypeFilter == 1) Serial.println(F("EXT")); + else Serial.println(F("NORM")); + + Serial.print(F("Change: ")); + Serial.println(showChangeOnly ? F("ON") : F("OFF")); } static void trimInPlace(char* s) { @@ -115,8 +221,7 @@ static char* nextToken(char* &p) { } bool parseHexToken(const char* tok, unsigned long &out) { - if (!tok || !*tok) return false; - // optional 0x/0X + if (! tok || ! *tok) return false; if ((tok[0] == '0') && (tok[1] == 'x' || tok[1] == 'X')) tok += 2; char* endp = nullptr; unsigned long val = strtoul(tok, &endp, 16); @@ -125,19 +230,239 @@ bool parseHexToken(const char* tok, unsigned long &out) { 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")); +bool parseDecToken(const char* tok, unsigned long &out) { + if (! tok || !*tok) return false; + char* endp = nullptr; + unsigned long val = strtoul(tok, &endp, 10); + if (endp == tok) return false; + out = val; + return true; } +// ========== Send Task Functions ========== + +void initSendTasks() { + for (size_t i = 0; i < MAX_SEND_TASKS; i++) { + sendTasks[i].active = false; + } +} + +void switchToSendMode() { + if (! canSendMode) { + CAN.setMode(MCP_NORMAL); + canSendMode = true; + Serial.println(F("Mode: NORM")); + } +} + +void switchToListenMode() { + if (canSendMode) { + CAN.setMode(MCP_LISTENONLY); + canSendMode = false; + Serial. println(F("Mode: LIST")); + } +} + +int8_t findFreeSendTaskSlot() { + for (size_t i = 0; i < MAX_SEND_TASKS; i++) { + if (!sendTasks[i].active) return (int8_t)i; + } + return -1; +} + +int8_t findTaskByIdIdx(unsigned long id) { + for (size_t i = 0; i < MAX_SEND_TASKS; i++) { + if (sendTasks[i].active && sendTasks[i].id == id) { + return (int8_t)i; + } + } + return -1; +} + +bool addSendTask(unsigned long id, bool extended, unsigned long interval_ms, byte* data, byte len) { + int8_t existingSlot = findTaskByIdIdx(id); + int8_t slot; + + if (existingSlot >= 0) { + slot = existingSlot; + Serial.print(F("Upd #")); + Serial.print(slot); + Serial.print(F(" 0x")); + Serial.println(id, HEX); + } else { + slot = findFreeSendTaskSlot(); + if (slot < 0) { + Serial.println(F("Full")); + return false; + } + Serial.print(F("Task #")); + Serial.print(slot); + Serial.print(F(" 0x")); + Serial.print(id, HEX); + } + + if (len > 8) len = 8; + + sendTasks[slot].id = id; + sendTasks[slot].extended = extended; + sendTasks[slot].interval_ms = interval_ms; + sendTasks[slot].len = len; + for (byte i = 0; i < len; i++) { + sendTasks[slot].data[i] = data[i]; + } + sendTasks[slot]. last_send_ms = millis(); + sendTasks[slot]. active = true; + + switchToSendMode(); + + if (extended) Serial.print(F("(E)")); + Serial.print(F(" @")); + Serial.print(interval_ms); + Serial.println(F("ms")); + + return true; +} + +void stopSendTask(size_t index) { + if (index >= MAX_SEND_TASKS || ! sendTasks[index].active) { + Serial.println(F("Invalid")); + return; + } + sendTasks[index].active = false; + Serial.print(F("Stop #")); + Serial.println(index); + + bool anyActive = false; + for (size_t i = 0; i < MAX_SEND_TASKS; i++) { + if (sendTasks[i].active) { + anyActive = true; + break; + } + } + if (! anyActive) switchToListenMode(); +} + +void stopAllSendTasks() { + for (size_t i = 0; i < MAX_SEND_TASKS; i++) { + sendTasks[i].active = false; + } + Serial.println(F("Stop all")); + switchToListenMode(); +} + +void listSendTasks() { + Serial.println(F("\n=== Tasks ===")); + bool anyActive = false; + for (size_t i = 0; i < MAX_SEND_TASKS; i++) { + if (sendTasks[i]. active) { + anyActive = true; + Serial.print(F("#")); + Serial.print(i); + Serial.print(F(" 0x")); + Serial.print(sendTasks[i].id, HEX); + if (sendTasks[i].extended) Serial.print(F("(E)")); + Serial.print(F(" @")); + Serial.print(sendTasks[i].interval_ms); + Serial.print(F("ms [")); + for (byte j = 0; j < sendTasks[i].len; j++) { + if (sendTasks[i].data[j] < 16) Serial.print('0'); + Serial.print(sendTasks[i].data[j], HEX); + if (j < sendTasks[i]. len - 1) Serial.print(' '); + } + Serial.println(F("]")); + } + } + if (!anyActive) Serial.println(F("")); + Serial.print(F("TX: ")); + Serial.println(showTxOutput ? F("ON") : F("OFF")); + Serial.println(); +} + +void processSendTasks() { + unsigned long now = millis(); + for (size_t i = 0; i < MAX_SEND_TASKS; i++) { + if (sendTasks[i]. active) { + if (now - sendTasks[i].last_send_ms >= sendTasks[i].interval_ms) { + byte sndStat = sendTasks[i].extended ? + CAN.sendMsgBuf(sendTasks[i]. id, 1, sendTasks[i].len, sendTasks[i].data) : + CAN.sendMsgBuf(sendTasks[i]. id, 0, sendTasks[i].len, sendTasks[i].data); + + if (showTxOutput) { + if (sndStat == CAN_OK) { + Serial.print(F("[TX#")); + Serial.print(i); + Serial.print(F("] 0x")); + Serial.println(sendTasks[i].id, HEX); + } else { + Serial.print(F("[ERR#")); + Serial.print(i); + Serial.print(F(": ")); + Serial.print(sndStat); + Serial. println(F("]")); + } + } + + sendTasks[i].last_send_ms = now; + } + } + } +} + +bool sendSinglePacket(unsigned long id, bool extended, byte* data, byte len) { + if (! canSendMode) switchToSendMode(); + + byte sndStat = extended ? + CAN.sendMsgBuf(id, 1, len, data) : + CAN.sendMsgBuf(id, 0, len, data); + + if (showTxOutput) { + if (sndStat == CAN_OK) { + Serial.print(F("[TX] 0x")); + Serial.print(id, HEX); + if (extended) Serial.print(F("(E)")); + Serial.print(F(" [")); + for (byte i = 0; i < len; i++) { + if (data[i] < 16) Serial.print('0'); + Serial.print(data[i], HEX); + if (i < len - 1) Serial.print(' '); + } + Serial.println(F("]")); + return true; + } else { + Serial.print(F("[TX ERR: ")); + Serial.print(sndStat); + Serial.println(F("]")); + return false; + } + } + + return (sndStat == CAN_OK); +} + +// ========== Help ========== + +void printHelp() { + Serial.println(F("\n=== Log ===")); + Serial.println(F("s summary")); + Serial.println(F("c clear")); + Serial.println(F("fe/fd filt on/off")); + Serial.println(F("fr toggle rev")); + Serial.println(F("fl list filt")); + Serial.println(F("fx clear filt")); + Serial.println(F("f/a set/add")); + Serial.println(F("fb/fext/fnorm")); + Serial.println(F("ce/cd change on/off")); + + Serial.println(F("\n=== Send ===")); + Serial.println(F("w ")); + Serial.println(F("t ")); + Serial.println(F("tl/ts/tx")); + Serial.println(F("txe/txd")); + Serial.println(F("mn/ml")); +} + +// ========== Command Handler ========== + void handleCommandLine(char* line) { trimInPlace(line); if (line[0] == 0) return; @@ -148,52 +473,85 @@ void handleCommandLine(char* 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.")); + Serial.println(F("Cleared")); + return; + } + + if (strcasecmp(cmd, "ce") == 0) { + showChangeOnly = true; + Serial.println(F("Change ON")); + return; + } + if (strcasecmp(cmd, "cd") == 0) { + showChangeOnly = false; + Serial.println(F("Change OFF")); + return; + } + + if (strcasecmp(cmd, "fb") == 0) { + frameTypeFilter = 0; + Serial.println(F("Frame: BOTH")); + return; + } + if (strcasecmp(cmd, "fext") == 0) { + frameTypeFilter = 1; + Serial.println(F("Frame: EXT")); + return; + } + if (strcasecmp(cmd, "fnorm") == 0) { + frameTypeFilter = 2; + Serial.println(F("Frame: NORM")); return; } if (strcasecmp(cmd, "fe") == 0) { filterEnabled = true; - Serial.println(F("Filter ENABLED.")); + Serial.println(F("Filter ON")); listFilter(); return; } if (strcasecmp(cmd, "fd") == 0) { filterEnabled = false; - Serial.println(F("Filter DISABLED. All IDs will be shown.")); + Serial.println(F("Filter OFF")); return; } + + if (strcasecmp(cmd, "fr") == 0) { + filterReverse = ! filterReverse; + Serial. print(F("Mode: ")); + Serial.println(filterReverse ? F("REV") : F("NORM")); + listFilter(); + return; + } + if (strcasecmp(cmd, "fl") == 0) { listFilter(); return; } if (strcasecmp(cmd, "fx") == 0) { clearFilter(); - Serial.println(F("Filter list cleared.")); + Serial.println(F("Filt clear")); 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. print(F("Filt: 0x")); Serial.println(id, HEX); return; } - Serial.println(F("Invalid hex after 'f'. Example: f 100")); + Serial.println(F("Bad ID")); return; } @@ -202,39 +560,142 @@ void handleCommandLine(char* line) { 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.")); + Serial.print(F("Add 0x")); + Serial.println(id, HEX); } else { - Serial.println(F("Failed to add (duplicate or list full).")); + Serial. println(F("Fail")); } filterEnabled = true; return; } - Serial.println(F("Invalid hex after 'a'. Example: a 203")); + Serial.println(F("Bad ID")); return; } + + if (strcasecmp(cmd, "mn") == 0) { + switchToSendMode(); + return; + } + if (strcasecmp(cmd, "ml") == 0) { + stopAllSendTasks(); + return; + } + + if (strcasecmp(cmd, "txe") == 0) { + showTxOutput = true; + Serial.println(F("TX ON")); + return; + } + if (strcasecmp(cmd, "txd") == 0) { + showTxOutput = false; + Serial.println(F("TX OFF")); + return; + } + + if (strcasecmp(cmd, "w") == 0) { + char* idTok = nextToken(p); + unsigned long id; + if (! parseHexToken(idTok, id)) { + Serial.println(F("Bad ID")); + return; + } + + byte data[8]; + byte len = 0; + char* dataTok; + while ((dataTok = nextToken(p)) != nullptr && len < 8) { + unsigned long byteVal; + if (parseHexToken(dataTok, byteVal)) { + data[len++] = (byte)byteVal; + } + } + + if (len == 0) { + Serial.println(F("No data")); + return; + } + + sendSinglePacket(id, false, data, len); + return; + } + + if (strcasecmp(cmd, "t") == 0) { + char* idTok = nextToken(p); + char* intervalTok = nextToken(p); + + unsigned long id, interval; + if (! parseHexToken(idTok, id) || !parseDecToken(intervalTok, interval)) { + Serial.println(F("Bad args")); + return; + } + + byte data[8]; + byte len = 0; + char* dataTok; + while ((dataTok = nextToken(p)) != nullptr && len < 8) { + unsigned long byteVal; + if (parseHexToken(dataTok, byteVal)) { + data[len++] = (byte)byteVal; + } + } + + if (len == 0) { + Serial.println(F("No data")); + return; + } + + addSendTask(id, false, interval, data, len); + return; + } + + if (strcasecmp(cmd, "tl") == 0) { + listSendTasks(); + return; + } + + if (strcasecmp(cmd, "ts") == 0) { + char* indexTok = nextToken(p); + unsigned long index; + if (!parseDecToken(indexTok, index)) { + Serial.println(F("Bad idx")); + return; + } + stopSendTask((size_t)index); + return; + } + + if (strcasecmp(cmd, "tx") == 0) { + stopAllSendTasks(); + return; + } + printHelp(); } +// ========== Setup ========== + void setup() { Serial.begin(115200); delay(50); pinMode(CAN_INT_PIN, INPUT); + initSendTasks(); - if (CAN.begin(MCP_ANY, CAN_SPEED, OSC) != CAN_OK) { - Serial.println(F("CAN init failed. Check wiring, bitrate, and 8 MHz oscillator.")); + if (CAN. begin(MCP_ANY, CAN_SPEED, OSC) != CAN_OK) { + Serial.println(F("CAN fail")); while (1) { delay(1000); } } - CAN.setMode(MCP_LISTENONLY); // sniffer mode - Serial.println(F("CAN listen-only started.")); + CAN.setMode(MCP_LISTENONLY); + Serial.println(F("CAN Ready")); printHelp(); listFilter(); } +// ========== Loop ========== + void loop() { + // Serial commands while (Serial.available() > 0) { char ch = (char)Serial.read(); if (ch == '\r') { @@ -258,46 +719,72 @@ void loop() { } } + // Process send tasks + processSendTasks(); + + // Receive CAN 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; - } + 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; + + // Check and update data, store old data + byte oldData[8]; + byte oldLen; + int16_t idx; + uint8_t changeStatus = checkAndUpdateData(packed, buf, len, idx, oldData, oldLen); + + // Check if should display based on filters + bool passesFilters = shouldDisplayId(id) && shouldDisplayFrameType(extended); + + // If change detection is ON, only show if data changed + if (showChangeOnly && changeStatus != 2) { + continue; // Skip if not changed (status 0=new, 1=no change, 2=changed) } - - // 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); + + if (passesFilters) { + // Show change indicator and old->new + if (showChangeOnly && changeStatus == 2) { + Serial.print(F("[CHG] 0x")); + Serial.print(id, HEX); + if (extended) Serial.print(F("(E)")); + Serial.print(F(" [")); + + // Show OLD data + for (byte i = 0; i < oldLen; i++) { + if (oldData[i] < 16) Serial.print('0'); + Serial.print(oldData[i], HEX); + if (i < oldLen - 1) Serial.print(' '); + } + + Serial.print(F("]->[")); + + // Show NEW data + for (byte i = 0; i < len; i++) { + if (buf[i] < 16) Serial.print('0'); + Serial.print(buf[i], HEX); + if (i < len - 1) Serial.print(' '); + } + Serial.println(F("]")); + } else { + // Normal display + Serial.print(millis()); + Serial.print(F("ms 0x")); + Serial.print(id, HEX); + if (extended) Serial.print(F("(E)")); + Serial.print(F(" [")); + for (byte i = 0; i < len; i++) { + if (buf[i] < 16) Serial.print('0'); + Serial.print(buf[i], HEX); + if (i < len - 1) Serial.print(' '); + } + Serial.println(F("]")); + } } } }