mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-06-29 10:35:24 +02:00
sniffer prototype
This commit is contained in:
parent
911bbd63be
commit
64f470a130
@ -51,6 +51,10 @@
|
||||
|
||||
#include "DCCEX.h"
|
||||
#include "Display_Implementation.h"
|
||||
#include "Sniffer.h"
|
||||
#include "DCCDecoder.h"
|
||||
Sniffer *dccSniffer = NULL;
|
||||
DCCDecoder *dccDecoder = NULL;
|
||||
|
||||
#ifdef CPU_TYPE_ERROR
|
||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h
|
||||
@ -124,6 +128,8 @@ void setup()
|
||||
// Start RMFT aka EX-RAIL (ignored if no automnation)
|
||||
RMFT::begin();
|
||||
|
||||
dccSniffer = new Sniffer(BOOSTER_INPUT);
|
||||
dccDecoder = new DCCDecoder();
|
||||
|
||||
// Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h.
|
||||
// This can be used to create turnouts, outputs, sensors etc. through the normal text commands.
|
||||
@ -158,8 +164,45 @@ void looptimer(unsigned long timeout, const FSH* message)
|
||||
lasttimestamp = now;
|
||||
}
|
||||
*********************************************/
|
||||
void loopdiag(unsigned long timeout)
|
||||
{
|
||||
static unsigned long lasttimestamp = 0;
|
||||
unsigned long now = millis();
|
||||
if (timeout != 0) {
|
||||
unsigned long diff = now - lasttimestamp;
|
||||
if (diff > timeout) {
|
||||
if (dccSniffer){
|
||||
uint64_t val = dccSniffer->getDebug();
|
||||
int n = 64;
|
||||
Serial.print("<* LOOPDIAG ");
|
||||
while (n--) {
|
||||
Serial.print(val&(1ULL<<n)?"1":"0");
|
||||
}
|
||||
Serial.println(" >\n");
|
||||
/*
|
||||
(dccSniffer->fetchPacket()).print(Serial);
|
||||
*/
|
||||
}
|
||||
lasttimestamp = millis();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// lasttimestamp = now;
|
||||
}
|
||||
void loop()
|
||||
{
|
||||
// Some debug for sniffer code
|
||||
//loopdiag(937); // Do not use a value that does divide even in 80Mhz ticks
|
||||
if (dccSniffer && dccDecoder) {
|
||||
DCCPacket p = dccSniffer->fetchPacket();
|
||||
if (p.len() != 0) {
|
||||
if (dccDecoder->parse(p)) {
|
||||
p.print(Serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
digitalWrite(2,LOW);
|
||||
|
||||
// The main sketch has responsibilities during loop()
|
||||
|
||||
// Responsibility 1: Handle DCC background processes
|
||||
|
172
DCCDecoder.cpp
Normal file
172
DCCDecoder.cpp
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* © 2025 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "DCCDecoder.h"
|
||||
#include "LocoTable.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include "DIAG.h"
|
||||
#include "DCC.h"
|
||||
|
||||
bool DCCDecoder::parse(DCCPacket &p) {
|
||||
const byte DECODER_MOBILE = 1;
|
||||
const byte DECODER_ACCESSORY = 2;
|
||||
byte decoderType = 0; // use 0 as none
|
||||
byte *d = p.data();
|
||||
byte *instr = 0; // will be set to point to the instruction part of the DCC packet (instr[0] to instr[n])
|
||||
uint16_t addr; // will be set to decoder addr (long/shor mobile or accessory)
|
||||
bool locoInfoChanged = false;
|
||||
|
||||
if (d[0] == 0B11111111) { // Idle packet
|
||||
return false;
|
||||
}
|
||||
// CRC verification here
|
||||
byte checksum = 0;
|
||||
for (byte n = 0; n < p.len(); n++)
|
||||
checksum ^= d[n];
|
||||
if (checksum) { // Result should be zero, if not it's an error!
|
||||
digitalWrite(2,HIGH);
|
||||
DIAG(F("Checksum error"));
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
Serial.print("< ");
|
||||
for(int n=0; n<8; n++) {
|
||||
Serial.print(d[0]&(1<<n)?"1":"0");
|
||||
}
|
||||
Serial.println(" >");
|
||||
*/
|
||||
if (bitRead(d[0],7) == 0) { // bit7 == 0 => loco short addr
|
||||
decoderType = DECODER_MOBILE;
|
||||
instr = d+1;
|
||||
addr = d[0];
|
||||
} else {
|
||||
if (bitRead(d[0],6) == 1) { // bit7 == 1 and bit6 == 1 => loco long addr
|
||||
decoderType = DECODER_MOBILE;
|
||||
instr = d+2;
|
||||
addr = 256 * (d[0] & 0B00111111) + d[1];
|
||||
} else { // bit7 == 1 and bit 6 == 0
|
||||
decoderType = DECODER_ACCESSORY;
|
||||
instr = d+1;
|
||||
addr = d[0] & 0B00111111;
|
||||
}
|
||||
}
|
||||
if (decoderType == DECODER_MOBILE) {
|
||||
switch (instr[0] & 0xE0) {
|
||||
case 0x20: // 001x-xxxx Extended commands
|
||||
if (instr[0] == 0B00111111) { // 128 speed steps
|
||||
if ((locoInfoChanged = LocoTable::updateLoco(addr, instr[1])) == true) {
|
||||
byte speed = instr[1] & 0B01111111;
|
||||
byte direction = instr[1] & 0B10000000;
|
||||
DCC::setThrottle(addr, speed, direction);
|
||||
//DIAG(F("UPDATE"));
|
||||
// send speed change to DCCEX here
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x40: // 010x-xxxx 28 (or 14 step) speed we assume 28
|
||||
case 0x60: // 011x-xxxx
|
||||
if ((locoInfoChanged = LocoTable::updateLoco(addr, instr[0] & 0B00111111)) == true) {
|
||||
byte speed = instr[1] & 0B00001111; // first only look at 4 bits
|
||||
if (speed > 1) { // neither stop nor emergency stop, recalculate speed
|
||||
speed = ((instr[1] & 0B00001111) << 1) + bitRead(instr[1], 4); // reshuffle bits
|
||||
speed = (speed - 3) * 9/2;
|
||||
}
|
||||
byte direction = instr[1] & 0B00100000;
|
||||
DCC::setThrottle(addr, speed, direction);
|
||||
}
|
||||
break;
|
||||
case 0x80: // 100x-xxxx Function group 1
|
||||
if ((locoInfoChanged = LocoTable::updateFunc(addr, instr[0], 1)) == true) {
|
||||
byte normalized = (instr[0] << 1 & 0x1e) | (instr[0] >> 4 & 0x01);
|
||||
DCCEXParser::funcmap(addr, normalized, 0, 4);
|
||||
}
|
||||
break;
|
||||
case 0xA0: // 101x-xxxx Function group 3 and 2
|
||||
{
|
||||
byte low, high;
|
||||
if (bitRead(instr[0], 4)) {
|
||||
low = 5;
|
||||
high = 8;
|
||||
} else {
|
||||
low = 9;
|
||||
high = 12;
|
||||
}
|
||||
if ((locoInfoChanged = LocoTable::updateFunc(addr, instr[0], low)) == true) {
|
||||
DCCEXParser::funcmap(addr, instr[0], low, high);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0xC0: // 110x-xxxx Extended (here are functions F13 and up
|
||||
switch (instr[0] & 0B00011111) {
|
||||
case 0B00011110: // F13-F20 Function Control
|
||||
if ((locoInfoChanged = LocoTable::updateFunc(addr, instr[0], 13)) == true) {
|
||||
DCCEXParser::funcmap(addr, instr[1], 13, 20);
|
||||
}
|
||||
if ((locoInfoChanged = LocoTable::updateFunc(addr, instr[0], 17)) == true) {
|
||||
DCCEXParser::funcmap(addr, instr[1], 13, 20);
|
||||
}
|
||||
break;
|
||||
case 0B00011111: // F21-F28 Function Control
|
||||
if ((locoInfoChanged = LocoTable::updateFunc(addr, instr[1], 21)) == true) {
|
||||
DCCEXParser::funcmap(addr, instr[1], 21, 28);
|
||||
} // updateFunc handles only the 4 low bits as that is the most common case
|
||||
if ((locoInfoChanged = LocoTable::updateFunc(addr, instr[1]>>4, 25)) == true) {
|
||||
DCCEXParser::funcmap(addr, instr[1], 21, 28);
|
||||
}
|
||||
break;
|
||||
/* do that later
|
||||
case 0B00011000: // F29-F36 Function Control
|
||||
break;
|
||||
case 0B00011001: // F37-F44 Function Control
|
||||
break;
|
||||
case 0B00011010: // F45-F52 Function Control
|
||||
break;
|
||||
case 0B00011011: // F53-F60 Function Control
|
||||
break;
|
||||
case 0B00011100: // F61-F68 Function Control
|
||||
break;
|
||||
*/
|
||||
}
|
||||
break;
|
||||
case 0xE0: // 111x-xxxx Config vars
|
||||
break;
|
||||
}
|
||||
return locoInfoChanged;
|
||||
}
|
||||
if (decoderType == DECODER_ACCESSORY) {
|
||||
if (instr[0] & 0B10000000) { // Basic Accessory
|
||||
addr = (((~instr[0]) & 0B01110000) << 2) + addr;
|
||||
byte port = (instr[0] & 0B00000110) >> 1;
|
||||
byte activate = (instr[0] & 0B00001000) >> 3;
|
||||
byte coil = (instr[0] & 0B00000001);
|
||||
locoInfoChanged = true;
|
||||
(void)addr; (void)port; (void)coil; (void)activate;
|
||||
DIAG(F("HL=%d LL=%d C=%d A=%d"), addr, port, coil, activate);
|
||||
DCC::setAccessory(addr, port, coil, activate);
|
||||
} else { // Accessory Extended NMRA spec, do we need to decode this?
|
||||
/*
|
||||
addr = (addr << 5) +
|
||||
((instr[0] & 0B01110000) >> 2) +
|
||||
((instr[0] & 0B00000110) >> 1);
|
||||
*/
|
||||
}
|
||||
return locoInfoChanged;
|
||||
}
|
||||
return false;
|
||||
}
|
28
DCCDecoder.h
Normal file
28
DCCDecoder.h
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* © 2025 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include "DCCPacket.h"
|
||||
|
||||
class DCCDecoder {
|
||||
public:
|
||||
DCCDecoder() {};
|
||||
bool parse(DCCPacket &p);
|
||||
private:
|
||||
};
|
||||
|
@ -39,6 +39,7 @@ struct DCCEXParser
|
||||
static void setRMFTFilter(FILTER_CALLBACK filter);
|
||||
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
||||
static const int MAX_COMMAND_PARAMS=10; // Must not exceed this
|
||||
static bool funcmap(int16_t cab, byte value, byte fstart, byte fstop);
|
||||
|
||||
private:
|
||||
|
||||
@ -77,7 +78,6 @@ struct DCCEXParser
|
||||
static FILTER_CALLBACK filterCallback;
|
||||
static FILTER_CALLBACK filterRMFTCallback;
|
||||
static AT_COMMAND_CALLBACK atCommandCallback;
|
||||
static bool funcmap(int16_t cab, byte value, byte fstart, byte fstop);
|
||||
static void sendFlashList(Print * stream,const int16_t flashList[]);
|
||||
|
||||
};
|
||||
|
84
DCCPacket.h
Normal file
84
DCCPacket.h
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* © 2025 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#ifndef DCCPacket_h
|
||||
#define DCCPacket_h
|
||||
#include <strings.h>
|
||||
|
||||
class DCCPacket {
|
||||
public:
|
||||
DCCPacket() {
|
||||
_len = 0;
|
||||
_data = NULL;
|
||||
};
|
||||
DCCPacket(byte *d, byte l) {
|
||||
_len = l;
|
||||
_data = new byte[_len];
|
||||
for (byte n = 0; n<_len; n++)
|
||||
_data[n] = d[n];
|
||||
};
|
||||
DCCPacket(const DCCPacket &old) {
|
||||
_len = old._len;
|
||||
_data = new byte[_len];
|
||||
for (byte n = 0; n<_len; n++)
|
||||
_data[n] = old._data[n];
|
||||
};
|
||||
DCCPacket &operator=(const DCCPacket &rhs) {
|
||||
if (this == &rhs)
|
||||
return *this;
|
||||
delete[]_data;
|
||||
_len = rhs._len;
|
||||
_data = new byte[_len];
|
||||
for (byte n = 0; n<_len; n++)
|
||||
_data[n] = rhs._data[n];
|
||||
return *this;
|
||||
};
|
||||
~DCCPacket() {
|
||||
if (_len) {
|
||||
delete[]_data;
|
||||
_len = 0;
|
||||
_data = NULL;
|
||||
}
|
||||
};
|
||||
inline bool operator==(const DCCPacket &right) {
|
||||
if (_len != right._len)
|
||||
return false;
|
||||
if (_len == 0)
|
||||
return true;
|
||||
return (bcmp(_data, right._data, _len) == 0);
|
||||
};
|
||||
void print(HardwareSerial &s) {
|
||||
s.print("<* DCCPAKET ");
|
||||
for (byte n = 0; n< _len; n++) {
|
||||
// byte b = 8;
|
||||
// while (b--) {
|
||||
// s.print(_data[n]&(1<<b)?"1":"0");
|
||||
// }
|
||||
s.print(_data[n], HEX);
|
||||
s.print(" ");
|
||||
}
|
||||
s.print("*>\n");
|
||||
};
|
||||
inline byte len() {return _len;};
|
||||
inline byte *data() {return _data;};
|
||||
private:
|
||||
byte _len = 0;
|
||||
byte *_data = NULL;
|
||||
};
|
||||
#endif
|
@ -1 +1 @@
|
||||
#define GITHUB_SHA "c389fe9"
|
||||
#define GITHUB_SHA "devel-202503301902Z"
|
||||
|
130
LocoTable.cpp
Normal file
130
LocoTable.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
/* Copyright (c) 2023 Harald Barth
|
||||
*
|
||||
* This source is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this software. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "LocoTable.h"
|
||||
|
||||
LocoTable::LOCO LocoTable::speedTable[MAX_LOCOS] = { {0,0,0,0,0,0} };
|
||||
int LocoTable::highestUsedReg = 0;
|
||||
|
||||
int LocoTable::lookupSpeedTable(int locoId, bool autoCreate) {
|
||||
// determine speed reg for this loco
|
||||
int firstEmpty = MAX_LOCOS;
|
||||
int reg;
|
||||
for (reg = 0; reg < MAX_LOCOS; reg++) {
|
||||
if (speedTable[reg].loco == locoId) break;
|
||||
if (speedTable[reg].loco == 0 && firstEmpty == MAX_LOCOS) firstEmpty = reg;
|
||||
}
|
||||
|
||||
// return -1 if not found and not auto creating
|
||||
if (reg == MAX_LOCOS && !autoCreate) return -1;
|
||||
if (reg == MAX_LOCOS) reg = firstEmpty;
|
||||
if (reg >= MAX_LOCOS) {
|
||||
//DIAG(F("Too many locos"));
|
||||
return -1;
|
||||
}
|
||||
if (reg==firstEmpty){
|
||||
speedTable[reg].loco = locoId;
|
||||
speedTable[reg].speedCode=128; // default direction forward
|
||||
speedTable[reg].groupFlags=0;
|
||||
speedTable[reg].functions=0;
|
||||
}
|
||||
if (reg > highestUsedReg) highestUsedReg = reg;
|
||||
return reg;
|
||||
}
|
||||
|
||||
// returns false only if loco existed but nothing was changed
|
||||
bool LocoTable::updateLoco(int loco, byte speedCode) {
|
||||
if (loco==0) {
|
||||
/*
|
||||
// broadcast stop/estop but dont change direction
|
||||
for (int reg = 0; reg < highestUsedReg; reg++) {
|
||||
if (speedTable[reg].loco==0) continue;
|
||||
byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
|
||||
if (speedTable[reg].speedCode != newspeed) {
|
||||
speedTable[reg].speedCode = newspeed;
|
||||
CommandDistributor::broadcastLoco(reg);
|
||||
}
|
||||
}
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
// determine speed reg for this loco
|
||||
int reg=lookupSpeedTable(loco, false);
|
||||
if (reg>=0) {
|
||||
speedTable[reg].speedcounter++;
|
||||
if (speedTable[reg].speedCode!=speedCode) {
|
||||
speedTable[reg].speedCode = speedCode;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// new
|
||||
reg=lookupSpeedTable(loco, true);
|
||||
if(reg >=0) speedTable[reg].speedCode = speedCode;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool LocoTable::updateFunc(int loco, byte func, int shift) {
|
||||
unsigned long previous;
|
||||
unsigned long newfunc;
|
||||
bool retval = false; // nothing was touched
|
||||
int reg = lookupSpeedTable(loco, false);
|
||||
if (reg < 0) { // not found
|
||||
retval = true;
|
||||
reg = lookupSpeedTable(loco, true);
|
||||
newfunc = previous = 0;
|
||||
} else {
|
||||
newfunc = previous = speedTable[reg].functions;
|
||||
}
|
||||
|
||||
speedTable[reg].funccounter++;
|
||||
|
||||
if(shift == 1) { // special case for light
|
||||
newfunc &= ~1UL;
|
||||
newfunc |= ((func & 0B10000) >> 4);
|
||||
}
|
||||
newfunc &= ~(0B1111UL << shift);
|
||||
newfunc |= ((func & 0B1111) << shift);
|
||||
|
||||
if (newfunc != previous) {
|
||||
speedTable[reg].functions = newfunc;
|
||||
retval = true;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
void LocoTable::dumpTable(Stream *output) {
|
||||
output->print("\n-----------Table---------\n");
|
||||
for (byte reg = 0; reg <= highestUsedReg; reg++) {
|
||||
if (speedTable[reg].loco != 0) {
|
||||
output->print(speedTable[reg].loco);
|
||||
output->print(' ');
|
||||
output->print(speedTable[reg].speedCode);
|
||||
output->print(' ');
|
||||
output->print(speedTable[reg].functions);
|
||||
output->print(" #funcpacks:");
|
||||
output->print(speedTable[reg].funccounter);
|
||||
output->print(" #speedpacks:");
|
||||
output->print(speedTable[reg].speedcounter);
|
||||
speedTable[reg].funccounter = 0;
|
||||
speedTable[reg].speedcounter = 0;
|
||||
output->print('\n');
|
||||
}
|
||||
}
|
||||
}
|
44
LocoTable.h
Normal file
44
LocoTable.h
Normal file
@ -0,0 +1,44 @@
|
||||
/* Copyright (c) 2023 Harald Barth
|
||||
*
|
||||
* This source is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this software. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "DCC.h" // fetch MAX_LOCOS from there
|
||||
|
||||
class LocoTable {
|
||||
public:
|
||||
void forgetLoco(int cab) {
|
||||
int reg=lookupSpeedTable(cab, false);
|
||||
if (reg>=0) speedTable[reg].loco=0;
|
||||
}
|
||||
static int lookupSpeedTable(int locoId, bool autoCreate);
|
||||
static bool updateLoco(int loco, byte speedCode);
|
||||
static bool updateFunc(int loco, byte func, int shift);
|
||||
static void dumpTable(Stream *output);
|
||||
|
||||
private:
|
||||
struct LOCO
|
||||
{
|
||||
int loco;
|
||||
byte speedCode;
|
||||
byte groupFlags;
|
||||
unsigned long functions;
|
||||
unsigned int funccounter;
|
||||
unsigned int speedcounter;
|
||||
};
|
||||
static LOCO speedTable[MAX_LOCOS];
|
||||
static int highestUsedReg;
|
||||
};
|
223
Sniffer.cpp
Normal file
223
Sniffer.cpp
Normal file
@ -0,0 +1,223 @@
|
||||
/*
|
||||
* © 2025 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#define DIAG_LED 33
|
||||
#include "Sniffer.h"
|
||||
#include "DIAG.h"
|
||||
//extern Sniffer *DCCSniffer;
|
||||
|
||||
static void packeterror() {
|
||||
digitalWrite(DIAG_LED,HIGH);
|
||||
}
|
||||
|
||||
static void clear_packeterror() {
|
||||
digitalWrite(DIAG_LED,LOW);
|
||||
}
|
||||
|
||||
static bool halfbits2byte(uint16_t b, byte *dccbyte) {
|
||||
/*
|
||||
if (b!=0 && b!=0xFFFF) {
|
||||
Serial.print("[ ");
|
||||
for(int n=0; n<16; n++) {
|
||||
Serial.print(b&(1<<n)?"1":"0");
|
||||
}
|
||||
Serial.println(" ]");
|
||||
}
|
||||
*/
|
||||
for(byte n=0; n<8; n++) {
|
||||
switch (b & 0x03) {
|
||||
case 0x01:
|
||||
case 0x02:
|
||||
// broken bits
|
||||
packeterror();
|
||||
return false;
|
||||
break;
|
||||
case 0x00:
|
||||
bitClear(*dccbyte, n);
|
||||
break;
|
||||
case 0x03:
|
||||
bitSet(*dccbyte, n);
|
||||
break;
|
||||
}
|
||||
b = b>>2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR blink_diag(int limit) {
|
||||
delay(500);
|
||||
for (int n=0 ; n<limit; n++) {
|
||||
digitalWrite(DIAG_LED,HIGH);
|
||||
delay(200);
|
||||
digitalWrite(DIAG_LED,LOW);
|
||||
delay(200);
|
||||
}
|
||||
}
|
||||
|
||||
static bool IRAM_ATTR cap_ISR_cb(mcpwm_unit_t mcpwm, mcpwm_capture_channel_id_t cap_channel, const cap_event_data_t *edata,void *user_data) {
|
||||
if (edata->cap_edge == MCPWM_BOTH_EDGE) {
|
||||
// should not happen at all
|
||||
// delays here might crash sketch
|
||||
blink_diag(2);
|
||||
return 0;
|
||||
}
|
||||
if (user_data) ((Sniffer *)user_data)->processInterrupt(edata->cap_value, edata->cap_edge == MCPWM_POS_EDGE);
|
||||
//if (DCCSniffer) DCCSniffer->processInterrupt(edata->cap_value, edata->cap_edge == MCPWM_POS_EDGE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Sniffer::Sniffer(byte snifferpin) {
|
||||
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_CAP_0, snifferpin);
|
||||
// set capture edge, BIT(0) - negative edge, BIT(1) - positive edge
|
||||
// MCPWM_POS_EDGE|MCPWM_NEG_EDGE should be 3.
|
||||
//mcpwm_capture_enable(MCPWM_UNIT_0, MCPWM_SELECT_CAP0, MCPWM_POS_EDGE|MCPWM_NEG_EDGE, 0);
|
||||
//mcpwm_isr_register(MCPWM_UNIT_0, sniffer_isr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL);
|
||||
//MCPWM0.int_ena.cap0_int_ena = 1; // Enable interrupt on CAP0 signal
|
||||
|
||||
mcpwm_capture_config_t MCPWM_cap_config = { //Capture channel configuration
|
||||
.cap_edge = MCPWM_BOTH_EDGE, // according to mcpwm.h
|
||||
.cap_prescale = 1, // 1 to 256 (see .h file)
|
||||
.capture_cb = cap_ISR_cb, // user defined ISR/callback
|
||||
.user_data = (void *)this // user defined argument to callback
|
||||
};
|
||||
pinMode(DIAG_LED ,OUTPUT);
|
||||
blink_diag(3); // so that we know we have DIAG_LED
|
||||
DIAG(F("Init sniffer on pin %d"), snifferpin);
|
||||
ESP_ERROR_CHECK(mcpwm_capture_enable_channel(MCPWM_UNIT_0, MCPWM_SELECT_CAP0, &MCPWM_cap_config));
|
||||
}
|
||||
|
||||
#define DCC_TOO_SHORT 4000L // 4000 ticks are 50usec
|
||||
#define DCC_ONE_LIMIT 6400L // 6400 ticks are 80usec
|
||||
|
||||
volatile int fakecounter = 0;
|
||||
|
||||
void IRAM_ATTR Sniffer::processInterrupt(int32_t capticks, bool posedge) {
|
||||
if (fakecounter >= 64)
|
||||
fakecounter = 0;
|
||||
fakecounter++;
|
||||
byte bit = 0;
|
||||
diffticks = capticks - lastticks;
|
||||
if (lastedge != posedge) {
|
||||
if (diffticks < DCC_TOO_SHORT) {
|
||||
return;
|
||||
}
|
||||
if (diffticks < DCC_ONE_LIMIT) {
|
||||
bit = 1;
|
||||
} else {
|
||||
bit = 0;
|
||||
}/*
|
||||
if (fakecounter == 7 || fakecounter == 34 || fakecounter == 62 || fakecounter == 63) {
|
||||
bit = 0;
|
||||
} else {
|
||||
bit = 1;
|
||||
}*/
|
||||
// update state variables for next round
|
||||
lastticks = capticks;
|
||||
lastedge = posedge;
|
||||
bitfield = bitfield << (uint64_t)1;
|
||||
bitfield = bitfield + (uint64_t)bit;
|
||||
|
||||
// now the halfbit is in the bitfield. Analyze...
|
||||
|
||||
if ((bitfield & 0xFFFFFF) == 0xFFFFFC){
|
||||
// This looks at the 24 last halfbits
|
||||
// and detects a preamble if
|
||||
// 22 are ONE and 2 are ZERO which is a
|
||||
// preabmle of 11 ONES and one ZERO
|
||||
if (inpacket) {
|
||||
// if we are already inpacket here we
|
||||
// got a preamble in the middle of a
|
||||
// packet
|
||||
packeterror();
|
||||
} else {
|
||||
clear_packeterror(); // everything fine again at end of preable after good packet
|
||||
}
|
||||
currentbyte = 0;
|
||||
dcclen = 0;
|
||||
inpacket = true;
|
||||
halfbitcounter = 18; // count 18 steps from 17 to 0 and then look at the byte
|
||||
return;
|
||||
}
|
||||
if (inpacket) {
|
||||
halfbitcounter--;
|
||||
if (halfbitcounter) {
|
||||
return; // wait until we have full byte
|
||||
} else {
|
||||
// have reached end of byte
|
||||
//if (currentbyte == 2) debugfield = bitfield;
|
||||
byte twohalfbits = bitfield & 0x03;
|
||||
switch (twohalfbits) {
|
||||
case 0x01:
|
||||
case 0x02:
|
||||
// broken bits
|
||||
inpacket = false;
|
||||
packeterror();
|
||||
return;
|
||||
break;
|
||||
case 0x00:
|
||||
case 0x03:
|
||||
// byte end
|
||||
uint16_t b = (bitfield & 0x3FFFF)>>2; // take 18 halfbits and use 16 of them
|
||||
if (!halfbits2byte(b, dccbytes + currentbyte)) {
|
||||
// broken halfbits
|
||||
inpacket = false;
|
||||
packeterror();
|
||||
return;
|
||||
}
|
||||
if (twohalfbits == 0x03) { // end of packet marker
|
||||
inpacket = false;
|
||||
dcclen = currentbyte+1;
|
||||
debugfield = bitfield;
|
||||
// put it into the out packet
|
||||
if (fetchflag) {
|
||||
// not good, should have been fetched
|
||||
// blink_diag(1);
|
||||
packeterror(); // or better?
|
||||
}
|
||||
DCCPacket temppacket(dccbytes, dcclen);
|
||||
if (!(temppacket == prevpacket)) {
|
||||
// we have something new to offer to the fetch routine
|
||||
outpacket.push_back(temppacket);
|
||||
prevpacket = temppacket;
|
||||
fetchflag = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
halfbitcounter = 18;
|
||||
currentbyte++; // everything done for this end of byte
|
||||
if (currentbyte >= MAXDCCPACKETLEN) {
|
||||
inpacket = false; // this is an error because we should have retured above
|
||||
packeterror(); // when endof packet marker was active
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // lastedge == posedge
|
||||
// this should not happen, check later
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
static void IRAM_ATTR sniffer_isr_handler(void *) {
|
||||
DCCSniffer.processInterrupt();
|
||||
}
|
||||
*/
|
||||
#endif // ESP32
|
77
Sniffer.h
Normal file
77
Sniffer.h
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* © 2025 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <Arduino.h>
|
||||
#include <list>
|
||||
#include "driver/mcpwm.h"
|
||||
#include "soc/mcpwm_struct.h"
|
||||
#include "soc/mcpwm_reg.h"
|
||||
|
||||
#define MAXDCCPACKETLEN 8
|
||||
#include "DCCPacket.h"
|
||||
|
||||
class Sniffer {
|
||||
public:
|
||||
Sniffer(byte snifferpin);
|
||||
void IRAM_ATTR processInterrupt(int32_t capticks, bool posedge);
|
||||
inline int32_t getTicks() {
|
||||
noInterrupts();
|
||||
int32_t i = diffticks;
|
||||
interrupts();
|
||||
return i;
|
||||
};
|
||||
inline int64_t getDebug() {
|
||||
noInterrupts();
|
||||
int64_t i = debugfield;
|
||||
interrupts();
|
||||
return i;
|
||||
};
|
||||
inline DCCPacket fetchPacket() {
|
||||
// if there is no new data, this will create a
|
||||
// packet with length 0 (which is no packet)
|
||||
DCCPacket p;
|
||||
noInterrupts();
|
||||
if (!outpacket.empty()) {
|
||||
p = outpacket.front();
|
||||
outpacket.pop_front();
|
||||
}
|
||||
if (fetchflag) {
|
||||
fetchflag = false; // (data has been fetched)
|
||||
}
|
||||
interrupts();
|
||||
return p;
|
||||
};
|
||||
private:
|
||||
// keep these vars in processInterrupt only
|
||||
uint64_t bitfield = 0;
|
||||
uint64_t debugfield = 0;
|
||||
int32_t diffticks;
|
||||
int32_t lastticks;
|
||||
bool lastedge;
|
||||
byte currentbyte = 0;
|
||||
byte dccbytes[MAXDCCPACKETLEN];
|
||||
byte dcclen = 0;
|
||||
bool inpacket = false;
|
||||
// these vars are used as interface to other parts of sniffer
|
||||
byte halfbitcounter = 0;
|
||||
bool fetchflag = false;
|
||||
std::list<DCCPacket> outpacket;
|
||||
DCCPacket prevpacket;
|
||||
};
|
||||
#endif
|
@ -3,7 +3,8 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "5.4.6"
|
||||
#define VERSION "5.4.99"
|
||||
// 5.4.99 - Special version number for experiments
|
||||
// 5.4.6 - Bugfix: Do not drop further commands in same packet
|
||||
// 5.4.5 - ESP32: Better detection of correct IDF version
|
||||
// - track power is always turned on after setJoin() not by setJoin()
|
||||
|
Loading…
x
Reference in New Issue
Block a user