mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program 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 program 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 program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.identifier.dcs;
|
||||
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.module.decode.dcs.DCSCode;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* Digital Coded Squelch code identifier. This is decoded from the transmitted signal and represents the FROM role.
|
||||
*/
|
||||
public class DCSIdentifier extends Identifier<DCSCode>
|
||||
{
|
||||
/**
|
||||
* Constructs an instance.
|
||||
* @param code detected.
|
||||
*/
|
||||
public DCSIdentifier(DCSCode code)
|
||||
{
|
||||
super(code, IdentifierClass.USER, Form.TONE, Role.FROM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return Protocol.DCS;
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,9 @@ import io.github.dsheirer.module.decode.am.AMDecoderState;
|
||||
import io.github.dsheirer.module.decode.am.DecodeConfigAM;
|
||||
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.dcs.DCSDecoder;
|
||||
import io.github.dsheirer.module.decode.dcs.DCSDecoderState;
|
||||
import io.github.dsheirer.module.decode.dcs.DCSMessageFilter;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRDecoder;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRDecoderState;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRTrafficChannelManager;
|
||||
@@ -456,6 +459,10 @@ public class DecoderFactory
|
||||
{
|
||||
switch(auxDecoder)
|
||||
{
|
||||
case DCS:
|
||||
modules.add(new DCSDecoder());
|
||||
modules.add(new DCSDecoderState());
|
||||
break;
|
||||
case FLEETSYNC2:
|
||||
modules.add(new Fleetsync2Decoder());
|
||||
modules.add(new Fleetsync2DecoderState());
|
||||
@@ -516,6 +523,9 @@ public class DecoderFactory
|
||||
|
||||
switch(decoder)
|
||||
{
|
||||
case DCS:
|
||||
filters.add(new DCSMessageFilter());
|
||||
break;
|
||||
case FLEETSYNC2:
|
||||
filters.add(new FleetsyncMessageFilter());
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -19,7 +19,6 @@
|
||||
package io.github.dsheirer.module.decode;
|
||||
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
@@ -39,6 +38,7 @@ public enum DecoderType
|
||||
P25_PHASE2("P25 Phase 2", "P25-2", Protocol.APCO25_PHASE2),
|
||||
|
||||
//Auxiliary Decoders
|
||||
DCS("Digital Coded Squelch (DCS)", "DCS", Protocol.DCS),
|
||||
FLEETSYNC2("Fleetsync II", "Fleetsync2", Protocol.FLEETSYNC),
|
||||
LJ_1200("LJ1200 173.075", "LJ1200", Protocol.LOJACK),
|
||||
MDC1200("MDC1200", "MDC1200", Protocol.MDC1200),
|
||||
@@ -73,7 +73,8 @@ public enum DecoderType
|
||||
* Auxiliary decoders that operate on in-band signalling in the decoded audio channel
|
||||
*/
|
||||
public static final EnumSet<DecoderType> AUX_DECODERS =
|
||||
EnumSet.of(DecoderType.FLEETSYNC2,
|
||||
EnumSet.of(DecoderType.DCS,
|
||||
DecoderType.FLEETSYNC2,
|
||||
DecoderType.LJ_1200,
|
||||
DecoderType.MDC1200,
|
||||
DecoderType.TAIT_1200);
|
||||
|
||||
@@ -121,6 +121,7 @@ public abstract class AnalogDecoderState extends DecoderState implements ISource
|
||||
startCallEvent();
|
||||
}
|
||||
|
||||
getIdentifierCollection().update(getTalkgroupIdentifier());
|
||||
mDecodeEvent.update(System.currentTimeMillis());
|
||||
broadcast(mDecodeEvent);
|
||||
}
|
||||
|
||||
281
src/main/java/io/github/dsheirer/module/decode/dcs/DCSCode.java
Normal file
281
src/main/java/io/github/dsheirer/module/decode/dcs/DCSCode.java
Normal file
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program 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 program 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 program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dcs;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* DCS codes as defined in ETSI TS 103 236 V1.1.1
|
||||
*
|
||||
* Code values are the bit reversed integer values of the 23-bit binary sequence representation. The values are bit
|
||||
* reversed for normal (N) codes and as specified in the ETSI chart for the inverted (I) codes to align with the
|
||||
* transmitted format.
|
||||
*/
|
||||
public enum DCSCode
|
||||
{
|
||||
//Inverted codes
|
||||
I023(7747603),
|
||||
I025(7043093),
|
||||
I026(6674454),
|
||||
I031(5371929),
|
||||
I032(6248474),
|
||||
I043(5990435),
|
||||
I047(1038375),
|
||||
I051(8169513),
|
||||
I054(7292972),
|
||||
I065(6101045),
|
||||
I071(6789177),
|
||||
I072(6895674),
|
||||
I073(3041339),
|
||||
I074(7632956),
|
||||
I114(3532876),
|
||||
I115(7518285),
|
||||
I116(8132686),
|
||||
I125(505941),
|
||||
I131(4012121),
|
||||
I132(3381338),
|
||||
I134(3070044),
|
||||
I143(3647587),
|
||||
I152(2017386),
|
||||
I155(4511853),
|
||||
I156(4880494),
|
||||
I162(7063666),
|
||||
I165(3266677),
|
||||
I172(391290),
|
||||
I174(1620092),
|
||||
I205(7248005),
|
||||
I223(6875283),
|
||||
I226(8063126),
|
||||
I243(4569251),
|
||||
I244(2074788),
|
||||
I245(5830821),
|
||||
I251(6453417),
|
||||
I261(1538225),
|
||||
I263(6195379),
|
||||
I265(4442293),
|
||||
I271(7948473),
|
||||
I306(850118),
|
||||
I311(3725513),
|
||||
I315(7104717),
|
||||
I331(2353369),
|
||||
I343(2717923),
|
||||
I346(3840230),
|
||||
I351(964841),
|
||||
I364(6838516),
|
||||
I365(3082485),
|
||||
I371(1411321),
|
||||
I411(7825673),
|
||||
I412(7981322),
|
||||
I413(4102411),
|
||||
I423(4954387),
|
||||
I431(7100697),
|
||||
I432(6486298),
|
||||
I445(8096037),
|
||||
I464(2615604),
|
||||
I465(6338869),
|
||||
I466(7215414),
|
||||
I503(3959107),
|
||||
I506(3115334),
|
||||
I516(4307278),
|
||||
I532(932186),
|
||||
I546(1698150),
|
||||
I565(817525),
|
||||
I606(6134150),
|
||||
I612(6756746),
|
||||
I624(1005972),
|
||||
I627(129431),
|
||||
I631(7506329),
|
||||
I632(8137114),
|
||||
I654(4995500),
|
||||
I662(2390450),
|
||||
I664(3750324),
|
||||
I703(2275779),
|
||||
I712(776650),
|
||||
I723(3770835),
|
||||
I731(1984985),
|
||||
I732(1108442),
|
||||
I734(895452),
|
||||
I743(1366499),
|
||||
I754(2161132),
|
||||
|
||||
//Normal codes
|
||||
N023(6557239),
|
||||
N025(5508971),
|
||||
N026(3411411),
|
||||
N031(4984773),
|
||||
N032(2887037),
|
||||
N043(6425453),
|
||||
N047(7474680),
|
||||
N051(4852383),
|
||||
N054(1706363),
|
||||
N065(5639261),
|
||||
N071(5115123),
|
||||
N072(3018315),
|
||||
N073(7211834),
|
||||
N074(1969943),
|
||||
N114(1641430),
|
||||
N115(5836455),
|
||||
N116(3738655),
|
||||
N125(5574384),
|
||||
N131(5049950),
|
||||
N132(2952422),
|
||||
N134(1904058),
|
||||
N143(6490870),
|
||||
N152(2820540),
|
||||
N155(5967249),
|
||||
N156(3870505),
|
||||
N162(2558443),
|
||||
N165(5705158),
|
||||
N172(3084240),
|
||||
N174(2035340),
|
||||
N205(5278907),
|
||||
N223(6589323),
|
||||
N226(3442799),
|
||||
N243(6459089),
|
||||
N244(1215228),
|
||||
N245(5410701),
|
||||
N251(4886307),
|
||||
N261(4624244),
|
||||
N263(6719677),
|
||||
N265(5671393),
|
||||
N271(5146959),
|
||||
N306(3248024),
|
||||
N311(4820366),
|
||||
N315(5868315),
|
||||
N331(5082082),
|
||||
N343(6524746),
|
||||
N346(3378350),
|
||||
N351(4951736),
|
||||
N364(1543435),
|
||||
N365(5736570),
|
||||
N371(5212372),
|
||||
N411(4737911),
|
||||
N412(2640335),
|
||||
N413(6835390),
|
||||
N423(6573289),
|
||||
N431(5000475),
|
||||
N432(2903971),
|
||||
N445(5392623),
|
||||
N464(1461234),
|
||||
N465(5656195),
|
||||
N466(3558459),
|
||||
N503(6376222),
|
||||
N506(3229946),
|
||||
N516(3755713),
|
||||
N532(2969144),
|
||||
N546(3361740),
|
||||
N565(5721880),
|
||||
N606(3198173),
|
||||
N612(2673779),
|
||||
N624(1363320),
|
||||
N627(7655360),
|
||||
N631(5032103),
|
||||
N632(2935327),
|
||||
N654(1756697),
|
||||
N662(2543378),
|
||||
N664(1494606),
|
||||
N703(6409890),
|
||||
N712(2739688),
|
||||
N723(6670542),
|
||||
N731(5097788),
|
||||
N732(3001220),
|
||||
N734(1952472),
|
||||
N743(6540692),
|
||||
N754(1822594),
|
||||
UNKNOWN(0);
|
||||
|
||||
private int mValue;
|
||||
|
||||
DCSCode(int value)
|
||||
{
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverted DCS codes
|
||||
*/
|
||||
private static final EnumSet<DCSCode> INVERTED_CODES = EnumSet.range(I023, I754);
|
||||
|
||||
/**
|
||||
* Lookup map for quickly finding a DCS code from the transmitted value.
|
||||
*/
|
||||
private static final Map<Integer,DCSCode> CODE_MAP = new HashMap<>();
|
||||
|
||||
static
|
||||
{
|
||||
for(DCSCode code: EnumSet.range(I023, N754))
|
||||
{
|
||||
CODE_MAP.put(code.getValue(), code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Integer value of the code from transmitted bits.
|
||||
* @return value
|
||||
*/
|
||||
public int getValue()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this is an inverted DCS code (true) or a normal DCS code (false).
|
||||
* @return true if inverted.
|
||||
*/
|
||||
public boolean isInverted()
|
||||
{
|
||||
return INVERTED_CODES.contains(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "DCS-" + name().substring(1, 4) + (isInverted() ? " Inverted" : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the value maps to a known DCS code.
|
||||
* @param value to test
|
||||
* @return true if the value matches a DCS code.
|
||||
*/
|
||||
public static boolean hasValue(int value)
|
||||
{
|
||||
return CODE_MAP.containsKey(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DCS code that matches the value, otherwise return UNKNOWN.
|
||||
* @param value to lookup
|
||||
* @return matching DCS code or UNKNOWN.
|
||||
*/
|
||||
public static DCSCode fromValue(int value)
|
||||
{
|
||||
DCSCode code = CODE_MAP.get(value);
|
||||
|
||||
if(code == null)
|
||||
{
|
||||
code = UNKNOWN;
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program 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 program 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 program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dcs;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import io.github.dsheirer.module.decode.Decoder;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.real.IRealBufferListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Digital Coded Squelch (DCS) demodulator/decoder designed to work with FM demodulated 8 kHz input audio samples and
|
||||
* decode 134.4-baud signalling present in the unfiltered demodulated audio stream.
|
||||
*
|
||||
* This decoder monitors the incoming sample stream looking for either a positive or negative sequence of samples where
|
||||
* the slope of these values exceeds a threshold and causes the symbol state to flip. The symbol state remains across
|
||||
* successive baud periods until the next opposing slope state change sequence is detected.
|
||||
*
|
||||
* This decoder uses a slope detector to decode the 134.4 bits per second stream and identify the DCS code. It is
|
||||
* designed to work with 8 kHz audio sample rate. Each baud period is (8,000 / 134.4) = 59.52 samples per symbol and
|
||||
* a full DCS tone sequence is 23 bits * 59.52 samples/bit = 1369 samples. The DCS tone sequence is continuously
|
||||
* transmitted at a rate of 134.4 bits-per-second / 23 = 5.84 repeats per second while the source continues transmitting.
|
||||
*/
|
||||
public class DCSDecoder extends Decoder implements IRealBufferListener, Listener<float[]>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(DCSDecoder.class);
|
||||
//Slope calculation sumXX value is always the same for 59 evenly spaced samples
|
||||
private static final double SLOPE_CALCULATION_SUM_XX = 2247.5;
|
||||
private static final float BAUD_LENGTH = 8000.0f / 134.4f;
|
||||
private static final float SLOPE_THRESHOLD = 0.002750f;
|
||||
private static final int MAX_ONES_SEQUENCE = 6;
|
||||
private static final int OVERLAP = (int)Math.ceil(BAUD_LENGTH);
|
||||
private static final int POST_TRANSITION_SAMPLES_TO_SKIP = 30;
|
||||
private static final int SLOPE_CALCULATION_PERIOD = 30;
|
||||
private static final int IDEAL_SYMBOL_TRANSITION_MIN = 11;
|
||||
private static final int IDEAL_SYMBOL_TRANSITION_MAX = 19;
|
||||
private static final int CODE_MASK = 0x7FFFFF; //23-bit mask
|
||||
private static float[] sLowPassFilterCoefficients;
|
||||
private boolean mSymbol = false;
|
||||
private float mBaudCounter = 0f;
|
||||
private float mMaxSlope = 0;
|
||||
private float[] mResidual = new float[OVERLAP];
|
||||
private int mCode = 0;
|
||||
private int mExcessiveOneSequenceCounter = 0;
|
||||
|
||||
static
|
||||
{
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate(8000)
|
||||
.gridDensity(16)
|
||||
.oddLength(true)
|
||||
.passBandCutoff(200)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandStart(300)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandRipple(0.03) //Approximately 60 dB attenuation
|
||||
.build();
|
||||
|
||||
try
|
||||
{
|
||||
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||
|
||||
if(designer.isValid())
|
||||
{
|
||||
sLowPassFilterCoefficients = designer.getImpulseResponse();
|
||||
}
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Filter design error", fde);
|
||||
}
|
||||
}
|
||||
|
||||
private IRealFilter mLowPassFilter = FilterFactory.getRealFilter(sLowPassFilterCoefficients);
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public DCSDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoder type
|
||||
*/
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.DCS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the IRealBufferListener interface
|
||||
*/
|
||||
@Override
|
||||
public Listener<float[]> getBufferListener()
|
||||
{
|
||||
return this::receive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the demodulated 8 kHz audio samples to extract the 300-baud LTR signalling and delivers the decoded
|
||||
* symbol array to the registered listener.
|
||||
* @param samples to demodulate.
|
||||
*/
|
||||
public void receive(float[] samples)
|
||||
{
|
||||
if(getMessageListener() != null)
|
||||
{
|
||||
float[] filtered = mLowPassFilter.filter(samples);
|
||||
float[] buffer = new float[filtered.length + OVERLAP];
|
||||
int samplesToSkip = 0;
|
||||
int timingAdjust = 0;
|
||||
float slope = 0;
|
||||
|
||||
try
|
||||
{
|
||||
System.arraycopy(mResidual, 0, buffer, 0, mResidual.length);
|
||||
System.arraycopy(filtered, 0, buffer, mResidual.length, filtered.length);
|
||||
System.arraycopy(filtered, filtered.length - OVERLAP, mResidual, 0, OVERLAP);
|
||||
|
||||
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++)
|
||||
{
|
||||
//Don't calculate slope if we're within the baud period following the last symbol transition
|
||||
if(samplesToSkip > 0)
|
||||
{
|
||||
samplesToSkip--;
|
||||
}
|
||||
else
|
||||
{
|
||||
slope = calculateSlope(buffer, bufferPointer);
|
||||
|
||||
if(mSymbol)
|
||||
{
|
||||
//Look for crest of slope at lowest point below the negative slope threshold for transition
|
||||
if(slope > mMaxSlope && mMaxSlope < -SLOPE_THRESHOLD)
|
||||
{
|
||||
mSymbol = false;
|
||||
|
||||
//Coarse symbol timing adjust - ideal toggle is middle of baud, between 11 & 19 samples
|
||||
if(mBaudCounter < IDEAL_SYMBOL_TRANSITION_MIN)
|
||||
{
|
||||
timingAdjust = 1;
|
||||
}
|
||||
else if(mBaudCounter > IDEAL_SYMBOL_TRANSITION_MAX)
|
||||
{
|
||||
timingAdjust = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
timingAdjust = 0;
|
||||
}
|
||||
|
||||
mBaudCounter += timingAdjust;
|
||||
samplesToSkip = POST_TRANSITION_SAMPLES_TO_SKIP + timingAdjust;
|
||||
}
|
||||
else if(slope < mMaxSlope)
|
||||
{
|
||||
mMaxSlope = slope;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Loop for crest of slope at the highest point above the positive threshold
|
||||
if(slope < mMaxSlope && mMaxSlope > SLOPE_THRESHOLD)
|
||||
{
|
||||
mSymbol = true;
|
||||
|
||||
//Coarse symbol timing adjust - ideal toggle is middle of baud, between 11 & 19 samples
|
||||
if(mBaudCounter < IDEAL_SYMBOL_TRANSITION_MIN)
|
||||
{
|
||||
timingAdjust = 1;
|
||||
}
|
||||
else if(mBaudCounter > IDEAL_SYMBOL_TRANSITION_MAX)
|
||||
{
|
||||
timingAdjust = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
timingAdjust = 0;
|
||||
}
|
||||
|
||||
mBaudCounter += timingAdjust;
|
||||
samplesToSkip = POST_TRANSITION_SAMPLES_TO_SKIP + timingAdjust;
|
||||
}
|
||||
else if(slope > mMaxSlope)
|
||||
{
|
||||
mMaxSlope = slope;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mBaudCounter++;
|
||||
|
||||
//Update the current code value once we've processed a baud/symbol period of samples
|
||||
if(mBaudCounter > BAUD_LENGTH)
|
||||
{
|
||||
//Update the code value adding in the current symbol
|
||||
mCode = (Integer.rotateLeft(mCode, 1) + (mSymbol ? 1 : 0)) & CODE_MASK;
|
||||
if(DCSCode.hasValue(mCode))
|
||||
{
|
||||
getMessageListener().receive(new DCSMessage(DCSCode.fromValue(mCode), System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
mBaudCounter -= BAUD_LENGTH;
|
||||
|
||||
//Check for a continuous sequence of ones that exceeds the max (6) for any DCS code and
|
||||
//flip/revert the symbol state back to zero.
|
||||
if(mSymbol)
|
||||
{
|
||||
mExcessiveOneSequenceCounter++;
|
||||
|
||||
if(mExcessiveOneSequenceCounter > MAX_ONES_SEQUENCE)
|
||||
{
|
||||
mSymbol = false;
|
||||
mMaxSlope = -1f;
|
||||
mExcessiveOneSequenceCounter = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mExcessiveOneSequenceCounter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.warn("Unexpected error while processing DCS samples", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the slope of the 30 samples in the values array starting at the specified offset. The baud period
|
||||
* is slightly more than 59 samples, however the voltage state transition occurs over a 30-sample period.
|
||||
*
|
||||
* Note: code modified from Apache Commons Math library, SimpleRegression class.
|
||||
* @param samples to load from
|
||||
* @param offset to the sample start for the slope calculation
|
||||
*/
|
||||
public float calculateSlope(float[] samples, int offset)
|
||||
{
|
||||
/** mean of accumulated x & y values, used in updating formulas */
|
||||
double xbar = 0;
|
||||
double ybar = samples[offset];
|
||||
|
||||
// double sumXX = 0;
|
||||
double sumXY = 0;
|
||||
double fact1, dx, dy;
|
||||
|
||||
for(int x = 1; x < SLOPE_CALCULATION_PERIOD; x++)
|
||||
{
|
||||
fact1 = 1.0 + x;
|
||||
// final double fact2 = x / (1.0 + x);
|
||||
dx = x - xbar;
|
||||
dy = samples[offset + x] - ybar;
|
||||
// sumXX += dx * dx * fact2;
|
||||
sumXY += dx * dy * (x / (1.0 + x));
|
||||
xbar += dx / fact1;
|
||||
ybar += dy / fact1;
|
||||
}
|
||||
|
||||
//Transfer the sumXX value to SLOPE_CALCULATION_SUM_XX since it's a constant value
|
||||
// mLog.info("SumXX: " + sumXX);
|
||||
|
||||
return (float)(sumXY / SLOPE_CALCULATION_SUM_XX);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program 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 program 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 program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dcs;
|
||||
|
||||
import io.github.dsheirer.channel.state.DecoderState;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Maintains the decoder state from the stream of decoded DCS code messages.
|
||||
*/
|
||||
public class DCSDecoderState extends DecoderState
|
||||
{
|
||||
private Map<DCSCode,Integer> mDcsCodeCountsMap = new HashMap<>();
|
||||
private DCSCode mCurrentCode = DCSCode.UNKNOWN;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public DCSDecoderState()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.DCS;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
{
|
||||
if(message instanceof DCSMessage dcsMessage)
|
||||
{
|
||||
mCurrentCode = dcsMessage.getDCSCode();
|
||||
|
||||
if(!mDcsCodeCountsMap.containsKey(mCurrentCode))
|
||||
{
|
||||
mDcsCodeCountsMap.put(mCurrentCode, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
mDcsCodeCountsMap.put(mCurrentCode, mDcsCodeCountsMap.get(mCurrentCode) + 1);
|
||||
}
|
||||
|
||||
getIdentifierCollection().update(dcsMessage.getIdentifiers());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveDecoderStateEvent(DecoderStateEvent event)
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case REQUEST_RESET:
|
||||
resetState();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActivitySummary()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("=============================\n");
|
||||
sb.append("Decoder:\tDigital Coded Squelch (DCS)\n\n");
|
||||
if(mDcsCodeCountsMap.isEmpty())
|
||||
{
|
||||
sb.append(" Detected Codes: (none)\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
for(Map.Entry<DCSCode,Integer> entry: mDcsCodeCountsMap.entrySet())
|
||||
{
|
||||
sb.append(" ").append(entry.getKey()).append(" - Count: ").append(entry.getValue());
|
||||
|
||||
if(entry.getKey().equals(mCurrentCode))
|
||||
{
|
||||
sb.append(" - Current\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program 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 program 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 program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dcs;
|
||||
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.dcs.DCSIdentifier;
|
||||
import io.github.dsheirer.message.Message;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Digital Coded Squelch (DCS) tone detected message
|
||||
*/
|
||||
public class DCSMessage extends Message
|
||||
{
|
||||
private final DCSCode mDCSCode;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param code that was detected
|
||||
* @param timestamp when the code was detected
|
||||
*/
|
||||
public DCSMessage(DCSCode code, long timestamp)
|
||||
{
|
||||
super(timestamp);
|
||||
mDCSCode = code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Digital Coded Squelch (DCS) Detected: " + mDCSCode.name();
|
||||
}
|
||||
|
||||
/**
|
||||
* The DCS code that was detected.
|
||||
* @return code
|
||||
*/
|
||||
public DCSCode getDCSCode()
|
||||
{
|
||||
return mDCSCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
return true; //We only send a message when the tone was valid
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return Protocol.DCS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return Collections.singletonList(new DCSIdentifier(mDCSCode));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program 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 program 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 program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dcs;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Message filter for Digital Coded Squelch (DCS)
|
||||
*/
|
||||
public class DCSMessageFilter extends Filter<IMessage>
|
||||
{
|
||||
private Map<String,FilterElement<String>> mElements = new HashMap<>();
|
||||
private static final String DCS_MESSAGE_FILTER_TAG = "DCS";
|
||||
|
||||
public DCSMessageFilter()
|
||||
{
|
||||
super("DCS Message Filter");
|
||||
mElements.put(DCS_MESSAGE_FILTER_TAG, new FilterElement<>("DCS Code", true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes(IMessage message)
|
||||
{
|
||||
if(mEnabled && canProcess(message))
|
||||
{
|
||||
return mElements.get(DCS_MESSAGE_FILTER_TAG).isEnabled();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess(IMessage message)
|
||||
{
|
||||
return message instanceof DCSMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FilterElement<?>> getFilterElements()
|
||||
{
|
||||
return new ArrayList<FilterElement<?>>(mElements.values());
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program 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 program 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 program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program 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 program 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 program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.fleetsync2;
|
||||
|
||||
@@ -33,13 +30,16 @@ import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.fleetsync2.identifier.FleetsyncIdentifier;
|
||||
import io.github.dsheirer.module.decode.fleetsync2.message.Fleetsync2Message;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Fleetsync2DecoderState extends DecoderState
|
||||
{
|
||||
private Set<FleetsyncIdentifier> mIdents = new TreeSet<>();
|
||||
private Map<FleetsyncIdentifier,Integer> mFromIdentCountsMap = new HashMap<>();
|
||||
private Map<FleetsyncIdentifier,Integer> mToIdentCountsMap = new HashMap<>();
|
||||
|
||||
public Fleetsync2DecoderState()
|
||||
{
|
||||
@@ -52,16 +52,6 @@ public class Fleetsync2DecoderState extends DecoderState
|
||||
return DecoderType.FLEETSYNC2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the overall decoder state and clears any accumulated event details
|
||||
*/
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
super.reset();
|
||||
mIdents.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
@@ -87,8 +77,30 @@ public class Fleetsync2DecoderState extends DecoderState
|
||||
|
||||
getIdentifierCollection().update(fleetsync.getIdentifiers());
|
||||
|
||||
mIdents.add(fleetsync.getFromIdentifier());
|
||||
mIdents.add(fleetsync.getToIdentifier());
|
||||
FleetsyncIdentifier from = fleetsync.getFromIdentifier();
|
||||
|
||||
if(mFromIdentCountsMap.containsKey(from))
|
||||
{
|
||||
mFromIdentCountsMap.put(from, mFromIdentCountsMap.get(from) + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
mFromIdentCountsMap.put(from, 1);
|
||||
}
|
||||
|
||||
FleetsyncIdentifier to = fleetsync.getToIdentifier();
|
||||
|
||||
if(to != null)
|
||||
{
|
||||
if(mToIdentCountsMap.containsKey(to))
|
||||
{
|
||||
mToIdentCountsMap.put(to, mToIdentCountsMap.get(to) + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
mToIdentCountsMap.put(to, 1);
|
||||
}
|
||||
}
|
||||
|
||||
switch(fleetsync.getMessageType())
|
||||
{
|
||||
@@ -158,17 +170,30 @@ public class Fleetsync2DecoderState extends DecoderState
|
||||
|
||||
sb.append("=============================\n");
|
||||
sb.append("Decoder:\tFleetsync II\n\n");
|
||||
sb.append("Fleetsync Idents\n");
|
||||
|
||||
if(mIdents.isEmpty())
|
||||
if(mFromIdentCountsMap.isEmpty() && mToIdentCountsMap.isEmpty())
|
||||
{
|
||||
sb.append("Fleetsync Idents\n");
|
||||
sb.append(" None\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("Fleetsync From Idents\n");
|
||||
|
||||
for (FleetsyncIdentifier mIdent : mIdents) {
|
||||
sb.append(" ").append(mIdent.formatted()).append("\n");
|
||||
List<FleetsyncIdentifier> fromIdents = new ArrayList<>(mFromIdentCountsMap.keySet());
|
||||
Collections.sort(fromIdents);
|
||||
for(FleetsyncIdentifier from: fromIdents)
|
||||
{
|
||||
sb.append(" ").append(from.formatted()).append(" - Count:").append(mFromIdentCountsMap.get(from)).append("\n");
|
||||
}
|
||||
|
||||
sb.append("\nFleetsync To Idents\n");
|
||||
|
||||
List<FleetsyncIdentifier> toIdents = new ArrayList<>(mToIdentCountsMap.keySet());
|
||||
Collections.sort(toIdents);
|
||||
for(FleetsyncIdentifier to: toIdents)
|
||||
{
|
||||
sb.append(" ").append(to.formatted()).append(" - Count:").append(mToIdentCountsMap.get(to)).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ public enum Protocol
|
||||
APCO25_PHASE2("APCO-25 P2", "APCO25PHASE2", 12000),
|
||||
ARS("ARS", "ARS", 0),
|
||||
CELLOCATOR("CELLOCATOR", "CELLOCATOR", 0),
|
||||
DCS("DCS", "DCS", 134),
|
||||
DMR("DMR", "DMR", 9600),
|
||||
FLEETSYNC("Fleetsync", "FLEETSYNC", 1200),
|
||||
IPV4("IPV4", "IPV4", 0),
|
||||
|
||||
Reference in New Issue
Block a user