Update dumperwithfilter.ino

This commit is contained in:
Finn
2026-02-15 14:08:47 +01:00
committed by GitHub
parent 681b3774f7
commit 3af9977916

View File

@@ -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(" <none>"));
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 <hex> -> set single filter to <hex> (replaces list)"));
Serial.println(F(" a <hex> -> add <hex> 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("<none>"));
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 <id> 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 <id> <data>"));
Serial.println(F("t <id> <ms> <data>"));
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("]"));
}
}
}
}