1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-12-23 12:51:24 +01:00
CommandStation-EX/Railcom.cpp
2024-10-24 09:43:52 +01:00

256 lines
10 KiB
C++

/*
* SEE ADDITIONAL COPYRIGHT ATTRIBUTION BELOW
* © 2024 Chris Harlow
* All rights reserved.
*
* This file is part of DCC-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/>.
*/
/** Sections of this code (the decode table constants)
* are taken from openmrn
* https://github.com/bakerstu/openmrn/blob/master/src/dcc/RailCom.cxx
* under the following copyright.
*
* Copyright (c) 2014, Balazs Racz
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
**/
#include "Railcom.h"
#include "defines.h"
#include "FSH.h"
#include "DCC.h"
#include "DIAG.h"
/** Table for 8-to-6 decoding of railcom data. This table can be indexed by the
* 8-bit value read from the railcom channel, and the return value will be
* either a 6-bit number, or one of the defined Railcom constrantrs. If the
* value is invalid, the INV constant is returned. */
// These values appear in the railcom_decode table to mean special symbols.
static constexpr uint8_t
// highest valid 6-bit value
MAX_VALID = 0x3F,
/// invalid value (not conforming to the 4bit weighting requirement)
INV = 0xff,
/// Railcom ACK; the decoder received the message ok. NOTE: There are
/// two codepoints that map to this.
ACK = 0xfe,
/// The decoder rejected the packet.
NACK = 0xfd,
/// The decoder is busy; send the packet again. This is typically
/// returned when a POM CV write is still pending; the caller must
/// re-try sending the packet later.
RCBUSY = 0xfc,
/// Reserved for future expansion.
RESVD1 = 0xfb,
/// Reserved for future expansion.
RESVD2 = 0xfa;
const uint8_t HIGHFLASH decode[256] =
// 0|8 1|9 2|a 3|b 4|c 5|d 6|e 7|f
{ INV, INV, INV, INV, INV, INV, INV, INV, // 0
INV, INV, INV, INV, INV, INV, INV, ACK, // 0
INV, INV, INV, INV, INV, INV, INV, 0x33, // 1
INV, INV, INV, 0x34, INV, 0x35, 0x36, INV, // 1
INV, INV, INV, INV, INV, INV, INV, 0x3A, // 2
INV, INV, INV, 0x3B, INV, 0x3C, 0x37, INV, // 2
INV, INV, INV, 0x3F, INV, 0x3D, 0x38, INV, // 3
INV, 0x3E, 0x39, INV, NACK, INV, INV, INV, // 3
INV, INV, INV, INV, INV, INV, INV, 0x24, // 4
INV, INV, INV, 0x23, INV, 0x22, 0x21, INV, // 4
INV, INV, INV, 0x1F, INV, 0x1E, 0x20, INV, // 5
INV, 0x1D, 0x1C, INV, 0x1B, INV, INV, INV, // 5
INV, INV, INV, 0x19, INV, 0x18, 0x1A, INV, // 6
INV, 0x17, 0x16, INV, 0x15, INV, INV, INV, // 6
INV, 0x25, 0x14, INV, 0x13, INV, INV, INV, // 7
0x32, INV, INV, INV, INV, INV, INV, INV, // 7
INV, INV, INV, INV, INV, INV, INV, RESVD2, // 8
INV, INV, INV, 0x0E, INV, 0x0D, 0x0C, INV, // 8
INV, INV, INV, 0x0A, INV, 0x09, 0x0B, INV, // 9
INV, 0x08, 0x07, INV, 0x06, INV, INV, INV, // 9
INV, INV, INV, 0x04, INV, 0x03, 0x05, INV, // a
INV, 0x02, 0x01, INV, 0x00, INV, INV, INV, // a
INV, 0x0F, 0x10, INV, 0x11, INV, INV, INV, // b
0x12, INV, INV, INV, INV, INV, INV, INV, // b
INV, INV, INV, RESVD1, INV, 0x2B, 0x30, INV, // c
INV, 0x2A, 0x2F, INV, 0x31, INV, INV, INV, // c
INV, 0x29, 0x2E, INV, 0x2D, INV, INV, INV, // d
0x2C, INV, INV, INV, INV, INV, INV, INV, // d
INV, RCBUSY, 0x28, INV, 0x27, INV, INV, INV, // e
0x26, INV, INV, INV, INV, INV, INV, INV, // e
ACK, INV, INV, INV, INV, INV, INV, INV, // f
INV, INV, INV, INV, INV, INV, INV, INV, // f
};
/// Packet identifiers from Mobile Decoders.
enum RailcomMobilePacketId
{
RMOB_POM = 0,
RMOB_ADRHIGH = 1,
RMOB_ADRLOW = 2,
RMOB_EXT = 3,
RMOB_DYN = 7,
RMOB_XPOM0 = 8,
RMOB_XPOM1 = 9,
RMOB_XPOM2 = 10,
RMOB_XPOM3 = 11,
RMOB_SUBID = 12,
RMOB_LOGON_ASSIGN_FEEDBACK = 13,
RMOB_LOGON_ENABLE_FEEDBACK = 15,
};
// each railcom block is represented by an instance of this class.
// The blockvpin is the vpin associated with this block for the purposes of
// a HAL driver for the railcom detection and the EXRAIL ONBLOCKENTER/ONBLOCKEXIT
Railcom::Railcom(uint16_t blockvpin) {
haveHigh=false;
haveLow=false;
packetsWithNoData=0;
lastChannel1Loco=0;
vpin=blockvpin;
}
uint16_t Railcom::expectLoco=0;
uint16_t Railcom::expectCV=0;
unsigned long Railcom::expectWait=0;
ACK_CALLBACK Railcom::expectCallback=0;
// Process is called by a raw data collector.
void Railcom::process(uint8_t * inbound, uint8_t length) {
if (length<2 || (inbound[0]==0 && inbound[1]==0)) {
noData();
return;
}
if (Diag::RAILCOM) {
static const char hexchars[]="0123456789ABCDEF";
if (length>2) {
USB_SERIAL.print(F("<*R "));
for (byte i=0;i<length;i++) {
if (i==2) Serial.write(' ');
USB_SERIAL.write(hexchars[inbound[i]>>4]);
USB_SERIAL.write(hexchars[inbound[i]& 0x0F ]);
}
USB_SERIAL.print(F(" *>\n"));
}
}
if (expectCV && DCCWaveform::getRailcomLastLocoAddress()==expectLoco) {
if (length>=4) {
auto v2=GETHIGHFLASH(decode,inbound[2]);
auto v3=GETHIGHFLASH(decode,inbound[3]);
uint16_t packet=(v2<<6) | (v3 & 0x3f);
// packet is 12 bits TTTTDDDDDDDD
byte type=(packet>>8) & 0x0F;
byte data= packet & 0xFF;
if (type==RMOB_POM) {
// DIAG(F("POM READ loco=%d cv(%d)=%d/0x%x"), expectLoco, expectCV,data,data);
expectCallback(data);
expectCV=0;
}
}
}
if (expectCV && (millis()-expectWait)> POM_READ_TIMEOUT) { // still waiting
expectCallback(-1);
expectCV=0;
}
auto v1=GETHIGHFLASH(decode,inbound[0]);
auto v2=(length>1) ? GETHIGHFLASH(decode,inbound[1]):INV;
uint16_t packet=(v1<<6) | (v2 & 0x3f);
// packet is 12 bits TTTTDDDDDDDD
byte type=(packet>>8) & 0x0F;
byte data= packet & 0xFF;
if (type==RMOB_ADRHIGH) {
holdoverHigh=data;
haveHigh=true;
packetsWithNoData=0;
}
else if (type==RMOB_ADRLOW) {
holdoverLow=data;
haveLow=true;
packetsWithNoData=0;
}
else {
// channel1 is unreadable or not loco address so maybe multiple locos in block
if (length>2 && GETHIGHFLASH(decode,inbound[0])!=INV) {
// it looks like we have channel2 data
auto thisLoco=DCCWaveform::getRailcomLastLocoAddress();
if (Diag::RAILCOM) DIAG(F("c2=%d"),thisLoco);
if (thisLoco==lastChannel1Loco) return;
if (thisLoco) DCC::setLocoInBlock(thisLoco,vpin,false); // this loco is in block, but not exclusive
return;
}
// channel1 no good and no channel2
noData();
return;
}
if (haveHigh && haveLow) {
uint16_t thisLoco=((holdoverHigh<<8)| holdoverLow) & 0x7FFF; // drop top bit
if (thisLoco!=lastChannel1Loco) {
// the exclusive DCC call is quite expensive, we dont want to call it every packet
if (Diag::RAILCOM) DIAG(F("h=%x l=%xc1=%d"),holdoverHigh, holdoverLow,thisLoco);
DCC::setLocoInBlock(thisLoco,vpin,true); // only this loco is in block
lastChannel1Loco=thisLoco;
}
}
}
void Railcom::noData() {
if (packetsWithNoData>MAX_WAIT_FOR_GLITCH) return;
if (packetsWithNoData==MAX_WAIT_FOR_GLITCH) {
// treat as no loco
haveHigh=false;
haveLow=false;
lastChannel1Loco=0;
// Previous locos (if any) is exiting block
DCC::clearBlock(vpin);
}
packetsWithNoData++;
}