mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
Closes #7 NXDN Decoder * Implements: NXDN4800, NXDN9600, Type-C and Type-D decoding and trunk tracking. * Redesigns tuner and channel frequency error management.
This commit is contained in:
@@ -1,252 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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.carrier;
|
||||
|
||||
import io.github.dsheirer.buffer.FloatAveragingBuffer;
|
||||
import io.github.dsheirer.dsp.window.WindowFactory;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
|
||||
import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation;
|
||||
import org.jtransforms.fft.FloatFFT_1D;
|
||||
|
||||
/**
|
||||
* Calculates the carrier offset from channel center. Processes incoming Complex Sample buffers to detect the carrier
|
||||
* signal and calculate a carrier offset measurement.
|
||||
* <p>
|
||||
* Calculates once a second by performing a 128-point FFT on each consecutive complex buffer. Once it detects a
|
||||
* sequence of 5 consecutive buffers with sufficient SNR (>15 dB), it calculates the average offset across those 5
|
||||
* buffers and then averages across the 10 most recent high-SNR sequences.
|
||||
*/
|
||||
public class CarrierOffsetProcessor
|
||||
{
|
||||
private static final int FFT_BIN_SIZE = 128;
|
||||
private static final int MEASUREMENT_COUNT_THRESHOLD = 5;
|
||||
private static final int SEARCH_WINDOW_MIDDLE = FFT_BIN_SIZE / 2;
|
||||
private static final long CALCULATION_TIME_INTERVAL_MS = 1000;
|
||||
private static final float CENTER_INDEX = FFT_BIN_SIZE / 2.0f - 1;
|
||||
private static final float[] WINDOW = WindowFactory.getWindow(WindowType.BLACKMAN_HARRIS_7, FFT_BIN_SIZE * 2);
|
||||
private static final FloatFFT_1D FFT = new FloatFFT_1D(FFT_BIN_SIZE);
|
||||
private final FloatAveragingBuffer mAveragingBuffer = new FloatAveragingBuffer(5);
|
||||
private final FloatAveragingBuffer mOffsetAverage = new FloatAveragingBuffer(10, 3);
|
||||
private final StandardDeviation mStandardDeviation = new StandardDeviation();
|
||||
private float mCarrierOffset;
|
||||
private float mResolution;
|
||||
private int mHalfWidth;
|
||||
private int mMeasurementCount = 0;
|
||||
private int mSearchWindowMin;
|
||||
private int mSearchWindowMax;
|
||||
private long mEstimatedOffset;
|
||||
private long mLastCalculationTimestamp = 0;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*/
|
||||
public CarrierOffsetProcessor()
|
||||
{
|
||||
setSampleRate(50000.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current channel sample rate.
|
||||
* @param sampleRate in Hertz
|
||||
*/
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
mResolution = (float)sampleRate / FFT_BIN_SIZE;
|
||||
mHalfWidth = (int)Math.floor(12500.0 / mResolution / 2.0); //Half of 12.5 kHz signal width
|
||||
mSearchWindowMin = SEARCH_WINDOW_MIDDLE - mHalfWidth;
|
||||
mSearchWindowMax = SEARCH_WINDOW_MIDDLE + mHalfWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this processor whenever the source tuner PPM value is changed or updated, so that we can immediately
|
||||
* recalculate the carrier offset with the updated settings.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
mMeasurementCount = 0;
|
||||
mLastCalculationTimestamp = 0;
|
||||
mEstimatedOffset = 0;
|
||||
mAveragingBuffer.reset();
|
||||
mStandardDeviation.clear();
|
||||
mOffsetAverage.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimated carrier frequency offset averaged across buffers that have sufficient SNR (>15 dB).
|
||||
* @return estimated average carrier offset. Note: this method can be accessed after each invocation of process()
|
||||
* method.
|
||||
*/
|
||||
public long getEstimatedOffset()
|
||||
{
|
||||
return mEstimatedOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if a signal was detected in the most recently processed sample buffer. A value of false indicates the
|
||||
* signal was either not present, or the SNR was less than 15 dB and therefore this processor could not calculate
|
||||
* the carrier offest estimation.
|
||||
* @return true if the most recent buffer had a signal that was also strong enough to process.
|
||||
*/
|
||||
public boolean hasCarrier()
|
||||
{
|
||||
return mCarrierOffset != Float.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the complex sample buffer by calculating the detected carrier offset and mixing the buffer samples by
|
||||
* remove the calculated offset. The current detected carrier offset is available via the getEstimatedOffset()
|
||||
* method.
|
||||
*
|
||||
* @return true if the estimated carrier offset value was updated.
|
||||
*/
|
||||
public boolean process(ComplexSamples samples)
|
||||
{
|
||||
if(mLastCalculationTimestamp + CALCULATION_TIME_INTERVAL_MS < samples.timestamp())
|
||||
{
|
||||
int bufferOffset = 0;
|
||||
|
||||
while((bufferOffset + FFT_BIN_SIZE) <= samples.length())
|
||||
{
|
||||
mCarrierOffset = calculateCarrierOffset(samples, bufferOffset);
|
||||
|
||||
//Float max value indicates no signal or low SNR - reset the counter so that we can keep trying.
|
||||
if(mCarrierOffset == Float.MAX_VALUE)
|
||||
{
|
||||
mMeasurementCount = 0;
|
||||
mStandardDeviation.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
mMeasurementCount++;
|
||||
mAveragingBuffer.add(mCarrierOffset);
|
||||
}
|
||||
|
||||
if(mMeasurementCount >= MEASUREMENT_COUNT_THRESHOLD)
|
||||
{
|
||||
if(mStandardDeviation.getResult() < 5.0)
|
||||
{
|
||||
mOffsetAverage.add(mAveragingBuffer.getAverage());
|
||||
mLastCalculationTimestamp = samples.timestamp();
|
||||
|
||||
//Set the buffer offset to the sample length to exit the loop
|
||||
bufferOffset = samples.length();
|
||||
}
|
||||
|
||||
mMeasurementCount = 0;
|
||||
mStandardDeviation.clear();
|
||||
}
|
||||
|
||||
bufferOffset += FFT_BIN_SIZE;
|
||||
}
|
||||
|
||||
mEstimatedOffset = (long)mOffsetAverage.getAverage();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the detected peak signal offset from the sample buffer.
|
||||
* @param complexSamples to process
|
||||
* @param offset into the complex samples buffer to calculate
|
||||
* @return estimated carrier offset from center of the channel.
|
||||
*/
|
||||
private float calculateCarrierOffset(ComplexSamples complexSamples, int offset)
|
||||
{
|
||||
float[] samples = complexSamples.toInterleaved(offset, FFT_BIN_SIZE).samples();
|
||||
WindowFactory.apply(WINDOW, samples);
|
||||
FFT.complexForward(samples);
|
||||
float[] magnitudes = ComplexDecibelConverter.convert(samples);
|
||||
float peakIndex = findPeak(magnitudes);
|
||||
|
||||
//Return float max value for Low SNR or no signal
|
||||
if(peakIndex == Float.MAX_VALUE)
|
||||
{
|
||||
return Float.MAX_VALUE;
|
||||
}
|
||||
|
||||
mStandardDeviation.increment(peakIndex);
|
||||
return mResolution * (peakIndex - CENTER_INDEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the FFT bin with the largest magnitude within a window of +/- 12.5 kHz of the center bin.
|
||||
* @param magnitudes from the FFT measurement.
|
||||
* @return center bin or Float.MAX_VALUE to indicate no signal or low SNR.
|
||||
*/
|
||||
private float findPeak(float[] magnitudes)
|
||||
{
|
||||
float peakValue = -150.0f;
|
||||
int peakIndex = 0;
|
||||
int min = mSearchWindowMin;
|
||||
int max = mSearchWindowMax;
|
||||
int half = mHalfWidth;
|
||||
|
||||
//Find the peak magnitude within twice the search window width of the center indices.
|
||||
for(int x = min; x <= max; x++)
|
||||
{
|
||||
if(magnitudes[x] > peakValue)
|
||||
{
|
||||
peakValue = magnitudes[x];
|
||||
peakIndex = x;
|
||||
}
|
||||
}
|
||||
|
||||
//From there, find the center point that best fits the signal to the search window width. We need a minimum
|
||||
// SNR for this to work.
|
||||
int left = peakIndex - half;
|
||||
int right = peakIndex + half;
|
||||
|
||||
//Do we have enough SNR?
|
||||
float snr = peakValue - ((magnitudes[left] + magnitudes[right]) / 2.0f);
|
||||
|
||||
if(snr > 15.0f)
|
||||
{
|
||||
float minimumSearchThreshold = peakValue - 16.0f; //Stop at bin values 16 db lower.
|
||||
|
||||
while(magnitudes[left] < minimumSearchThreshold && left < peakIndex)
|
||||
{
|
||||
left++;
|
||||
}
|
||||
|
||||
while(magnitudes[right] < minimumSearchThreshold && right > peakIndex)
|
||||
{
|
||||
right--;
|
||||
}
|
||||
|
||||
int binWidth = right - left;
|
||||
|
||||
if(binWidth == 0)
|
||||
{
|
||||
return peakIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
return left + (binWidth / 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
//No signal or low SNR - return MAX VALUE to indicate no signal.
|
||||
return Float.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 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
|
||||
@@ -81,6 +81,12 @@ import io.github.dsheirer.module.decode.mpt1327.Sync;
|
||||
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
|
||||
import io.github.dsheirer.module.decode.nbfm.NBFMDecoder;
|
||||
import io.github.dsheirer.module.decode.nbfm.NBFMDecoderState;
|
||||
import io.github.dsheirer.module.decode.nxdn.DecodeConfigNXDN;
|
||||
import io.github.dsheirer.module.decode.nxdn.NXDNDecoder;
|
||||
import io.github.dsheirer.module.decode.nxdn.NXDNDecoderState;
|
||||
import io.github.dsheirer.module.decode.nxdn.NXDNTrafficChannelManager;
|
||||
import io.github.dsheirer.module.decode.nxdn.audio.NXDNAudioModule;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.filter.NXDNMessageFilterSet;
|
||||
import io.github.dsheirer.module.decode.p25.P25TrafficChannelManager;
|
||||
import io.github.dsheirer.module.decode.p25.audio.P25P1AudioModule;
|
||||
import io.github.dsheirer.module.decode.p25.audio.P25P2AudioModule;
|
||||
@@ -171,9 +177,6 @@ public class DecoderFactory
|
||||
processDMR(channel, userPreferences, modules, aliasList, (DecodeConfigDMR)decodeConfig,
|
||||
trafficChannelManager, channelDescriptor);
|
||||
break;
|
||||
case NBFM:
|
||||
processNBFM(channel, modules, aliasList, decodeConfig);
|
||||
break;
|
||||
case LTR:
|
||||
processLTRStandard(channel, modules, aliasList, (DecodeConfigLTRStandard) decodeConfig);
|
||||
break;
|
||||
@@ -184,6 +187,12 @@ public class DecoderFactory
|
||||
processMPT1327(channelMapModel, channel, modules, aliasList, channelType,
|
||||
(DecodeConfigMPT1327) decodeConfig, userPreferences);
|
||||
break;
|
||||
case NBFM:
|
||||
processNBFM(channel, modules, aliasList, decodeConfig);
|
||||
break;
|
||||
case NXDN:
|
||||
processNXDN(channel, userPreferences, modules, aliasList, decodeConfig, trafficChannelManager, channelDescriptor);
|
||||
break;
|
||||
case PASSPORT:
|
||||
processPassport(channel, modules, aliasList, decodeConfig);
|
||||
break;
|
||||
@@ -462,6 +471,55 @@ public class DecoderFactory
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates decoder modules for the NXDN decoder.
|
||||
* @param channel configuration with center frequency
|
||||
* @param modules to receive the decoder modules
|
||||
* @param aliasList for aliases.
|
||||
* @param decodeConfig with details
|
||||
*/
|
||||
private static void processNXDN(Channel channel, UserPreferences userPreferences, List<Module> modules,
|
||||
AliasList aliasList, DecodeConfiguration decodeConfig,
|
||||
TrafficChannelManager trafficChannelManager, IChannelDescriptor channelDescriptor)
|
||||
{
|
||||
if(decodeConfig instanceof DecodeConfigNXDN configNXDN)
|
||||
{
|
||||
//Add a channel rotation monitor when we have multiple control channel frequencies specified
|
||||
if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency sctmf &&
|
||||
sctmf.hasMultipleFrequencies())
|
||||
{
|
||||
List<State> activeStates = new ArrayList<>();
|
||||
activeStates.add(State.CONTROL);
|
||||
modules.add(new ChannelRotationMonitor(activeStates, sctmf.getFrequencyRotationDelay(), userPreferences));
|
||||
}
|
||||
|
||||
modules.add(new NXDNDecoder(configNXDN));
|
||||
modules.add(new NXDNAudioModule(userPreferences, aliasList));
|
||||
|
||||
if(channel.getChannelType() == ChannelType.STANDARD)
|
||||
{
|
||||
NXDNTrafficChannelManager primaryTCM = new NXDNTrafficChannelManager(channel);
|
||||
modules.add(primaryTCM);
|
||||
modules.add(new NXDNDecoderState(channel, primaryTCM));
|
||||
}
|
||||
else if(trafficChannelManager instanceof NXDNTrafficChannelManager parentTCM)
|
||||
{
|
||||
NXDNDecoderState decoderState = new NXDNDecoderState(channel, parentTCM);
|
||||
decoderState.setCurrentChannel(channelDescriptor);
|
||||
modules.add(decoderState);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.warn("Expected non-null NXDN traffic channel manager for channel " + channel.getName());
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Can't create NXDN decoder - unrecognized config: " + decodeConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates modules for DMR decoder setup.
|
||||
*
|
||||
@@ -622,9 +680,9 @@ public class DecoderFactory
|
||||
|
||||
for(Module module : modules)
|
||||
{
|
||||
if(module instanceof Decoder)
|
||||
if(module instanceof Decoder decoder)
|
||||
{
|
||||
filterSet.addFilters(getMessageFilter(((Decoder)module).getDecoderType()));
|
||||
filterSet.addFilters(getMessageFilter(decoder.getDecoderType()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,6 +726,9 @@ public class DecoderFactory
|
||||
case MPT1327:
|
||||
filters.add(new MPT1327MessageFilter());
|
||||
break;
|
||||
case NXDN:
|
||||
filters.add(new NXDNMessageFilterSet());
|
||||
break;
|
||||
case P25_PHASE1:
|
||||
filters.add(new P25P1MessageFilterSet());
|
||||
break;
|
||||
@@ -708,6 +769,8 @@ public class DecoderFactory
|
||||
return new DecodeConfigMPT1327();
|
||||
case NBFM:
|
||||
return new DecodeConfigNBFM();
|
||||
case NXDN:
|
||||
return new DecodeConfigNXDN();
|
||||
case PASSPORT:
|
||||
return new DecodeConfigPassport();
|
||||
case P25_PHASE1:
|
||||
@@ -767,6 +830,13 @@ public class DecoderFactory
|
||||
copyNBFM.setSquelchNoiseCloseThreshold(origNBFM.getSquelchNoiseCloseThreshold());
|
||||
copyNBFM.setTalkgroup(origNBFM.getTalkgroup());
|
||||
return copyNBFM;
|
||||
case NXDN:
|
||||
DecodeConfigNXDN origNXDN = (DecodeConfigNXDN)config;
|
||||
DecodeConfigNXDN copyNXDN = new DecodeConfigNXDN();
|
||||
copyNXDN.setChannelMap(origNXDN.getChannelMap());
|
||||
copyNXDN.setTransmissionMode(origNXDN.getTransmissionMode());
|
||||
copyNXDN.setTrafficChannelPoolSize(origNXDN.getTrafficChannelPoolSize());
|
||||
return copyNXDN;
|
||||
case P25_PHASE1:
|
||||
DecodeConfigP25Phase1 originalP25 = (DecodeConfigP25Phase1)config;
|
||||
DecodeConfigP25Phase1 copyP25 = new DecodeConfigP25Phase1();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -28,11 +28,12 @@ public enum DecoderType
|
||||
{
|
||||
//Primary Decoders
|
||||
AM("AM", "AM", Protocol.UNKNOWN),
|
||||
DMR("DMR","DMR", Protocol.DMR),
|
||||
DMR("DMR", "DMR", Protocol.DMR),
|
||||
LTR("LTR", "LTR", Protocol.LTR),
|
||||
LTR_NET("LTR-Net", "LTR-Net", Protocol.LTR_NET),
|
||||
MPT1327("MPT1327", "MPT1327", Protocol.MPT1327),
|
||||
NBFM("NBFM", "NBFM", Protocol.NBFM),
|
||||
NXDN("NXDN", "NXDN", Protocol.NXDN),
|
||||
PASSPORT("Passport", "Passport", Protocol.PASSPORT),
|
||||
P25_PHASE1("P25 Phase 1", "P25-1", Protocol.APCO25),
|
||||
P25_PHASE2("P25 Phase 2", "P25-2", Protocol.APCO25_PHASE2),
|
||||
@@ -44,9 +45,9 @@ public enum DecoderType
|
||||
MDC1200("MDC1200", "MDC1200", Protocol.MDC1200),
|
||||
TAIT_1200("Tait 1200", "Tait 1200", Protocol.TAIT1200);
|
||||
|
||||
private String mDisplayString;
|
||||
private String mShortDisplayString;
|
||||
private Protocol mProtocol;
|
||||
private final String mDisplayString;
|
||||
private final String mShortDisplayString;
|
||||
private final Protocol mProtocol;
|
||||
|
||||
DecoderType(String displayString, String shortDisplayString, Protocol protocol)
|
||||
{
|
||||
@@ -58,38 +59,23 @@ public enum DecoderType
|
||||
/**
|
||||
* Primary decoders that operate on I/Q sample streams
|
||||
*/
|
||||
public static EnumSet<DecoderType> PRIMARY_DECODERS =
|
||||
EnumSet.of(DecoderType.AM,
|
||||
DecoderType.DMR,
|
||||
DecoderType.LTR,
|
||||
DecoderType.LTR_NET,
|
||||
DecoderType.MPT1327,
|
||||
DecoderType.NBFM,
|
||||
DecoderType.P25_PHASE1,
|
||||
DecoderType.P25_PHASE2,
|
||||
DecoderType.PASSPORT);
|
||||
public static final EnumSet<DecoderType> PRIMARY_DECODERS = EnumSet.of(AM, DMR, LTR, LTR_NET, MPT1327, NBFM, NXDN,
|
||||
P25_PHASE1, P25_PHASE2, PASSPORT);
|
||||
|
||||
/**
|
||||
* Auxiliary decoders that operate on in-band signalling in the decoded audio channel
|
||||
*/
|
||||
public static final EnumSet<DecoderType> AUX_DECODERS =
|
||||
EnumSet.of(DecoderType.DCS,
|
||||
DecoderType.FLEETSYNC2,
|
||||
DecoderType.LJ_1200,
|
||||
DecoderType.MDC1200,
|
||||
DecoderType.TAIT_1200);
|
||||
public static final EnumSet<DecoderType> AUX_DECODERS = EnumSet.of(DCS, FLEETSYNC2, LJ_1200, MDC1200, TAIT_1200);
|
||||
|
||||
/**
|
||||
* Decoders that produce a (recordable) bitstream
|
||||
*/
|
||||
public static final EnumSet<DecoderType> BITSTREAM_DECODERS = EnumSet.of(DecoderType.DMR,
|
||||
DecoderType.MPT1327, DecoderType.P25_PHASE1, DecoderType.P25_PHASE2);
|
||||
public static final EnumSet<DecoderType> BITSTREAM_DECODERS = EnumSet.of(DMR, MPT1327, NXDN, P25_PHASE1, P25_PHASE2);
|
||||
|
||||
/**
|
||||
* Decoders that produce (recordable) MBE audio codec frames
|
||||
*/
|
||||
public static final EnumSet<DecoderType> MBE_AUDIO_CODEC_DECODERS =
|
||||
EnumSet.of(DecoderType.DMR, DecoderType.P25_PHASE1, DecoderType.P25_PHASE2);
|
||||
public static final EnumSet<DecoderType> MBE_AUDIO_CODEC_DECODERS = EnumSet.of(DMR, NXDN, P25_PHASE1, P25_PHASE2);
|
||||
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 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
|
||||
@@ -29,8 +29,19 @@ import io.github.dsheirer.source.SourceEvent;
|
||||
*/
|
||||
public abstract class FeedbackDecoder extends PrimaryDecoder implements ISourceEventProvider
|
||||
{
|
||||
private static final double TWO_PI = 2 * Math.PI;
|
||||
private Listener<SourceEvent> mSourceEventListener;
|
||||
private Listener<Float> mSymbolListener;
|
||||
private double mSampleRate;
|
||||
|
||||
/**
|
||||
* Sets the decimated final sample rate that will be used for reporting decoder PLL errors.
|
||||
* @param rate in Hertz
|
||||
*/
|
||||
protected void setDecimatedSampleRate(double rate)
|
||||
{
|
||||
mSampleRate = rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protocol description suitable for display in the user interface
|
||||
@@ -68,13 +79,19 @@ public abstract class FeedbackDecoder extends PrimaryDecoder implements ISourceE
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the current phase-locked loop (PLL) measurement and sends to the parent tuner for self correction.
|
||||
* @param pllError as measured in the decoder.
|
||||
* Processes the current phase-locked loop (PLL) measurement and sends to the channel frequency error manager to
|
||||
* correct the error by mixing the incoming sample stream to remove the error.
|
||||
*
|
||||
* Note: this source event error measurement is rebroadcast by the ProcessingChain and received by the
|
||||
* ChannelFrequencyErrorManager that lives within the TunerChannelSource where it adjusts the channel's mixer to
|
||||
* incrementally correct and drive the error toward zero in concert with the parent tuner's auto PPM correction.
|
||||
*
|
||||
* @param pllError as measured in the decoder, in Radians.
|
||||
*/
|
||||
public void processPLLError(float pllError, int baudRate)
|
||||
public void processPLLError(float pllError)
|
||||
{
|
||||
long frequencyError = (long)(baudRate / 2.0 * (pllError / Math.PI));
|
||||
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(frequencyError, "Decoder measured error sync locked"));
|
||||
long correctionRequestHertz = (long)(mSampleRate / TWO_PI * pllError);
|
||||
broadcast(SourceEvent.frequencyCorrectionRequest(correctionRequestHertz));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.analog;
|
||||
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.channel.IBandChannelDescriptor;
|
||||
import io.github.dsheirer.channel.state.DecoderState;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent;
|
||||
import io.github.dsheirer.channel.state.State;
|
||||
@@ -42,7 +42,7 @@ public abstract class AnalogDecoderState extends DecoderState implements ISource
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AnalogDecoderState.class);
|
||||
private Listener<SourceEvent> mSourceEventListener = new SourceEventListener();
|
||||
private DecodeEvent mDecodeEvent;
|
||||
private IChannelDescriptor mChannelDescriptor = null;
|
||||
private IBandChannelDescriptor mChannelDescriptor = null;
|
||||
|
||||
public AnalogDecoderState()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 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
|
||||
@@ -31,6 +31,7 @@ import io.github.dsheirer.module.decode.ltrnet.DecodeConfigLTRNet;
|
||||
import io.github.dsheirer.module.decode.ltrstandard.DecodeConfigLTRStandard;
|
||||
import io.github.dsheirer.module.decode.mpt1327.DecodeConfigMPT1327;
|
||||
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
|
||||
import io.github.dsheirer.module.decode.nxdn.DecodeConfigNXDN;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25;
|
||||
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
|
||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
@@ -39,13 +40,14 @@ import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = DecodeConfigAM.class, name = "decodeConfigAM"),
|
||||
@JsonSubTypes.Type(value = DecodeConfigDMR.class, name = "decodeConfigDMR"),
|
||||
@JsonSubTypes.Type(value = DecodeConfigLTRNet.class, name = "decodeConfigLTRNet"),
|
||||
@JsonSubTypes.Type(value = DecodeConfigLTRStandard.class, name = "decodeConfigLTRStandard"),
|
||||
@JsonSubTypes.Type(value = DecodeConfigMPT1327.class, name = "decodeConfigMPT1327"),
|
||||
@JsonSubTypes.Type(value = DecodeConfigNBFM.class, name = "decodeConfigNBFM"),
|
||||
@JsonSubTypes.Type(value = DecodeConfigNXDN.class, name = "decodeConfigNXDN"),
|
||||
@JsonSubTypes.Type(value = DecodeConfigP25.class, name = "decodeConfigP25"),
|
||||
@JsonSubTypes.Type(value = DecodeConfigPassport.class, name = "decodeConfigPassport"),
|
||||
@JsonSubTypes.Type(value = DecodeConfigDMR.class, name = "decodeConfigDMR")
|
||||
@JsonSubTypes.Type(value = DecodeConfigPassport.class, name = "decodeConfigPassport")
|
||||
})
|
||||
@JacksonXmlRootElement(localName = "decode_configuration")
|
||||
public abstract class DecodeConfiguration extends Configuration
|
||||
@@ -76,4 +78,10 @@ public abstract class DecodeConfiguration extends Configuration
|
||||
{
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public int getBitRate()
|
||||
{
|
||||
return getDecoderType().getProtocol().getBitRate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 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
|
||||
@@ -28,10 +28,8 @@ import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter;
|
||||
import io.github.dsheirer.dsp.psk.demod.DifferentialDemodulatorFactory;
|
||||
import io.github.dsheirer.dsp.psk.demod.DifferentialDemodulatorFloat;
|
||||
import io.github.dsheirer.dsp.squelch.PowerMonitor;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.SyncLossMessage;
|
||||
import io.github.dsheirer.module.carrier.CarrierOffsetProcessor;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.FeedbackDecoder;
|
||||
import io.github.dsheirer.module.decode.dmr.audio.DMRAudioModule;
|
||||
@@ -48,7 +46,6 @@ import io.github.dsheirer.sample.buffer.IByteBufferProvider;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.IComplexSamplesListener;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
import io.github.dsheirer.source.ISourceEventProvider;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.wave.ComplexWaveSource;
|
||||
import java.io.File;
|
||||
@@ -82,8 +79,8 @@ import org.slf4j.LoggerFactory;
|
||||
* The DMRMessageProcessor processes messages from the message framer to extract and reassemble link control and
|
||||
* embedded link control parameters.
|
||||
*/
|
||||
public class DMRDecoder extends FeedbackDecoder implements IByteBufferProvider, IComplexSamplesListener, ISourceEventListener,
|
||||
ISourceEventProvider, Listener<ComplexSamples>
|
||||
public class DMRDecoder extends FeedbackDecoder implements IByteBufferProvider, IComplexSamplesListener,
|
||||
ISourceEventListener, Listener<ComplexSamples>
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DMRDecoder.class);
|
||||
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##");
|
||||
@@ -100,8 +97,6 @@ public class DMRDecoder extends FeedbackDecoder implements IByteBufferProvider,
|
||||
private IRealDecimationFilter mDecimationFilterQ;
|
||||
private RealFIRFilter mRRCFilterI;
|
||||
private RealFIRFilter mRRCFilterQ;
|
||||
private final PowerMonitor mPowerMonitor = new PowerMonitor();
|
||||
private final CarrierOffsetProcessor mCarrierOffsetProcessor = new CarrierOffsetProcessor();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -140,9 +135,6 @@ public class DMRDecoder extends FeedbackDecoder implements IByteBufferProvider,
|
||||
SYMBOL_RATE + " symbol rate)");
|
||||
}
|
||||
|
||||
mPowerMonitor.setSampleRate((int)sampleRate);
|
||||
mCarrierOffsetProcessor.setSampleRate(sampleRate);
|
||||
|
||||
mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter(sampleRate));
|
||||
mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter(sampleRate));
|
||||
|
||||
@@ -158,6 +150,10 @@ public class DMRDecoder extends FeedbackDecoder implements IByteBufferProvider,
|
||||
mDecimationFilterQ = DecimationFilterFactory.getRealDecimationFilter(decimation);
|
||||
|
||||
float decimatedSampleRate = (float)sampleRate / decimation;
|
||||
|
||||
//Set the decimated sample rate to use for PLL error reporting.
|
||||
setDecimatedSampleRate(decimatedSampleRate);
|
||||
|
||||
float rrcAlpha = Math.abs((float)(5760.0 / decimatedSampleRate));
|
||||
int symbolLength = (int)Math.floor((-44 * rrcAlpha) + 33);
|
||||
symbolLength += symbolLength % 2; //Make the symbol length even
|
||||
@@ -190,9 +186,6 @@ public class DMRDecoder extends FeedbackDecoder implements IByteBufferProvider,
|
||||
float[] i = mIBasebandFilter.filter(samples.i());
|
||||
float[] q = mQBasebandFilter.filter(samples.q());
|
||||
|
||||
//Process buffer for power measurements
|
||||
mPowerMonitor.process(i, q);
|
||||
|
||||
i = mDecimationFilterI.decimateReal(i);
|
||||
q = mDecimationFilterQ.decimateReal(q);
|
||||
|
||||
@@ -201,25 +194,6 @@ public class DMRDecoder extends FeedbackDecoder implements IByteBufferProvider,
|
||||
|
||||
float[] demodulated = mDemodulator.demodulate(i, q);
|
||||
mSymbolProcessor.receive(demodulated);
|
||||
|
||||
//Estimate carrier offset and broadcast at each update. This value is used in the channel spectral display,
|
||||
// and it's also processed by the tuner's PPM error monitor to auto-adjust the tuner PPM value.
|
||||
if(mCarrierOffsetProcessor.process(samples))
|
||||
{
|
||||
//Tuner PPM Monitor - negate the value to indicate channel error from tuner's PPM that's causing the offset
|
||||
mPowerMonitor.broadcast(SourceEvent.frequencyErrorMeasurement(-mCarrierOffsetProcessor.getEstimatedOffset()));
|
||||
|
||||
//Channel spectral display - when there's a carrier send the estimate, otherwise send a zero to cause the
|
||||
//display to blank the carrier offset measurement indicator line
|
||||
if(mCarrierOffsetProcessor.hasCarrier())
|
||||
{
|
||||
mPowerMonitor.broadcast(SourceEvent.carrierOffsetMeasurement(mCarrierOffsetProcessor.getEstimatedOffset()));
|
||||
}
|
||||
else
|
||||
{
|
||||
mPowerMonitor.broadcast(SourceEvent.carrierOffsetMeasurement(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,21 +273,6 @@ public class DMRDecoder extends FeedbackDecoder implements IByteBufferProvider,
|
||||
return "DMR C4FM";
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source event listener to receive source events from this decoder.
|
||||
*/
|
||||
@Override
|
||||
public void setSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
mPowerMonitor.setSourceEventListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSourceEventListener()
|
||||
{
|
||||
mPowerMonitor.setSourceEventListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
@@ -340,9 +299,6 @@ public class DMRDecoder extends FeedbackDecoder implements IByteBufferProvider,
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
setSampleRate(sourceEvent.getValue().doubleValue());
|
||||
break;
|
||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||
mCarrierOffsetProcessor.reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 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
|
||||
@@ -67,7 +67,6 @@ public class DMRSoftSymbolProcessor
|
||||
private static final int BUFFER_PROTECTED_REGION_DIBITS = 92;
|
||||
private static final int BUFFER_WORKSPACE_LENGTH_DIBITS = 25; //This can be adjusted for efficiency
|
||||
private static final int BUFFER_LENGTH_DIBITS = BUFFER_PROTECTED_REGION_DIBITS + BUFFER_WORKSPACE_LENGTH_DIBITS;
|
||||
private static final int SYMBOL_RATE = 4800;
|
||||
private static final float EQUALIZER_LOOP_GAIN = 0.15f;
|
||||
private static final float MAXIMUM_EQUALIZER_BALANCE = (float)(Math.PI / 3.0);
|
||||
private static final float MAXIMUM_EQUALIZER_GAIN = 1.25f;
|
||||
@@ -565,7 +564,7 @@ public class DMRSoftSymbolProcessor
|
||||
mEqualizerBalance = Math.min(mEqualizerBalance, MAXIMUM_EQUALIZER_BALANCE);
|
||||
mEqualizerBalance = Math.max(mEqualizerBalance, -MAXIMUM_EQUALIZER_BALANCE);
|
||||
|
||||
mFeedbackDecoder.processPLLError(mEqualizerBalance, SYMBOL_RATE);
|
||||
mFeedbackDecoder.processPLLError(mEqualizerBalance);
|
||||
|
||||
//Constrain gain between 1.0f and 1.35f
|
||||
mEqualizerGain = Math.min(mEqualizerGain, MAXIMUM_EQUALIZER_GAIN);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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,7 @@
|
||||
package io.github.dsheirer.module.decode.dmr;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.channel.IBandChannelDescriptor;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.controller.channel.Channel.ChannelType;
|
||||
import io.github.dsheirer.controller.channel.ChannelConversionRequest;
|
||||
@@ -225,7 +225,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
* @param currentFrequency of the standard channel
|
||||
* @param restChannel to identify the new channel frequency to start
|
||||
*/
|
||||
public void convertToTrafficChannel(Channel channel, long currentFrequency, IChannelDescriptor restChannel,
|
||||
public void convertToTrafficChannel(Channel channel, long currentFrequency, IBandChannelDescriptor restChannel,
|
||||
DMRNetworkConfigurationMonitor networkConfigurationMonitor)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 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
|
||||
@@ -162,7 +162,7 @@ public class DecodeConfigDMR extends DecodeConfiguration
|
||||
@Override
|
||||
public ChannelSpecification getChannelSpecification()
|
||||
{
|
||||
return new ChannelSpecification(50000.0, 12500, 6500.0, 7200.0);
|
||||
return new ChannelSpecification(24000.0, 12500, 6500.0, 7200.0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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,7 @@
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.channel;
|
||||
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.channel.IBandChannelDescriptor;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
@@ -35,7 +35,7 @@ import org.apache.commons.lang3.Validate;
|
||||
*
|
||||
* Note: timeslots are tracked as 1 and 2
|
||||
*/
|
||||
public abstract class DMRChannel extends IntegerIdentifier implements IChannelDescriptor, ITimeslotFrequencyReceiver
|
||||
public abstract class DMRChannel extends IntegerIdentifier implements IBandChannelDescriptor, ITimeslotFrequencyReceiver
|
||||
{
|
||||
private TimeslotFrequency mTimeslotFrequency;
|
||||
private int mTimeslot;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 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
|
||||
@@ -17,7 +17,7 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
package io.github.dsheirer.module.decode.event;
|
||||
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
@@ -28,33 +28,33 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Wrapper to track the state of a traffic channel event to manage updates from the control channel and the traffic
|
||||
* channel and to assist in determining when the communicants of a traffic channel have changed, indicating the need
|
||||
* for a new event.
|
||||
* Base channel event tracker
|
||||
* @param <T> type of decode event being tracked.
|
||||
*/
|
||||
public class P25TrafficChannelEventTracker
|
||||
public abstract class ChannelEventTracker<T extends DecodeEvent>
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(P25TrafficChannelEventTracker.class);
|
||||
private static final long STALE_EVENT_THRESHOLD_MS = 2000;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelEventTracker.class);
|
||||
private static final long STALE_EVENT_THRESHOLD_MS = 3000;
|
||||
private static final long MAX_TDMA_DATA_CHANNEL_EVENT_DURATION_MS = 15000;
|
||||
private P25ChannelGrantEvent mEvent;
|
||||
private boolean mStarted = false;
|
||||
private boolean mComplete = false;
|
||||
private final T mEvent;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param event to track for the traffic channel.
|
||||
* @param event to be tracked
|
||||
*/
|
||||
public P25TrafficChannelEventTracker(P25ChannelGrantEvent event)
|
||||
public ChannelEventTracker(T event)
|
||||
{
|
||||
mEvent = event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the underlying traffic channel event that is being tracked.
|
||||
*
|
||||
* @return event.
|
||||
*/
|
||||
public P25ChannelGrantEvent getEvent()
|
||||
public T getEvent()
|
||||
{
|
||||
return mEvent;
|
||||
}
|
||||
@@ -62,6 +62,7 @@ public class P25TrafficChannelEventTracker
|
||||
/**
|
||||
* Indicates if this event is stale relative to the provided timestamp. Staleness is determined by the time delta
|
||||
* between the event's end time and timestamp argument.
|
||||
*
|
||||
* @param timestamp to check for staleness
|
||||
* @return true if the delta time exceeds a threshold.
|
||||
*/
|
||||
@@ -76,23 +77,23 @@ public class P25TrafficChannelEventTracker
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the TDMA data channel duration exceeds the threshold (15 seconds)
|
||||
* Indicates if the data channel duration exceeds the threshold (15 seconds)
|
||||
*/
|
||||
public boolean exceedsMaxTDMADataDuration()
|
||||
public boolean exceedsMaxDataDuration()
|
||||
{
|
||||
return getEvent().getDuration() > MAX_TDMA_DATA_CHANNEL_EVENT_DURATION_MS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the identifier to the tracked event if the event's identifier collection does not already have it.
|
||||
*
|
||||
* @param identifier to add
|
||||
*/
|
||||
public void addIdentifierIfMissing(Identifier identifier)
|
||||
{
|
||||
if(identifier != null && !getEvent().getIdentifierCollection().hasIdentifier(identifier))
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(getEvent().getIdentifierCollection()
|
||||
.getIdentifiers());
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(getEvent().getIdentifierCollection().getIdentifiers());
|
||||
mic.update(identifier);
|
||||
getEvent().setIdentifierCollection(mic);
|
||||
}
|
||||
@@ -101,7 +102,8 @@ public class P25TrafficChannelEventTracker
|
||||
/**
|
||||
* Adds the additional details to the tracked event if the current event details are null or if they do not contain
|
||||
* the additional details.
|
||||
* @param additionalDetails
|
||||
*
|
||||
* @param additionalDetails for the event
|
||||
*/
|
||||
public void addDetailsIfMissing(String additionalDetails)
|
||||
{
|
||||
@@ -135,6 +137,7 @@ public class P25TrafficChannelEventTracker
|
||||
|
||||
/**
|
||||
* Indicates if the tracked event from identifier is non null and that it is different to the from argument.
|
||||
*
|
||||
* @param fromToCompare against the current event from identifier.
|
||||
* @return true if they are different.
|
||||
*/
|
||||
@@ -153,6 +156,7 @@ public class P25TrafficChannelEventTracker
|
||||
/**
|
||||
* Checks the call for staleness and verifies that the call event TO identifier is equal to the TO identifier
|
||||
* in the provided identifier collection.
|
||||
*
|
||||
* @param toCompareIC containing a TO and optionally a FROM identifier to compare.
|
||||
* @param timestamp to check for staleness
|
||||
* @return true if the call identifiers are the same and the call is not stale.
|
||||
@@ -195,6 +199,7 @@ public class P25TrafficChannelEventTracker
|
||||
|
||||
/**
|
||||
* Indicates if the event has been marked as complete by traffic channel signalling.
|
||||
*
|
||||
* @return complete status.
|
||||
*/
|
||||
public boolean isComplete()
|
||||
@@ -204,6 +209,7 @@ public class P25TrafficChannelEventTracker
|
||||
|
||||
/**
|
||||
* Indicates if the event has been marked as started by the traffic channel for HDU or LDU signalling.
|
||||
*
|
||||
* @return started status.
|
||||
*/
|
||||
public boolean isStarted()
|
||||
@@ -236,6 +242,7 @@ public class P25TrafficChannelEventTracker
|
||||
*
|
||||
* Note: once the event is being updated from the traffic channel, any attempts to update from the control channel
|
||||
* are ignored.
|
||||
*
|
||||
* @param timestamp to assign.
|
||||
*/
|
||||
public void updateDurationTraffic(long timestamp)
|
||||
@@ -255,6 +262,7 @@ public class P25TrafficChannelEventTracker
|
||||
* Mark the event as complete and assign final end timestamp to the event.
|
||||
*
|
||||
* Note: further attempts to complete an already complete event are ignored.
|
||||
*
|
||||
* @param timestamp to assign.
|
||||
* @return true if the timestamp was updated
|
||||
*/
|
||||
@@ -272,6 +280,7 @@ public class P25TrafficChannelEventTracker
|
||||
|
||||
/**
|
||||
* Updates the details for the tracked event.
|
||||
*
|
||||
* @param details to update
|
||||
*/
|
||||
public void setDetails(String details)
|
||||
@@ -279,8 +288,46 @@ public class P25TrafficChannelEventTracker
|
||||
getEvent().setDetails(details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefixes the details the current event details text
|
||||
* @param details to prefix
|
||||
*/
|
||||
public void prefixDetails(String details)
|
||||
{
|
||||
if(getEvent().getDetails() == null)
|
||||
{
|
||||
getEvent().setDetails(details);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!getEvent().getDetails().contains(details))
|
||||
{
|
||||
getEvent().setDetails(details + " " + getEvent().getDetails());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the details to the end of the existing event text or sets the event details.
|
||||
*/
|
||||
public void addDetails(String details)
|
||||
{
|
||||
if(getEvent().getDetails() == null)
|
||||
{
|
||||
getEvent().setDetails(details);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!getEvent().getDetails().contains(details))
|
||||
{
|
||||
getEvent().setDetails(getEvent().getDetails() + " " + details);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the channel descriptor for the tracked event.
|
||||
*
|
||||
* @param channelDescriptor to update.
|
||||
*/
|
||||
public void addChannelDescriptorIfMissing(IChannelDescriptor channelDescriptor)
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 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
|
||||
@@ -101,12 +101,19 @@ public class DecodeEventDuplicateDetector
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique event key that is a combinatio of the TO identifier value and the details of the event.
|
||||
* Generates a unique event key that is a combination of the TO identifier value and the details of the event.
|
||||
* @param event for the key
|
||||
* @return generated key.
|
||||
*/
|
||||
private static String getKey(IDecodeEvent event)
|
||||
{
|
||||
if(event.getIdentifierCollection().getFromIdentifier() != null)
|
||||
{
|
||||
return event.getIdentifierCollection().getToIdentifier().toString() +
|
||||
event.getIdentifierCollection().getFromIdentifier().toString() +
|
||||
event.getDetails();
|
||||
}
|
||||
|
||||
return event.getIdentifierCollection().getToIdentifier().toString() + event.getDetails();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 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
|
||||
@@ -59,24 +59,24 @@ import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.TableModel;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
|
||||
/**
|
||||
* Decode event table view
|
||||
*/
|
||||
public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DecodeEventPanel.class);
|
||||
private static final String TABLE_PREFERENCE_KEY = "decode.event.panel";
|
||||
|
||||
private JTable mTable;
|
||||
private JTableColumnWidthMonitor mTableColumnWidthMonitor;
|
||||
private DecodeEventModel mEventModel = new DecodeEventModel();
|
||||
private final JTable mTable;
|
||||
private final DecodeEventModel mEventModel = new DecodeEventModel();
|
||||
private DecodeEventHistory mCurrentEventHistory;
|
||||
private JScrollPane mEmptyScroller;
|
||||
private IconModel mIconModel;
|
||||
private AliasModel mAliasModel;
|
||||
private UserPreferences mUserPreferences;
|
||||
private TimestampCellRenderer mTimestampCellRenderer;
|
||||
private FilterSet<IDecodeEvent> mFilterSet = new DecodeEventFilterSet();
|
||||
private TableRowSorter<TableModel> mTableRowSorter;
|
||||
private HistoryManagementPanel<IDecodeEvent> mHistoryManagementPanel;
|
||||
private final IconModel mIconModel;
|
||||
private final AliasModel mAliasModel;
|
||||
private final UserPreferences mUserPreferences;
|
||||
private final TimestampCellRenderer mTimestampCellRenderer;
|
||||
private final FilterSet<IDecodeEvent> mFilterSet = new DecodeEventFilterSet();
|
||||
private final HistoryManagementPanel<IDecodeEvent> mHistoryManagementPanel;
|
||||
|
||||
|
||||
/**
|
||||
@@ -93,19 +93,19 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
mUserPreferences = userPreferences;
|
||||
mTimestampCellRenderer = new TimestampCellRenderer();
|
||||
mTable = new JTable(mEventModel);
|
||||
mTableRowSorter = new TableRowSorter<>(mEventModel);
|
||||
mTableRowSorter.setRowFilter(new EventRowFilter());
|
||||
mTable.setRowSorter(mTableRowSorter);
|
||||
mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY);
|
||||
TableRowSorter<TableModel> tableRowSorter = new TableRowSorter<>(mEventModel);
|
||||
tableRowSorter.setRowFilter(new EventRowFilter());
|
||||
mTable.setRowSorter(tableRowSorter);
|
||||
JTableColumnWidthMonitor tableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY);
|
||||
updateCellRenderers();
|
||||
mHistoryManagementPanel = new HistoryManagementPanel<>(mEventModel, "Event Filter Editor");
|
||||
mHistoryManagementPanel.updateFilterSet(mFilterSet);
|
||||
add(mHistoryManagementPanel, "span,growx");
|
||||
mEmptyScroller = new JScrollPane(mTable);
|
||||
add(mEmptyScroller);
|
||||
JScrollPane emptyScroller = new JScrollPane(mTable);
|
||||
add(emptyScroller);
|
||||
|
||||
//Register filter change listener to refresh the table any time the event filters are changed.
|
||||
mFilterSet.register(() -> mEventModel.fireTableDataChanged());
|
||||
mFilterSet.register(mEventModel::fireTableDataChanged);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@@ -122,7 +122,7 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
{
|
||||
if(preferenceType == PreferenceType.DECODE_EVENT || preferenceType == PreferenceType.TALKGROUP_FORMAT)
|
||||
{
|
||||
EventQueue.invokeLater(() -> mTimestampCellRenderer.updatePreferences());
|
||||
EventQueue.invokeLater(mTimestampCellRenderer::updatePreferences);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
|
||||
for(Identifier identifier: identifiers)
|
||||
{
|
||||
if(sb.length() > 0)
|
||||
if(!sb.isEmpty())
|
||||
{
|
||||
sb.append(",");
|
||||
}
|
||||
@@ -262,9 +262,8 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
ImageIcon icon = null;
|
||||
String text = null;
|
||||
|
||||
if(value instanceof IdentifierCollection)
|
||||
if(value instanceof IdentifierCollection identifierCollection)
|
||||
{
|
||||
IdentifierCollection identifierCollection = (IdentifierCollection)value;
|
||||
List<Identifier> identifiers = identifierCollection.getIdentifiers(mRole);
|
||||
|
||||
if(identifiers != null && !identifiers.isEmpty())
|
||||
@@ -281,13 +280,13 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
|
||||
if(!aliases.isEmpty())
|
||||
{
|
||||
if(sb.length() > 0)
|
||||
if(!sb.isEmpty())
|
||||
{
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
color = aliases.get(0).getDisplayColor();
|
||||
icon = mIconModel.getIcon(aliases.get(0).getIconName(), IconModel.DEFAULT_ICON_SIZE);
|
||||
color = aliases.getFirst().getDisplayColor();
|
||||
icon = mIconModel.getIcon(aliases.getFirst().getIconName(), IconModel.DEFAULT_ICON_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,9 +336,9 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
}
|
||||
}
|
||||
|
||||
public class DurationCellRenderer extends DefaultTableCellRenderer
|
||||
public static class DurationCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
private DecimalFormat mDecimalFormat = new DecimalFormat("0.0");
|
||||
private final DecimalFormat mDecimalFormat = new DecimalFormat("0.0");
|
||||
|
||||
public DurationCellRenderer()
|
||||
{
|
||||
@@ -372,9 +371,9 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
/**
|
||||
* Frequency value cell renderer
|
||||
*/
|
||||
public class FrequencyCellRenderer extends DefaultTableCellRenderer
|
||||
public static class FrequencyCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
private DecimalFormat mFrequencyFormatter = new DecimalFormat("0.00000");
|
||||
private final DecimalFormat mFrequencyFormatter = new DecimalFormat("0.00000");
|
||||
|
||||
public FrequencyCellRenderer()
|
||||
{
|
||||
@@ -388,10 +387,8 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
|
||||
String formatted = null;
|
||||
|
||||
if(value instanceof IChannelDescriptor)
|
||||
if(value instanceof IChannelDescriptor channelDescriptor)
|
||||
{
|
||||
IChannelDescriptor channelDescriptor = (IChannelDescriptor)value;
|
||||
|
||||
long frequency = channelDescriptor.getDownlinkFrequency();
|
||||
|
||||
if(frequency > 0)
|
||||
@@ -409,7 +406,7 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
/**
|
||||
* Channel descriptor value cell renderer
|
||||
*/
|
||||
public class ChannelDescriptorCellRenderer extends DefaultTableCellRenderer
|
||||
public static class ChannelDescriptorCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
public ChannelDescriptorCellRenderer()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.ltrnet;
|
||||
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.channel.IBandChannelDescriptor;
|
||||
import io.github.dsheirer.channel.state.DecoderState;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent.Event;
|
||||
@@ -97,7 +97,7 @@ public class LTRNetDecoderState extends DecoderState
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IChannelDescriptor getCurrentChannel()
|
||||
protected IBandChannelDescriptor getCurrentChannel()
|
||||
{
|
||||
if(mCurrentChannelNumber > 0)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -15,16 +14,16 @@
|
||||
*
|
||||
* 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.ltrnet.channel;
|
||||
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.channel.IBandChannelDescriptor;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
public class LtrNetChannel implements IChannelDescriptor, Comparable<LtrNetChannel>
|
||||
public class LtrNetChannel implements IBandChannelDescriptor, Comparable<LtrNetChannel>
|
||||
{
|
||||
private long mDownlink;
|
||||
private long mUplink;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -15,16 +14,16 @@
|
||||
*
|
||||
* 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.ltrstandard.channel;
|
||||
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.channel.IBandChannelDescriptor;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
public class LtrChannel implements IChannelDescriptor, Comparable<LtrChannel>
|
||||
public class LtrChannel implements IBandChannelDescriptor, Comparable<LtrChannel>
|
||||
{
|
||||
private long mDownlink;
|
||||
private int mChannel;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.mpt1327;
|
||||
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.channel.IBandChannelDescriptor;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
@@ -52,7 +52,7 @@ public class MPT1327ChannelGrantEvent extends MPT1327DecodeEvent
|
||||
protected long mDuration;
|
||||
protected DecodeEventType mDecodeEventType;
|
||||
protected IdentifierCollection mIdentifierCollection;
|
||||
protected IChannelDescriptor mChannelDescriptor;
|
||||
protected IBandChannelDescriptor mChannelDescriptor;
|
||||
protected String mDetails;
|
||||
|
||||
/**
|
||||
@@ -90,7 +90,7 @@ public class MPT1327ChannelGrantEvent extends MPT1327DecodeEvent
|
||||
* Sets the channel descriptor for this event
|
||||
* @param channelDescriptor
|
||||
*/
|
||||
public MPT1327ChannelGrantDecodeEventBuilder channel(IChannelDescriptor channelDescriptor)
|
||||
public MPT1327ChannelGrantDecodeEventBuilder channel(IBandChannelDescriptor channelDescriptor)
|
||||
{
|
||||
mChannelDescriptor = channelDescriptor;
|
||||
return this;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -15,22 +14,21 @@
|
||||
*
|
||||
* 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.mpt1327.channel;
|
||||
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.channel.IBandChannelDescriptor;
|
||||
import io.github.dsheirer.controller.channel.map.ChannelMap;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* MPT-1327 Channel
|
||||
*/
|
||||
public class MPT1327Channel implements IChannelDescriptor
|
||||
public class MPT1327Channel implements IBandChannelDescriptor
|
||||
{
|
||||
private int mChannelNumber;
|
||||
private ChannelMap mChannelMap;
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.ChannelFrequency;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.proprietary.Encoding;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.TransmissionMode;
|
||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* NXDN decoder configuration
|
||||
*/
|
||||
public class DecodeConfigNXDN extends DecodeConfiguration
|
||||
{
|
||||
public static final int CHANNEL_ROTATION_DELAY_MINIMUM_MS = 200;
|
||||
public static final int CHANNEL_ROTATION_DELAY_DEFAULT_MS = 500;
|
||||
public static final int CHANNEL_ROTATION_DELAY_MAXIMUM_MS = 2000;
|
||||
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
|
||||
private boolean mIgnoreDataCalls = false;
|
||||
private boolean mIgnoreEncryptedCalls = false;
|
||||
|
||||
private static final ChannelSpecification CHANNEL_4800 = new ChannelSpecification(12000.0, 6250, 3000.0, 3125.0);
|
||||
private static final ChannelSpecification CHANNEL_9600 = new ChannelSpecification(24000.0, 12500, 5200.0, 6250.0);
|
||||
private TransmissionMode mTransmissionMode = TransmissionMode.M4800;
|
||||
private List<ChannelFrequency> mChannelMap = new ArrayList<>();
|
||||
private Encoding mEncoding = Encoding.UTF8;
|
||||
|
||||
/**
|
||||
* Default constructor for Jackson.
|
||||
*/
|
||||
public DecodeConfigNXDN()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the parent method to indicate 4800 or 9600 bits per second, depending on the mode.
|
||||
*/
|
||||
@Override
|
||||
public int getBitRate()
|
||||
{
|
||||
return getTransmissionMode().getSymbolRate() * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param transmissionMode for the system
|
||||
*/
|
||||
public DecodeConfigNXDN(TransmissionMode transmissionMode)
|
||||
{
|
||||
mTransmissionMode = transmissionMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional user-provided channel to frequency mapping
|
||||
* @return map
|
||||
*/
|
||||
@JacksonXmlProperty(localName = "channelMap")
|
||||
public List<ChannelFrequency> getChannelMap()
|
||||
{
|
||||
return mChannelMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional user-provided channel to frequency mapping
|
||||
* @param channelMap with entries to configure channels
|
||||
*/
|
||||
public void setChannelMap(List<ChannelFrequency> channelMap)
|
||||
{
|
||||
mChannelMap = channelMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the channel frequency entry to the map
|
||||
* @param channelFrequency to add
|
||||
*/
|
||||
public void add(ChannelFrequency channelFrequency)
|
||||
{
|
||||
mChannelMap.add(channelFrequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore data calls
|
||||
* @return true if ignore
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "ignore_data_calls")
|
||||
public boolean isIgnoreDataCalls()
|
||||
{
|
||||
return mIgnoreDataCalls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ignore data calls
|
||||
* @param ignore data calls
|
||||
*/
|
||||
public void setIgnoreDataCalls(boolean ignore)
|
||||
{
|
||||
mIgnoreDataCalls = ignore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore encrypted calls
|
||||
* @return true if ignore
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "ignore_encrypted_calls")
|
||||
public boolean isIgnoreEncryptedCalls()
|
||||
{
|
||||
return mIgnoreEncryptedCalls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ignore encrypted calls
|
||||
* @param ignore encrypted calls
|
||||
*/
|
||||
public void setIgnoreEncryptedCalls(boolean ignore)
|
||||
{
|
||||
mIgnoreEncryptedCalls = ignore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traffic channel pool size.
|
||||
* @return
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "traffic_channel_pool_size")
|
||||
public int getTrafficChannelPoolSize()
|
||||
{
|
||||
return mTrafficChannelPoolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the traffic channel pool size which is the maximum number of
|
||||
* simultaneous traffic channels that can be allocated.
|
||||
*
|
||||
* This limits the maximum calls so that busy systems won't cause more
|
||||
* traffic channels to be allocated than the decoder/software/host computer
|
||||
* can support.
|
||||
*/
|
||||
public void setTrafficChannelPoolSize(int size)
|
||||
{
|
||||
mTrafficChannelPoolSize = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmission mode for this system.
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "mode")
|
||||
public TransmissionMode getTransmissionMode()
|
||||
{
|
||||
return mTransmissionMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets/changes the transmission mode
|
||||
* @param transmissionMode to use
|
||||
*/
|
||||
public void setTransmissionMode(TransmissionMode transmissionMode)
|
||||
{
|
||||
mTransmissionMode = transmissionMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Character set for encoding talker alias values
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "charset")
|
||||
public Encoding getEncoding()
|
||||
{
|
||||
return mEncoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the character set for talker alias encoding
|
||||
* @param encoding to use
|
||||
*/
|
||||
public void setEncoding(Encoding encoding)
|
||||
{
|
||||
mEncoding = encoding;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.NXDN;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public ChannelSpecification getChannelSpecification()
|
||||
{
|
||||
return switch(getTransmissionMode())
|
||||
{
|
||||
case M4800, TYPE_D -> CHANNEL_4800;
|
||||
case M9600 -> CHANNEL_9600;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn;
|
||||
|
||||
import io.github.dsheirer.module.decode.event.ChannelEventTracker;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
|
||||
/**
|
||||
* Wrapper to track the state of a traffic channel event to manage updates from the control channel and the traffic
|
||||
* channel and to assist in determining when the communicants of a traffic channel have changed, indicating the need
|
||||
* for a new event.
|
||||
*/
|
||||
public class NXDNChannelEventTracker extends ChannelEventTracker<DecodeEvent>
|
||||
{
|
||||
private boolean mTrafficChannelAllocated = false;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param event to track for the traffic channel.
|
||||
*/
|
||||
public NXDNChannelEventTracker(DecodeEvent event)
|
||||
{
|
||||
super(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if a traffic channel was allocated for this event
|
||||
*/
|
||||
public boolean isTrafficChannelAllocated()
|
||||
{
|
||||
return mTrafficChannelAllocated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets flag to indicate if traffic channel was allocated
|
||||
* @param allocated as the flag
|
||||
*/
|
||||
public void setTrafficChannelAllocated(boolean allocated)
|
||||
{
|
||||
mTrafficChannelAllocated = allocated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn;
|
||||
|
||||
import io.github.dsheirer.controller.channel.event.PreloadDataContent;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.ChannelFrequency;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.ChannelInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ChannelAccessInformation;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Channel access info and optional channel frequencies to preload into a traffic channel.
|
||||
*/
|
||||
public class NXDNChannelInfoPreloadData extends PreloadDataContent<ChannelInformation>
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param info from the channel to preload
|
||||
* @param frequencies optional provided by the user in the config
|
||||
*/
|
||||
public NXDNChannelInfoPreloadData(ChannelAccessInformation info, List<ChannelFrequency> frequencies)
|
||||
{
|
||||
super(new ChannelInformation(info, frequencies));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn;
|
||||
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* NXDN Decode Event
|
||||
*/
|
||||
public class NXDNDecodeEvent extends DecodeEvent
|
||||
{
|
||||
/**
|
||||
* Constructs a decode event
|
||||
* @param decodeEventType for the event
|
||||
* @param start timestamp
|
||||
*/
|
||||
public NXDNDecodeEvent(DecodeEventType decodeEventType, long start)
|
||||
{
|
||||
super(decodeEventType, start);
|
||||
setProtocol(Protocol.NXDN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new decode event builder with the specified start timestamp.
|
||||
* @param timeStart for the event
|
||||
* @return builder
|
||||
*/
|
||||
public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart)
|
||||
{
|
||||
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart);
|
||||
decodeEventBuilder.protocol(Protocol.NXDN);
|
||||
decodeEventBuilder.timeslot(0);
|
||||
return decodeEventBuilder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.decimate.DecimationFilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter;
|
||||
import io.github.dsheirer.dsp.fm.FmDemodulatorFactory;
|
||||
import io.github.dsheirer.dsp.fm.IDemodulator;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.FeedbackDecoder;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.TransmissionMode;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IByteBufferProvider;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.IComplexSamplesListener;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
import io.github.dsheirer.source.wave.ComplexWaveSource;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* NXDN Decoder for 4800, 9600 and Type-C/D systems.
|
||||
*/
|
||||
public class NXDNDecoder extends FeedbackDecoder implements IByteBufferProvider, IComplexSamplesListener,
|
||||
ISourceEventListener, Listener<ComplexSamples>
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NXDNDecoder.class);
|
||||
private static final Map<String,float[]> BASEBAND_FILTERS = new HashMap<>();
|
||||
private final DecodeConfigNXDN mConfig;
|
||||
private final NXDNSymbolProcessor mSymbolProcessor;
|
||||
private final NXDNMessageFramer mMessageFramer;
|
||||
private final NXDNMessageProcessor mMessageProcessor;
|
||||
private IRealDecimationFilter mDecimationFilterI;
|
||||
private IRealDecimationFilter mDecimationFilterQ;
|
||||
private IRealFilter mBasebandFilterI;
|
||||
private IRealFilter mBasebandFilterQ;
|
||||
private IRealFilter mPulseShapingFilter;
|
||||
private IDemodulator mDemodulator;
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.NXDN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param config for the decoder
|
||||
*/
|
||||
public NXDNDecoder(DecodeConfigNXDN config)
|
||||
{
|
||||
mConfig = config;
|
||||
mMessageProcessor = new NXDNMessageProcessor(config);
|
||||
mMessageProcessor.setMessageListener(getMessageListener());
|
||||
mMessageFramer = new NXDNMessageFramer(mMessageProcessor, config.getTransmissionMode());
|
||||
mSymbolProcessor = new NXDNSymbolProcessor(mMessageFramer, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preloads NXDN channel details passed from the control channel used to resolve channel frequency information
|
||||
* in traffic channel broadcast messaging.
|
||||
*
|
||||
* @param preloadData containing a DMR network configuration monitor.
|
||||
*/
|
||||
@Subscribe
|
||||
public void preload(NXDNChannelInfoPreloadData preloadData)
|
||||
{
|
||||
if(mMessageProcessor != null)
|
||||
{
|
||||
mMessageProcessor.preload(preloadData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocolDescription()
|
||||
{
|
||||
return "NXDN " + mConfig.getTransmissionMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sample rate and configures internal decoder components.
|
||||
* @param sampleRate of the channel to decode
|
||||
*/
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
float samplesPerSymbol = (float)sampleRate / mConfig.getTransmissionMode().getSymbolRate();
|
||||
|
||||
if(samplesPerSymbol <= 4.0f)
|
||||
{
|
||||
throw new IllegalArgumentException("Sample rate [" + sampleRate + "] must be at least " + (mConfig.getTransmissionMode().getSymbolRate() * 4) + " Hz");
|
||||
}
|
||||
|
||||
int decimation = 1;
|
||||
|
||||
//Identify decimation that gets us as close to 5.0 Samples Per Symbol as possible
|
||||
while(samplesPerSymbol >= 10.0)
|
||||
{
|
||||
decimation *= 2;
|
||||
samplesPerSymbol /= 2;
|
||||
}
|
||||
|
||||
mDecimationFilterI = DecimationFilterFactory.getRealDecimationFilter(decimation);
|
||||
mDecimationFilterQ = DecimationFilterFactory.getRealDecimationFilter(decimation);
|
||||
float decimatedSampleRate = (float)sampleRate / decimation;
|
||||
|
||||
//Set the decimated sample rate to use for PLL error reporting.
|
||||
setDecimatedSampleRate(decimatedSampleRate);
|
||||
|
||||
int symbolLength = 26;
|
||||
float rrcAlpha = 0.2f;
|
||||
float[] rrcTaps = FilterFactory.getRRC(samplesPerSymbol, symbolLength, rrcAlpha);
|
||||
mPulseShapingFilter = new RealFIRFilter(rrcTaps);
|
||||
float[] basebandTaps = getBasebandFilter(decimatedSampleRate, mConfig);
|
||||
mBasebandFilterI = FilterFactory.getRealFilter(basebandTaps);
|
||||
mBasebandFilterQ = FilterFactory.getRealFilter(basebandTaps);
|
||||
mDemodulator = FmDemodulatorFactory.getFmDemodulator();
|
||||
mSymbolProcessor.setSamplesPerSymbol(samplesPerSymbol);
|
||||
mMessageProcessor.setMessageListener(getMessageListener());
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary method for processing incoming complex sample buffers
|
||||
* @param samples containing channelized complex samples
|
||||
*/
|
||||
@Override
|
||||
public void receive(ComplexSamples samples)
|
||||
{
|
||||
//Update the message framer with the timestamp from the incoming sample buffer.
|
||||
mMessageFramer.setTimestamp(samples.timestamp());
|
||||
|
||||
float[] i = mDecimationFilterI.decimateReal(samples.i());
|
||||
float[] q = mDecimationFilterQ.decimateReal(samples.q());
|
||||
|
||||
i = mBasebandFilterI.filter(i);
|
||||
q = mBasebandFilterQ.filter(q);
|
||||
|
||||
float[] demodulated = mDemodulator.demodulate(i, q);
|
||||
demodulated = mPulseShapingFilter.filter(demodulated);
|
||||
|
||||
//Process demodulated samples into symbols and apply message sync detection and framing.
|
||||
mSymbolProcessor.process(demodulated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a baseband filter for this decoder using the current sample rate
|
||||
*/
|
||||
private float[] getBasebandFilter(double sampleRate, DecodeConfigNXDN config)
|
||||
{
|
||||
|
||||
String mapKey = config.getTransmissionMode().name() + sampleRate;
|
||||
|
||||
float[] coefficients = null;
|
||||
|
||||
if(BASEBAND_FILTERS.containsKey(mapKey))
|
||||
{
|
||||
coefficients = BASEBAND_FILTERS.get(mapKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
ChannelSpecification channel = config.getChannelSpecification();
|
||||
|
||||
FIRFilterSpecification specification = FIRFilterSpecification
|
||||
.lowPassBuilder()
|
||||
.sampleRate(sampleRate)
|
||||
.passBandCutoff(channel.getPassFrequency())
|
||||
.passBandAmplitude(1.0).passBandRipple(0.01)
|
||||
.stopBandAmplitude(0.0).stopBandStart(channel.getStopFrequency())
|
||||
.stopBandRipple(0.01).build();
|
||||
|
||||
try
|
||||
{
|
||||
coefficients = FilterFactory.getTaps(specification);
|
||||
|
||||
if(coefficients != null)
|
||||
{
|
||||
BASEBAND_FILTERS.put(mapKey, coefficients);
|
||||
}
|
||||
}
|
||||
catch(Exception fde) //FilterDesignException
|
||||
{
|
||||
System.out.println("Error");
|
||||
}
|
||||
}
|
||||
|
||||
if(coefficients == null)
|
||||
{
|
||||
throw new IllegalStateException("Unable to design low pass filter for sample rate [" + sampleRate + "]");
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the IByteBufferProvider interface - delegates to the symbol processor
|
||||
*/
|
||||
@Override
|
||||
public void setBufferListener(Listener<ByteBuffer> listener)
|
||||
{
|
||||
mSymbolProcessor.setBufferListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the IByteBufferProvider interface - delegates to the symbol processor
|
||||
*/
|
||||
@Override
|
||||
public void removeBufferListener(Listener<ByteBuffer> listener)
|
||||
{
|
||||
mSymbolProcessor.setBufferListener(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the IByteBufferProvider interface - delegates to the symbol processor
|
||||
*/
|
||||
@Override
|
||||
public boolean hasBufferListeners()
|
||||
{
|
||||
return mSymbolProcessor.hasBufferListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<SourceEvent> getSourceEventListener()
|
||||
{
|
||||
return this::process;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
super.stop();
|
||||
mSymbolProcessor.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process source events
|
||||
*/
|
||||
private void process(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||
mSymbolProcessor.resetBalance();
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
setSampleRate(sourceEvent.getValue().doubleValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ComplexSamples> getComplexSamplesListener()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener implementation used in the main method for offline demodulating an I/Q file..
|
||||
*/
|
||||
public static class DebugListener implements Listener<IMessage>
|
||||
{
|
||||
private int mInvalidMessages;
|
||||
private int mValidMessages;
|
||||
private int mValidMessageBitErrors;
|
||||
|
||||
public void log()
|
||||
{
|
||||
System.out.println("Invalid: " + mInvalidMessages + " Valid: " + mValidMessages + " Valid-Bit Errors: " + mValidMessageBitErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
{
|
||||
if(message instanceof NXDNMessage nxdn)
|
||||
{
|
||||
if(nxdn.isValid())
|
||||
{
|
||||
System.out.println("BE:" + nxdn.getMessage().getCorrectedBitCount() + " " + nxdn);
|
||||
mValidMessages++;
|
||||
mValidMessageBitErrors += nxdn.getMessage().getCorrectedBitCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println("\t\tBE:" + nxdn.getMessage().getCorrectedBitCount() + " " + nxdn);
|
||||
mInvalidMessages++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println(message.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
LOGGER.info("Starting ...");
|
||||
|
||||
DecodeConfigNXDN config = null;
|
||||
// String directory = "D:\\Recordings\\NXDN\\"; //Windows
|
||||
String directory = "/run/media/denny/T9/Recordings/NXDN/"; //Linux
|
||||
|
||||
//NXDN 9600 Channels
|
||||
// config = new DecodeConfigNXDN(TransmissionMode.M9600);
|
||||
// config.add(new ChannelFrequency(105, 451887500, 0));
|
||||
// String file = directory + "20251127_063701_451887500_Bush-NXDN-96_Sentinel-Heights_Control_45_baseband.wav";
|
||||
|
||||
//Weird 938 MHz site in Springfield, VA that is all CRC errors.
|
||||
// String file = directory + "20260324_033833_938736125_SYSTEM_SITE_null_47_baseband.wav";
|
||||
|
||||
//NXDN 4800 Channels
|
||||
config = new DecodeConfigNXDN(TransmissionMode.M4800);
|
||||
// String file = directory + "20251128_052514_150845000_MobileTech-NXDN-48_Fulton_Control_47_baseband.wav";
|
||||
//Lower SNR recordings
|
||||
String file = directory + "20260430_023716_150867500_Mobiletech-Communications-(NXDN)_Pulaski_Pulaski_11_baseband.wav";
|
||||
// String file = directory + "20260430_023556_152982500_Mobiletech-Communications-(NXDN)_Manlius_Manlius_15_baseband.wav";
|
||||
|
||||
//This traffic channel sample Has Sync Detects between 1,096,430 - 1,111,430 samples (decimated sample rate: 12,500 Hz)
|
||||
// String file = directory + "20260104_065056_153582500_Mobiletech-Communications-(NXDN)_Fulton_LCN-3_50_baseband.wav";
|
||||
// String file = directory + "20260104_065240_153582500_Mobiletech-Communications-(NXDN)_Fulton_LCN-3_50_baseband.wav";
|
||||
// String file = directory + "AceVentura Sample 1/20260122_071724_154987500_SCFR_SITE_null_1_baseband.wav";
|
||||
// String file = directory + "AceVentura Sample 2/20260122_073239_154987500_SCFR_SITE_null_1_baseband.wav";
|
||||
// String file = directory + "20260322_063443_150845000_Mobiletech-Communications-(NXDN)_Fulton_Fulton_2_baseband.wav";
|
||||
|
||||
|
||||
//Type-D Channels
|
||||
// config = new DecodeConfigNXDN(TransmissionMode.TYPE_D);
|
||||
// String file = directory + "Type C and D/Action Radio Hennepin tadscottsmith/20260402_152326_464125000_Action-Radio---Hennepin_LCN-1_103_baseband.wav";
|
||||
// String file = directory + "Type C and D/Action Radio Hennepin 2 tadscottsmith/recordings/20260330_221120_464650000_Action-Radio_Hennepin_LCN-4_92_baseband.wav";
|
||||
// String file = directory + "Type C and D/20260314_091952_451775000_Heimes_Omaha_TYPE_D_114_baseband.wav";
|
||||
// String file = directory + "Type C and D/20260318_034719_452675000_Heimes_Omaha_TYPE_D_0_baseband.wav";
|
||||
// String file = directory + "Type C and D/20260320_065339_451450000_Heimes_Omaha_TYPE_D_0_baseband.wav";
|
||||
|
||||
//This file has PPM mis-adjusted by 8.0 to test for equalizer balance
|
||||
// String file = directory + "20260119_044740_150845000_Mobiletech-Communications-(NXDN)_Oswego_Control_2_baseband.wav";
|
||||
|
||||
System.out.println("Processing File: " + file);
|
||||
boolean autoReplay = false;
|
||||
|
||||
NXDNDecoder decoder = new NXDNDecoder(config);
|
||||
decoder.start();
|
||||
|
||||
DebugListener listener = new DebugListener();
|
||||
decoder.setMessageListener(listener);
|
||||
|
||||
try(ComplexWaveSource source = new ComplexWaveSource(new File(file), autoReplay))
|
||||
{
|
||||
source.setListener(iNativeBuffer -> {
|
||||
Iterator<ComplexSamples> it = iNativeBuffer.iterator();
|
||||
|
||||
while(it.hasNext())
|
||||
{
|
||||
decoder.receive(it.next());
|
||||
}
|
||||
});
|
||||
source.start();
|
||||
decoder.setSampleRate(source.getSampleRate());
|
||||
System.out.println("Recording File Sample Rate: " + source.getSampleRate());
|
||||
|
||||
while(true)
|
||||
{
|
||||
source.next(2048, true);
|
||||
}
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
LOGGER.error("Error", ioe);
|
||||
}
|
||||
|
||||
listener.log();
|
||||
|
||||
LOGGER.info("Finished");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,738 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn;
|
||||
|
||||
import io.github.dsheirer.channel.state.ChangeChannelTimeoutEvent;
|
||||
import io.github.dsheirer.channel.state.DecoderState;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent;
|
||||
import io.github.dsheirer.channel.state.State;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNLayer3Message;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.ControlChannelInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.SiteInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.Audio;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DataCallAssignment;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DataCallAssignmentDuplicateControl;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DataCallReceptionRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DataCallResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.RemoteControlRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.RemoteControlRequestWithESN;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.RemoteControlResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.ShortDataCallRequestHeader;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.ShortDataCallResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.StatusInquiryRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.StatusInquiryResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.StatusRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.StatusResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCall;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallAssignment;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallAssignmentDuplicateControl;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallAssignmentDuplicateTraffic;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallConnectionResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallReceptionRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.data.GPS;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.data.NXDNPacketMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.AuthenticationInquiryRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.AuthenticationInquiryRequestMultiSystem;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.AuthenticationInquiryResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.AuthenticationInquiryResponseMultiSystem;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.AuthenticationParameterInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.GroupRegistrationResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.GroupRegistrationResponseTypeD;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.RegistrationClearResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.RegistrationCommand;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.RegistrationResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.RegistrationResponseTypeD;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.proprietary.TalkerAliasComplete;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* NXDN decoder state
|
||||
*/
|
||||
public class NXDNDecoderState extends DecoderState
|
||||
{
|
||||
private static final int IDLE_DURING_CALL_MAX_COUNT = 5;
|
||||
private final Channel mChannel;
|
||||
private final NXDNNetworkConfigurationMonitor mNetworkConfigurationMonitor = new NXDNNetworkConfigurationMonitor();
|
||||
private final NXDNTrafficChannelManager mTrafficChannelManager;
|
||||
private boolean mEncryptedCallStateDetermined = false;
|
||||
private boolean mEncryptedCall = false;
|
||||
private int mDisconnectResponseCount = 0;
|
||||
private int mIdleDuringCallCount = IDLE_DURING_CALL_MAX_COUNT;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param channel for this state
|
||||
* @param trafficChannelManager shared with this channel
|
||||
*/
|
||||
public NXDNDecoderState(Channel channel, NXDNTrafficChannelManager trafficChannelManager)
|
||||
{
|
||||
mChannel = channel;
|
||||
mTrafficChannelManager = trafficChannelManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if we have a non-null traffic channel manager
|
||||
*/
|
||||
private boolean hasTrafficChannelManager()
|
||||
{
|
||||
return mTrafficChannelManager != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
super.start();
|
||||
|
||||
//Change the default (45-second) traffic channel timeout to 1 second
|
||||
if(mChannel.isTrafficChannel())
|
||||
{
|
||||
broadcast(new ChangeChannelTimeoutEvent(this, Channel.ChannelType.TRAFFIC, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.NXDN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveDecoderStateEvent(DecoderStateEvent event)
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case REQUEST_RESET:
|
||||
resetState();
|
||||
break;
|
||||
case NOTIFICATION_SOURCE_FREQUENCY:
|
||||
setCurrentFrequency(event.getFrequency());
|
||||
|
||||
//Only update the traffic channel manager if we're not a traffic channel.
|
||||
if(hasTrafficChannelManager() && mChannel.isStandardChannel())
|
||||
{
|
||||
mTrafficChannelManager.setCurrentControlFrequency(getCurrentFrequency(), mChannel);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActivitySummary()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("NXDN Channel Activity Summary\n");
|
||||
|
||||
if(mChannel.getDecodeConfiguration() instanceof DecodeConfigNXDN configNXDN)
|
||||
{
|
||||
sb.append("Transmission Mode: ").append(configNXDN.getTransmissionMode()).append("\n");
|
||||
}
|
||||
sb.append(mNetworkConfigurationMonitor.getSummary()).append("\n");
|
||||
sb.append(mTrafficChannelManager.getTalkerAliasManager().getAliasSummary()).append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(IMessage iMessage)
|
||||
{
|
||||
if(iMessage instanceof NXDNMessage nxdn && nxdn.isValid())
|
||||
{
|
||||
if(nxdn instanceof NXDNLayer3Message layer3)
|
||||
{
|
||||
processLayer3(layer3);
|
||||
}
|
||||
else if(nxdn instanceof Audio audio)
|
||||
{
|
||||
processAudio(audio);
|
||||
}
|
||||
else if(nxdn instanceof NXDNPacketMessage packetMessage)
|
||||
{
|
||||
processPacketMessage(packetMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the arguments as a new decode event
|
||||
* @param identifiers involved in the event
|
||||
* @param timestamp of the message/event
|
||||
* @param decodeEventType of the event
|
||||
* @param details for the event
|
||||
*/
|
||||
private void broadcastEvent(List<Identifier> identifiers, long timestamp, DecodeEventType decodeEventType, String details)
|
||||
{
|
||||
MutableIdentifierCollection mic = getMutableIdentifierCollection(identifiers);
|
||||
mTrafficChannelManager.getTalkerAliasManager().enrichMutable(mic);
|
||||
broadcast(NXDNDecodeEvent.builder(decodeEventType, timestamp)
|
||||
.channel(getCurrentChannel())
|
||||
.details(details)
|
||||
.identifiers(mic)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of the current identifier collection, removes any USER identifiers and adds the argument identifiers
|
||||
* passing each identifier through the patch group manager to replace with a patch group if it exists
|
||||
* @param identifiers to add to the collection copy
|
||||
* @return collection
|
||||
*/
|
||||
private MutableIdentifierCollection getMutableIdentifierCollection(List<Identifier> identifiers)
|
||||
{
|
||||
MutableIdentifierCollection requestCollection = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||
requestCollection.remove(IdentifierClass.USER);
|
||||
|
||||
for(Identifier identifier: identifiers)
|
||||
{
|
||||
requestCollection.update(identifier);
|
||||
}
|
||||
|
||||
return requestCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of the current identifier collection, removes any USER identifiers and adds the argument identifier
|
||||
* passed through the patch group manager to replace with a patch group if it exists
|
||||
* @param identifier to add to the collection copy
|
||||
* @return collection
|
||||
*/
|
||||
private MutableIdentifierCollection getMutableIdentifierCollection(Identifier identifier)
|
||||
{
|
||||
return getMutableIdentifierCollection(Collections.singletonList(identifier));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process layer 3 messages
|
||||
* @param layer3 message to process
|
||||
*/
|
||||
private void processLayer3(NXDNLayer3Message layer3)
|
||||
{
|
||||
State state = layer3.getMessageType().isControl() ? State.CONTROL : State.ACTIVE;
|
||||
DecoderStateEvent.Event event = DecoderStateEvent.Event.DECODE;
|
||||
|
||||
switch(layer3.getMessageType())
|
||||
{
|
||||
case CONTROL_OUT_01_CC_VOICE_CALL_RESPONSE:
|
||||
case TYPE_D_OUT_00_CC_CALL_RESPONSE:
|
||||
if(layer3 instanceof VoiceCallResponse vcr)
|
||||
{
|
||||
//Controller responds to a voice call request with either a channel grant, or this message if
|
||||
//it can't support the call or the called party is not available
|
||||
broadcastEvent(vcr.getIdentifiers(), vcr.getTimestamp(), DecodeEventType.RESPONSE,
|
||||
"VOICE CALL REQUEST - RESPONSE:" + vcr.getCause().toString());
|
||||
}
|
||||
break;
|
||||
case TRAFFIC_OUT_01_CC_VOICE_CALL:
|
||||
case TYPE_D_OUT_01_CC_VOICE_CALL:
|
||||
if(layer3 instanceof VoiceCall vc)
|
||||
{
|
||||
mIdleDuringCallCount = 0;
|
||||
getIdentifierCollection().update(vc.getIdentifiers());
|
||||
mEncryptedCallStateDetermined = true;
|
||||
mEncryptedCall = vc.getEncryptionKeyIdentifier().isEncrypted();
|
||||
state = mEncryptedCall ? State.ENCRYPTED : State.CALL;
|
||||
event = DecoderStateEvent.Event.START;
|
||||
mTrafficChannelManager.processVoiceCall(vc, getCurrentChannel());
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_02_CC_VOICE_CALL_RECEPTION_REQUEST:
|
||||
case TRAFFIC_OUT_02_CC_VOICE_CALL_RECEPTION_REQUEST:
|
||||
if(layer3 instanceof VoiceCallReceptionRequest vcrr)
|
||||
{
|
||||
broadcastEvent(vcrr.getIdentifiers(), vcrr.getTimestamp(), DecodeEventType.PAGE,
|
||||
"VOICE CALL RECEPTION REQUEST");
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_03_CC_VOICE_CALL_CONNECTION_RESPONSE:
|
||||
if(layer3 instanceof VoiceCallConnectionResponse vccr)
|
||||
{
|
||||
//Controller responds to a voice call connection request for the called party with either a channel
|
||||
//grant, or this message if it can't support the call or connect the called party
|
||||
broadcastEvent(vccr.getIdentifiers(), vccr.getTimestamp(), DecodeEventType.RESPONSE,
|
||||
"CALL CONNECTION REQUEST - RESPONSE:" + vccr.getCause().toString());
|
||||
}
|
||||
break;
|
||||
case TRAFFIC_OUT_03_CC_VOICE_CALL_INITIALIZATION_VECTOR:
|
||||
case TYPE_D_OUT_03_CC_VOICE_CALL_INITIALIZATION_VECTOR:
|
||||
state = State.CALL;
|
||||
event = DecoderStateEvent.Event.CONTINUATION;
|
||||
mIdleDuringCallCount = 0;
|
||||
break;
|
||||
case CONTROL_OUT_04_CC_VOICE_CALL_ASSIGNMENT:
|
||||
case TRAFFIC_OUT_04_CC_VOICE_CALL_ASSIGNMENT:
|
||||
case TYPE_D_OUT_04_CC_VOICE_CALL_ASSIGNMENT:
|
||||
if(layer3 instanceof VoiceCallAssignment vca)
|
||||
{
|
||||
//Decode event is created by the traffic channel manager
|
||||
mTrafficChannelManager.processVoiceCallAssignment(vca);
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_05_CC_VOICE_CALL_ASSIGNMENT_DUPLICATE:
|
||||
if(layer3 instanceof VoiceCallAssignmentDuplicateControl vcadc)
|
||||
{
|
||||
//This informs when there are 2-calls ongoing where a radio can participate in either call
|
||||
mTrafficChannelManager.processVoiceCallAssignment(vcadc);
|
||||
}
|
||||
break;
|
||||
case TRAFFIC_OUT_05_CC_VOICE_CALL_ASSIGNMENT_DUPLICATE:
|
||||
if(layer3 instanceof VoiceCallAssignmentDuplicateTraffic vcadt)
|
||||
{
|
||||
//This informs when there are 2-calls ongoing where a radio can participate in either call
|
||||
mTrafficChannelManager.processVoiceCallAssignment(vcadt);
|
||||
}
|
||||
break;
|
||||
case TRAFFIC_OUT_07_CC_TRANSMISSION_RELEASE_EXTENSION:
|
||||
case TRAFFIC_OUT_08_CC_TRANSMISSION_RELEASE:
|
||||
case TYPE_D_OUT_08_CC_TRANSMISSION_RELEASE:
|
||||
mTrafficChannelManager.processEndCall(getCurrentChannel(), layer3.getTimestamp());
|
||||
mEncryptedCallStateDetermined = false;
|
||||
mEncryptedCall = false;
|
||||
mIdleDuringCallCount = IDLE_DURING_CALL_MAX_COUNT;
|
||||
//Only remove the FROM identifier, anticipating a continuation call for the same talkgroup
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
break;
|
||||
case CONTROL_OUT_09_CC_DATA_CALL_RESPONSE:
|
||||
if(layer3 instanceof DataCallResponse dcr)
|
||||
{
|
||||
//Controller responds to a data call request with either a channel grant, or this message if
|
||||
//it can't support the call or the called party is not available
|
||||
broadcastEvent(dcr.getIdentifiers(), dcr.getTimestamp(), DecodeEventType.RESPONSE,
|
||||
"DATA CALL REQUEST - RESPONSE:" + dcr.getCause().toString());
|
||||
}
|
||||
break;
|
||||
case TRAFFIC_OUT_09_CC_DATA_CALL_HEADER:
|
||||
case TYPE_D_OUT_09_CC_DATA_CALL_HEADER:
|
||||
state = State.DATA;
|
||||
break;
|
||||
case CONTROL_OUT_10_CC_DATA_CALL_RECEPTION_REQUEST:
|
||||
if(layer3 instanceof DataCallReceptionRequest dcrr)
|
||||
{
|
||||
broadcastEvent(dcrr.getIdentifiers(), dcrr.getTimestamp(), DecodeEventType.PAGE,
|
||||
"DATA CALL RECEPTION REQUEST");
|
||||
}
|
||||
break;
|
||||
case TRAFFIC_OUT_10_CC_DATA_CALL_RECEPTION_REQUEST:
|
||||
break;
|
||||
case TRAFFIC_OUT_11_CC_DATA_CALL_BLOCK:
|
||||
case TYPE_D_OUT_11_CC_DATA_CALL_BLOCK:
|
||||
state = State.DATA;
|
||||
break;
|
||||
case TRAFFIC_OUT_12_CC_DATA_CALL_ACKNOWLEDGE:
|
||||
case TYPE_D_OUT_12_CC_DATA_CALL_ACKNOWLEDGE:
|
||||
break;
|
||||
case CONTROL_OUT_13_CC_DATA_CALL_ASSIGNMENT_DUPLICATE:
|
||||
if(layer3 instanceof DataCallAssignmentDuplicateControl dcadc)
|
||||
{
|
||||
mTrafficChannelManager.processDataCallAssignment(dcadc);
|
||||
}
|
||||
break;
|
||||
case TRAFFIC_OUT_13_CC_DATA_CALL_ASSIGNMENT_DUPLICATE:
|
||||
break;
|
||||
case CONTROL_OUT_14_CC_DATA_CALL_ASSIGNMENT:
|
||||
if(layer3 instanceof DataCallAssignment dca)
|
||||
{
|
||||
mTrafficChannelManager.processDataCallAssignment(dca);
|
||||
}
|
||||
break;
|
||||
case TRAFFIC_OUT_14_CC_DATA_CALL_ASSIGNMENT:
|
||||
break;
|
||||
case TRAFFIC_OUT_15_CC_HEADER_DELAY:
|
||||
case TYPE_D_OUT_15_CC_HEADER_DELAY:
|
||||
break;
|
||||
case CONTROL_OUT_16_CC_IDLE:
|
||||
break;
|
||||
case TRAFFIC_OUT_16_CC_IDLE:
|
||||
case TYPE_D_OUT_16_CC_IDLE:
|
||||
//Hack: on both 4800 and 9600 systems there can be instances during a call where they send IDLE frames
|
||||
//We'll track when we're in a call and continue a call state until the sequence of IDLE messages
|
||||
//exceeds a threshold and then the state will be flipped to ACTIVE from CALL.
|
||||
mIdleDuringCallCount++;
|
||||
|
||||
if(mIdleDuringCallCount < IDLE_DURING_CALL_MAX_COUNT)
|
||||
{
|
||||
state = State.CALL;
|
||||
}
|
||||
else
|
||||
{
|
||||
mEncryptedCallStateDetermined = false;
|
||||
mEncryptedCall = false;
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_17_CC_DISCONNECT:
|
||||
//Ignore. If this happens on the current/active control channel, in a multi-channel configuration,
|
||||
//the frequency rotation manager will simply move to the next channel.
|
||||
break;
|
||||
case TRAFFIC_OUT_17_CC_DISCONNECT:
|
||||
case TYPE_D_OUT_17_CC_DISCONNECT:
|
||||
mTrafficChannelManager.processEndCall(getCurrentChannel(), layer3.getTimestamp());
|
||||
mEncryptedCallStateDetermined = false;
|
||||
mEncryptedCall = false;
|
||||
getIdentifierCollection().remove(IdentifierClass.USER);
|
||||
mDisconnectResponseCount++;
|
||||
|
||||
//Delay channel teardown until after we receive at least 2x disconnect responses
|
||||
if(mChannel.isTrafficChannel() && mDisconnectResponseCount >= 2)
|
||||
{
|
||||
//Signal that this traffic channel has ended.
|
||||
event = DecoderStateEvent.Event.END;
|
||||
state = State.FADE;
|
||||
mDisconnectResponseCount = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
//Broadcast messages
|
||||
case CONTROL_OUT_23_BC_DIGITAL_STATION_ID_INFORMATION:
|
||||
case CONTROL_OUT_24_BC_SITE_INFORMATION:
|
||||
case CONTROL_OUT_25_BC_SERVICE_INFORMATION:
|
||||
case CONTROL_OUT_27_BC_ADJACENT_SITE_INFORMATION:
|
||||
case CONTROL_OUT_28_BC_FAILURE_STATUS_INFORMATION:
|
||||
mNetworkConfigurationMonitor.process(layer3);
|
||||
getIdentifierCollection().remove(IdentifierClass.USER);
|
||||
break;
|
||||
case CONTROL_OUT_26_BC_CONTROL_CHANNEL_INFORMATION:
|
||||
mNetworkConfigurationMonitor.process(layer3);
|
||||
|
||||
if(layer3 instanceof ControlChannelInformation cci)
|
||||
{
|
||||
setCurrentChannel(cci.getChannel1());
|
||||
}
|
||||
break;
|
||||
case TRAFFIC_OUT_24_BC_SITE_INFORMATION:
|
||||
if(layer3 instanceof SiteInformation si)
|
||||
{
|
||||
//Update traffic channel manager so it can use this when allocation traffic channels.
|
||||
mTrafficChannelManager.setChannelAccessInformation(si.getChannelAccessInformation());
|
||||
}
|
||||
//Deliberate fall through
|
||||
case TRAFFIC_OUT_23_BC_DIGITAL_STATION_ID_INFORMATION:
|
||||
case TYPE_D_OUT_23_BC_DIGITAL_STATION_ID:
|
||||
case TRAFFIC_OUT_25_BC_SERVICE_INFORMATION:
|
||||
case TYPE_D_OUT_25_BC_SERVICE_INFORMATION:
|
||||
case TRAFFIC_OUT_26_BC_CONTROL_CHANNEL_INFORMATION:
|
||||
case TRAFFIC_OUT_27_BC_ADJACENT_SITE_INFORMATION:
|
||||
case TYPE_D_OUT_27_BC_ADJACENT_SITE_INFORMATION:
|
||||
case TRAFFIC_OUT_28_BC_FAILURE_STATUS_INFORMATION:
|
||||
mNetworkConfigurationMonitor.process(layer3);
|
||||
break;
|
||||
case CONTROL_OUT_32_MM_REGISTRATION_RESPONSE:
|
||||
if(layer3 instanceof RegistrationResponse rr)
|
||||
{
|
||||
broadcastEvent(rr.getIdentifiers(), rr.getTimestamp(), DecodeEventType.REGISTER, rr.getCause().toString());
|
||||
}
|
||||
break;
|
||||
case TYPE_D_OUT_32_MM_REGISTRATION_RESPONSE:
|
||||
if(layer3 instanceof RegistrationResponseTypeD rr)
|
||||
{
|
||||
broadcastEvent(rr.getIdentifiers(), rr.getTimestamp(), DecodeEventType.REGISTER, rr.getCause().toString());
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_34_MM_REGISTRATION_CLEAR_RESPONSE:
|
||||
case TYPE_D_OUT_34_MM_REGISTRATION_CLEAR_RESPONSE:
|
||||
if(layer3 instanceof RegistrationClearResponse rcr)
|
||||
{
|
||||
broadcastEvent(rcr.getIdentifiers(), rcr.getTimestamp(), DecodeEventType.RESPONSE,
|
||||
"CLEAR REGISTRATION REQUEST - RESPONSE:" + rcr.getCause());
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_35_MM_REGISTRATION_COMMAND:
|
||||
if(layer3 instanceof RegistrationCommand rc)
|
||||
{
|
||||
broadcastEvent(rc.getIdentifiers(), rc.getTimestamp(), DecodeEventType.REGISTER,
|
||||
rc.getRegistrationOption().isPriorityStation() ? "PRIORITY STATION" : "");
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_36_MM_GROUP_REGISTRATION_RESPONSE:
|
||||
if(layer3 instanceof GroupRegistrationResponse grr)
|
||||
{
|
||||
broadcastEvent(grr.getIdentifiers(), grr.getTimestamp(), DecodeEventType.RESPONSE,
|
||||
"GROUP REGISTRATION REQUEST - RESPONSE:" + grr.getCause());
|
||||
}
|
||||
break;
|
||||
case TYPE_D_OUT_36_MM_GROUP_REGISTRATION_RESPONSE:
|
||||
if(layer3 instanceof GroupRegistrationResponseTypeD grr)
|
||||
{
|
||||
broadcastEvent(grr.getIdentifiers(), grr.getTimestamp(), DecodeEventType.RESPONSE,
|
||||
"GROUP REGISTRATION REQUEST - RESPONSE:" + grr.getCause());
|
||||
}
|
||||
break;
|
||||
case TYPE_D_OUT_38_MM_AUTHENTICATION_PARAMETER_INFORMATION:
|
||||
if(layer3 instanceof AuthenticationParameterInformation api)
|
||||
{
|
||||
broadcastEvent(api.getIdentifiers(), api.getTimestamp(), DecodeEventType.REQUEST,
|
||||
"AUTHENTICATE: " + api.getAuthenticationParameter());
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_40_MM_AUTHENTICATION_INQUIRY_REQUEST:
|
||||
case TYPE_D_OUT_40_MM_AUTHENTICATION_INQUIRY_REQUEST:
|
||||
if(layer3 instanceof AuthenticationInquiryRequest air)
|
||||
{
|
||||
broadcastEvent(air.getIdentifiers(), air.getTimestamp(), DecodeEventType.REQUEST,
|
||||
"AUTHENTICATE:" + air.getAuthenticationParameter());
|
||||
}
|
||||
break;
|
||||
case TYPE_D_OUT_41_MM_AUTHENTICATION_INQUIRY_RESPONSE:
|
||||
if(layer3 instanceof AuthenticationInquiryResponse air)
|
||||
{
|
||||
broadcastEvent(air.getIdentifiers(), air.getTimestamp(), DecodeEventType.RESPONSE,
|
||||
"AUTHENTICATION:" + air.getAuthenticationValue());
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM:
|
||||
case TRAFFIC_OUT_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM:
|
||||
if(layer3 instanceof AuthenticationInquiryRequestMultiSystem air)
|
||||
{
|
||||
broadcastEvent(air.getIdentifiers(), air.getTimestamp(), DecodeEventType.REQUEST,
|
||||
"AUTHENTICATE (MULTI-SYSTEM):" + air.getAuthenticationParameter());
|
||||
}
|
||||
break;
|
||||
case TRAFFIC_OUT_43_MM_AUTHORIZATION_INQUIRY_RESPONSE_MULTI_SYSTEM:
|
||||
if(layer3 instanceof AuthenticationInquiryResponseMultiSystem airms)
|
||||
{
|
||||
broadcast(NXDNDecodeEvent.builder(DecodeEventType.RESPONSE, layer3.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(getMutableIdentifierCollection(airms.getIdentifiers()))
|
||||
.details("AUTHENTICATION: " + airms.getAuthenticationValue())
|
||||
.build());
|
||||
}
|
||||
break;
|
||||
case TYPE_D_OUT_43_MM_DATA_WRITE_HEADER:
|
||||
case TYPE_D_OUT_44_MM_DATA_WRITE_DATA:
|
||||
state = State.DATA;
|
||||
break;
|
||||
case CONTROL_OUT_48_CC_STATUS_INQUIRY_REQUEST:
|
||||
case TRAFFIC_OUT_48_CC_STATUS_INQUIRY_REQUEST:
|
||||
case TYPE_D_OUT_48_CC_STATUS_INQUIRY_REQUEST:
|
||||
if(layer3 instanceof StatusInquiryRequest sir)
|
||||
{
|
||||
broadcastEvent(sir.getIdentifiers(), sir.getTimestamp(), DecodeEventType.REQUEST,
|
||||
"STATUS INQUIRY");
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_49_CC_STATUS_INQUIRY_RESPONSE:
|
||||
case TRAFFIC_OUT_49_CC_STATUS_INQUIRY_RESPONSE:
|
||||
case TYPE_D_OUT_49_CC_STATUS_INQUIRY_RESPONSE:
|
||||
if(layer3 instanceof StatusInquiryResponse sir)
|
||||
{
|
||||
broadcastEvent(sir.getIdentifiers(), sir.getTimestamp(), DecodeEventType.RESPONSE,
|
||||
"STATUS INQUIRY:" + sir.getStatus() + ":" + sir.getStatusValue() + " CAUSE:" + sir.getCause());
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_50_CC_STATUS_REQUEST:
|
||||
case TRAFFIC_OUT_50_CC_STATUS_REQUEST:
|
||||
case TYPE_D_OUT_50_CC_STATUS_REQUEST:
|
||||
if(layer3 instanceof StatusRequest sr)
|
||||
{
|
||||
broadcastEvent(sr.getIdentifiers(), sr.getTimestamp(), DecodeEventType.REQUEST,
|
||||
"STATUS REQUEST:" + sr.getStatus());
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_51_CC_STATUS_RESPONSE:
|
||||
case TRAFFIC_OUT_51_CC_STATUS_RESPONSE:
|
||||
case TYPE_D_OUT_51_CC_STATUS_RESPONSE:
|
||||
if(layer3 instanceof StatusResponse sr)
|
||||
{
|
||||
broadcastEvent(sr.getIdentifiers(), sr.getTimestamp(), DecodeEventType.REQUEST,
|
||||
"STATUS RESPONSE:" + sr.getCause());
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_52_CC_REMOTE_CONTROL_REQUEST:
|
||||
case TRAFFIC_OUT_52_CC_REMOTE_CONTROL_REQUEST:
|
||||
case TYPE_D_OUT_52_CC_REMOTE_CONTROL_REQUEST:
|
||||
if(layer3 instanceof RemoteControlRequest rcr)
|
||||
{
|
||||
broadcastEvent(rcr.getIdentifiers(), rcr.getTimestamp(), DecodeEventType.REQUEST,
|
||||
"REMOTE CONTROL REQUEST - PARAMETERS:" + rcr.getControlParameters());
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_53_CC_REMOTE_CONTROL_RESPONSE:
|
||||
case TRAFFIC_OUT_53_CC_REMOTE_CONTROL_RESPONSE:
|
||||
case TYPE_D_OUT_53_CC_REMOTE_CONTROL_RESPONSE:
|
||||
if(layer3 instanceof RemoteControlResponse rcr)
|
||||
{
|
||||
broadcastEvent(rcr.getIdentifiers(), rcr.getTimestamp(), DecodeEventType.REQUEST,
|
||||
"REMOTE CONTROL " + rcr.getControlCommand() + " " + rcr.getCause());
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_54_CC_REMOTE_CONTROL_REQUEST_WITH_ESN:
|
||||
if(layer3 instanceof RemoteControlRequestWithESN rcrwe)
|
||||
{
|
||||
broadcastEvent(rcrwe.getIdentifiers(), rcrwe.getTimestamp(), DecodeEventType.REQUEST,
|
||||
"REMOTE CONTROL REQUEST WITH ESN - PARAMETERS:" + rcrwe.getAuthenticationParameter());
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_56_CC_SHORT_DATA_CALL_REQUEST_HEADER:
|
||||
case TRAFFIC_OUT_56_CC_SHORT_DATA_CALL_REQUEST_HEADER:
|
||||
case TYPE_D_OUT_56_CC_SHORT_DATA_CALL_REQUEST_HEADER:
|
||||
if(layer3 instanceof ShortDataCallRequestHeader sdcrh)
|
||||
{
|
||||
broadcast(NXDNDecodeEvent.builder(DecodeEventType.REQUEST, layer3.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(getMutableIdentifierCollection(sdcrh.getIdentifiers()))
|
||||
.details("SHORT DATA CALL")
|
||||
.build());
|
||||
state = State.DATA;
|
||||
}
|
||||
break;
|
||||
case TRAFFIC_OUT_57_CC_SHORT_DATA_CALL_BLOCK:
|
||||
case TYPE_D_OUT_57_CC_SHORT_DATA_CALL_REQUEST_USER_DATA:
|
||||
state = State.DATA;
|
||||
case CONTROL_OUT_57_CC_SHORT_DATA_CALL_REQUEST_USER_DATA:
|
||||
break;
|
||||
case TRAFFIC_OUT_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR:
|
||||
case TYPE_D_OUT_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR:
|
||||
state = State.DATA;
|
||||
case CONTROL_OUT_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR:
|
||||
break;
|
||||
case CONTROL_OUT_59_CC_SHORT_DATA_CALL_RESPONSE:
|
||||
case TRAFFIC_OUT_59_CC_SHORT_DATA_CALL_RESPONSE:
|
||||
case TYPE_D_OUT_59_CC_SHORT_DATA_CALL_RESPONSE:
|
||||
if(layer3 instanceof ShortDataCallResponse sdcr)
|
||||
{
|
||||
broadcast(NXDNDecodeEvent.builder(DecodeEventType.RESPONSE, layer3.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(getMutableIdentifierCollection(sdcr.getIdentifiers()))
|
||||
.details("SHORT DATA CALL: " + sdcr.getCause())
|
||||
.build());
|
||||
}
|
||||
break;
|
||||
|
||||
case PROPRIETARY_FORM:
|
||||
break;
|
||||
case TALKER_ALIAS:
|
||||
state = State.CALL;
|
||||
event = DecoderStateEvent.Event.CONTINUATION;
|
||||
break;
|
||||
|
||||
case TALKER_ALIAS_COMPLETE:
|
||||
if(layer3 instanceof TalkerAliasComplete tac)
|
||||
{
|
||||
state = State.CALL;
|
||||
event = DecoderStateEvent.Event.CONTINUATION;
|
||||
var radio = getIdentifierCollection().getIdentifier(IdentifierClass.USER, Form.RADIO, Role.FROM);
|
||||
|
||||
if(radio instanceof RadioIdentifier ri)
|
||||
{
|
||||
mTrafficChannelManager.processTalkerAlias(getCurrentChannel(), tac.getTalkerAlias(), ri, tac.getTimestamp());
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TRAFFIC_IN_01_CC_VOICE_CALL:
|
||||
state = State.CALL;
|
||||
break;
|
||||
case TRAFFIC_IN_02_CC_VOICE_CALL_RECEPTION_RESPONSE:
|
||||
case TRAFFIC_IN_03_CC_VOICE_CALL_INITIALIZATION_VECTOR:
|
||||
state = State.CALL;
|
||||
break;
|
||||
case TRAFFIC_IN_08_CC_TRANSMISSION_RELEASE:
|
||||
case TRAFFIC_IN_09_CC_DATA_CALL_HEADER:
|
||||
state = State.DATA;
|
||||
break;
|
||||
case TRAFFIC_IN_11_CC_DATA_CALL_BLOCK:
|
||||
state = State.DATA;
|
||||
break;
|
||||
case TRAFFIC_IN_12_CC_DATA_CALL_ACKNOWLEDGE:
|
||||
case TRAFFIC_IN_15_CC_HEADER_DELAY:
|
||||
case TRAFFIC_IN_16_CC_IDLE:
|
||||
case TRAFFIC_IN_17_CC_DISCONNECT_REQUEST:
|
||||
case TRAFFIC_IN_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM:
|
||||
case TRAFFIC_IN_43_MM_AUTHORIZATION_INQUIRY_RESPONSE_MULTI_SYSTEM:
|
||||
case TRAFFIC_IN_48_CC_STATUS_INQUIRY_REQUEST:
|
||||
case TRAFFIC_IN_49_CC_STATUS_INQUIRY_RESPONSE:
|
||||
case TRAFFIC_IN_50_CC_STATUS_REQUEST:
|
||||
case TRAFFIC_IN_51_CC_STATUS_RESPONSE:
|
||||
case TRAFFIC_IN_52_CC_REMOTE_CONTROL_REQUEST:
|
||||
case TRAFFIC_IN_53_CC_REMOTE_CONTROL_RESPONSE:
|
||||
case TRAFFIC_IN_56_CC_SHORT_DATA_CALL_REQUEST_HEADER:
|
||||
case TRAFFIC_IN_57_CC_SHORT_DATA_CALL_BLOCK:
|
||||
state = State.DATA;
|
||||
break;
|
||||
case TRAFFIC_IN_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR:
|
||||
state = State.DATA;
|
||||
break;
|
||||
|
||||
case UNKNOWN:
|
||||
break;
|
||||
}
|
||||
|
||||
broadcast(new DecoderStateEvent(this, event, state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an audio message
|
||||
* @param audio to process
|
||||
*/
|
||||
private void processAudio(Audio audio)
|
||||
{
|
||||
mIdleDuringCallCount = 0;
|
||||
State state = State.CALL;
|
||||
|
||||
if(mEncryptedCallStateDetermined && mEncryptedCall)
|
||||
{
|
||||
state = State.ENCRYPTED;
|
||||
}
|
||||
|
||||
mTrafficChannelManager.processCallProgressUpdate(getCurrentChannel(), audio.getTimestamp());
|
||||
|
||||
broadcast(new DecoderStateEvent(this, DecoderStateEvent.Event.CONTINUATION, state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process packet-based messages
|
||||
* @param message to process
|
||||
*/
|
||||
private void processPacketMessage(NXDNPacketMessage message)
|
||||
{
|
||||
if(message instanceof GPS gps)
|
||||
{
|
||||
PlottableDecodeEvent plottableEvent = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, gps.getTimestamp())
|
||||
.identifiers(new IdentifierCollection(message.getPacketSequence().getHeader().getIdentifiers()))
|
||||
.channel(getCurrentChannel())
|
||||
.details(gps.getLocationFormatted() + " HDG: " + gps.getHeading() + " SPD:" + gps.getSpeed() +
|
||||
" ELE:" + gps.getElevation())
|
||||
.protocol(Protocol.NXDN)
|
||||
.location(gps.getLocation())
|
||||
.heading(gps.getHeading())
|
||||
.speed(gps.getSpeed())
|
||||
.build();
|
||||
broadcast(plottableEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* Base NXDN message implementation
|
||||
*/
|
||||
public abstract class NXDNMessage implements IMessage
|
||||
{
|
||||
protected static int OCTET_0 = 0;
|
||||
protected static int OCTET_1 = 8;
|
||||
protected static int OCTET_2 = 16;
|
||||
protected static int OCTET_3 = 24;
|
||||
protected static int OCTET_4 = 32;
|
||||
protected static int OCTET_5 = 40;
|
||||
protected static int OCTET_6 = 48;
|
||||
protected static int OCTET_7 = 56;
|
||||
protected static int OCTET_8 = 64;
|
||||
protected static int OCTET_9 = 72;
|
||||
protected static int OCTET_10 = 80;
|
||||
protected static int OCTET_11 = 88;
|
||||
protected static int OCTET_12 = 96;
|
||||
protected static int OCTET_13 = 104;
|
||||
protected static int OCTET_14 = 112;
|
||||
protected static int OCTET_15 = 120;
|
||||
protected static int OCTET_16 = 128;
|
||||
protected static int OCTET_17 = 136;
|
||||
protected static int OCTET_18 = 144;
|
||||
protected static int OCTET_19 = 152;
|
||||
protected static int OCTET_20 = 160;
|
||||
protected final int mRAN;
|
||||
protected final LICH mLICH;
|
||||
|
||||
private CorrectedBinaryMessage mMessage;
|
||||
private long mTimestamp;
|
||||
private boolean mValid = true;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
*/
|
||||
public NXDNMessage(CorrectedBinaryMessage message, long timestamp, int ran, LICH lich)
|
||||
{
|
||||
mMessage = message;
|
||||
mTimestamp = timestamp;
|
||||
mRAN = ran;
|
||||
mLICH = lich;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp()
|
||||
{
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Underlying binary message
|
||||
*/
|
||||
public CorrectedBinaryMessage getMessage()
|
||||
{
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
return mValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this message as valid or invalid.
|
||||
*
|
||||
* @param valid flag
|
||||
*/
|
||||
public void setValid(boolean valid)
|
||||
{
|
||||
mValid = valid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return Protocol.NXDN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTimeslot()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatted message prefix indicating CRC status, RAN and RF Channel.
|
||||
*/
|
||||
public StringBuilder getMessageBuilder()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append("[CRC-ERROR] ");
|
||||
}
|
||||
sb.append(getLICH().getRFChannel().name());
|
||||
if(hasRAN())
|
||||
{
|
||||
sb.append(" RAN:").append(getRAN());
|
||||
}
|
||||
sb.append(" ");
|
||||
return sb;
|
||||
}
|
||||
|
||||
public boolean hasRAN()
|
||||
{
|
||||
return mRAN != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Radio access network (RAN) from the frame carrier
|
||||
*/
|
||||
public int getRAN()
|
||||
{
|
||||
return mRAN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Link Information Channel (LICH) from the frame carrier
|
||||
*/
|
||||
public LICH getLICH()
|
||||
{
|
||||
return mLICH;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.SyncLossMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer1.Frame;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer1.sync.NXDNHardSyncDetector;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer1.sync.NXDNSyncDetector;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICHTracker;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.TransmissionMode;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Provides message framing for the demodulated dibit stream. This framer is notified by an external sync detection
|
||||
* process using the two syncDetected() methods below to indicate if the NID that follows the sync was correctly error
|
||||
* detected and corrected. When the NID does not pass error correction, we use a PLACEHOLDER data unit ID to allow the
|
||||
* uncertain message to assemble and then we'll inspect before and after data unit IDs and the quantity of captured
|
||||
* dibits to make a best guess on what the assembled message represents.
|
||||
*/
|
||||
public class NXDNMessageFramer
|
||||
{
|
||||
private static final int FRAME_LENGTH = 192;
|
||||
private static final int SYNC_LENGTH = 10;
|
||||
private static final int PAYLOAD_LENGTH = FRAME_LENGTH - SYNC_LENGTH;
|
||||
private static final int SYNC_DETECTION_BIT_ERROR_THRESHOLD = 8;
|
||||
private final Dibit[] mDibitBuffer = new Dibit[192];
|
||||
private final LICHTracker mLICHTracker;
|
||||
private final Listener<IMessage> mMessageListener;
|
||||
private final NXDNHardSyncDetector mHardSyncDetector = new NXDNHardSyncDetector();
|
||||
private final int mSymbolsPerSecond;
|
||||
private final int mCoarseSyncLossThreshold;
|
||||
private boolean mSyncDetected = false;
|
||||
private int mDibitBufferPointer = 0;
|
||||
private int mDibitSinceTimestampCounter = 0;
|
||||
private int mDibitsSinceLastMessage = FRAME_LENGTH;
|
||||
private long mReferenceTimestamp = 0;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param messageListener to receive framed messages
|
||||
* @param transmissionMode to indicate how many symbols per second for accounting
|
||||
*/
|
||||
public NXDNMessageFramer(Listener<IMessage> messageListener, TransmissionMode transmissionMode)
|
||||
{
|
||||
mMessageListener = messageListener;
|
||||
mLICHTracker = new LICHTracker(transmissionMode);
|
||||
mSymbolsPerSecond = transmissionMode.getSymbolRate();
|
||||
mCoarseSyncLossThreshold = mSymbolsPerSecond + FRAME_LENGTH;
|
||||
Arrays.fill(mDibitBuffer, Dibit.D00_PLUS_1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or updates the current dibit stream time from an incoming sample buffer.
|
||||
* @param time to use as a reference timestamp.
|
||||
*/
|
||||
public void setTimestamp(long time)
|
||||
{
|
||||
mReferenceTimestamp = time;
|
||||
mDibitSinceTimestampCounter = 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the timestamp accurate to the currently received dibit.
|
||||
* @return timestamp in milliseconds.
|
||||
*/
|
||||
private long getTimestamp()
|
||||
{
|
||||
if(mReferenceTimestamp > 0)
|
||||
{
|
||||
return mReferenceTimestamp + (long)(1000.0 * mDibitSinceTimestampCounter / 4800);
|
||||
}
|
||||
else
|
||||
{
|
||||
mDibitSinceTimestampCounter = 0;
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a currently assembling message once it's complete.
|
||||
* @return true if any of the messages from the frame are valid.
|
||||
*/
|
||||
private boolean dispatchMessage()
|
||||
{
|
||||
CorrectedBinaryMessage cbm = new CorrectedBinaryMessage(PAYLOAD_LENGTH * 2);
|
||||
//The buffer pointer is the next symbol after the completed message, ie start of sync
|
||||
int bufferDibitPointer = mDibitBufferPointer + SYNC_LENGTH;
|
||||
bufferDibitPointer %= FRAME_LENGTH;
|
||||
int messageDibitPointer = 0;
|
||||
|
||||
Dibit dibit;
|
||||
while(messageDibitPointer < PAYLOAD_LENGTH)
|
||||
{
|
||||
dibit = mDibitBuffer[bufferDibitPointer++];
|
||||
cbm.add(dibit.getBit1(), dibit.getBit2());
|
||||
bufferDibitPointer %= FRAME_LENGTH;
|
||||
messageDibitPointer++;
|
||||
}
|
||||
|
||||
boolean validMessage = false;
|
||||
Frame frame = new Frame(cbm, getTimestamp(), mLICHTracker);
|
||||
|
||||
for(NXDNMessage message: frame.getMessages())
|
||||
{
|
||||
validMessage |= message.isValid();
|
||||
dispatch(message);
|
||||
}
|
||||
|
||||
mDibitsSinceLastMessage = 0;
|
||||
|
||||
return validMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the previous 10 symbols in the dibit buffer match the sync pattern. This uses the
|
||||
* buffer wrapping to determine if the just collected dibit sync sequence contains a sync pattern but was not
|
||||
* triggered by the sync detector. This should only be used at the message interval when we anticipate arrival of
|
||||
* the succeeding message.
|
||||
*
|
||||
* @return bit errors for the received dibit sequence against the sync pattern
|
||||
*/
|
||||
private int checkSync()
|
||||
{
|
||||
long value = 0;
|
||||
int dibitBufferPointer = mDibitBufferPointer - SYNC_LENGTH;
|
||||
|
||||
if(dibitBufferPointer < 0)
|
||||
{
|
||||
dibitBufferPointer += FRAME_LENGTH;
|
||||
}
|
||||
|
||||
for(int x = 0; x < SYNC_LENGTH; x++)
|
||||
{
|
||||
value <<= 2;
|
||||
value += mDibitBuffer[dibitBufferPointer++].getValue();
|
||||
dibitBufferPointer %= FRAME_LENGTH;
|
||||
}
|
||||
|
||||
return Long.bitCount(value ^ NXDNSyncDetector.SYNC_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the message to an optional listener
|
||||
* @param message to dispatch
|
||||
*/
|
||||
private void dispatch(IMessage message)
|
||||
{
|
||||
if(mMessageListener != null)
|
||||
{
|
||||
mMessageListener.receive(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification that the sync pattern was detected. The most recently received dibit should be the last symbol
|
||||
* in the sync pattern and the next dibit received should be the start of the frame content.
|
||||
*/
|
||||
public void syncDetected()
|
||||
{
|
||||
//Ignore false/extra sync when we're already in sync detection
|
||||
if(!mSyncDetected)
|
||||
{
|
||||
mSyncDetected = true;
|
||||
|
||||
//Update sync loss accounting when we have more/less than the expected sync pattern symbol count.
|
||||
if(mDibitsSinceLastMessage > SYNC_LENGTH)
|
||||
{
|
||||
dispatch(new SyncLossMessage(getTimestamp(),
|
||||
(mDibitsSinceLastMessage - SYNC_LENGTH) * 2, Protocol.NXDN));
|
||||
mDibitsSinceLastMessage = SYNC_LENGTH;
|
||||
}
|
||||
else if(mDibitsSinceLastMessage < SYNC_LENGTH)
|
||||
{
|
||||
mDibitsSinceLastMessage = SYNC_LENGTH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process symbol decision without sync detection.
|
||||
* @param symbol that was demodulated.
|
||||
*/
|
||||
public boolean process(Dibit symbol)
|
||||
{
|
||||
//Check when we didn't get a sync detect, and count-wise, we're at the next frame's end-sync location
|
||||
if(!mSyncDetected && mDibitsSinceLastMessage == SYNC_LENGTH)
|
||||
{
|
||||
int bitErrors = checkSync();
|
||||
|
||||
if(bitErrors <= SYNC_DETECTION_BIT_ERROR_THRESHOLD)
|
||||
{
|
||||
mSyncDetected = true;
|
||||
}
|
||||
}
|
||||
|
||||
mDibitBuffer[mDibitBufferPointer++] = symbol;
|
||||
mDibitBufferPointer %= FRAME_LENGTH;
|
||||
mDibitsSinceLastMessage++;
|
||||
mDibitSinceTimestampCounter++;
|
||||
|
||||
if(mSyncDetected && mDibitsSinceLastMessage == FRAME_LENGTH)
|
||||
{
|
||||
mSyncDetected = false;
|
||||
return dispatchMessage();
|
||||
}
|
||||
else if(mDibitsSinceLastMessage >= mCoarseSyncLossThreshold)
|
||||
{
|
||||
mDibitsSinceLastMessage -= mSymbolsPerSecond;
|
||||
dispatch(new SyncLossMessage(getTimestamp(), mSymbolsPerSecond * 2, Protocol.NXDN));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process symbol decision with (hard) sync detection
|
||||
* @param symbol to process
|
||||
*/
|
||||
public void processWithHardSyncDetection(Dibit symbol)
|
||||
{
|
||||
process(symbol);
|
||||
|
||||
if(mHardSyncDetector.process(symbol))
|
||||
{
|
||||
syncDetected();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn;
|
||||
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.ChannelFrequency;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.ChannelInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.SACCHAssembler;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.SACCHFragment;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.IChannelInformationReceiver;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.Idle;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.SiteInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.Disconnect;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.IPacketHeader;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.ShortDataInitializationVector;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.UserData;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.data.PacketSequenceAssembler;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.proprietary.TalkerAlias;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.proprietary.TalkerAliasAssembler;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ChannelAccessInformation;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* NXDN Message Processor.
|
||||
*
|
||||
* Performs post-message creation processing and enrichment before the message is sent downstream.
|
||||
*/
|
||||
public class NXDNMessageProcessor implements Listener<IMessage>
|
||||
{
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(NXDNMessageProcessor.class);
|
||||
private ChannelAccessInformation mChannelAccessInformation;
|
||||
private final Map<Integer, ChannelFrequency> mChannelFrequencyMap = new HashMap<>();
|
||||
private final PacketSequenceAssembler mPacketSequenceAssembler = new PacketSequenceAssembler();
|
||||
private final SACCHAssembler mSACCHAssembler = new SACCHAssembler();
|
||||
private final TalkerAliasAssembler mTalkerAliasAssembler;
|
||||
|
||||
/**
|
||||
* Sets channel preload information. This is used to preload a traffic channel with system channel information
|
||||
* from the control channel when a traffic channel is allocated, on startup.
|
||||
* @param data to preload
|
||||
*/
|
||||
public void preload(NXDNChannelInfoPreloadData data)
|
||||
{
|
||||
ChannelInformation channelInformation = data.getData();
|
||||
if(mChannelAccessInformation == null)
|
||||
{
|
||||
mChannelAccessInformation = channelInformation.getChannelAccessInformation();
|
||||
}
|
||||
|
||||
for(ChannelFrequency channelFrequency: channelInformation.getChannelFrequencies())
|
||||
{
|
||||
if(!mChannelFrequencyMap.containsKey(channelFrequency.getChannel()))
|
||||
{
|
||||
mChannelFrequencyMap.put(channelFrequency.getChannel(), channelFrequency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Downstream message listener
|
||||
*/
|
||||
private Listener<IMessage> mMessageListener;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param configNXDN that optionally contains a channel map for channel mode systems
|
||||
*/
|
||||
public NXDNMessageProcessor(DecodeConfigNXDN configNXDN)
|
||||
{
|
||||
for(ChannelFrequency channelFrequency: configNXDN.getChannelMap())
|
||||
{
|
||||
mChannelFrequencyMap.put(channelFrequency.getChannel(), channelFrequency);
|
||||
}
|
||||
|
||||
mTalkerAliasAssembler = new TalkerAliasAssembler(configNXDN.getEncoding());
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the message for enrichment or reassembly of fragments and sends the enriched message and any additional
|
||||
* messages that were created during the enrichment to the registered message listener.
|
||||
* @param message to process
|
||||
*/
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
{
|
||||
List<NXDNMessage> additionalMessagesToDispatch = null;
|
||||
|
||||
if(message != null && message.isValid())
|
||||
{
|
||||
//Capture channel access information from Site Information message
|
||||
if(message instanceof SiteInformation siteInformation)
|
||||
{
|
||||
mChannelAccessInformation = siteInformation.getChannelAccessInformation();
|
||||
}
|
||||
|
||||
//Enrich messages that carry channel information with the channel access info.
|
||||
if(mChannelAccessInformation != null && message instanceof IChannelInformationReceiver receiver)
|
||||
{
|
||||
receiver.receive(mChannelAccessInformation, mChannelFrequencyMap);
|
||||
}
|
||||
|
||||
//SACCH fragments are either standalone 4/4, or a sequence of 4 in a superframe. Reassemble them.
|
||||
if(message instanceof SACCHFragment fragment)
|
||||
{
|
||||
dispatch(message);
|
||||
message = null;
|
||||
//Publish the completed SACCH message to this method so that it can be processed correctly and dispatch
|
||||
//the original fragment and nullify the message to ensure correct sequencing where fragment 4 is
|
||||
//dispatched first, and then the assembled SACCH message.
|
||||
receive(mSACCHAssembler.process(fragment));
|
||||
}
|
||||
|
||||
if(message instanceof TalkerAlias fragment)
|
||||
{
|
||||
dispatch(message);
|
||||
//Assign the completed alias to the message to dispatch below, to ensure proper message output sequence
|
||||
message = mTalkerAliasAssembler.process(fragment);
|
||||
}
|
||||
else if(message instanceof Idle || message instanceof Disconnect)
|
||||
{
|
||||
//Clear any captured talker alias fragments at the end of the call or idle mode
|
||||
mTalkerAliasAssembler.reset();
|
||||
}
|
||||
else if(message instanceof IPacketHeader header)
|
||||
{
|
||||
mPacketSequenceAssembler.process(header);
|
||||
}
|
||||
else if(message instanceof UserData userDataBlock)
|
||||
{
|
||||
additionalMessagesToDispatch = mPacketSequenceAssembler.process(userDataBlock);
|
||||
}
|
||||
else if(message instanceof ShortDataInitializationVector iv)
|
||||
{
|
||||
mPacketSequenceAssembler.process(iv);
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(message);
|
||||
|
||||
if(additionalMessagesToDispatch != null)
|
||||
{
|
||||
for(NXDNMessage additional: additionalMessagesToDispatch)
|
||||
{
|
||||
dispatch(additional);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-process the message for frequency band details.
|
||||
* @param message to post process and dispatch
|
||||
*/
|
||||
private void dispatch(IMessage message)
|
||||
{
|
||||
if(message != null && mMessageListener != null)
|
||||
{
|
||||
mMessageListener.receive(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares for disposal of this instance.
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
mMessageListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message listener to receive the output from this processor.
|
||||
* @param listener to receive output messages
|
||||
*/
|
||||
public void setMessageListener(Listener<IMessage> listener)
|
||||
{
|
||||
mMessageListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears a registered message listener.
|
||||
*/
|
||||
public void removeMessageListener()
|
||||
{
|
||||
mMessageListener = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn;
|
||||
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNLayer3Message;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.AdjacentSiteInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.AdjacentSiteInformationTypeD;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.ControlChannelInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.DigitalStationIDInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.FailureStatusInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.Neighbor;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.ServiceInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.SiteInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.RepeaterIdle;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.SiteID;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Processes NXDN Layer 3 messages to assemble a snapshot of the site's broadcast configuration details
|
||||
*/
|
||||
public class NXDNNetworkConfigurationMonitor
|
||||
{
|
||||
private ControlChannelInformation mControlChannelInformation;
|
||||
private DigitalStationIDInformation mDigitalStationIDInformation;
|
||||
private FailureStatusInformation mFailureStatusInformation;
|
||||
private ServiceInformation mServiceInformation;
|
||||
private SiteInformation mSiteInformation;
|
||||
private Map<Integer, Neighbor> mNeighborMap = new HashMap<>();
|
||||
|
||||
private AdjacentSiteInformationTypeD mTypeDNeighborA;
|
||||
private AdjacentSiteInformationTypeD mTypeDNeighborB;
|
||||
private Integer mTypeDRepeater;
|
||||
private List<Integer> mTypeDObservedRepeaters = new ArrayList<>();
|
||||
private SiteID mTypeDSiteID;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public NXDNNetworkConfigurationMonitor()
|
||||
{
|
||||
}
|
||||
|
||||
public String getSummary()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(mDigitalStationIDInformation != null)
|
||||
{
|
||||
sb.append("Current Channel Station ID\n ").append(mDigitalStationIDInformation).append("\n");
|
||||
}
|
||||
if(mSiteInformation != null)
|
||||
{
|
||||
sb.append("\nCurrent Site\n ").append(mSiteInformation).append("\n");
|
||||
}
|
||||
else if(mTypeDSiteID != null)
|
||||
{
|
||||
sb.append("\nCurrent Site\n ").append(mTypeDSiteID).append("\n");
|
||||
}
|
||||
if(mServiceInformation != null)
|
||||
{
|
||||
sb.append("\nCurrent Site Services\n ").append(mServiceInformation).append("\n");
|
||||
}
|
||||
if(mFailureStatusInformation != null)
|
||||
{
|
||||
sb.append("\nFailure Status\n ").append(mFailureStatusInformation).append("\n");
|
||||
}
|
||||
if(mControlChannelInformation != null)
|
||||
{
|
||||
sb.append("\nCurrent Site Control Channel\n ").append(mControlChannelInformation).append("\n");
|
||||
}
|
||||
|
||||
if(mTypeDRepeater != null)
|
||||
{
|
||||
sb.append("\nType-D Current Repeater: ").append(mTypeDRepeater).append("\n");
|
||||
}
|
||||
|
||||
if(!mTypeDObservedRepeaters.isEmpty())
|
||||
{
|
||||
Collections.sort(mTypeDObservedRepeaters);
|
||||
sb.append("\nType-D Observed Repeater Numbers:").append(mTypeDObservedRepeaters).append("\n");
|
||||
}
|
||||
|
||||
if(!mNeighborMap.isEmpty())
|
||||
{
|
||||
sb.append("\nNeighbor Sites\n");
|
||||
List<Integer> ids = new ArrayList<>(mNeighborMap.keySet());
|
||||
Collections.sort(ids);
|
||||
|
||||
for(Integer id : ids)
|
||||
{
|
||||
sb.append(" ").append(mNeighborMap.get(id)).append("\n");
|
||||
}
|
||||
}
|
||||
else if(mTypeDNeighborA != null || mTypeDNeighborB != null)
|
||||
{
|
||||
sb.append("\nNeighbor Sites\n");
|
||||
|
||||
if(mTypeDNeighborA != null)
|
||||
{
|
||||
sb.append(" ").append(mTypeDNeighborA.getSystemID1()).append(" SITE:").append(mTypeDNeighborA.getSite1()).append("\n");
|
||||
|
||||
if(mTypeDNeighborA.hasSite2())
|
||||
{
|
||||
sb.append(" ").append(mTypeDNeighborA.getSystemID2()).append(" SITE:").append(mTypeDNeighborA.getSite2()).append("\n");
|
||||
}
|
||||
}
|
||||
if(mTypeDNeighborB != null)
|
||||
{
|
||||
sb.append(" ").append(mTypeDNeighborB.getSystemID1()).append(" SITE:").append(mTypeDNeighborB.getSite1()).append("\n");
|
||||
|
||||
if(mTypeDNeighborB.hasSite2())
|
||||
{
|
||||
sb.append(" ").append(mTypeDNeighborB.getSystemID2()).append(" SITE:").append(mTypeDNeighborB.getSite2()).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("\nNeighbor Sites\n NONE\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void process(NXDNLayer3Message layer3)
|
||||
{
|
||||
switch(layer3.getMessageType())
|
||||
{
|
||||
case CONTROL_OUT_23_BC_DIGITAL_STATION_ID_INFORMATION:
|
||||
case TRAFFIC_OUT_23_BC_DIGITAL_STATION_ID_INFORMATION:
|
||||
case TYPE_D_OUT_23_BC_DIGITAL_STATION_ID:
|
||||
if(layer3 instanceof DigitalStationIDInformation dsii)
|
||||
{
|
||||
mDigitalStationIDInformation = dsii;
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_24_BC_SITE_INFORMATION:
|
||||
case TRAFFIC_OUT_24_BC_SITE_INFORMATION:
|
||||
mSiteInformation = (SiteInformation) layer3;
|
||||
break;
|
||||
case CONTROL_OUT_25_BC_SERVICE_INFORMATION:
|
||||
case TRAFFIC_OUT_25_BC_SERVICE_INFORMATION:
|
||||
case TYPE_D_OUT_25_BC_SERVICE_INFORMATION:
|
||||
if(layer3 instanceof ServiceInformation sii)
|
||||
{
|
||||
mServiceInformation = sii;
|
||||
}
|
||||
break;
|
||||
case CONTROL_OUT_26_BC_CONTROL_CHANNEL_INFORMATION:
|
||||
case TRAFFIC_OUT_26_BC_CONTROL_CHANNEL_INFORMATION:
|
||||
mControlChannelInformation = (ControlChannelInformation) layer3;
|
||||
break;
|
||||
case CONTROL_OUT_28_BC_FAILURE_STATUS_INFORMATION:
|
||||
case TRAFFIC_OUT_28_BC_FAILURE_STATUS_INFORMATION:
|
||||
mFailureStatusInformation = (FailureStatusInformation) layer3;
|
||||
break;
|
||||
case CONTROL_OUT_27_BC_ADJACENT_SITE_INFORMATION:
|
||||
case TRAFFIC_OUT_27_BC_ADJACENT_SITE_INFORMATION:
|
||||
if(layer3 instanceof AdjacentSiteInformation adjacent)
|
||||
{
|
||||
if(adjacent.hasChannel1())
|
||||
{
|
||||
Neighbor n1 = adjacent.getNeighbor1();
|
||||
|
||||
if(n1 != null)
|
||||
{
|
||||
mNeighborMap.put(n1.id(), n1);
|
||||
|
||||
if(adjacent.hasChannel2())
|
||||
{
|
||||
Neighbor n2 = adjacent.getNeighbor2();
|
||||
|
||||
if(n2 != null)
|
||||
{
|
||||
mNeighborMap.put(n2.id(), n2);
|
||||
|
||||
if(adjacent.hasChannel3())
|
||||
{
|
||||
Neighbor n3 = adjacent.getNeighbor3();
|
||||
|
||||
if(n3 != null)
|
||||
{
|
||||
mNeighborMap.put(n3.id(), n3);
|
||||
|
||||
if(adjacent.hasChannel4())
|
||||
{
|
||||
Neighbor n4 = adjacent.getNeighbor4();
|
||||
|
||||
if(n4 != null)
|
||||
{
|
||||
mNeighborMap.put(n4.id(), n4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case TYPE_D_OUT_27_BC_ADJACENT_SITE_INFORMATION:
|
||||
if(layer3 instanceof AdjacentSiteInformationTypeD atd)
|
||||
{
|
||||
if(atd.isIndex())
|
||||
{
|
||||
mTypeDNeighborB = atd;
|
||||
}
|
||||
else
|
||||
{
|
||||
mTypeDNeighborA = atd;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TYPE_D_SCCH_OUT_INFO_4_REPEATER_IDLE:
|
||||
if(layer3 instanceof RepeaterIdle ri)
|
||||
{
|
||||
mTypeDRepeater = ri.getRepeater();
|
||||
|
||||
if(!mTypeDObservedRepeaters.contains(ri.getRepeater2()))
|
||||
{
|
||||
mTypeDObservedRepeaters.add(ri.getRepeater2());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TYPE_D_SCCH_OUT_INFO_4_SITE_ID:
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,794 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn;
|
||||
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.controller.channel.ChannelEvent;
|
||||
import io.github.dsheirer.controller.channel.IChannelEventListener;
|
||||
import io.github.dsheirer.controller.channel.IChannelEventProvider;
|
||||
import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.identifier.alias.TalkerAliasManager;
|
||||
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventDuplicateDetector;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEventProvider;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.ChannelFrequency;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.NXDNChannel;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.NXDNChannelFake;
|
||||
import io.github.dsheirer.module.decode.nxdn.identifier.NXDNTalkerAliasIdentifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DataCallAssignment;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCall;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallAssignment;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallAssignmentDuplicateTraffic;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.AudioCodec;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.CallTimer;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.CallType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ChannelAccessInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.DataCallOption;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.Duplex;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.TransmissionMode;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.VoiceCallOption;
|
||||
import io.github.dsheirer.module.decode.traffic.TrafficChannelManager;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Manages trunked system traffic channel activations
|
||||
*/
|
||||
public class NXDNTrafficChannelManager extends TrafficChannelManager implements IDecodeEventProvider,
|
||||
IChannelEventListener, IChannelEventProvider
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NXDNTrafficChannelManager.class);
|
||||
public static final String MAX_TRAFFIC_CHANNELS_EXCEEDED = "MAX TRAFFIC CHANNELS EXCEEDED";
|
||||
public static final String CHANNEL_START_REJECTED = "CHANNEL START REJECTED";
|
||||
private final Channel mParentChannel;
|
||||
private final DecodeEventDuplicateDetector mDuplicateDetector = new DecodeEventDuplicateDetector();
|
||||
private final List<ChannelFrequency> mChannelFrequencies = new ArrayList<>();
|
||||
private final Lock mLock = new ReentrantLock();
|
||||
private final Map<Long, Channel> mAllocatedTrafficChannelMap = new HashMap<>();
|
||||
private final Map<Long, NXDNChannelEventTracker> mEventTrackerMap = new HashMap<>();
|
||||
private final Queue<Channel> mAvailableTrafficChannelQueue = new LinkedTransferQueue<>();
|
||||
private final TalkerAliasManager mTalkerAliasManager = new TalkerAliasManager();
|
||||
private final TrafficChannelTeardownMonitor mTrafficChannelTeardownMonitor = new TrafficChannelTeardownMonitor();
|
||||
private List<Channel> mManagedTrafficChannels;
|
||||
private Listener<IDecodeEvent> mDecodeEventListener;
|
||||
private Listener<ChannelEvent> mChannelEventListener;
|
||||
private ChannelAccessInformation mChannelAccessInformation;
|
||||
private boolean mIgnoreDataCalls;
|
||||
private boolean mIgnoreEncryptedCalls;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param parentChannel configuration for the parent channel
|
||||
*/
|
||||
public NXDNTrafficChannelManager(Channel parentChannel)
|
||||
{
|
||||
mParentChannel = parentChannel;
|
||||
|
||||
if(parentChannel.getDecodeConfiguration() instanceof DecodeConfigNXDN configNXDN)
|
||||
{
|
||||
mIgnoreDataCalls = configNXDN.isIgnoreDataCalls();
|
||||
mIgnoreEncryptedCalls = configNXDN.isIgnoreEncryptedCalls();
|
||||
mChannelFrequencies.addAll(configNXDN.getChannelMap());
|
||||
createTrafficChannels(configNXDN);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Talker alias manager
|
||||
*/
|
||||
public TalkerAliasManager getTalkerAliasManager()
|
||||
{
|
||||
return mTalkerAliasManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the channel access info from the control channel.
|
||||
*
|
||||
* @param info from the control channel
|
||||
*/
|
||||
public void setChannelAccessInformation(ChannelAccessInformation info)
|
||||
{
|
||||
if(mChannelAccessInformation == null)
|
||||
{
|
||||
mChannelAccessInformation = info;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates up to the maximum number of traffic channels for use in allocating traffic channels.
|
||||
*
|
||||
* @param decodeConfig to use for each traffic channel
|
||||
*/
|
||||
private void createTrafficChannels(DecodeConfigNXDN decodeConfig)
|
||||
{
|
||||
if(mManagedTrafficChannels == null)
|
||||
{
|
||||
int trafficChannelPoolSize = decodeConfig.getTrafficChannelPoolSize();
|
||||
List<Channel> trafficChannelList = new ArrayList<>();
|
||||
|
||||
if(trafficChannelPoolSize > 0)
|
||||
{
|
||||
for(int x = 0; x < trafficChannelPoolSize; x++)
|
||||
{
|
||||
Channel trafficChannel = new Channel("T-" + mParentChannel.getName(), Channel.ChannelType.TRAFFIC);
|
||||
trafficChannel.setAliasListName(mParentChannel.getAliasListName());
|
||||
trafficChannel.setSystem(mParentChannel.getSystem());
|
||||
trafficChannel.setSite(mParentChannel.getSite());
|
||||
trafficChannel.setDecodeConfiguration(decodeConfig);
|
||||
trafficChannel.setEventLogConfiguration(mParentChannel.getEventLogConfiguration());
|
||||
trafficChannel.setRecordConfiguration(mParentChannel.getRecordConfiguration());
|
||||
trafficChannelList.add(trafficChannel);
|
||||
}
|
||||
}
|
||||
|
||||
mAvailableTrafficChannelQueue.addAll(trafficChannelList);
|
||||
mManagedTrafficChannels = Collections.unmodifiableList(trafficChannelList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current event tracker for the specified channel and if the tracker is stale relative to the
|
||||
* timestamp, returns null, otherwise returns the current tracker.
|
||||
*
|
||||
* @param frequency to look up the tracker
|
||||
* @param timestamp to compare for staleness
|
||||
* @return tracker or null
|
||||
*/
|
||||
private NXDNChannelEventTracker getTrackerRemoveIfStale(long frequency, long timestamp)
|
||||
{
|
||||
NXDNChannelEventTracker tracker = mEventTrackerMap.get(frequency);
|
||||
|
||||
if(tracker != null && tracker.isStale(timestamp))
|
||||
{
|
||||
removeTracker(frequency);
|
||||
tracker = null;
|
||||
}
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the decode event from the tracker.
|
||||
*
|
||||
* @param tracker containing a decode event.
|
||||
*/
|
||||
public void broadcast(NXDNChannelEventTracker tracker)
|
||||
{
|
||||
broadcast(tracker.getEvent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts an initial or update decode event to any registered listener.
|
||||
*/
|
||||
public void broadcast(DecodeEvent decodeEvent)
|
||||
{
|
||||
if(mDecodeEventListener != null)
|
||||
{
|
||||
if(decodeEvent.getEventType() == DecodeEventType.DATA_CALL && mDuplicateDetector.isDuplicate(decodeEvent,
|
||||
System.currentTimeMillis()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mDecodeEventListener.receive(decodeEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println("Decode event listener is null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the decode event type based on the call type and encryption.
|
||||
*
|
||||
* @param callType for the call
|
||||
* @param encryption for the call
|
||||
* @return decode event type
|
||||
*/
|
||||
private DecodeEventType getType(CallType callType, EncryptionKeyIdentifier encryption)
|
||||
{
|
||||
boolean encrypted = encryption.isEncrypted();
|
||||
return switch(callType)
|
||||
{
|
||||
case GROUP_BROADCAST, GROUP_CONFERENCE ->
|
||||
encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP;
|
||||
case INDIVIDUAL ->
|
||||
encrypted ? DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED : DecodeEventType.CALL_UNIT_TO_UNIT;
|
||||
case INTERCONNECT, SPEED_DIAL ->
|
||||
encrypted ? DecodeEventType.CALL_INTERCONNECT_ENCRYPTED : DecodeEventType.CALL_INTERCONNECT;
|
||||
default -> encrypted ? DecodeEventType.CALL_ENCRYPTED : DecodeEventType.CALL;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a traffic channel event tracker
|
||||
*
|
||||
* @param eventType for the decode event
|
||||
* @param ic collection of from/to identifiers
|
||||
* @param channel to track
|
||||
* @param timestamp for the event
|
||||
* @return tracker
|
||||
*/
|
||||
private NXDNChannelEventTracker createTracker(DecodeEventType eventType, IdentifierCollection ic,
|
||||
NXDNChannel channel, long timestamp)
|
||||
{
|
||||
long frequency = channel != null ? channel.getDownlinkFrequency() : 0;
|
||||
|
||||
DecodeEvent event = NXDNDecodeEvent.builder(eventType, timestamp)
|
||||
.timeslot(0)
|
||||
.channel(channel)
|
||||
.identifiers(ic)
|
||||
.build();
|
||||
NXDNChannelEventTracker tracker = new NXDNChannelEventTracker(event);
|
||||
mEventTrackerMap.put(frequency, tracker);
|
||||
return tracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a data call assignment
|
||||
*
|
||||
* @param dca to process
|
||||
*/
|
||||
public void processDataCallAssignment(DataCallAssignment dca)
|
||||
{
|
||||
if(dca.hasChannel() && dca.getChannel().getDownlinkFrequency() > 0)
|
||||
{
|
||||
DecodeEventType eventType = dca.getEncryptionKeyIdentifier().isEncrypted() ?
|
||||
DecodeEventType.DATA_CALL_ENCRYPTED : DecodeEventType.DATA_CALL;
|
||||
processDataCall(dca.getIdentifiers(), dca.getChannel(), eventType, dca.getTimestamp(), dca.getCallOption(),
|
||||
dca.getCallTimer());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a voice call assignment or notification
|
||||
* @param identifiers for the call
|
||||
* @param channel for the call
|
||||
*/
|
||||
private void processDataCall(List<Identifier> identifiers, NXDNChannel channel, DecodeEventType eventType,
|
||||
long timestamp, DataCallOption dco, CallTimer callTimer)
|
||||
{
|
||||
if(channel.getDownlinkFrequency() > 0)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
NXDNChannelEventTracker tracker = getTrackerRemoveIfStale(channel.getDownlinkFrequency(), timestamp);
|
||||
MutableIdentifierCollection ic = new MutableIdentifierCollection(identifiers);
|
||||
if(tracker != null)
|
||||
{
|
||||
if(tracker.isSameCallCheckingToAndFrom(ic, timestamp))
|
||||
{
|
||||
tracker.updateDurationControl(timestamp);
|
||||
broadcast(tracker);
|
||||
}
|
||||
else
|
||||
{
|
||||
removeTracker(channel.getDownlinkFrequency());
|
||||
tracker = null;
|
||||
}
|
||||
}
|
||||
|
||||
if(tracker == null)
|
||||
{
|
||||
tracker = createTracker(eventType, ic, channel, timestamp);
|
||||
Duplex duplex = dco.getDuplex();
|
||||
TransmissionMode mode = dco.getTransmissionMode();
|
||||
tracker.addDetails("TIMER:" + callTimer + " " + duplex + " " + mode);
|
||||
}
|
||||
|
||||
if(mIgnoreDataCalls)
|
||||
{
|
||||
tracker.prefixDetails("IGNORED DATA");
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!tracker.isTrafficChannelAllocated() && channel.isValid() &&
|
||||
channel.getDownlinkFrequency() != getCurrentControlFrequency() &&
|
||||
!mAllocatedTrafficChannelMap.containsKey(channel.getDownlinkFrequency()))
|
||||
{
|
||||
//Retrieve a channel from the traffic channel queue
|
||||
Channel traffic = mAvailableTrafficChannelQueue.poll();
|
||||
|
||||
if(traffic != null)
|
||||
{
|
||||
requestTrafficChannelStart(traffic, channel, ic, tracker);
|
||||
}
|
||||
else
|
||||
{
|
||||
tracker.addDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED + " " + tracker.getEvent().getDetails());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
broadcast(tracker);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a voice call assignment indicating there is a call on another channel.
|
||||
*
|
||||
* @param vca to process
|
||||
*/
|
||||
public void processVoiceCallAssignment(VoiceCallAssignment vca)
|
||||
{
|
||||
if(vca.hasChannel() && vca.getChannel().getDownlinkFrequency() > 0)
|
||||
{
|
||||
processVoiceCall(vca.getIdentifiers(), vca.getChannel(), vca.getCallType(), vca.getEncryptionKeyIdentifier(),
|
||||
vca.getTimestamp(), vca.getCallOption(), vca.getCallTimer());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a voice call assignment duplicate notification from a traffic channel
|
||||
*
|
||||
* @param vca to process
|
||||
*/
|
||||
public void processVoiceCallAssignment(VoiceCallAssignmentDuplicateTraffic vca)
|
||||
{
|
||||
NXDNChannel channel = vca.getChannel();
|
||||
|
||||
if(channel == null || channel.getDownlinkFrequency() == 0)
|
||||
{
|
||||
//If the channel is null or not configured, use the talkgroup value so we can track the call event
|
||||
channel = new NXDNChannelFake(vca.getDestination().getValue());
|
||||
}
|
||||
|
||||
processVoiceCall(vca.getIdentifiers(), channel, vca.getCallType(), vca.getEncryptionKeyIdentifier(),
|
||||
vca.getTimestamp(), vca.getCallOption(), vca.getCallTimer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a traffic channel voice call message
|
||||
* @param voiceCall message
|
||||
* @param channel for the call, can be null
|
||||
*/
|
||||
public void processVoiceCall(VoiceCall voiceCall, IChannelDescriptor channel)
|
||||
{
|
||||
NXDNChannel nxdn = null;
|
||||
if(channel instanceof NXDNChannel nxdnChannel)
|
||||
{
|
||||
nxdn = nxdnChannel;
|
||||
}
|
||||
|
||||
processVoiceCall(voiceCall.getIdentifiers(), nxdn, voiceCall.getCallType(), voiceCall.getEncryptionKeyIdentifier(),
|
||||
voiceCall.getTimestamp(), voiceCall.getCallOption(), CallTimer.UNSPECIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or updates the talker alias for the tracked call event.
|
||||
* @param channel for the call
|
||||
* @param talkerAlias to add or update
|
||||
* @param timestamp for the talker alias message
|
||||
*/
|
||||
public void processTalkerAlias(IChannelDescriptor channel, NXDNTalkerAliasIdentifier talkerAlias,
|
||||
RadioIdentifier radio, long timestamp)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
long frequency = channel != null ? channel.getDownlinkFrequency() : 0;
|
||||
NXDNChannelEventTracker tracker = getTrackerRemoveIfStale(frequency, timestamp);
|
||||
|
||||
if(tracker != null)
|
||||
{
|
||||
tracker.addIdentifierIfMissing(talkerAlias);
|
||||
broadcast(tracker);
|
||||
}
|
||||
|
||||
getTalkerAliasManager().update(radio, talkerAlias);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the end time for an ongoing call to reflect progress
|
||||
* @param channel descriptor for the traffic channel
|
||||
*/
|
||||
public void processCallProgressUpdate(IChannelDescriptor channel, long timestamp)
|
||||
{
|
||||
mLock.lock();
|
||||
try
|
||||
{
|
||||
long frequency = channel != null ? channel.getDownlinkFrequency() : 0;
|
||||
NXDNChannelEventTracker tracker = getTrackerRemoveIfStale(frequency, timestamp);
|
||||
|
||||
if(tracker != null)
|
||||
{
|
||||
tracker.updateDurationTraffic(timestamp);
|
||||
broadcast(tracker);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the current call and removes the event tracker
|
||||
* @param channel for the call
|
||||
* @param timestamp for the end call
|
||||
*/
|
||||
public void processEndCall(IChannelDescriptor channel, long timestamp)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
long frequency = channel != null ? channel.getDownlinkFrequency() : 0;
|
||||
NXDNChannelEventTracker tracker = getTrackerRemoveIfStale(frequency, timestamp);
|
||||
|
||||
if(tracker != null)
|
||||
{
|
||||
tracker.completeTraffic(timestamp);
|
||||
broadcast(tracker);
|
||||
removeTracker(frequency);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a voice call assignment or notification
|
||||
* @param identifiers for the call
|
||||
* @param channel for the call
|
||||
*/
|
||||
private void processVoiceCall(List<Identifier> identifiers, NXDNChannel channel, CallType callType,
|
||||
EncryptionKeyIdentifier encryption, long timestamp, VoiceCallOption vco,
|
||||
CallTimer callTimer)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
long frequency = channel != null ? channel.getDownlinkFrequency() : 0;
|
||||
|
||||
try
|
||||
{
|
||||
NXDNChannelEventTracker tracker = getTrackerRemoveIfStale(frequency, timestamp);
|
||||
MutableIdentifierCollection ic = new MutableIdentifierCollection(identifiers);
|
||||
mTalkerAliasManager.enrich(ic);
|
||||
if(tracker != null)
|
||||
{
|
||||
if(tracker.isSameCallCheckingToAndFrom(ic, timestamp))
|
||||
{
|
||||
tracker.updateDurationControl(timestamp);
|
||||
}
|
||||
else
|
||||
{
|
||||
removeTracker(frequency);
|
||||
tracker = null;
|
||||
}
|
||||
}
|
||||
|
||||
if(tracker == null)
|
||||
{
|
||||
DecodeEventType eventType = getType(callType, encryption);
|
||||
tracker = createTracker(eventType, ic, channel, timestamp);
|
||||
AudioCodec audioCodec = vco.getCodec();
|
||||
TransmissionMode mode = vco.getTransmissionMode();
|
||||
|
||||
if(encryption.isEncrypted())
|
||||
{
|
||||
tracker.addDetails(encryption + " TIMER:" + callTimer + " " + audioCodec + " " + mode);
|
||||
}
|
||||
else
|
||||
{
|
||||
tracker.addDetails("TIMER:" + callTimer + " " + audioCodec + " " + mode);
|
||||
}
|
||||
}
|
||||
|
||||
boolean ignoreEncrypted = mIgnoreEncryptedCalls && encryption.isEncrypted();
|
||||
|
||||
if(ignoreEncrypted)
|
||||
{
|
||||
tracker.prefixDetails("IGNORED ENCRYPTED");
|
||||
}
|
||||
else if(!tracker.isTrafficChannelAllocated() && channel != null && channel.isValid() &&
|
||||
channel.getDownlinkFrequency() != getCurrentControlFrequency() &&
|
||||
!mAllocatedTrafficChannelMap.containsKey(frequency))
|
||||
{
|
||||
//Retrieve a channel from the traffic channel queue
|
||||
Channel traffic = mAvailableTrafficChannelQueue.poll();
|
||||
|
||||
if(traffic != null)
|
||||
{
|
||||
requestTrafficChannelStart(traffic, channel, ic, tracker);
|
||||
}
|
||||
else
|
||||
{
|
||||
tracker.addDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED + " " + tracker.getEvent().getDetails());
|
||||
}
|
||||
}
|
||||
|
||||
broadcast(tracker);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a channel start request to the ChannelProcessingManager.
|
||||
* Note: this method is not thread safe and the calling method must protect access using mLock.
|
||||
*
|
||||
* @param trafficChannel to use for the traffic channel
|
||||
* @param nxdnChannel that describes the traffic channel downlink frequency
|
||||
* @param ic containing identifiers for the call
|
||||
* @param tracker to update with channel allocation flag
|
||||
*/
|
||||
private void requestTrafficChannelStart(Channel trafficChannel, NXDNChannel nxdnChannel,
|
||||
IdentifierCollection ic, NXDNChannelEventTracker tracker)
|
||||
{
|
||||
if(nxdnChannel != null && nxdnChannel.getDownlinkFrequency() > 0 && getInterModuleEventBus() != null)
|
||||
{
|
||||
SourceConfigTuner sourceConfig = new SourceConfigTuner();
|
||||
sourceConfig.setFrequency(nxdnChannel.getDownlinkFrequency());
|
||||
if(mParentChannel.getSourceConfiguration() instanceof SourceConfigTuner parentConfigTuner)
|
||||
{
|
||||
sourceConfig.setPreferredTuner(parentConfigTuner.getPreferredTuner());
|
||||
}
|
||||
trafficChannel.setSourceConfiguration(sourceConfig);
|
||||
mAllocatedTrafficChannelMap.put(nxdnChannel.getDownlinkFrequency(), trafficChannel);
|
||||
ChannelStartProcessingRequest startChannelRequest = new ChannelStartProcessingRequest(trafficChannel,
|
||||
nxdnChannel, ic, this);
|
||||
startChannelRequest.addPreloadDataContent(new NXDNChannelInfoPreloadData(mChannelAccessInformation, mChannelFrequencies));
|
||||
getInterModuleEventBus().post(startChannelRequest);
|
||||
tracker.setTrafficChannelAllocated(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Return the channel to the traffic channel pool if we didn't start it.
|
||||
mAvailableTrafficChannelQueue.add(trafficChannel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ChannelEvent> getChannelEventListener()
|
||||
{
|
||||
return mTrafficChannelTeardownMonitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the channel event ot an optionally registered listener
|
||||
* @param event to broadcast
|
||||
*/
|
||||
private void broadcast(ChannelEvent event)
|
||||
{
|
||||
if(mChannelEventListener != null)
|
||||
{
|
||||
mChannelEventListener.receive(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChannelEventListener(Listener<ChannelEvent> listener)
|
||||
{
|
||||
mChannelEventListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeChannelEventListener()
|
||||
{
|
||||
mChannelEventListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the current control frequency to ensure we don't allocated traffic channels for the same frequency.
|
||||
* @param previous frequency for the control channel (to remove from allocated channels)
|
||||
* @param current frequency for the control channel (to add to allocated channels)
|
||||
* @param channel for the current control channel
|
||||
*/
|
||||
@Override
|
||||
protected void processControlFrequencyUpdate(long previous, long current, Channel channel)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
//Clear any traffic channel that is already allocated on the new/current control frequency
|
||||
if(mAllocatedTrafficChannelMap.containsKey(current))
|
||||
{
|
||||
broadcast(new ChannelEvent(mAllocatedTrafficChannelMap.get(current), ChannelEvent.Event.REQUEST_DISABLE));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
mAvailableTrafficChannelQueue.clear();
|
||||
|
||||
List<Channel> channels = new ArrayList<>(mAllocatedTrafficChannelMap.values());
|
||||
|
||||
//Issue a disable request for each traffic channel
|
||||
for(Channel channel: channels)
|
||||
{
|
||||
LOGGER.info("Stopping NXDN traffic channel: " + channel);
|
||||
broadcast(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_DISABLE));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDecodeEventListener(Listener<IDecodeEvent> listener)
|
||||
{
|
||||
mDecodeEventListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDecodeEventListener(Listener<IDecodeEvent> listener)
|
||||
{
|
||||
mDecodeEventListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remotes the event tracker from the tracker map
|
||||
* @param frequency for the tracker
|
||||
*/
|
||||
private void removeTracker(long frequency)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
mEventTrackerMap.remove(frequency);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors channel teardown events to detect when traffic channel processing has ended or channel start has been
|
||||
* rejected. Reclaims the traffic channel instance for reuse by future traffic channel grants.
|
||||
*/
|
||||
public class TrafficChannelTeardownMonitor implements Listener<ChannelEvent>
|
||||
{
|
||||
/**
|
||||
* Process channel events from the ChannelProcessingManager to account for owned child traffic channels.
|
||||
* Note: this method sees events for ALL channels and not just P25 channels managed by this instance.
|
||||
*
|
||||
* @param channelEvent to process
|
||||
*/
|
||||
@Override
|
||||
public void receive(ChannelEvent channelEvent)
|
||||
{
|
||||
Channel channel = channelEvent.getChannel();
|
||||
|
||||
if(mManagedTrafficChannels.contains(channel))
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
switch(channelEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_PROCESSING_STOP:
|
||||
mAllocatedTrafficChannelMap.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> entry.getValue() == channel)
|
||||
.map(Map.Entry::getKey)
|
||||
.findFirst()
|
||||
.ifPresent(frequency -> {
|
||||
mAllocatedTrafficChannelMap.remove(frequency);
|
||||
//Don't remove the tracker. There's a chance the control channel can still
|
||||
//reference the now terminating traffic channel and cause a residual event
|
||||
//creation and channel allocation and we'll use the tracker to keep that from
|
||||
//happening.
|
||||
//removeTracker(frequency);
|
||||
mAvailableTrafficChannelQueue.add(channel);
|
||||
});
|
||||
break;
|
||||
case NOTIFICATION_PROCESSING_START_REJECTED:
|
||||
mAllocatedTrafficChannelMap.entrySet().stream()
|
||||
.filter(entry -> entry.getValue() == channel)
|
||||
.map(Map.Entry::getKey)
|
||||
.findFirst()
|
||||
.ifPresent(rejectedFrequency -> {
|
||||
mAllocatedTrafficChannelMap.remove(rejectedFrequency);
|
||||
mAvailableTrafficChannelQueue.add(channel);
|
||||
|
||||
//Leave the event in the map so that it doesn't get recreated. The channel
|
||||
//processing manager set the 'tuner not available' in the details already
|
||||
NXDNChannelEventTracker tracker = mEventTrackerMap.get(rejectedFrequency);
|
||||
|
||||
if(tracker != null)
|
||||
{
|
||||
if(!tracker.getEvent().getDetails().contains(CHANNEL_START_REJECTED))
|
||||
{
|
||||
tracker.addDetails(CHANNEL_START_REJECTED + " " + channelEvent.getDescription() +
|
||||
(tracker.getEvent().getDetails() != null ? " - " + tracker.getEvent().getDetails() : ""));
|
||||
}
|
||||
tracker.setTrafficChannelAllocated(false);
|
||||
broadcast(tracker);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn;
|
||||
|
||||
/**
|
||||
* Tracks sync detections for fixed length messages
|
||||
*/
|
||||
public class SyncTracker
|
||||
{
|
||||
private final int mSymbolsPerMessage;
|
||||
private final int mSyncLossThreshold;
|
||||
private final int mMaxSyncWindowSize;
|
||||
private int mSyncCount = 0;
|
||||
private int mSymbolCount;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param symbolsPerMessage a count of how many symbols per message, between each sync interval (ie message length).
|
||||
* @param messageWindowSize window size for retaining sync across messages sequences
|
||||
*/
|
||||
public SyncTracker(int symbolsPerMessage, int messageWindowSize)
|
||||
{
|
||||
mSymbolsPerMessage = symbolsPerMessage;
|
||||
mSyncLossThreshold = symbolsPerMessage * 2;
|
||||
mMaxSyncWindowSize = messageWindowSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the symbol count.
|
||||
*/
|
||||
public void symbol()
|
||||
{
|
||||
mSymbolCount++;
|
||||
|
||||
if(mSymbolCount >= mSyncLossThreshold)
|
||||
{
|
||||
mSymbolCount -= mSymbolsPerMessage;
|
||||
mSyncCount--;
|
||||
mSyncCount = Math.max(mSyncCount, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates when a sync pattern is detected.
|
||||
*/
|
||||
public void syncDetected()
|
||||
{
|
||||
mSyncCount++;
|
||||
mSyncCount = Math.min(mSyncCount, mMaxSyncWindowSize);
|
||||
mSymbolCount -= mSymbolsPerMessage;
|
||||
mSymbolCount = Math.max(mSymbolCount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if there has been at least one sync detection in the past maxSyncSequence message windows.
|
||||
* @return true if there is at least one sync detection
|
||||
*/
|
||||
public boolean isSynchronized()
|
||||
{
|
||||
return mSyncCount > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.audio;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.audio.codec.mbe.AmbeAudioModule;
|
||||
import io.github.dsheirer.audio.squelch.SquelchState;
|
||||
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
|
||||
import io.github.dsheirer.dsp.gain.NonClippingGain;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.Audio;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.Disconnect;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.TransmissionRelease;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCall;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallWithOptionalLocation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.AudioCodec;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* NXDN AMBE audio module
|
||||
*/
|
||||
public class NXDNAudioModule extends AmbeAudioModule
|
||||
{
|
||||
private final SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||
private final NonClippingGain mGain = new NonClippingGain(5.0f, 0.95f);
|
||||
private final List<Audio> mCachedAudioMessages = new ArrayList<>();
|
||||
private boolean mEncryptedCall = false;
|
||||
private boolean mEncryptedCallStateEstablished = false;
|
||||
private AudioCodec mAudioCodec;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences component
|
||||
* @param aliasList for the current channel
|
||||
*/
|
||||
public NXDNAudioModule(UserPreferences userPreferences, AliasList aliasList)
|
||||
{
|
||||
super(userPreferences, aliasList, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<SquelchStateEvent> getSquelchStateListener()
|
||||
{
|
||||
return mSquelchStateListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
getIdentifierCollection().clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes audio and layer 3 messages to decode audio and to determine the encrypted status of a call event.
|
||||
*/
|
||||
public void receive(IMessage message)
|
||||
{
|
||||
if(hasAudioCodec())
|
||||
{
|
||||
if(message instanceof Audio audio)
|
||||
{
|
||||
if(mEncryptedCallStateEstablished)
|
||||
{
|
||||
processAudio(audio);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Cache audio until we can determine the encryption state
|
||||
mCachedAudioMessages.add(audio);
|
||||
}
|
||||
}
|
||||
else if(message.isValid())
|
||||
{
|
||||
if(message instanceof VoiceCall voiceCall)
|
||||
{
|
||||
mEncryptedCall = voiceCall.getEncryptionKeyIdentifier().isEncrypted();
|
||||
mEncryptedCallStateEstablished = true;
|
||||
mAudioCodec = voiceCall.getCallOption().getCodec();
|
||||
processCachedAudio();
|
||||
}
|
||||
else if(message instanceof VoiceCallWithOptionalLocation voiceCall)
|
||||
{
|
||||
mEncryptedCall = voiceCall.getEncryptionKeyIdentifier().isEncrypted();
|
||||
mEncryptedCallStateEstablished = true;
|
||||
mAudioCodec = voiceCall.getCallOption().getCodec();
|
||||
processCachedAudio();
|
||||
}
|
||||
else if(message instanceof Disconnect || message instanceof TransmissionRelease)
|
||||
{
|
||||
closeAudioSegment();
|
||||
mCachedAudioMessages.clear();
|
||||
mEncryptedCall = false;
|
||||
mEncryptedCallStateEstablished = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes any cached audio frames that were pending an encryption state determination.
|
||||
*/
|
||||
private void processCachedAudio()
|
||||
{
|
||||
for(Audio audio : mCachedAudioMessages)
|
||||
{
|
||||
processAudio(audio);
|
||||
}
|
||||
|
||||
mCachedAudioMessages.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an audio packet by decoding the IMBE audio frames and rebroadcasting them as PCM audio packets.
|
||||
*/
|
||||
private void processAudio(Audio audio)
|
||||
{
|
||||
if(!mEncryptedCall && mAudioCodec != null && mAudioCodec.equals(AudioCodec.HALF_RATE)) //Full rate not yet supported
|
||||
{
|
||||
for(byte[] frame : audio.getAudioFrames())
|
||||
{
|
||||
float[] generatedAudio = getAudioCodec().getAudio(frame);
|
||||
generatedAudio = mGain.apply(generatedAudio);
|
||||
addAudio(generatedAudio);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Encrypted audio processing not supported
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for squelch state to process end of call actions. At call end the encrypted call state established
|
||||
* flag is reset so that the encrypted audio state for the next call can be properly detected and we send an
|
||||
* END audio packet so that downstream processors like the audio recorder can properly close out a call sequence.
|
||||
*/
|
||||
public class SquelchStateListener implements Listener<SquelchStateEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(SquelchStateEvent event)
|
||||
{
|
||||
if(event.getSquelchState() == SquelchState.SQUELCH)
|
||||
{
|
||||
closeAudioSegment();
|
||||
mEncryptedCallStateEstablished = false;
|
||||
mEncryptedCall = false;
|
||||
mCachedAudioMessages.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.audio;
|
||||
|
||||
|
||||
import io.github.dsheirer.audio.codec.mbe.MBECallSequence;
|
||||
import io.github.dsheirer.audio.codec.mbe.MBECallSequenceRecorder;
|
||||
import io.github.dsheirer.audio.codec.mbe.VoiceFrame;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.NXDNMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNLayer3Message;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.Audio;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCall;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallInitializationVector;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.CallInProgressDestinationInfo2;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.CallInProgressDestinationInfo4;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.CallInProgressSourceID;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.CallInfo;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* NXDN AMBE+ Frame recorder generates call sequence recordings containing JSON representations of audio
|
||||
* frames, optional encryption and call identifiers.
|
||||
*/
|
||||
public class NXDNCallSequenceRecorder extends MBECallSequenceRecorder
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(NXDNCallSequenceRecorder.class);
|
||||
public static final String PROTOCOL = "NXDN";
|
||||
private MBECallSequence mCallSequence;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param userPreferences to obtain the recording directory
|
||||
* @param channelFrequency for the channel to record
|
||||
* @param system defined by the user
|
||||
* @param site defined by the user
|
||||
*/
|
||||
public NXDNCallSequenceRecorder(UserPreferences userPreferences, long channelFrequency, String system, String site)
|
||||
{
|
||||
super(userPreferences, channelFrequency, system, site);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops and flushes any partial frame sequence from the processors
|
||||
*/
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary message interface for receiving frames and metadata messages to record
|
||||
*/
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
{
|
||||
if(message instanceof NXDNMessage nxdn)
|
||||
{
|
||||
if(nxdn.isValid())
|
||||
{
|
||||
if(nxdn instanceof Audio audio)
|
||||
{
|
||||
process(audio);
|
||||
}
|
||||
else if(nxdn instanceof NXDNLayer3Message layer3)
|
||||
{
|
||||
process(layer3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and optionally creates a new call sequence.
|
||||
*/
|
||||
private MBECallSequence getCallSequence()
|
||||
{
|
||||
if(mCallSequence == null)
|
||||
{
|
||||
mCallSequence = new MBECallSequence(PROTOCOL);
|
||||
}
|
||||
|
||||
return mCallSequence;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Flushes any partial call sequence
|
||||
*/
|
||||
public void flush()
|
||||
{
|
||||
if(mCallSequence != null)
|
||||
{
|
||||
writeCallSequence(mCallSequence);
|
||||
mCallSequence = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes any NXDN layer 3 message
|
||||
*/
|
||||
public void process(NXDNLayer3Message layer3)
|
||||
{
|
||||
switch(layer3.getMessageType())
|
||||
{
|
||||
case TRAFFIC_IN_01_CC_VOICE_CALL:
|
||||
case TRAFFIC_OUT_01_CC_VOICE_CALL:
|
||||
case TYPE_D_IN_01_CC_VOICE_CALL:
|
||||
case TYPE_D_OUT_01_CC_VOICE_CALL:
|
||||
if(layer3 instanceof VoiceCall vc)
|
||||
{
|
||||
getCallSequence().setCallType(vc.getCallType().toString());
|
||||
getCallSequence().setFromIdentifier(vc.getSource());
|
||||
getCallSequence().setToIdentifier(vc.getDestination());
|
||||
|
||||
if(vc.getEncryptionKeyIdentifier().isEncrypted())
|
||||
{
|
||||
getCallSequence().setEncrypted(true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TRAFFIC_IN_03_CC_VOICE_CALL_INITIALIZATION_VECTOR:
|
||||
case TRAFFIC_OUT_03_CC_VOICE_CALL_INITIALIZATION_VECTOR:
|
||||
case TYPE_D_IN_03_CC_VOICE_CALL_INITIALIZATION_VECTOR:
|
||||
case TYPE_D_OUT_03_CC_VOICE_CALL_INITIALIZATION_VECTOR:
|
||||
if(layer3 instanceof VoiceCallInitializationVector iv)
|
||||
{
|
||||
//TODO: implement superframes with IV injected into each
|
||||
}
|
||||
break;
|
||||
case TRAFFIC_OUT_07_CC_TRANSMISSION_RELEASE_EXTENSION:
|
||||
case TRAFFIC_OUT_08_CC_TRANSMISSION_RELEASE:
|
||||
case TYPE_D_OUT_07_CC_TRANSMISSION_RELEASE_EXTENSION:
|
||||
case TYPE_D_OUT_08_CC_TRANSMISSION_RELEASE:
|
||||
writeCallSequence(mCallSequence);
|
||||
mCallSequence = null;
|
||||
break;
|
||||
case TYPE_D_SCCH_IN_INFO_4_CALL_IN_PROGRESS_DESTINATION:
|
||||
case TYPE_D_SCCH_OUT_INFO_4_CALL_IN_PROGRESS_DESTINATION:
|
||||
if(layer3 instanceof CallInProgressDestinationInfo4 info4)
|
||||
{
|
||||
getCallSequence().setToIdentifier(info4.getDestination());
|
||||
}
|
||||
break;
|
||||
case TYPE_D_SCCH_IN_INFO_3_CALL_IN_PROGRESS_SOURCE:
|
||||
case TYPE_D_SCCH_OUT_INFO_3_CALL_IN_PROGRESS_SOURCE:
|
||||
if(layer3 instanceof CallInProgressSourceID source)
|
||||
{
|
||||
getCallSequence().setFromIdentifier(source.getSource());
|
||||
}
|
||||
break;
|
||||
case TYPE_D_SCCH_IN_INFO_2_CALL_IN_PROGRESS_DESTINATION:
|
||||
case TYPE_D_SCCH_OUT_INFO_2_CALL_IN_PROGRESS_DESTINATION:
|
||||
if(layer3 instanceof CallInProgressDestinationInfo2 info2)
|
||||
{
|
||||
getCallSequence().setToIdentifier(info2.getDestination());
|
||||
}
|
||||
break;
|
||||
case TYPE_D_SCCH_IN_INFO_1_CALL_INFO:
|
||||
case TYPE_D_SCCH_OUT_INFO_1_CALL_INFO:
|
||||
if(layer3 instanceof CallInfo ci && ci.getEncryptionKey().isEncrypted())
|
||||
{
|
||||
getCallSequence().setEncrypted(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process audio messages
|
||||
*/
|
||||
private void process(Audio audio)
|
||||
{
|
||||
List<byte[]> voiceFrames = audio.getAudioFrames();
|
||||
long timestamp = audio.getTimestamp();
|
||||
|
||||
for(int frame = 0; frame < voiceFrames.size(); frame++)
|
||||
{
|
||||
if(frame == 0)
|
||||
{
|
||||
BinaryMessage frameBits = BinaryMessage.from(voiceFrames.get(frame));
|
||||
VoiceFrame voiceFrame = new VoiceFrame(timestamp, frameBits.toHexString());
|
||||
|
||||
if(audio.hasSACCHFragment())
|
||||
{
|
||||
voiceFrame.setTag(audio.getSACCHFragment().getStructure().toString());
|
||||
}
|
||||
|
||||
getCallSequence().add(voiceFrame);
|
||||
//Voice frames are 20 milliseconds each, so we increment the timestamp by 20 for each one
|
||||
timestamp += 20;
|
||||
}
|
||||
else
|
||||
{
|
||||
BinaryMessage frameBits = BinaryMessage.from(voiceFrames.get(frame));
|
||||
getCallSequence().addVoiceFrame(timestamp, frameBits.toHexString());
|
||||
//Voice frames are 20 milliseconds each, so we increment the timestamp by 20 for each one
|
||||
timestamp += 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.channel;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
|
||||
/**
|
||||
* NXDN Channel to Frequency mapping.
|
||||
*/
|
||||
public class ChannelFrequency
|
||||
{
|
||||
private int mChannel;
|
||||
private long mDownlink;
|
||||
private long mUplink;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param number for the channel (1 - 2048)
|
||||
* @param down link frequency in Hertz
|
||||
* @param up link frequency in Hertz
|
||||
*/
|
||||
public ChannelFrequency(int number, long down, long up)
|
||||
{
|
||||
mChannel = number;
|
||||
mDownlink = down;
|
||||
mUplink = up;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty constructor for Jackson deserialization
|
||||
*/
|
||||
public ChannelFrequency()
|
||||
{
|
||||
//Empty Jackson constructor
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel number
|
||||
* @return channel number 1-2048
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "channel")
|
||||
public int getChannel()
|
||||
{
|
||||
return mChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the channel number
|
||||
* @param channel in range 1 to 2048
|
||||
*/
|
||||
public void setChannel(int channel)
|
||||
{
|
||||
mChannel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downlink frequency
|
||||
* @return frequency in Hertz
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "downlink")
|
||||
public long getDownlink()
|
||||
{
|
||||
return mDownlink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the downlink frequency
|
||||
* @param downlink frequency in Hertz
|
||||
*/
|
||||
public void setDownlink(long downlink)
|
||||
{
|
||||
mDownlink = downlink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uplink frequency
|
||||
* @return frequency in Hertz
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "uplink")
|
||||
public long getUplink()
|
||||
{
|
||||
return mUplink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the uplink frequency
|
||||
* @param uplink frequency in Hertz
|
||||
*/
|
||||
public void setUplink(long uplink)
|
||||
{
|
||||
mUplink = uplink;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.channel;
|
||||
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ChannelAccessInformation;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Channel access and channel map information
|
||||
*/
|
||||
public class ChannelInformation
|
||||
{
|
||||
private final ChannelAccessInformation mChannelAccessInformation;
|
||||
private final List<ChannelFrequency> mChannelFrequencies;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param access info
|
||||
* @param map of optional channels/frequencies
|
||||
*/
|
||||
public ChannelInformation(ChannelAccessInformation access, List<ChannelFrequency> channels)
|
||||
{
|
||||
mChannelAccessInformation = access;
|
||||
mChannelFrequencies = channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel access info from the control channel
|
||||
* @return access info
|
||||
*/
|
||||
public ChannelAccessInformation getChannelAccessInformation()
|
||||
{
|
||||
return mChannelAccessInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel frequencies
|
||||
* @return list
|
||||
*/
|
||||
public List<ChannelFrequency> getChannelFrequencies()
|
||||
{
|
||||
return mChannelFrequencies;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.channel;
|
||||
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.IChannelInformationReceiver;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* Base NXDN channel
|
||||
*/
|
||||
public abstract class NXDNChannel implements IChannelDescriptor, IChannelInformationReceiver
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public NXDNChannel()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this channel has a non-zero downlink frequency.
|
||||
*/
|
||||
public boolean isValid()
|
||||
{
|
||||
return getDownlinkFrequency() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return Protocol.NXDN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTimeslotCount()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTDMAChannel()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("DN:").append(getDownlinkFrequency() / 1E6D);
|
||||
|
||||
long up = getUplinkFrequency();
|
||||
|
||||
if(up > 0)
|
||||
{
|
||||
sb.append(" UP:").append(up / 1E6D);
|
||||
}
|
||||
|
||||
sb.append(" MHZ");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.channel;
|
||||
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.Bandwidth;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ChannelAccessInformation;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* NXDN channel that uses Direct Frequency Assignment.
|
||||
*/
|
||||
public class NXDNChannelDFA extends NXDNChannel
|
||||
{
|
||||
private ChannelAccessInformation mChannelAccessInformation;
|
||||
private final int mOutboundChannelNumber;
|
||||
private final int mInboundChannelNumber;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param outboundChannelNumber from message
|
||||
* @param inboundChannelNumber from message
|
||||
* @param bandwidth from message
|
||||
*/
|
||||
public NXDNChannelDFA(int outboundChannelNumber, int inboundChannelNumber, Bandwidth bandwidth)
|
||||
{
|
||||
mOutboundChannelNumber = outboundChannelNumber;
|
||||
mInboundChannelNumber = inboundChannelNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ChannelAccessInformation channelAccessInformation, Map<Integer, ChannelFrequency> channelFrequencyMap)
|
||||
{
|
||||
mChannelAccessInformation = channelAccessInformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDownlinkFrequency()
|
||||
{
|
||||
if(mChannelAccessInformation != null)
|
||||
{
|
||||
return mChannelAccessInformation.getFrequency(mOutboundChannelNumber);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mChannelAccessInformation != null)
|
||||
{
|
||||
return mChannelAccessInformation.getFrequency(mInboundChannelNumber);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.channel;
|
||||
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ChannelAccessInformation;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Fake channel that uses a talkgroup value for tracking call detection events by frequency, where the talkgroup value
|
||||
* represents the tracked frequency.
|
||||
*/
|
||||
public class NXDNChannelFake extends NXDNChannel
|
||||
{
|
||||
private int mTalkgroup;
|
||||
|
||||
public NXDNChannelFake(int talkgroup)
|
||||
{
|
||||
mTalkgroup = talkgroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always return false to indicate this a a non-value, fake channel.
|
||||
*/
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDownlinkFrequency()
|
||||
{
|
||||
return mTalkgroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ChannelAccessInformation channelAccessInformation, Map<Integer, ChannelFrequency> channelFrequencyMap)
|
||||
{
|
||||
//No-op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.channel;
|
||||
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ChannelAccessInformation;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* NXDN channel that uses a lookup table for the frequency values.
|
||||
*/
|
||||
public class NXDNChannelLookup extends NXDNChannel
|
||||
{
|
||||
private ChannelFrequency mChannelFrequency;
|
||||
private final int mChannelNumber;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param channelNumber for this channel referenced against a global system channel lookup table.
|
||||
*/
|
||||
public NXDNChannelLookup(int channelNumber)
|
||||
{
|
||||
mChannelNumber = channelNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ChannelAccessInformation channelAccessInformation, Map<Integer, ChannelFrequency> channelFrequencyMap)
|
||||
{
|
||||
mChannelFrequency = channelFrequencyMap.get(mChannelNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel number
|
||||
*/
|
||||
public int getChannelNumber()
|
||||
{
|
||||
return mChannelNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDownlinkFrequency()
|
||||
{
|
||||
if(mChannelFrequency != null)
|
||||
{
|
||||
return mChannelFrequency.getDownlink();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mChannelFrequency != null)
|
||||
{
|
||||
return mChannelFrequency.getUplink();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(mChannelFrequency != null)
|
||||
{
|
||||
sb.append("DN:").append(getDownlinkFrequency() / 1E6D);
|
||||
|
||||
long up = getUplinkFrequency();
|
||||
|
||||
if(up > 0)
|
||||
{
|
||||
sb.append(" UP:").append(up / 1E6D);
|
||||
}
|
||||
|
||||
sb.append(" MHZ");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("CHANNEL-MODE [" + mChannelNumber + "] IS MISSING CHANNEL:FREQUENCY MAPPING");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.channel;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.util.Callback;
|
||||
|
||||
/**
|
||||
* JavaFX wrapper for the channel frequency object to make the fields observable.
|
||||
*/
|
||||
public class ObservableChannelFrequency
|
||||
{
|
||||
private IntegerProperty mChannel = new SimpleIntegerProperty();
|
||||
private LongProperty mDownlink = new SimpleLongProperty();
|
||||
private LongProperty mUplink = new SimpleLongProperty();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param channelFrequency object with configuration details
|
||||
*/
|
||||
public ObservableChannelFrequency(ChannelFrequency channelFrequency)
|
||||
{
|
||||
mChannel.set(channelFrequency.getChannel());
|
||||
mDownlink.set(channelFrequency.getDownlink());
|
||||
mUplink.set(channelFrequency.getUplink());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance with no values.
|
||||
*/
|
||||
public ObservableChannelFrequency()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ChannelFrequency instance from this observable wrapper.
|
||||
*/
|
||||
public ChannelFrequency getChannel()
|
||||
{
|
||||
return new ChannelFrequency(mChannel.get(), mDownlink.get(), mUplink.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel number
|
||||
*/
|
||||
public IntegerProperty channelProperty()
|
||||
{
|
||||
return mChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downlink frequency in Hertz
|
||||
* @return
|
||||
*/
|
||||
public LongProperty downlinkProperty()
|
||||
{
|
||||
return mDownlink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uplink frequency in Hertz
|
||||
*/
|
||||
public LongProperty uplinkProperty()
|
||||
{
|
||||
return mUplink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an observable property extractor for use with observable lists to detect changes internal to this object.
|
||||
*/
|
||||
public static Callback<ObservableChannelFrequency, Observable[]> extractor()
|
||||
{
|
||||
return (ObservableChannelFrequency ocf) -> new Observable[] {ocf.channelProperty(), ocf.downlinkProperty(),
|
||||
ocf.uplinkProperty()};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.identifier;
|
||||
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.string.StringIdentifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.LocationCategory;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* Indicates the network location category
|
||||
*/
|
||||
public class NXDNCategory extends StringIdentifier
|
||||
{
|
||||
public NXDNCategory(String value, IdentifierClass identifierClass, Form form, Role role)
|
||||
{
|
||||
super(value, identifierClass, form, role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return Protocol.NXDN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance
|
||||
* @param category for the system/site values
|
||||
* @return category identifier
|
||||
*/
|
||||
public static NXDNCategory create(LocationCategory category)
|
||||
{
|
||||
return new NXDNCategory(category.toString(), IdentifierClass.NETWORK, Form.CATEGORY, Role.BROADCAST);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.identifier;
|
||||
|
||||
import io.github.dsheirer.identifier.encryption.EncryptionKey;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.CipherType;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Encryption;
|
||||
|
||||
/**
|
||||
* NXDN Encryption Parameters
|
||||
*/
|
||||
public class NXDNEncryptionKey extends EncryptionKey
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param algorithm id
|
||||
* @param keyId value
|
||||
*/
|
||||
public NXDNEncryptionKey(int algorithm, int keyId)
|
||||
{
|
||||
super(algorithm, keyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cipher type for the call.
|
||||
* @return type
|
||||
*/
|
||||
public CipherType getCipherType()
|
||||
{
|
||||
return CipherType.fromValue(getAlgorithm());
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(isEncrypted())
|
||||
{
|
||||
sb.append(getCipherType());
|
||||
sb.append(" KEY:").append(getKey());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("UNENCRYPTED");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncrypted()
|
||||
{
|
||||
return getCipherType() != CipherType.UNENCRYPTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new NXDN encryption algorithm
|
||||
*/
|
||||
public static NXDNEncryptionKey create(int algorithm, int keyId)
|
||||
{
|
||||
return new NXDNEncryptionKey(algorithm, keyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new NXDN encryption algorithm
|
||||
*/
|
||||
public static NXDNEncryptionKey create(Encryption encryption, int keyId)
|
||||
{
|
||||
return create(encryption.getValue(), keyId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.github.dsheirer.module.decode.nxdn.identifier;
|
||||
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
|
||||
/**
|
||||
* Inter-system, ISSI or Roaming NXDN radio identifier.
|
||||
*/
|
||||
public class NXDNFullyQualifiedRadioIdentifier extends NXDNRadioIdentifier
|
||||
{
|
||||
private int mSystem;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param system that is home for the radio
|
||||
* @param radio identifier
|
||||
* @param role for the identifier
|
||||
*/
|
||||
public NXDNFullyQualifiedRadioIdentifier(int system, int radio, Role role)
|
||||
{
|
||||
super(radio, role);
|
||||
mSystem = system;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mSystem + "." + super.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fully qualified radio identifier with a role of TO
|
||||
* @param system that is home for the radio
|
||||
* @param radio identifier
|
||||
* @return identifier
|
||||
*/
|
||||
public static NXDNFullyQualifiedRadioIdentifier createTo(int system, int radio)
|
||||
{
|
||||
return new NXDNFullyQualifiedRadioIdentifier(system, radio, Role.TO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fully qualified radio identifier with a role of FRON
|
||||
* @param system that is home for the radio
|
||||
* @param radio identifier
|
||||
* @return identifier
|
||||
*/
|
||||
public static NXDNFullyQualifiedRadioIdentifier createFrom(int system, int radio)
|
||||
{
|
||||
return new NXDNFullyQualifiedRadioIdentifier(system, radio, Role.FROM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fully qualified radio identifier with a role of ANY
|
||||
* @param system that is home for the radio
|
||||
* @param radio identifier
|
||||
* @return identifier
|
||||
*/
|
||||
public static NXDNFullyQualifiedRadioIdentifier createAny(int system, int radio)
|
||||
{
|
||||
return new NXDNFullyQualifiedRadioIdentifier(system, radio, Role.ANY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.github.dsheirer.module.decode.nxdn.identifier;
|
||||
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
|
||||
public class NXDNFullyQualifiedTalkgroupIdentifier extends NXDNTalkgroupIdentifier
|
||||
{
|
||||
private int mSystem;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param value for the talkgroup
|
||||
* @param role for the talkgroup
|
||||
*/
|
||||
public NXDNFullyQualifiedTalkgroupIdentifier(int system, int value, Role role)
|
||||
{
|
||||
super(value, role);
|
||||
mSystem = system;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance with a role of TO
|
||||
* @param system that is home for the talkgroup
|
||||
* @param talkgroup value
|
||||
* @return TO instance
|
||||
*/
|
||||
public static NXDNFullyQualifiedTalkgroupIdentifier createTo(int system, int talkgroup)
|
||||
{
|
||||
return new NXDNFullyQualifiedTalkgroupIdentifier(system, talkgroup, Role.TO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance with a role of ANY
|
||||
* @param system that is home for the talkgroup
|
||||
* @param talkgroup value
|
||||
* @return ANY instance
|
||||
*/
|
||||
public static NXDNFullyQualifiedTalkgroupIdentifier createAny(int system, int talkgroup)
|
||||
{
|
||||
return new NXDNFullyQualifiedTalkgroupIdentifier(system, talkgroup, Role.ANY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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.nxdn.identifier;
|
||||
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* NXDN Radio Access Number (RAN)
|
||||
*/
|
||||
public class NXDNRadioAccessNumber extends IntegerIdentifier
|
||||
{
|
||||
public NXDNRadioAccessNumber(int ran)
|
||||
{
|
||||
super(ran, IdentifierClass.NETWORK, Form.RAN, Role.BROADCAST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return Protocol.NXDN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the identifier
|
||||
* @param ran value
|
||||
* @return identifier
|
||||
*/
|
||||
public static NXDNRadioAccessNumber create(int ran)
|
||||
{
|
||||
return new NXDNRadioAccessNumber(ran);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.identifier;
|
||||
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* NXDN Radio Identifier
|
||||
*/
|
||||
public class NXDNRadioIdentifier extends RadioIdentifier
|
||||
{
|
||||
private static final DecimalFormat REPEATER_FORMAT = new DecimalFormat("00");
|
||||
private static final DecimalFormat RADIO_FORMAT = new DecimalFormat("0000");
|
||||
private boolean mTypeD = false;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param value for the ID
|
||||
* @param role played by the ID
|
||||
*/
|
||||
public NXDNRadioIdentifier(Integer value, Role role)
|
||||
{
|
||||
super(value, role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return Protocol.NXDN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the identifier is for a Type-D system.
|
||||
*/
|
||||
public boolean isTypeD()
|
||||
{
|
||||
return mTypeD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Home repeater value for a Type-D identifier.
|
||||
*/
|
||||
public int getTypeDHomeRepeater()
|
||||
{
|
||||
return (getValue() >> 11) & 0x1F;
|
||||
}
|
||||
|
||||
/**
|
||||
* Radio ID for a Type-D identifier.
|
||||
* @return radio ID.
|
||||
*/
|
||||
public int getTypeDRadio()
|
||||
{
|
||||
return getValue() & 0x7FF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Type-D flag for this ID.
|
||||
* @param typeD true to indicate that this is a Type-D identifier.
|
||||
*/
|
||||
public void setTypeD(boolean typeD)
|
||||
{
|
||||
mTypeD = typeD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
if(isTypeD())
|
||||
{
|
||||
return REPEATER_FORMAT.format(getTypeDHomeRepeater()) + "-" + RADIO_FORMAT.format(getTypeDRadio());
|
||||
}
|
||||
return switch(getValue())
|
||||
{
|
||||
case 0xFFF0 -> "0xFFF0 TRUNKING CONTROLLER";
|
||||
case 0xFFF1 -> "0xFFF1 PSTN ID";
|
||||
case 0xFFF2 -> "0xFFF2 SPECIAL ID";
|
||||
case 0xFFF3 -> "0xFFF3 SPECIAL ID";
|
||||
case 0xFFF4 -> "0xFFF4 SPECIAL ID";
|
||||
case 0xFFF5 -> "0xFFF5 CONVENTIONAL PSTN ID";
|
||||
case 0xFFFF -> "0xFFFF ALL UNITS";
|
||||
default -> super.toString();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an NXDN radio identifier with the FROM role.
|
||||
* @param value of the radio ID
|
||||
* @return identifier
|
||||
*/
|
||||
public static NXDNRadioIdentifier createFrom(int value)
|
||||
{
|
||||
return new NXDNRadioIdentifier(value, Role.FROM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an NXDN radio identifier with the TO role.
|
||||
* @param value of the radio ID
|
||||
* @return identifier
|
||||
*/
|
||||
public static NXDNRadioIdentifier createTo(int value)
|
||||
{
|
||||
return new NXDNRadioIdentifier(value, Role.TO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an NXDN radio identifier with the ANY role.
|
||||
* @param value of the radio ID
|
||||
* @return identifier
|
||||
*/
|
||||
public static NXDNRadioIdentifier createAny(int value)
|
||||
{
|
||||
return new NXDNRadioIdentifier(value, Role.ANY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Type-D radio identifier with the FROM role.
|
||||
* @param radio value that combines the home repeater and the radio
|
||||
* @return identifier
|
||||
*/
|
||||
public static NXDNRadioIdentifier createTypeDFrom(int radio)
|
||||
{
|
||||
NXDNRadioIdentifier identifier = new NXDNRadioIdentifier(radio, Role.FROM);
|
||||
identifier.setTypeD(true);
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Type-D radio identifier with the TO role.
|
||||
* @param radio value that combines the home repeater and the radio
|
||||
* @return identifier
|
||||
*/
|
||||
public static NXDNRadioIdentifier createTypeDTo(int radio)
|
||||
{
|
||||
NXDNRadioIdentifier identifier = new NXDNRadioIdentifier(radio, Role.TO);
|
||||
identifier.setTypeD(true);
|
||||
return identifier;
|
||||
}
|
||||
/**
|
||||
* Creates a Type-D radio identifier with the ANY role.
|
||||
* @param radio value that combines the home repeater and the radio
|
||||
* @return identifier
|
||||
*/
|
||||
public static NXDNRadioIdentifier createTypeDAny(int radio)
|
||||
{
|
||||
NXDNRadioIdentifier identifier = new NXDNRadioIdentifier(radio, Role.ANY);
|
||||
identifier.setTypeD(true);
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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.nxdn.identifier;
|
||||
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
public class NXDNSite extends IntegerIdentifier
|
||||
{
|
||||
public NXDNSite(int value)
|
||||
{
|
||||
super(value, IdentifierClass.NETWORK, Form.SITE, Role.BROADCAST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return Protocol.NXDN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new site identifier
|
||||
*/
|
||||
public static NXDNSite create(int site)
|
||||
{
|
||||
return new NXDNSite(site);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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.nxdn.identifier;
|
||||
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* NXDN trunking system ID
|
||||
*/
|
||||
public class NXDNSystem extends IntegerIdentifier
|
||||
{
|
||||
public NXDNSystem(int value)
|
||||
{
|
||||
super(value, IdentifierClass.NETWORK, Form.SYSTEM, Role.BROADCAST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return Protocol.NXDN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new system identifier
|
||||
*/
|
||||
public static NXDNSystem create(int system)
|
||||
{
|
||||
return new NXDNSystem(system);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.identifier;
|
||||
|
||||
import io.github.dsheirer.identifier.alias.TalkerAliasIdentifier;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* NXDN talker alias identifier
|
||||
*/
|
||||
public class NXDNTalkerAliasIdentifier extends TalkerAliasIdentifier
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param alias value
|
||||
*/
|
||||
public NXDNTalkerAliasIdentifier(String alias)
|
||||
{
|
||||
super(alias);
|
||||
}
|
||||
@Override
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return Protocol.NXDN;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.identifier;
|
||||
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* NXDN Talkgroup Identifier
|
||||
*/
|
||||
public class NXDNTalkgroupIdentifier extends TalkgroupIdentifier
|
||||
{
|
||||
private static final DecimalFormat REPEATER_FORMAT = new DecimalFormat("00");
|
||||
private static final DecimalFormat TALKGROUP_FORMAT = new DecimalFormat("0000");
|
||||
private boolean mTypeD = false;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param value for the talkgroup
|
||||
* @param role for the talkgroup
|
||||
*/
|
||||
public NXDNTalkgroupIdentifier(Integer value, Role role)
|
||||
{
|
||||
super(value, role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return Protocol.NXDN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the identifier is for a Type-D system.
|
||||
*/
|
||||
public boolean isTypeD()
|
||||
{
|
||||
return mTypeD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Type-D flag for this ID.
|
||||
* @param typeD true to indicate that this is a Type-D identifier.
|
||||
*/
|
||||
public void setTypeD(boolean typeD)
|
||||
{
|
||||
mTypeD = typeD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Home repeater value for a Type-D identifier.
|
||||
*/
|
||||
public int getTypeDHomeRepeater()
|
||||
{
|
||||
return (getValue() >> 11) & 0x1F;
|
||||
}
|
||||
|
||||
/**
|
||||
* Talkgroup ID for a Type-D identifier.
|
||||
* @return radio ID.
|
||||
*/
|
||||
public int getTypeDTalkgroup()
|
||||
{
|
||||
return getValue() & 0x7FF;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
if(isTypeD())
|
||||
{
|
||||
return REPEATER_FORMAT.format(getTypeDHomeRepeater()) + "-" + TALKGROUP_FORMAT.format(getTypeDTalkgroup());
|
||||
}
|
||||
|
||||
return switch(getValue())
|
||||
{
|
||||
case 0x0 -> "0x0000 NULL GROUP";
|
||||
case 0xFFF0 -> "0xFFF0 RESERVED GROUP";
|
||||
case 0xFFFF -> "0xFFFF ALL GROUPS";
|
||||
default -> super.toString();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an NXDN talkgroup identifier with the TO role.
|
||||
* @param value of the talkgroup
|
||||
* @return identifier
|
||||
*/
|
||||
public static NXDNTalkgroupIdentifier createTo(int value)
|
||||
{
|
||||
return new NXDNTalkgroupIdentifier(value, Role.TO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Type-D talkgroup identifier with the TO role.
|
||||
* @param value for the home repeater and talkgroup combined into a 16-bit integer
|
||||
* @return identifier
|
||||
*/
|
||||
public static NXDNTalkgroupIdentifier createTypeDTo(int value)
|
||||
{
|
||||
NXDNTalkgroupIdentifier identifier = new NXDNTalkgroupIdentifier(value, Role.TO);
|
||||
identifier.setTypeD(true);
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer1;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Arrays;
|
||||
import org.apache.commons.math3.stat.descriptive.moment.Variance;
|
||||
|
||||
/**
|
||||
* LMS adaptive equalizer for real-valued C4FM symbols. Trains taps on externally triggered sync detection combined
|
||||
* with dynamic tap updates for a fixed symbol count and then freezes the taps until the error variance between sync
|
||||
* detections exceeds a fixed threshold, whereby a tap retraining is enqueued for the next sync detection.
|
||||
*/
|
||||
public class C4FMEqualizer
|
||||
{
|
||||
private static final DecimalFormat DF = new DecimalFormat("0.0000");
|
||||
//Maximum possible error is PI/4 = 0.78, but we clip it to less than half as the allowable error for tap updates
|
||||
private static final float MAX_SYMBOL_ERROR = 0.3f;
|
||||
private static final double TRAINING_IMPROVEMENT_GOAL_PER_ITERATION = 0.001f;
|
||||
private static final double TRAINING_CONVERGENCE_THRESHOLD = 0.16f;
|
||||
private static final double TRAINING_SKIP_VARIANCE_THRESHOLD = 0.05;
|
||||
private static final int TRAINING_ITERATIONS_MAX = 30;
|
||||
private final Variance mVariance = new Variance();
|
||||
private final float[] mBuffer;
|
||||
private final float[] mReversedSyncSymbols;
|
||||
private final float[] mTaps;
|
||||
private final int mCenterTap;
|
||||
private final int mDelaySymbolCount;
|
||||
private final int mTapUpdateSymbolCount;
|
||||
private final int mTrainingSkipCountThreshold;
|
||||
private boolean mEnabled = false;
|
||||
private boolean mSyncDetected = false;
|
||||
private int mSymbolsUntilTraining = 0;
|
||||
private int mTapUpdateSymbolsRemaining = 0;
|
||||
private int mSkippedTrainingCount = 0;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param syncSymbols sync pattern that will be used to train the equalizer on sync detections
|
||||
* @param symbolLength for the equalizer - will be made to odd length, increasing by one if necessary (5-11)
|
||||
* @param tapUpdateSymbolCount symbol count following sync detection and training to continue tap updates.
|
||||
* @param maxSkipTrainingCount maximum number of consecutive sync detections and skipped trainings that can occur without retraining.
|
||||
*/
|
||||
public C4FMEqualizer(float[] syncSymbols, int symbolLength, int tapUpdateSymbolCount, int maxSkipTrainingCount)
|
||||
{
|
||||
//Store the sync pattern in reverse order
|
||||
mReversedSyncSymbols = new float[syncSymbols.length];
|
||||
for(int x = 0; x < syncSymbols.length; x++)
|
||||
{
|
||||
mReversedSyncSymbols[syncSymbols.length - x - 1] = syncSymbols[x];
|
||||
}
|
||||
|
||||
//Make the symbol count odd length for equal delay on either side of the center tap
|
||||
symbolLength += (symbolLength % 2 == 0) ? 1 : 0;
|
||||
mCenterTap = symbolLength; //This works since we're going to double the buffer length
|
||||
mDelaySymbolCount = symbolLength / 2; //Delay until new symbol is aligned to center tap
|
||||
mTaps = new float[symbolLength * 2];
|
||||
mTaps[mCenterTap] = 1.0f;
|
||||
int bufferLength = mReversedSyncSymbols.length * 2 + mTaps.length - 2;
|
||||
mBuffer = new float[bufferLength];
|
||||
mTapUpdateSymbolCount = tapUpdateSymbolCount;
|
||||
mTrainingSkipCountThreshold = maxSkipTrainingCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of the current tap values
|
||||
*/
|
||||
public String getTapsDescription()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<html>EQ Taps<br>");
|
||||
for(int i = 0; i < mTaps.length; i++)
|
||||
{
|
||||
sb.append(i).append(": ").append(DF.format(mTaps[i])).append("<br>");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Symbol processing delay.
|
||||
* @return processing delay in symbols.
|
||||
*/
|
||||
public int getDelaySymbolCount()
|
||||
{
|
||||
return mDelaySymbolCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of the equalizer in symbols. Note: the internal tap count is twice as long since this
|
||||
* is a fractional (x2) equalizer.
|
||||
* @return length in symbols.
|
||||
*/
|
||||
public int getLength()
|
||||
{
|
||||
return mTaps.length / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the equalizer when a valid sync pattern is detected
|
||||
*/
|
||||
public void enable()
|
||||
{
|
||||
mEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the enabled state.
|
||||
* @return enabled state
|
||||
*/
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the equalizer when the signal is lost so that noise doesn't contaminate the equalizer taps.
|
||||
*/
|
||||
public void disable()
|
||||
{
|
||||
mEnabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the equalizer that a sync pattern is detected and queues a training sequence once a couple more
|
||||
* symbols arrive once the equalizer delay line has aligned to output the sync pattern.
|
||||
*/
|
||||
public void syncDetected()
|
||||
{
|
||||
mSyncDetected = true;
|
||||
mSymbolsUntilTraining = mDelaySymbolCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the sync patter is lost and the equalizer should reset.
|
||||
*/
|
||||
public void syncLost()
|
||||
{
|
||||
reset();
|
||||
disable();
|
||||
}
|
||||
|
||||
/*
|
||||
* Resets the equalizer taps
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
Arrays.fill(mTaps, 0f);
|
||||
mTaps[mCenterTap] = 1.0f;
|
||||
mVariance.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the samples and produce a delayed, equalized soft symbol ready for symbol decision. Updates the
|
||||
* equalizer taps to correct for the error in the equalized sample relative to the hard symbol decision.
|
||||
*
|
||||
* @param fractional sample that falls halfway between symbols
|
||||
* @param symbol to equalize
|
||||
* @return equalized symbol.
|
||||
*/
|
||||
public float process(float fractional, float symbol)
|
||||
{
|
||||
//Shift buffer samples to the right two places and add new sample and symbol.
|
||||
System.arraycopy(mBuffer, 0, mBuffer, 2, mBuffer.length - 2);
|
||||
mBuffer[0] = fractional;
|
||||
mBuffer[1] = symbol;
|
||||
|
||||
//Since there's a buffer delay, count down from sync detection until the pattern is aligned with the buffer
|
||||
// contents and we can start training
|
||||
if(mSyncDetected)
|
||||
{
|
||||
if(--mSymbolsUntilTraining <= 0)
|
||||
{
|
||||
train();
|
||||
}
|
||||
}
|
||||
|
||||
float equalized = 0f;
|
||||
|
||||
if(mEnabled)
|
||||
{
|
||||
int tapIndex;
|
||||
|
||||
for(tapIndex = 0; tapIndex < mTaps.length; tapIndex++)
|
||||
{
|
||||
equalized += mTaps[tapIndex] * mBuffer[tapIndex];
|
||||
}
|
||||
|
||||
if(mTapUpdateSymbolsRemaining > 0)
|
||||
{
|
||||
mTapUpdateSymbolsRemaining--;
|
||||
float error = Dibit.getError(equalized);
|
||||
error = Math.clamp(error, -MAX_SYMBOL_ERROR, MAX_SYMBOL_ERROR);
|
||||
|
||||
//Step size (mu) in range 0.0001 to 0.01
|
||||
float stepSize = 0.01f;
|
||||
|
||||
for(tapIndex = 0; tapIndex < mTaps.length; tapIndex++)
|
||||
{
|
||||
if(tapIndex != mCenterTap)
|
||||
{
|
||||
mTaps[tapIndex] += stepSize * error * mBuffer[tapIndex];
|
||||
// mTaps[tapIndex] += stepSize * error * mBuffer[tapIndex] * TAP_LEAKAGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Track the residual error that remains after equalization
|
||||
mVariance.increment(Dibit.getError(equalized));
|
||||
}
|
||||
else
|
||||
{
|
||||
//Return the uncorrected, delayed symbol when we're disabled
|
||||
equalized = mBuffer[mCenterTap];
|
||||
}
|
||||
|
||||
return equalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trains the equalizer against the buffered detected sync pattern. Trains against the sync pattern sequence
|
||||
* multiple times while the per-iteration improvement continues to become smaller and stops training once the
|
||||
* improvement falls below a threshold goal or exceeds a maximum iterations. If the training converges to
|
||||
* below a threshold, we snapshot and use the trained taps, otherwise fallback to the previous training snapshot.
|
||||
*/
|
||||
private void train()
|
||||
{
|
||||
mSyncDetected = false;
|
||||
|
||||
//Skip retrain when we're still doing dynamic tap adjustments following the previous training session.
|
||||
if(mTapUpdateSymbolsRemaining > 0)
|
||||
{
|
||||
mVariance.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
//Ensure we've collected enough variance samples in the buffer and we have sufficient variance
|
||||
if(mVariance.getN() <= mDelaySymbolCount || mVariance.getResult() > TRAINING_SKIP_VARIANCE_THRESHOLD ||
|
||||
++mSkippedTrainingCount > mTrainingSkipCountThreshold)
|
||||
{
|
||||
mSkippedTrainingCount = 0;
|
||||
//If the current variance tracked since the last training session using dynamic tap adjustments at each
|
||||
// symbol is excessive, fallback to the most recent training snapshot as a better starting point
|
||||
if(mVariance.getResult() > 0.225)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
float accumulator, error;
|
||||
double improvement, previousVariance;
|
||||
int symbolIndex, index, trainingIterationCount = 0;
|
||||
Variance variance = new Variance();
|
||||
|
||||
//Step size (mu) in range 0.0001 to 0.01
|
||||
float stepSize = 0.005f;
|
||||
|
||||
do
|
||||
{
|
||||
previousVariance = variance.getN() == 0 ? 20.0 : variance.getResult();
|
||||
variance.clear();
|
||||
|
||||
|
||||
for(symbolIndex = 0; symbolIndex < mReversedSyncSymbols.length; symbolIndex++)
|
||||
{
|
||||
accumulator = 0.0f;
|
||||
for(index = 0; index < mTaps.length; index++)
|
||||
{
|
||||
accumulator += mTaps[index] * mBuffer[index + (2 * symbolIndex)];
|
||||
}
|
||||
|
||||
error = mReversedSyncSymbols[symbolIndex] - accumulator;
|
||||
variance.increment(error);
|
||||
|
||||
for(index = 0; index < mTaps.length; index++)
|
||||
{
|
||||
if(index != mCenterTap)
|
||||
{
|
||||
mTaps[index] += error * stepSize * mBuffer[index + (2 * symbolIndex)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trainingIterationCount++;
|
||||
improvement = previousVariance - variance.getResult();
|
||||
}
|
||||
while(trainingIterationCount < TRAINING_ITERATIONS_MAX && improvement > TRAINING_IMPROVEMENT_GOAL_PER_ITERATION);
|
||||
|
||||
// System.out.println("Training Iterations:" + trainingIterationCount + " End Variance:" + DF.format(variance.getResult()));
|
||||
|
||||
//If we failed to converge, reset the taps to default and use blind training
|
||||
if(variance.getResult() > TRAINING_CONVERGENCE_THRESHOLD)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
mTapUpdateSymbolsRemaining = mTapUpdateSymbolCount;
|
||||
}
|
||||
|
||||
mVariance.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer1;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.FragmentedIntField;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.module.decode.nxdn.NXDNMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.Interleaver;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICHTracker;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.RFChannel;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.SACCHFragment;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.Scrambler;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNLayer3Message;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageFactory;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.Audio;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.coding.Convolution;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.coding.NXDNCRC;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.coding.PunctureProvider;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.coding.PunctureProviderCACAndFACCH2_UDCH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.coding.PunctureProviderFACCH1_UDCH2_FACCH3;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.coding.PunctureProviderLongCAC;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.coding.PunctureProviderNone;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.coding.PunctureProviderSACCHAndSCCH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.AudioCodec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* NXDN layer 1 frame.
|
||||
*/
|
||||
public class Frame
|
||||
{
|
||||
private static final BinaryMessage SCRAMBLE_SEQUENCE_LICH = Scrambler.generate(16);
|
||||
private static final BinaryMessage SCRAMBLE_SEQUENCE_FULL = Scrambler.generate(364);
|
||||
private static final BinaryMessage SCRAMBLE_SEQUENCE_CONTROL_INBOUND = Scrambler.generate(268);
|
||||
private static final BinaryMessage SCRAMBLE_SEQUENCE_CONTROL_OUTBOUND = Scrambler.generate(340);
|
||||
private static final Interleaver INTERLEAVER_16_9 = new Interleaver(16, 9);
|
||||
private static final Interleaver INTERLEAVER_12_29 = new Interleaver(12, 29);
|
||||
private static final Interleaver INTERLEAVER_12_25 = new Interleaver(12, 25);
|
||||
private static final Interleaver INTERLEAVER_12_21 = new Interleaver(12, 21);
|
||||
private static final Interleaver INTERLEAVER_12_5 = new Interleaver(12, 5);
|
||||
private static final PunctureProvider PUNCTURE_PROVIDER_CAC_FACCH2_UDCH = new PunctureProviderCACAndFACCH2_UDCH();
|
||||
private static final PunctureProvider PUNCTURE_PROVIDER_FACCH1_FACCH3_UDCH2 = new PunctureProviderFACCH1_UDCH2_FACCH3();
|
||||
private static final PunctureProvider PUNCTURE_PROVIDER_LONG_CAC = new PunctureProviderLongCAC();
|
||||
private static final PunctureProvider PUNCTURE_PROVIDER_NONE = new PunctureProviderNone();
|
||||
private static final PunctureProvider PUNCTURE_PROVIDER_SACCH_SCCH = new PunctureProviderSACCHAndSCCH();
|
||||
private static final IntField STRUCTURE = IntField.length2(0);
|
||||
private static final IntField RADIO_ACCESS_NUMBER = IntField.length6(2);
|
||||
private static final FragmentedIntField LICH_FIELD = FragmentedIntField.of(0, 2, 4, 6, 8, 10, 12);
|
||||
private static final FragmentedIntField LICH_PARITY_FIELD = FragmentedIntField.of(0, 2, 4, 6);
|
||||
private static final int LICH_EVEN_PARITY_CHECK_BIT = 14;
|
||||
private static final int RAN_TYPE_D = 0;
|
||||
|
||||
static
|
||||
{
|
||||
//Clear the LICH bits in the complete scramble sequences so that we don't double-scramble the bits.
|
||||
SCRAMBLE_SEQUENCE_FULL.xor(SCRAMBLE_SEQUENCE_LICH);
|
||||
SCRAMBLE_SEQUENCE_CONTROL_INBOUND.xor(SCRAMBLE_SEQUENCE_LICH);
|
||||
SCRAMBLE_SEQUENCE_CONTROL_OUTBOUND.xor(SCRAMBLE_SEQUENCE_LICH);
|
||||
}
|
||||
|
||||
private LICH mLICH;
|
||||
private List<NXDNMessage> mMessages = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructs a frame. Note: channel and direction are used as fallback values when we can't exactly match the
|
||||
* LICH value for this frame due to decoding errors.
|
||||
* @param message with frame bits
|
||||
* @param timestamp for the frame.
|
||||
* @param tracker for accessing tracked RF channel type and direction.
|
||||
*/
|
||||
public Frame(CorrectedBinaryMessage message, long timestamp, LICHTracker tracker)
|
||||
{
|
||||
message.xor(SCRAMBLE_SEQUENCE_LICH);
|
||||
boolean lichParityValid = checkLICHParity(message);
|
||||
int lichValue = message.getInt(LICH_FIELD);
|
||||
mLICH = LICH.fromValue(lichValue, tracker.isTypeD());
|
||||
|
||||
//If failed parity check or UNKNOWN LICH, force the highest occurring channel/direction from the tracker
|
||||
if((!lichParityValid && tracker.getChannel() != RFChannel.UNKNOWN) || mLICH == LICH.UNKNOWN)
|
||||
{
|
||||
mLICH = LICH.fromValue(lichValue, tracker.getChannel(), tracker.getDirection());
|
||||
}
|
||||
else
|
||||
{
|
||||
tracker.track(mLICH);
|
||||
}
|
||||
|
||||
//Descramble the message according to the RF Channel Type and repeater direction. Control channel uses the
|
||||
//full scramble sequence truncated based on repeater direction. All others use a full scramble sequence.
|
||||
//RDCH, RTCH and RTCH2
|
||||
if(mLICH.getRFChannel() == RFChannel.RCCH)
|
||||
{
|
||||
if(mLICH.isOutbound())
|
||||
{
|
||||
message.xor(SCRAMBLE_SEQUENCE_CONTROL_OUTBOUND);
|
||||
CorrectedBinaryMessage payload = decodeCAC(message);
|
||||
int ran = payload.getInt(RADIO_ACCESS_NUMBER);
|
||||
CorrectedBinaryMessage cac = payload.getSubMessage(8, 152);
|
||||
cac.setCorrectedBitCount(payload.getCorrectedBitCount());
|
||||
NXDNMessageType type = NXDNMessageType.getControl(cac, mLICH);
|
||||
NXDNLayer3Message layer3 = NXDNMessageFactory.get(type, cac, timestamp, ran, mLICH);
|
||||
layer3.setValid(NXDNCRC.checkCAC(payload));
|
||||
mMessages.add(layer3);
|
||||
}
|
||||
else
|
||||
{
|
||||
message.xor(SCRAMBLE_SEQUENCE_CONTROL_INBOUND);
|
||||
CorrectedBinaryMessage payload = mLICH.isLongCAC() ? decodeLongCAC(message) : decodeShortCAC(message);
|
||||
int ran = payload.getInt(RADIO_ACCESS_NUMBER);
|
||||
CorrectedBinaryMessage cac = mLICH.isLongCAC() ? payload.getSubMessage(8, 136) : payload.getSubMessage(8, 104);
|
||||
cac.setCorrectedBitCount(payload.getCorrectedBitCount());
|
||||
NXDNMessageType type = NXDNMessageType.getControl(cac, mLICH);
|
||||
NXDNLayer3Message layer3 = NXDNMessageFactory.get(type, cac, timestamp, ran, mLICH);
|
||||
layer3.setValid(mLICH.isLongCAC() ? NXDNCRC.checkLongCAC(payload) : NXDNCRC.checkShortCAC(payload));
|
||||
mMessages.add(layer3);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
message.xor(SCRAMBLE_SEQUENCE_FULL);
|
||||
|
||||
if(mLICH.hasSACCH()) //SACCH + Voice/FACCH frames
|
||||
{
|
||||
CorrectedBinaryMessage sacch = decodeSACCH(message);
|
||||
SACCHFragment sacchFragment = new SACCHFragment(sacch, timestamp, mLICH);
|
||||
sacchFragment.setValid(NXDNCRC.checkSACCH(sacch));
|
||||
mMessages.add(sacchFragment);
|
||||
|
||||
if(mLICH.hasAudio())
|
||||
{
|
||||
List<byte[]> frames = new ArrayList<>();
|
||||
|
||||
if(mLICH.isVoiceFirst())
|
||||
{
|
||||
frames.add(message.getSubMessage(76, 148).toByteArray());
|
||||
frames.add(message.getSubMessage(148, 220).toByteArray());
|
||||
}
|
||||
|
||||
if(mLICH.isVoiceSecond())
|
||||
{
|
||||
frames.add(message.getSubMessage(220, 292).toByteArray());
|
||||
frames.add(message.getSubMessage(292, 364).toByteArray());
|
||||
}
|
||||
|
||||
Audio audio = new Audio(AudioCodec.HALF_RATE, frames, timestamp, sacchFragment.getRAN(), sacchFragment.getLICH());
|
||||
audio.setSACCHFragment(sacchFragment);
|
||||
mMessages.add(audio);
|
||||
}
|
||||
|
||||
if(mLICH.isFACCH1First())
|
||||
{
|
||||
CorrectedBinaryMessage payload = decodeFACCH1First(message);
|
||||
NXDNMessageType type = NXDNMessageType.getTraffic(payload, mLICH);
|
||||
NXDNLayer3Message layer3 = NXDNMessageFactory.get(type, payload, timestamp, sacchFragment.getRAN(), mLICH);
|
||||
layer3.setValid(NXDNCRC.checkFACCH1(payload));
|
||||
mMessages.add(layer3);
|
||||
}
|
||||
|
||||
if(mLICH.isFACCH1Second())
|
||||
{
|
||||
CorrectedBinaryMessage payload = decodeFACCH1Second(message);
|
||||
NXDNMessageType type = NXDNMessageType.getTraffic(payload, mLICH);
|
||||
NXDNLayer3Message layer3 = NXDNMessageFactory.get(type, payload, timestamp, sacchFragment.getRAN(), mLICH);
|
||||
layer3.setValid(NXDNCRC.checkFACCH1(payload));
|
||||
mMessages.add(layer3);
|
||||
}
|
||||
}
|
||||
else if(mLICH.hasSCCH()) //Type-D segments with SCCH
|
||||
{
|
||||
CorrectedBinaryMessage scch = decodeSCCH(message);
|
||||
NXDNMessageType typeSCCH = NXDNMessageType.getSCCH(scch, mLICH);
|
||||
NXDNLayer3Message scchMessage = NXDNMessageFactory.get(typeSCCH, scch, timestamp, RAN_TYPE_D, mLICH);
|
||||
scchMessage.setValid(NXDNCRC.checkSCCH(scch));
|
||||
mMessages.add(scchMessage);
|
||||
|
||||
if(mLICH.isFACCH1First())
|
||||
{
|
||||
CorrectedBinaryMessage payload = decodeFACCH1First(message);
|
||||
NXDNMessageType type = NXDNMessageType.getTypeD(payload, mLICH);
|
||||
NXDNLayer3Message layer3 = NXDNMessageFactory.get(type, payload, timestamp, RAN_TYPE_D, mLICH);
|
||||
layer3.setValid(NXDNCRC.checkFACCH1(payload));
|
||||
mMessages.add(layer3);
|
||||
}
|
||||
|
||||
if(mLICH.isFACCH1Second())
|
||||
{
|
||||
CorrectedBinaryMessage payload = decodeFACCH1Second(message);
|
||||
NXDNMessageType type = NXDNMessageType.getTypeD(payload, mLICH);
|
||||
NXDNLayer3Message layer3 = NXDNMessageFactory.get(type, payload, timestamp, RAN_TYPE_D, mLICH);
|
||||
layer3.setValid(NXDNCRC.checkFACCH1(payload));
|
||||
mMessages.add(layer3);
|
||||
}
|
||||
|
||||
if(mLICH.isData()) //FACCH3 or UDCH2
|
||||
{
|
||||
CorrectedBinaryMessage payloadA = decodeUDCH2AndFACCH3A(message);
|
||||
CorrectedBinaryMessage payloadB = decodeUDCH2AndFACCH3B(message);
|
||||
boolean validA = NXDNCRC.checkFACCH3(payloadA);
|
||||
boolean validB = NXDNCRC.checkFACCH3(payloadB);
|
||||
CorrectedBinaryMessage combined = new CorrectedBinaryMessage(160);
|
||||
NXDNMessageType type = NXDNMessageType.getTypeD(combined, mLICH);
|
||||
NXDNLayer3Message layer3 = NXDNMessageFactory.get(type, combined, timestamp, RAN_TYPE_D, mLICH);
|
||||
layer3.setValid(validA && validB);
|
||||
mMessages.add(layer3);
|
||||
}
|
||||
|
||||
if(mLICH.hasAudio())
|
||||
{
|
||||
List<byte[]> frames = new ArrayList<>();
|
||||
|
||||
if(mLICH.isVoiceFirst())
|
||||
{
|
||||
frames.add(message.getSubMessage(76, 148).toByteArray());
|
||||
frames.add(message.getSubMessage(148, 220).toByteArray());
|
||||
}
|
||||
|
||||
if(mLICH.isVoiceSecond())
|
||||
{
|
||||
frames.add(message.getSubMessage(220, 292).toByteArray());
|
||||
frames.add(message.getSubMessage(292, 364).toByteArray());
|
||||
}
|
||||
|
||||
mMessages.add(new Audio(AudioCodec.HALF_RATE, frames, timestamp, RAN_TYPE_D, mLICH));
|
||||
}
|
||||
}
|
||||
else if(mLICH.getRFChannel() != RFChannel.RTCH2) //UDCH or FACCH2 frames on non-Type-D
|
||||
{
|
||||
CorrectedBinaryMessage payload = decodeUDCHAndFACCH2(message);
|
||||
int ran = payload.getInt(RADIO_ACCESS_NUMBER);
|
||||
CorrectedBinaryMessage facch2 = payload.getSubMessage(8, 184);
|
||||
facch2.setCorrectedBitCount(payload.getCorrectedBitCount());
|
||||
NXDNMessageType type = NXDNMessageType.getTraffic(facch2, mLICH);
|
||||
NXDNLayer3Message layer3 = NXDNMessageFactory.get(type, facch2, timestamp, ran, mLICH);
|
||||
layer3.setValid(NXDNCRC.checkFACCH2(payload));
|
||||
mMessages.add(layer3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the LICH field passes the even parity check.
|
||||
* @param message to test
|
||||
* @return true if the parity check passes.
|
||||
*/
|
||||
private static boolean checkLICHParity(CorrectedBinaryMessage message)
|
||||
{
|
||||
int onesBitCount = Integer.bitCount(message.getInt(LICH_PARITY_FIELD));
|
||||
return (onesBitCount % 2 == 1) == message.get(LICH_EVEN_PARITY_CHECK_BIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the frame payload
|
||||
* @param message from the frame
|
||||
* @param offset to the start of the payload
|
||||
* @param interleaver for the payload type
|
||||
* @param punctureProvider for the payload type
|
||||
* @return decoded message
|
||||
*/
|
||||
private static CorrectedBinaryMessage decode(CorrectedBinaryMessage message, int offset, Interleaver interleaver, PunctureProvider punctureProvider)
|
||||
{
|
||||
CorrectedBinaryMessage deinterleaved = interleaver.deinterleave(message, offset);
|
||||
return Convolution.decode(punctureProvider.depuncture(deinterleaved), punctureProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the first FACCH1 frame
|
||||
* @param message to decode
|
||||
* @return decoded message
|
||||
*/
|
||||
public static CorrectedBinaryMessage decodeFACCH1First(CorrectedBinaryMessage message)
|
||||
{
|
||||
return decode(message, 76, INTERLEAVER_16_9, PUNCTURE_PROVIDER_FACCH1_FACCH3_UDCH2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the second FACCH1 frame
|
||||
* @param message to decode
|
||||
* @return decoded message
|
||||
*/
|
||||
public static CorrectedBinaryMessage decodeFACCH1Second(CorrectedBinaryMessage message)
|
||||
{
|
||||
return decode(message, 220, INTERLEAVER_16_9, PUNCTURE_PROVIDER_FACCH1_FACCH3_UDCH2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the FACCH2 frame
|
||||
* @param message to decode
|
||||
* @return decoded message
|
||||
*/
|
||||
public static CorrectedBinaryMessage decodeUDCHAndFACCH2(CorrectedBinaryMessage message)
|
||||
{
|
||||
return decode(message, 16, INTERLEAVER_12_29, PUNCTURE_PROVIDER_CAC_FACCH2_UDCH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the first half of the FACCH3 and UDCH2 frame on Type-D systems
|
||||
* @param message to decode
|
||||
* @return decoded message
|
||||
*/
|
||||
public static CorrectedBinaryMessage decodeUDCH2AndFACCH3A(CorrectedBinaryMessage message)
|
||||
{
|
||||
return decode(message, 76, INTERLEAVER_16_9, PUNCTURE_PROVIDER_FACCH1_FACCH3_UDCH2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the second half of the FACCH3 and UDCH2 frame on Type-D systems
|
||||
* @param message to decode
|
||||
* @return decoded message
|
||||
*/
|
||||
public static CorrectedBinaryMessage decodeUDCH2AndFACCH3B(CorrectedBinaryMessage message)
|
||||
{
|
||||
return decode(message, 220, INTERLEAVER_16_9, PUNCTURE_PROVIDER_FACCH1_FACCH3_UDCH2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the SACCH frame
|
||||
* @param message to decode
|
||||
* @return decoded message
|
||||
*/
|
||||
public static CorrectedBinaryMessage decodeSACCH(CorrectedBinaryMessage message)
|
||||
{
|
||||
return decode(message, 16, INTERLEAVER_12_5, PUNCTURE_PROVIDER_SACCH_SCCH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the SCCH frame in a Type-D system
|
||||
* @param message to decode
|
||||
* @return decoded message
|
||||
*/
|
||||
public static CorrectedBinaryMessage decodeSCCH(CorrectedBinaryMessage message)
|
||||
{
|
||||
return decode(message, 16, INTERLEAVER_12_5, PUNCTURE_PROVIDER_SACCH_SCCH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the outbound control frame
|
||||
* @param message to decode
|
||||
* @return decoded message
|
||||
*/
|
||||
public static CorrectedBinaryMessage decodeCAC(CorrectedBinaryMessage message)
|
||||
{
|
||||
return decode(message, 16, INTERLEAVER_12_25, PUNCTURE_PROVIDER_CAC_FACCH2_UDCH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the inbound long control frame
|
||||
* @param message to decode
|
||||
* @return decoded message
|
||||
*/
|
||||
public static CorrectedBinaryMessage decodeLongCAC(CorrectedBinaryMessage message)
|
||||
{
|
||||
return decode(message, 16, INTERLEAVER_12_21, PUNCTURE_PROVIDER_LONG_CAC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the inbound short control frame
|
||||
* @param message to decode
|
||||
* @return decoded message
|
||||
*/
|
||||
public static CorrectedBinaryMessage decodeShortCAC(CorrectedBinaryMessage message)
|
||||
{
|
||||
return decode(message, 16, INTERLEAVER_12_21, PUNCTURE_PROVIDER_NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Message(s( from this frame.
|
||||
*/
|
||||
public List<NXDNMessage> getMessages()
|
||||
{
|
||||
return mMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Link information channel (LICH)
|
||||
* @return LICH
|
||||
*/
|
||||
public LICH getLICH()
|
||||
{
|
||||
return mLICH;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer1.sync;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
|
||||
/**
|
||||
* NXDN sync pattern detector that processes hard symbol (ie dibit) decisions.
|
||||
*/
|
||||
public class NXDNHardSyncDetector extends NXDNSyncDetector
|
||||
{
|
||||
private static final int MAXIMUM_BIT_ERROR = 2;
|
||||
private long mValue;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public NXDNHardSyncDetector()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the dibit.
|
||||
*
|
||||
* @param dibit to process
|
||||
* @return true if a sync pattern is detected.
|
||||
*/
|
||||
public boolean process(Dibit dibit)
|
||||
{
|
||||
mValue = (Long.rotateLeft(mValue, 2) & SYNC_MASK) + dibit.getValue();
|
||||
return Long.bitCount(mValue ^ SYNC_PATTERN) <= MAXIMUM_BIT_ERROR;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Bit error count when comparing the current buffer contents to the known sync pattern.
|
||||
*/
|
||||
public int getCurrentBitErrors()
|
||||
{
|
||||
return Long.bitCount(mValue ^ SYNC_PATTERN);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer1.sync;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* NXDN Sync Detector. Uses soft demodulated symbols to detect sync patterns in the demodulated symbol stream.
|
||||
*/
|
||||
public abstract class NXDNSoftSyncDetector extends NXDNSyncDetector
|
||||
{
|
||||
//symbol buffer is extended by 6x to support SIMD 512 calculations
|
||||
protected float[] mSymbols = new float[SYNC_DIBIT_LENGTH * 2 + 6];
|
||||
protected int mSymbolPointer = 0;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public NXDNSoftSyncDetector()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this sync detector and flushes any stored soft symbols from the buffer.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
Arrays.fill(mSymbols, 0.0f);
|
||||
mSymbolPointer = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Worker method to be implemented by the subclass implementation.
|
||||
*
|
||||
* @return sync correlation score.
|
||||
*/
|
||||
public abstract float calculate();
|
||||
|
||||
/**
|
||||
* Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft
|
||||
* symbols that include this recent soft symbol.
|
||||
*
|
||||
* @param dibitSymbol to process that represents the soft demodulated symbol value in phase angle/radians.
|
||||
* @return correlation score.
|
||||
*/
|
||||
public float process(float dibitSymbol)
|
||||
{
|
||||
mSymbols[mSymbolPointer] = dibitSymbol;
|
||||
mSymbols[mSymbolPointer + SYNC_DIBIT_LENGTH] = dibitSymbol;
|
||||
mSymbolPointer++;
|
||||
mSymbolPointer %= SYNC_DIBIT_LENGTH;
|
||||
return calculate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer1.sync;
|
||||
|
||||
/**
|
||||
* Scalar implementation of NXDN Soft Sync Detector.
|
||||
*/
|
||||
public class NXDNSoftSyncDetectorScalar extends NXDNSoftSyncDetector
|
||||
{
|
||||
/**
|
||||
* Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft
|
||||
* symbols that include this recent soft symbol.
|
||||
*
|
||||
* @return correlation score.
|
||||
*/
|
||||
public float calculate()
|
||||
{
|
||||
float symbol;
|
||||
float score = 0;
|
||||
|
||||
for(int x = 0; x < SYNC_DIBIT_LENGTH; x++)
|
||||
{
|
||||
symbol = mSymbols[mSymbolPointer + x];
|
||||
score += SYMBOLS[x] * symbol;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer1.sync;
|
||||
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* SIMD Vector 128 implementation of NXDN Soft Sync Detector.
|
||||
*/
|
||||
public class NXDNSoftSyncDetectorVector128 extends NXDNSoftSyncDetector
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
|
||||
private static final float[] SYNC_EXTENDED = new float[4];
|
||||
|
||||
public NXDNSoftSyncDetectorVector128()
|
||||
{
|
||||
//Create an array of 4x elements containing the final 2x symbols for SIMD loading.
|
||||
SYNC_EXTENDED[2] = SYMBOLS[8];
|
||||
SYNC_EXTENDED[3] = SYMBOLS[9];
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the demodulated soft symbol value and returns a correlation value against the preceding 10 soft
|
||||
* symbols that include this recent soft symbol.
|
||||
*
|
||||
* @return correlation score.
|
||||
*/
|
||||
public float calculate()
|
||||
{
|
||||
FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = FloatVector.fromArray(VECTOR_SPECIES, SYMBOLS, 0)
|
||||
.fma(FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer), accumulator);
|
||||
accumulator = FloatVector.fromArray(VECTOR_SPECIES, SYMBOLS, 4)
|
||||
.fma(FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 4), accumulator);
|
||||
accumulator = FloatVector.fromArray(VECTOR_SPECIES, SYNC_EXTENDED, 0)
|
||||
.fma(FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 6), accumulator);
|
||||
return accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer1.sync;
|
||||
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* SIMD Vector 256 implementation of NXDN Soft Sync Detector.
|
||||
*/
|
||||
public class NXDNSoftSyncDetectorVector256 extends NXDNSoftSyncDetector
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
|
||||
private static final float[] SYNC_EXTENDED = new float[8];
|
||||
|
||||
public NXDNSoftSyncDetectorVector256()
|
||||
{
|
||||
//Create an array of 8x elements containing the final 2x symbols for SIMD loading.
|
||||
SYNC_EXTENDED[2] = SYMBOLS[8];
|
||||
SYNC_EXTENDED[3] = SYMBOLS[9];
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the demodulated soft symbol value and returns a correlation value against the preceding 10 soft
|
||||
* symbols that include this recent soft symbol.
|
||||
*
|
||||
* @return correlation score.
|
||||
*/
|
||||
public float calculate()
|
||||
{
|
||||
FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = FloatVector.fromArray(VECTOR_SPECIES, SYMBOLS, 0)
|
||||
.fma(FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer), accumulator);
|
||||
accumulator = FloatVector.fromArray(VECTOR_SPECIES, SYNC_EXTENDED, 0)
|
||||
.fma(FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 6), accumulator);
|
||||
return accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer1.sync;
|
||||
|
||||
import java.util.Arrays;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* SIMD Vector 512 implementation of NXDN Soft Sync Detector.
|
||||
*/
|
||||
public class NXDNSoftSyncDetectorVector512 extends NXDNSoftSyncDetector
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
|
||||
//Extend the symbols out to 16x using zero padding after the 10th symbol
|
||||
private static final float[] SYNC_EXTENDED = Arrays.copyOf(SYMBOLS, 16);
|
||||
|
||||
/**
|
||||
* Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft
|
||||
* symbols that include this recent soft symbol.
|
||||
*
|
||||
* @return correlation score.
|
||||
*/
|
||||
public float calculate()
|
||||
{
|
||||
return FloatVector.fromArray(VECTOR_SPECIES, SYNC_EXTENDED, 0)
|
||||
.mul(FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer))
|
||||
.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer1.sync;
|
||||
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* SIMD Vector 64 implementation of NXDN Soft Sync Detector.
|
||||
*/
|
||||
public class NXDNSoftSyncDetectorVector64 extends NXDNSoftSyncDetector
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
|
||||
|
||||
/**
|
||||
* Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft
|
||||
* symbols that include this recent soft symbol.
|
||||
*
|
||||
* @return correlation score.
|
||||
*/
|
||||
public float calculate()
|
||||
{
|
||||
FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
|
||||
for(int x = 0; x < SYNC_DIBIT_LENGTH; x += VECTOR_SPECIES.length())
|
||||
{
|
||||
accumulator = FloatVector.fromArray(VECTOR_SPECIES, SYMBOLS, x)
|
||||
.fma(FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + x), accumulator);
|
||||
}
|
||||
|
||||
return accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer1.sync;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import org.apache.commons.math3.analysis.interpolation.SplineInterpolator;
|
||||
import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
|
||||
|
||||
/**
|
||||
* Base implementation of NXDN sync detector.
|
||||
*/
|
||||
public abstract class NXDNSyncDetector
|
||||
{
|
||||
public static final long SYNC_PATTERN = 0xCDF59;
|
||||
public static final long SYNC_MASK = 0xFFFFF;
|
||||
public static final int SYNC_DIBIT_LENGTH = 10;
|
||||
public static final Dibit[] DIBITS = toDibits(SYNC_PATTERN, SYNC_DIBIT_LENGTH);
|
||||
public static final float[] SYMBOLS = toSymbols(DIBITS);
|
||||
private static Map<Integer,ReferenceSyncWaveform> sReferenceWaveformMap = new TreeMap<>();
|
||||
|
||||
/**
|
||||
* Sync pattern length in dibits.
|
||||
*/
|
||||
public int getSyncPatternDibitLength()
|
||||
{
|
||||
return SYNC_DIBIT_LENGTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dibits array for the sync pattern
|
||||
* @return dibits array.
|
||||
*/
|
||||
public Dibit[] getSyncDibits()
|
||||
{
|
||||
return DIBITS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Symbols array for the sync pattern.
|
||||
* @return array of (ideal) soft symbol values.
|
||||
*/
|
||||
public float[] getSyncSymbols()
|
||||
{
|
||||
return SYMBOLS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the sync pattern to dibits.
|
||||
* @param pattern to convert
|
||||
* @param dibitLength in dibits
|
||||
* @return array of dibits for the sync pattern.
|
||||
*/
|
||||
protected static Dibit[] toDibits(long pattern, int dibitLength)
|
||||
{
|
||||
Dibit[] dibits = new Dibit[dibitLength];
|
||||
long mask = 3;
|
||||
int dibitValue;
|
||||
|
||||
for(int x = 0; x < dibitLength; x++)
|
||||
{
|
||||
dibitValue = (int)((pattern & mask) >> (2 * x));
|
||||
|
||||
dibits[dibitLength - x - 1] = switch(dibitValue)
|
||||
{
|
||||
case 0 -> Dibit.D00_PLUS_1;
|
||||
case 1 -> Dibit.D01_PLUS_3;
|
||||
case 2 -> Dibit.D10_MINUS_1;
|
||||
default -> Dibit.D11_MINUS_3;
|
||||
};
|
||||
|
||||
mask = mask << 2;
|
||||
}
|
||||
|
||||
return dibits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to convert a dibit array into an array of ideal soft symbol values.
|
||||
* @param dibits for the sync pattern
|
||||
* @return ideal soft symbol array.
|
||||
*/
|
||||
protected static float[] toSymbols(Dibit[] dibits)
|
||||
{
|
||||
float[] symbols = new float[dibits.length];
|
||||
|
||||
for(int x = 0; x < dibits.length; x++)
|
||||
{
|
||||
symbols[x] = dibits[x].getIdealPhase();
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reference waveform representation of the sync pattern that can be used to correlate against a
|
||||
* candidate detected sync waveform to discriminate between a valid and invalid sync detection. The generated
|
||||
* waveform will be aligned where the final sample is time-aligned to the final symbol.
|
||||
*
|
||||
* @param samplesPerSymbol count
|
||||
* @return a reference waveform and ideal correlation score.
|
||||
*/
|
||||
public ReferenceSyncWaveform getReferenceWaveform(double samplesPerSymbol)
|
||||
{
|
||||
int key = (int)(samplesPerSymbol * 1000.0);
|
||||
|
||||
if(sReferenceWaveformMap.containsKey(key))
|
||||
{
|
||||
return sReferenceWaveformMap.get(key);
|
||||
}
|
||||
|
||||
float[] symbols = getSyncSymbols();
|
||||
int sampleCount = (int)Math.ceil(samplesPerSymbol * (symbols.length - 1)) + 1;
|
||||
float[] samples = new float[sampleCount];
|
||||
double sampleInterval = 1.0 / samplesPerSymbol;
|
||||
|
||||
double[] x = new double[symbols.length + 2];
|
||||
double[] y = new double[symbols.length + 2];
|
||||
|
||||
for(int i = -1; i < x.length - 1; i++)
|
||||
{
|
||||
x[i + 1] = i;
|
||||
|
||||
if(0 <= i && i < symbols.length)
|
||||
{
|
||||
y[i + 1] = symbols[i];
|
||||
}
|
||||
}
|
||||
|
||||
double start = symbols.length - 1;
|
||||
int samplePointer = sampleCount - 1;
|
||||
SplineInterpolator interpolator = new SplineInterpolator();
|
||||
PolynomialSplineFunction function = interpolator.interpolate(x, y);
|
||||
|
||||
float accumulator = 0;
|
||||
float sample;
|
||||
float max = 0;
|
||||
while(samplePointer >= 0)
|
||||
{
|
||||
sample = (float)function.value(start);
|
||||
|
||||
if(Math.abs(sample) > max)
|
||||
{
|
||||
max = Math.abs(sample);
|
||||
}
|
||||
samples[samplePointer--] = sample;
|
||||
accumulator += sample * sample;
|
||||
start -= sampleInterval;
|
||||
}
|
||||
|
||||
ReferenceSyncWaveform referenceWaveform = new ReferenceSyncWaveform(samples, accumulator, max);
|
||||
sReferenceWaveformMap.put(key, referenceWaveform);
|
||||
return referenceWaveform;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer1.sync;
|
||||
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationType;
|
||||
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Factory for creating NXDN Soft Sync Detectors
|
||||
*/
|
||||
public class NXDNSyncDetectorFactory
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(NXDNSyncDetectorFactory.class);
|
||||
|
||||
/**
|
||||
* Creates the implementation using calibration data to select the optimal implementation from scalar and vector options.
|
||||
* @return demodulator instance
|
||||
*/
|
||||
public static NXDNSoftSyncDetector getStandardDetector()
|
||||
{
|
||||
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.NXDN_SOFT_SYNC_DETECTOR);
|
||||
|
||||
switch(implementation)
|
||||
{
|
||||
case VECTOR_SIMD_64:
|
||||
return new NXDNSoftSyncDetectorVector64();
|
||||
case VECTOR_SIMD_128:
|
||||
return new NXDNSoftSyncDetectorVector128();
|
||||
case VECTOR_SIMD_256:
|
||||
return new NXDNSoftSyncDetectorVector256();
|
||||
case VECTOR_SIMD_512:
|
||||
return new NXDNSoftSyncDetectorVector512();
|
||||
case SCALAR:
|
||||
default:
|
||||
return new NXDNSoftSyncDetectorScalar();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer1.sync;
|
||||
|
||||
/**
|
||||
* Waveform representing the ideal sync pattern, resampled at the sample rate.
|
||||
* @param samples in samples
|
||||
* @param idealCorrelationScore as dot-product of the ideal multiplied by itself.
|
||||
* @param maxSample maximum allowable sample value
|
||||
*/
|
||||
public record ReferenceSyncWaveform(float[] samples, float idealCorrelationScore, float maxSample){};
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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.nxdn.layer2;
|
||||
|
||||
/**
|
||||
* Channel direction indicator where inbound is SU to repeater and outbound is repeater to SU
|
||||
*/
|
||||
public enum Direction
|
||||
{
|
||||
INBOUND, OUTBOUND;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer2;
|
||||
|
||||
/**
|
||||
* NXDN frame structure
|
||||
*/
|
||||
public enum Framing
|
||||
{
|
||||
SUPER,
|
||||
SINGLE,
|
||||
UNKNOWN;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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.nxdn.layer2;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
|
||||
/**
|
||||
* Base NXDN interleaver configuration.
|
||||
*/
|
||||
public class Interleaver
|
||||
{
|
||||
private int[] mInterleaveIndexes;
|
||||
private int mWidth;
|
||||
private int mDepth;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param width of the interleave
|
||||
* @param depth of the interleave
|
||||
*/
|
||||
public Interleaver(int width, int depth)
|
||||
{
|
||||
mWidth = width;
|
||||
mDepth = depth;
|
||||
mInterleaveIndexes = new int[width * depth];
|
||||
|
||||
int pointer = 0;
|
||||
|
||||
for(int column = 0; column < width; column++)
|
||||
{
|
||||
for(int row = 0; row < depth; row++)
|
||||
{
|
||||
mInterleaveIndexes[pointer++] = (row * mWidth) + column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs interleave of the original message
|
||||
* @param message original
|
||||
* @param offset into the output message where the interleaved data should be placed
|
||||
* @return output message with interleaved payload positioned starting at the offset.
|
||||
*/
|
||||
public CorrectedBinaryMessage interleave(CorrectedBinaryMessage message, int offset)
|
||||
{
|
||||
CorrectedBinaryMessage interleaved = new CorrectedBinaryMessage(mInterleaveIndexes.length + offset);
|
||||
|
||||
for(int index = 0; index < mInterleaveIndexes.length; index++)
|
||||
{
|
||||
if(message.get(index + offset))
|
||||
{
|
||||
interleaved.set(offset + mInterleaveIndexes[index]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return interleaved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs deinterleave of the interleaved message
|
||||
* @param interleaved containing the interleaved payload starting at the offset
|
||||
* @param offset into the message to the start of the interleaved payload.
|
||||
* @return deinterleaved message starting at index 0
|
||||
*/
|
||||
public CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage interleaved, int offset)
|
||||
{
|
||||
CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(mInterleaveIndexes.length);
|
||||
|
||||
for(int index = 0; index < mInterleaveIndexes.length; index++)
|
||||
{
|
||||
deinterleaved.set(mInterleaveIndexes[index], interleaved.get(index + offset));
|
||||
}
|
||||
|
||||
return deinterleaved;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Link Information Channel (LICH) enumeration identifies the channel configuration and direction and optional values.
|
||||
*/
|
||||
public enum LICH
|
||||
{
|
||||
//Trunked - Control
|
||||
RCCH_INBOUND_SINGLE_CAC_SHORT(0x18, RFChannel.RCCH, Framing.SINGLE, Structure.CAC_SHORT, Direction.INBOUND),
|
||||
RCCH_INBOUND_SINGLE_CAC_LONG(0x08, RFChannel.RCCH, Framing.SINGLE, Structure.CAC_LONG, Direction.INBOUND),
|
||||
RCCH_INBOUND_SINGLE_UNKNOWN(-1, RFChannel.RCCH, Framing.UNKNOWN, Structure.UNKNOWN, Direction.INBOUND),
|
||||
RCCH_OUTBOUND_SINGLE_CAC_NORMAL(0x01, RFChannel.RCCH, Framing.SINGLE, Structure.CAC_NORMAL, Direction.OUTBOUND),
|
||||
RCCH_OUTBOUND_SINGLE_CAC_IDLE(0x03, RFChannel.RCCH, Framing.SINGLE, Structure.CAC_IDLE, Direction.OUTBOUND),
|
||||
RCCH_OUTBOUND_SINGLE_CAC_COMMON(0x05, RFChannel.RCCH, Framing.SINGLE, Structure.CAC_COMMON, Direction.OUTBOUND),
|
||||
RCCH_OUTBOUND_SINGLE_UNKNOWN(-1, RFChannel.RCCH, Framing.SINGLE, Structure.CAC_NORMAL, Direction.OUTBOUND),
|
||||
|
||||
//Trunked - Traffic
|
||||
RTCH_INBOUND_SUPER_VOICE_VOICE(0x36, RFChannel.RTCH, Framing.SUPER, Structure.VOICE_VOICE, Direction.INBOUND),
|
||||
RTCH_INBOUND_SUPER_VOICE_FACCH1(0x34, RFChannel.RTCH, Framing.SUPER, Structure.VOICE_FACCH1, Direction.INBOUND),
|
||||
RTCH_INBOUND_SUPER_FACCH1_VOICE(0x32, RFChannel.RTCH, Framing.SUPER, Structure.FACCH1_VOICE, Direction.INBOUND),
|
||||
RTCH_INBOUND_SUPER_FACCH1_FACCH1(0x30, RFChannel.RTCH, Framing.SUPER, Structure.FACCH1_FACCH1, Direction.INBOUND),
|
||||
RTCH_INBOUND_SINGLE_FACCH1_FACCH1(0x20, RFChannel.RTCH, Framing.SINGLE, Structure.FACCH1_FACCH1, Direction.INBOUND),
|
||||
RTCH_INBOUND_SUPER_IDLE(0x38, RFChannel.RTCH, Framing.SUPER, Structure.FACCH1_FACCH1, Direction.INBOUND),
|
||||
RTCH_INBOUND_SINGLE_UDCH(0x2E, RFChannel.RTCH, Framing.SINGLE, Structure.UDCH, Direction.INBOUND),
|
||||
RTCH_INBOUND_SINGLE_FACCH2(0x28, RFChannel.RTCH, Framing.SINGLE, Structure.FACCH2, Direction.INBOUND),
|
||||
RTCH_INBOUND_UNKNOWN(-1, RFChannel.RTCH, Framing.UNKNOWN, Structure.UNKNOWN, Direction.INBOUND),
|
||||
|
||||
RTCH_OUTBOUND_SUPER_VOICE_VOICE(0x37, RFChannel.RTCH, Framing.SUPER, Structure.VOICE_VOICE, Direction.OUTBOUND),
|
||||
RTCH_OUTBOUND_SUPER_VOICE_FACCH1(0x35, RFChannel.RTCH, Framing.SUPER, Structure.VOICE_FACCH1, Direction.OUTBOUND),
|
||||
RTCH_OUTBOUND_SUPER_FACCH1_VOICE(0x33, RFChannel.RTCH, Framing.SUPER, Structure.FACCH1_VOICE, Direction.OUTBOUND),
|
||||
RTCH_OUTBOUND_SUPER_FACCH1_FACCH1(0x31, RFChannel.RTCH, Framing.SUPER, Structure.FACCH1_FACCH1, Direction.OUTBOUND),
|
||||
RTCH_OUTBOUND_SINGLE_FACCH1_FACCH1(0x21, RFChannel.RTCH, Framing.SINGLE, Structure.FACCH1_FACCH1, Direction.OUTBOUND),
|
||||
RTCH_OUTBOUND_SUPER_IDLE(0x39, RFChannel.RTCH, Framing.SUPER, Structure.FACCH1_FACCH1, Direction.OUTBOUND),
|
||||
RTCH_OUTBOUND_SINGLE_UDCH(0x2F, RFChannel.RTCH, Framing.SINGLE, Structure.UDCH, Direction.OUTBOUND),
|
||||
RTCH_OUTBOUND_SINGLE_FACCH2(0x29, RFChannel.RTCH, Framing.SINGLE, Structure.FACCH2, Direction.OUTBOUND),
|
||||
RTCH_OUTBOUND_UNKNOWN(-1, RFChannel.RTCH, Framing.UNKNOWN, Structure.UNKNOWN, Direction.OUTBOUND),
|
||||
|
||||
//Conventional repeater or direct SU-to-SU
|
||||
RDCH_INBOUND_SUPER_VOICE_VOICE(0x56, RFChannel.RDCH, Framing.SUPER, Structure.VOICE_VOICE, Direction.INBOUND),
|
||||
RDCH_INBOUND_SUPER_VOICE_FACCH1(0x54, RFChannel.RDCH, Framing.SUPER, Structure.VOICE_FACCH1, Direction.INBOUND),
|
||||
RDCH_INBOUND_SUPER_FACCH1_VOICE(0x52, RFChannel.RDCH, Framing.SUPER, Structure.FACCH1_VOICE, Direction.INBOUND),
|
||||
RDCH_INBOUND_SUPER_FACCH1_FACCH1(0x50, RFChannel.RDCH, Framing.SUPER, Structure.FACCH1_FACCH1, Direction.INBOUND),
|
||||
RDCH_INBOUND_SINGLE_FACCH1_FACCH1(0x40, RFChannel.RDCH, Framing.SINGLE, Structure.FACCH1_FACCH1, Direction.INBOUND),
|
||||
RDCH_INBOUND_SUPER_IDLE(0x58, RFChannel.RDCH, Framing.SUPER, Structure.FACCH1_FACCH1, Direction.INBOUND),
|
||||
RDCH_INBOUND_SINGLE_UDCH(0x4E, RFChannel.RDCH, Framing.SINGLE, Structure.UDCH, Direction.INBOUND),
|
||||
RDCH_INBOUND_SINGLE_FACCH2(0x48, RFChannel.RDCH, Framing.SINGLE, Structure.FACCH2, Direction.INBOUND),
|
||||
RDCH_INBOUND_UNKNOWN(-1, RFChannel.RDCH, Framing.UNKNOWN, Structure.UNKNOWN, Direction.INBOUND),
|
||||
|
||||
RDCH_OUTBOUND_SUPER_VOICE_VOICE(0x57, RFChannel.RDCH, Framing.SUPER, Structure.VOICE_VOICE, Direction.OUTBOUND),
|
||||
RDCH_OUTBOUND_SUPER_VOICE_FACCH1(0x55, RFChannel.RDCH, Framing.SUPER, Structure.VOICE_FACCH1, Direction.OUTBOUND),
|
||||
RDCH_OUTBOUND_SUPER_FACCH1_VOICE(0x53, RFChannel.RDCH, Framing.SUPER, Structure.FACCH1_VOICE, Direction.OUTBOUND),
|
||||
RDCH_OUTBOUND_SUPER_FACCH1_FACCH1(0x51, RFChannel.RDCH, Framing.SUPER, Structure.FACCH1_FACCH1, Direction.OUTBOUND),
|
||||
RDCH_OUTBOUND_SINGLE_FACCH1_FACCH1(0x41, RFChannel.RDCH, Framing.SINGLE, Structure.FACCH1_FACCH1, Direction.OUTBOUND),
|
||||
RDCH_OUTBOUND_SUPER_IDLE(0x59, RFChannel.RDCH, Framing.SUPER, Structure.FACCH1_FACCH1, Direction.OUTBOUND),
|
||||
RDCH_OUTBOUND_SINGLE_UDCH(0x4F, RFChannel.RDCH, Framing.SINGLE, Structure.UDCH, Direction.OUTBOUND),
|
||||
RDCH_OUTBOUND_SINGLE_FACCH2(0x49, RFChannel.RDCH, Framing.SINGLE, Structure.FACCH2, Direction.OUTBOUND),
|
||||
RDCH_OUTBOUND_UNKNOWN(-1, RFChannel.RDCH, Framing.UNKNOWN, Structure.UNKNOWN, Direction.OUTBOUND),
|
||||
|
||||
//Trunked Composite Control
|
||||
RTCH_C_OUTBOUND_SUPER_VOICE_VOICE(0x77, RFChannel.RTCHC, Framing.SUPER, Structure.VOICE_VOICE, Direction.OUTBOUND),
|
||||
RTCH_C_OUTBOUND_SUPER_VOICE_FACCH1(0x75, RFChannel.RTCHC, Framing.SUPER, Structure.VOICE_FACCH1, Direction.OUTBOUND),
|
||||
RTCH_C_OUTBOUND_SUPER_FACCH1_VOICE(0x73, RFChannel.RTCHC, Framing.SUPER, Structure.FACCH1_VOICE, Direction.OUTBOUND),
|
||||
RTCH_C_OUTBOUND_SUPER_FACCH1_FACCH1(0x71, RFChannel.RTCHC, Framing.SUPER, Structure.FACCH1_FACCH1, Direction.OUTBOUND),
|
||||
RTCH_C_OUTBOUND_SINGLE_FACCH1_FACCH1(0x61, RFChannel.RTCHC, Framing.SINGLE, Structure.FACCH1_FACCH1, Direction.OUTBOUND),
|
||||
RTCH_C_OUTBOUND_SUPER_IDLE(0x79, RFChannel.RTCHC, Framing.SUPER, Structure.FACCH1_FACCH1, Direction.OUTBOUND),
|
||||
RTCH_C_OUTBOUND_SINGLE_UDCH(0x6F, RFChannel.RTCHC, Framing.SINGLE, Structure.UDCH, Direction.OUTBOUND),
|
||||
RTCH_C_OUTBOUND_SINGLE_FACCH2(0x69, RFChannel.RTCHC, Framing.SINGLE, Structure.FACCH2, Direction.OUTBOUND),
|
||||
RTCH_C_OUTBOUND_UNKNOWN(-1, RFChannel.RTCHC, Framing.UNKNOWN, Structure.UNKNOWN, Direction.OUTBOUND),
|
||||
|
||||
//Type-D Trunking (RTCH2)
|
||||
RTCH_2_INBOUND_SUPER_VOICE_VOICE(0x76, RFChannel.RTCH2, Framing.SUPER, Structure.VOICE_VOICE, Direction.INBOUND),
|
||||
RTCH_2_INBOUND_SUPER_VOICE_FACCH1(0x74, RFChannel.RTCH2, Framing.SUPER, Structure.VOICE_FACCH1, Direction.INBOUND),
|
||||
RTCH_2_INBOUND_SUPER_FACCH1_VOICE(0x72, RFChannel.RTCH2, Framing.SUPER, Structure.FACCH1_VOICE, Direction.INBOUND),
|
||||
RTCH_2_INBOUND_SUPER_FACCH1_FACCH1(0x70, RFChannel.RTCH2, Framing.SUPER, Structure.FACCH1_FACCH1, Direction.INBOUND),
|
||||
RTCH_2_INBOUND_SINGLE_UDCH2(0x6E, RFChannel.RTCH2, Framing.SINGLE, Structure.UDCH2, Direction.INBOUND),
|
||||
RTCH_2_INBOUND_SINGLE_FACCH3(0x68, RFChannel.RTCH2, Framing.SINGLE, Structure.FACCH3, Direction.INBOUND),
|
||||
RTCH_2_INBOUND_SINGLE_FACCH1_GUARD(0x62, RFChannel.RTCH2, Framing.SINGLE, Structure.FACCH1_GUARD, Direction.INBOUND),
|
||||
RTCH_2_INBOUND_SINGLE_FACCH1_FACCH1(0x60, RFChannel.RTCH2, Framing.SINGLE, Structure.FACCH1_FACCH1, Direction.INBOUND),
|
||||
RTCH_2_INBOUND_UNKNOWN(-1, RFChannel.RTCH2, Framing.UNKNOWN, Structure.UNKNOWN, Direction.INBOUND),
|
||||
|
||||
RTCH_2_OUTBOUND_SUPER_VOICE_VOICE(0x77, RFChannel.RTCH2, Framing.SUPER, Structure.VOICE_VOICE, Direction.OUTBOUND),
|
||||
RTCH_2_OUTBOUND_SUPER_VOICE_FACCH1(0x75, RFChannel.RTCH2, Framing.SUPER, Structure.VOICE_FACCH1, Direction.OUTBOUND),
|
||||
RTCH_2_OUTBOUND_SUPER_FACCH1_VOICE(0x73, RFChannel.RTCH2, Framing.SUPER, Structure.FACCH1_VOICE, Direction.OUTBOUND),
|
||||
RTCH_2_OUTBOUND_SUPER_FACCH1_FACCH1(0x71, RFChannel.RTCH2, Framing.SUPER, Structure.FACCH1_FACCH1, Direction.OUTBOUND),
|
||||
RTCH_2_OUTBOUND_SINGLE_UDCH2(0x6F, RFChannel.RTCH2, Framing.SINGLE, Structure.UDCH2, Direction.OUTBOUND),
|
||||
RTCH_2_OUTBOUND_SINGLE_FACCH3(0x69, RFChannel.RTCH2, Framing.SINGLE, Structure.FACCH3, Direction.OUTBOUND),
|
||||
RTCH_2_OUTBOUND_SINGLE_FACCH1_GUARD(0x63, RFChannel.RTCH2, Framing.SINGLE, Structure.FACCH1_GUARD, Direction.OUTBOUND),
|
||||
RTCH_2_OUTBOUND_SINGLE_FACCH1_FACCH1(0x61, RFChannel.RTCH2, Framing.SINGLE, Structure.FACCH1_FACCH1, Direction.OUTBOUND),
|
||||
RTCH_2_OUTBOUND_UNKNOWN(-1, RFChannel.RTCH2, Framing.UNKNOWN, Structure.UNKNOWN, Direction.OUTBOUND),
|
||||
|
||||
UNKNOWN(-1, RFChannel.UNKNOWN, Framing.UNKNOWN, Structure.UNKNOWN, Direction.OUTBOUND);
|
||||
|
||||
private int mValue;
|
||||
private RFChannel mRFChannel;
|
||||
private Framing mFraming;
|
||||
private Structure mStructure;
|
||||
private Direction mDirection;
|
||||
|
||||
private static final Map<Integer, LICH> LOOKUP_MAP_STANDARD = new TreeMap<>();
|
||||
private static final Map<Integer, LICH> LOOKUP_MAP_TYPE_D = new TreeMap<>();
|
||||
|
||||
static
|
||||
{
|
||||
for(LICH lich : LICH.values())
|
||||
{
|
||||
if(lich.getRFChannel() == RFChannel.RTCH2)
|
||||
{
|
||||
LOOKUP_MAP_TYPE_D.put(lich.getValue(), lich);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOOKUP_MAP_STANDARD.put(lich.getValue(), lich);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param value as transmitted
|
||||
* @param rfChannel channel type
|
||||
* @param framing structure
|
||||
* @param structure options for the channel
|
||||
* @param direction inbound or outbound
|
||||
*/
|
||||
LICH(int value, RFChannel rfChannel, Framing framing, Structure structure, Direction direction)
|
||||
{
|
||||
mValue = value;
|
||||
mRFChannel = rfChannel;
|
||||
mFraming = framing;
|
||||
mStructure = structure;
|
||||
mDirection = direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this frame contains a FACCH1 message in the first half of the frame
|
||||
*/
|
||||
public boolean isFACCH1First()
|
||||
{
|
||||
return getStructure().isFACCH1First();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this frame contains a FACCH1 message in the second half of the frame
|
||||
*/
|
||||
public boolean isFACCH1Second()
|
||||
{
|
||||
return getStructure().isFACCH1Second();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this frame contains a data fragment (FACCH2, FACCH3, UDCH or UDCH2)
|
||||
*/
|
||||
public boolean isData()
|
||||
{
|
||||
return getStructure().isData();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Indicates if the frame has audio frames in either or both of the payloads.
|
||||
*/
|
||||
public boolean hasAudio()
|
||||
{
|
||||
return getStructure().isVoice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this frame has voice data in the first half of the frame.
|
||||
*/
|
||||
public boolean isVoiceFirst()
|
||||
{
|
||||
return getStructure() == Structure.VOICE_VOICE || getStructure() == Structure.VOICE_FACCH1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this frame has voice data in the second half of the frame.
|
||||
*/
|
||||
public boolean isVoiceSecond()
|
||||
{
|
||||
return getStructure() == Structure.VOICE_VOICE || getStructure() == Structure.FACCH1_VOICE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the RDCH or RTCH frame has a SACCH field.
|
||||
*/
|
||||
public boolean hasSACCH()
|
||||
{
|
||||
return getRFChannel() != RFChannel.RTCH2 && (getStructure().isFACCH1() || getStructure().isVoice());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this is a Type-D frame with a SCCH segment.
|
||||
*/
|
||||
public boolean hasSCCH()
|
||||
{
|
||||
return getRFChannel() == RFChannel.RTCH2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this is a (4x) super frame (true) or single frame (false)
|
||||
*/
|
||||
public boolean isSuperFrame()
|
||||
{
|
||||
return getFraming() == Framing.SUPER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the LICH from the value.
|
||||
* @param value to lookup
|
||||
* @param isTypeD to differentiate between Type-D versus Standard systems.
|
||||
* @return matching entry or UNKNOWN if the value doesn't match any LICH values.
|
||||
*/
|
||||
public static LICH fromValue(int value, boolean isTypeD)
|
||||
{
|
||||
if(isTypeD && LOOKUP_MAP_TYPE_D.containsKey(value))
|
||||
{
|
||||
return LOOKUP_MAP_TYPE_D.get(value);
|
||||
}
|
||||
else if(!isTypeD && LOOKUP_MAP_STANDARD.containsKey(value))
|
||||
{
|
||||
return LOOKUP_MAP_STANDARD.get(value);
|
||||
}
|
||||
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the matching entry from the transmitted value. The channel and direction parameters should be from
|
||||
* a tracker for the majority of observed values for the channel since the channel type and direction normally
|
||||
* won't change within the transmission. These two additional values are for instance when the LICH has bit
|
||||
* errors that prevent a clean match up.
|
||||
* @param value transmitted
|
||||
* @param channel as tracked
|
||||
* @param direction as tracked
|
||||
* @return matching entry or UNKNOWN
|
||||
*/
|
||||
public static LICH fromValue(int value, RFChannel channel, Direction direction)
|
||||
{
|
||||
if(channel == RFChannel.RTCH2)
|
||||
{
|
||||
if(LOOKUP_MAP_TYPE_D.containsKey(value))
|
||||
{
|
||||
return LOOKUP_MAP_TYPE_D.get(value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(LOOKUP_MAP_STANDARD.containsKey(value))
|
||||
{
|
||||
return LOOKUP_MAP_STANDARD.get(value);
|
||||
}
|
||||
}
|
||||
|
||||
switch(channel)
|
||||
{
|
||||
case RCCH:
|
||||
return direction == Direction.OUTBOUND ? RCCH_OUTBOUND_SINGLE_UNKNOWN : RCCH_INBOUND_SINGLE_UNKNOWN;
|
||||
case RTCH:
|
||||
return direction == Direction.OUTBOUND ? RTCH_OUTBOUND_UNKNOWN : RTCH_INBOUND_UNKNOWN;
|
||||
case RTCH2:
|
||||
return direction == Direction.OUTBOUND ? RTCH_2_OUTBOUND_UNKNOWN : RTCH_2_INBOUND_UNKNOWN;
|
||||
case RDCH:
|
||||
return direction == Direction.OUTBOUND ? RDCH_OUTBOUND_UNKNOWN : RDCH_INBOUND_UNKNOWN;
|
||||
case RTCHC:
|
||||
return RTCH_C_OUTBOUND_UNKNOWN;
|
||||
default:
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmitted value.
|
||||
*/
|
||||
public int getValue()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* RF channel type
|
||||
*/
|
||||
public RFChannel getRFChannel()
|
||||
{
|
||||
return mRFChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Framing
|
||||
*/
|
||||
public Framing getFraming()
|
||||
{
|
||||
return mFraming;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the channel
|
||||
*/
|
||||
public Structure getStructure()
|
||||
{
|
||||
return mStructure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel direction
|
||||
*/
|
||||
public Direction getDirection()
|
||||
{
|
||||
return mDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the channel direction is outbound (true) or inbound (false).
|
||||
* @return true if outbound
|
||||
*/
|
||||
public boolean isOutbound()
|
||||
{
|
||||
return getDirection().equals(Direction.OUTBOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the channel is carrying a long CAC (true) or short CAC (false)
|
||||
* @return true if long CAC
|
||||
*/
|
||||
public boolean isLongCAC()
|
||||
{
|
||||
return this.equals(LICH.RCCH_INBOUND_SINGLE_CAC_LONG);
|
||||
}
|
||||
|
||||
static void main()
|
||||
{
|
||||
List<LICH> liches = new ArrayList<>(Arrays.stream(LICH.values()).toList());
|
||||
liches.sort(Comparator.comparingInt(LICH::getValue));
|
||||
|
||||
for(LICH setting : liches)
|
||||
{
|
||||
System.out.println(Integer.toHexString(setting.getValue()).toUpperCase() + ": " + setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer2;
|
||||
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.TransmissionMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Tracks the observed channel type and direction occurrence counts.
|
||||
*/
|
||||
public class LICHTracker
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LICHTracker.class);
|
||||
private static final int BUFFER_SIZE = 5;
|
||||
private final RFChannel[] mRFChannelBuffer = new RFChannel[BUFFER_SIZE];
|
||||
private final TransmissionMode mTransmissionMode;
|
||||
private int mBufferPointer = 0;
|
||||
private RFChannel mTrackedChannel = RFChannel.RCCH;
|
||||
private Direction mTrackedDirection = Direction.OUTBOUND;
|
||||
private List<Tracker> mTrackers = new ArrayList<>();
|
||||
private Map<RFChannel,Tracker> mTrackerMap = new HashMap<>();
|
||||
private int mCountOutbound;
|
||||
private int mCountInbound;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public LICHTracker(TransmissionMode transmissionMode)
|
||||
{
|
||||
mTransmissionMode = transmissionMode;
|
||||
|
||||
for(RFChannel channel : RFChannel.values())
|
||||
{
|
||||
Tracker tracker = new Tracker(channel);
|
||||
mTrackers.add(tracker);
|
||||
mTrackerMap.put(channel, tracker);
|
||||
}
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the transmission mode is Type D
|
||||
*/
|
||||
public boolean isTypeD()
|
||||
{
|
||||
return mTransmissionMode.isTypeD();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the channel tracked values.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
Arrays.fill(mRFChannelBuffer, RFChannel.UNKNOWN);
|
||||
mTrackedChannel = RFChannel.UNKNOWN;
|
||||
mTrackedDirection = Direction.OUTBOUND;
|
||||
mTrackers.forEach(Tracker::reset);
|
||||
}
|
||||
|
||||
/**
|
||||
* RF channel with the highest observed counts.
|
||||
*/
|
||||
public RFChannel getChannel()
|
||||
{
|
||||
return mTrackedChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel direction with the highest observed counts
|
||||
*/
|
||||
public Direction getDirection()
|
||||
{
|
||||
return mTrackedDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the decoded LICH
|
||||
* @param lich to track
|
||||
*/
|
||||
public void track(LICH lich)
|
||||
{
|
||||
if(lich == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//Update RF channel count and tracked RF channel as necessary.
|
||||
mTrackerMap.get(mRFChannelBuffer[mBufferPointer]).decrement();
|
||||
|
||||
mRFChannelBuffer[mBufferPointer++] = lich.getRFChannel();
|
||||
mBufferPointer %= BUFFER_SIZE;
|
||||
mTrackerMap.get(lich.getRFChannel()).increment();
|
||||
|
||||
if(mTrackedChannel != lich.getRFChannel())
|
||||
{
|
||||
Collections.sort(mTrackers);
|
||||
mTrackedChannel = mTrackers.getLast().getRFChannel();
|
||||
}
|
||||
|
||||
if(lich.getDirection() == Direction.OUTBOUND)
|
||||
{
|
||||
mCountOutbound = Math.min(++mCountOutbound, BUFFER_SIZE);
|
||||
mCountInbound = Math.max(--mCountInbound, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
mCountInbound = Math.min(++mCountInbound, BUFFER_SIZE);
|
||||
mCountOutbound = Math.max(--mCountOutbound, 0);
|
||||
}
|
||||
|
||||
if(lich.getDirection() != mTrackedDirection)
|
||||
{
|
||||
mTrackedDirection = (mCountOutbound > mCountInbound) ? Direction.OUTBOUND : Direction.INBOUND;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error while updating LICH tracker for LICH: {}", lich);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the occurrence count of an RF channel
|
||||
*/
|
||||
static class Tracker implements Comparable<Tracker>
|
||||
{
|
||||
private final RFChannel mChannel;
|
||||
private int mCount;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param channel to track occurrence counts
|
||||
*/
|
||||
public Tracker(RFChannel channel)
|
||||
{
|
||||
mChannel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracked channel
|
||||
*/
|
||||
public RFChannel getRFChannel()
|
||||
{
|
||||
return mChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of occurrences
|
||||
*/
|
||||
public int getCount()
|
||||
{
|
||||
return mCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the counter
|
||||
*/
|
||||
public void increment()
|
||||
{
|
||||
mCount = Math.min(++mCount, BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements the counter
|
||||
*/
|
||||
public void decrement()
|
||||
{
|
||||
mCount = Math.max(--mCount, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Tracker o)
|
||||
{
|
||||
return Integer.compare(mCount, o.mCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the counter to zero
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
mCount = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "COUNT: " + mCount + " " + mChannel.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer2;
|
||||
|
||||
/**
|
||||
* Layer 2 RF Channel Type enumeration
|
||||
*/
|
||||
public enum RFChannel
|
||||
{
|
||||
RCCH("CONTROL CHANNEL"),
|
||||
RTCH("TRAFFIC CHANNEL"),
|
||||
RTCH2("TRAFFIC CHANNEL 2"),
|
||||
RDCH("DATA CHANNEL"),
|
||||
RTCHC("COMPOSITE CONTROL CHANNEL"),
|
||||
UNKNOWN("UNKNOWN");
|
||||
|
||||
private final String mLabel;
|
||||
|
||||
RFChannel(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer2;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNLayer3Message;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageFactory;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
|
||||
/**
|
||||
* Assembles SACCH fragments into a Layer 3 message
|
||||
*/
|
||||
public class SACCHAssembler
|
||||
{
|
||||
private SACCHFragment mFragment1;
|
||||
private SACCHFragment mFragment2;
|
||||
private SACCHFragment mFragment3;
|
||||
private SACCHFragment mFragment4;
|
||||
|
||||
/**
|
||||
* Processes the SACCH fragment sequence to reassemble the embedded layer 3 message
|
||||
* @param fragment to process
|
||||
* @return assembled layer 3 message or null.
|
||||
*/
|
||||
public NXDNLayer3Message process(SACCHFragment fragment)
|
||||
{
|
||||
if(fragment.isValid())
|
||||
{
|
||||
switch(fragment.getStructure())
|
||||
{
|
||||
case SACCH_1_OF_4:
|
||||
mFragment1 = fragment;
|
||||
break;
|
||||
case SACCH_2_OF_4:
|
||||
mFragment2 = fragment;
|
||||
break;
|
||||
case SACCH_3_OF_4:
|
||||
mFragment3 = fragment;
|
||||
break;
|
||||
case SACCH_4_OF_4_LAST_OR_SINGLE:
|
||||
mFragment4 = fragment;
|
||||
return assemble();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles the fragments into a complete message
|
||||
* @return
|
||||
*/
|
||||
private NXDNLayer3Message assemble()
|
||||
{
|
||||
NXDNLayer3Message layer3 = null;
|
||||
|
||||
if(mFragment1 != null && mFragment2 != null && mFragment3 != null && mFragment4 != null && mFragment4.isSuperFrame())
|
||||
{
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage(72);
|
||||
message.load(0, mFragment1.getFragment());
|
||||
message.load(18, mFragment2.getFragment());
|
||||
message.load(36, mFragment3.getFragment());
|
||||
message.load(54, mFragment4.getFragment());
|
||||
NXDNMessageType type = NXDNMessageType.getTraffic(message, mFragment4.getLICH());
|
||||
layer3 = NXDNMessageFactory.get(type, message, mFragment4.getTimestamp(), mFragment4.getRAN(), mFragment4.getLICH());
|
||||
}
|
||||
else if(mFragment4 != null && !mFragment4.isSuperFrame())
|
||||
{
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage(18);
|
||||
message.load(0, mFragment4.getFragment());
|
||||
NXDNMessageType type = NXDNMessageType.getTraffic(message, mFragment4.getLICH());
|
||||
layer3 = NXDNMessageFactory.get(type, message, mFragment4.getTimestamp(), mFragment4.getRAN(), mFragment4.getLICH());
|
||||
}
|
||||
|
||||
mFragment1 = null;
|
||||
mFragment2 = null;
|
||||
mFragment3 = null;
|
||||
mFragment4 = null;
|
||||
|
||||
return layer3;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer2;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.NXDNMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.Structure;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SACCH message fragment
|
||||
*/
|
||||
public class SACCHFragment extends NXDNMessage
|
||||
{
|
||||
private static final IntField STRUCTURE = IntField.length2(0);
|
||||
private static final IntField RADIO_ACCESS_NUMBER = IntField.length6(2);
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
*/
|
||||
public SACCHFragment(CorrectedBinaryMessage message, long timestamp, LICH lich)
|
||||
{
|
||||
super(message, timestamp, message.getInt(RADIO_ACCESS_NUMBER), lich);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = getMessageBuilder();
|
||||
sb.append(getStructure());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
sb.append(" LICH:").append(getLICH());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* SACCH message fragment
|
||||
*/
|
||||
public CorrectedBinaryMessage getFragment()
|
||||
{
|
||||
return getMessage().getSubMessage(8, 26);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the SACCH is standalone (false) or a superframe fragment (true).
|
||||
*/
|
||||
public boolean isSuperFrame()
|
||||
{
|
||||
return getLICH().isSuperFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure of the SACCH message
|
||||
* @return
|
||||
*/
|
||||
public Structure getStructure()
|
||||
{
|
||||
return Structure.fromTrafficValue(getMessage().getInt(STRUCTURE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Radio Access Network (RAN) code
|
||||
*/
|
||||
public int getRAN()
|
||||
{
|
||||
return getMessage().getInt(RADIO_ACCESS_NUMBER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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.nxdn.layer2;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* NXDN Scrambler.
|
||||
*
|
||||
* Note: The first four bits of the scramble sequence are all zeros meaning that the first two symbols of the LICH do
|
||||
* not get scrambled. This aligns with the ICD hinting that you could use the first two symbols of the LICH that
|
||||
* represent the RF channel type as part of the sync pattern for sync detection, once you concretely identify the
|
||||
* RF channel type.
|
||||
*/
|
||||
public class Scrambler
|
||||
{
|
||||
private static final int INITIAL_FILL = 0x0E4;
|
||||
|
||||
/**
|
||||
* Generates the scrambler sequence for the requested length.
|
||||
* @param length of scramble sequence
|
||||
* @return generated scramble sequence.
|
||||
*/
|
||||
public static BinaryMessage generate(int length)
|
||||
{
|
||||
BinaryMessage sequence = new BinaryMessage(length);
|
||||
|
||||
int register = INITIAL_FILL;
|
||||
boolean output;
|
||||
int x = 0;
|
||||
|
||||
while(x < length)
|
||||
{
|
||||
output = (register & 1) == 1;
|
||||
|
||||
if(output)
|
||||
{
|
||||
sequence.set(x);
|
||||
}
|
||||
|
||||
x +=2;
|
||||
|
||||
//XOR output with 5-4 tap for the feedback bit
|
||||
if(output ^ ((register & 0x10) == 0x10))
|
||||
{
|
||||
//Set the high order feedback bit before we right shift it into place.
|
||||
register |= 0x200;
|
||||
}
|
||||
|
||||
//Unsigned right shift by 1 with no integer shifting wrap-around
|
||||
register >>>= 1;
|
||||
}
|
||||
|
||||
return sequence;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer2;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* LICH structure enumeration
|
||||
*/
|
||||
public enum Structure
|
||||
{
|
||||
BEACON,
|
||||
CAC_NORMAL,
|
||||
CAC_IDLE,
|
||||
CAC_COMMON,
|
||||
CAC_LONG,
|
||||
CAC_SHORT,
|
||||
VOICE_VOICE,
|
||||
FACCH1_VOICE,
|
||||
VOICE_FACCH1,
|
||||
FACCH1_FACCH1,
|
||||
FACCH1_GUARD,
|
||||
FACCH2,
|
||||
FACCH3,
|
||||
UDCH,
|
||||
UDCH2,
|
||||
UNKNOWN;
|
||||
|
||||
/**
|
||||
* Data carrying structures
|
||||
*/
|
||||
private static final EnumSet<Structure> DATA = EnumSet.of(FACCH2, FACCH3, UDCH, UDCH2);
|
||||
|
||||
/**
|
||||
* FACCH1 in first fragment
|
||||
*/
|
||||
private static final EnumSet<Structure> FACCH1_FIRST = EnumSet.of(FACCH1_FACCH1, FACCH1_GUARD, FACCH1_VOICE);
|
||||
|
||||
/**
|
||||
* FACCH1 in second fragment
|
||||
*/
|
||||
private static final EnumSet<Structure> FACCH1_SECOND = EnumSet.of(FACCH1_FACCH1, VOICE_FACCH1);
|
||||
|
||||
/**
|
||||
* Indicates if the frame contains audio frames
|
||||
*/
|
||||
public boolean isVoice()
|
||||
{
|
||||
return this.equals(VOICE_VOICE) || this.equals(FACCH1_VOICE) || this.equals(VOICE_FACCH1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this structure contains a FACCH1 frame
|
||||
*/
|
||||
public boolean isFACCH1()
|
||||
{
|
||||
return FACCH1_FIRST.contains(this) || FACCH1_SECOND.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if a FACCH1 is in the first half of the frame
|
||||
*/
|
||||
public boolean isFACCH1First()
|
||||
{
|
||||
return FACCH1_FIRST.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if a FACCH1 is in the second half of the frame
|
||||
*/
|
||||
public boolean isFACCH1Second()
|
||||
{
|
||||
return FACCH1_SECOND.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this is a data structure
|
||||
*/
|
||||
public boolean isData()
|
||||
{
|
||||
return DATA.contains(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.module.decode.nxdn.NXDNMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
|
||||
/**
|
||||
* Base NXDN layer 3 message implementation
|
||||
*/
|
||||
public abstract class NXDNLayer3Message extends NXDNMessage
|
||||
{
|
||||
protected static int FLAG_1_INDEX = 0;
|
||||
protected static int FLAG_2_INDEX = 1;
|
||||
protected static final IntField MESSAGE_TYPE = IntField.length6(OCTET_0 + 2);
|
||||
protected static final IntField IDENTIFIER_OCTET_3 = IntField.length16(OCTET_3);
|
||||
protected static final IntField IDENTIFIER_OCTET_5 = IntField.length16(OCTET_5);
|
||||
private final NXDNMessageType mType;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
*/
|
||||
public NXDNLayer3Message(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, ran, lich);
|
||||
mType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the message type value from the binary message.
|
||||
*
|
||||
* @param message with message type value.
|
||||
* @return value.
|
||||
*/
|
||||
public static int getTypeValue(CorrectedBinaryMessage message)
|
||||
{
|
||||
return message.getInt(MESSAGE_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Message type
|
||||
*
|
||||
* @return message type
|
||||
*/
|
||||
public NXDNMessageType getMessageType()
|
||||
{
|
||||
return mType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this is a Type-D message type
|
||||
*/
|
||||
public boolean isTypeD()
|
||||
{
|
||||
return getMessageType().isTypeD();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.AdjacentSiteInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.AdjacentSiteInformationTypeD;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.ControlChannelInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.DigitalStationIDInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.FailureStatusInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.Idle;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.ServiceInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.ServiceInformationTypeD;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.SiteInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.CallConnectionResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DataCallAcknowledge;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DataCallAssignment;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DataCallAssignmentDuplicateControl;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DataCallAssignmentDuplicateTraffic;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DataCallBlock;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DataCallHeader;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DataCallReceptionRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DataCallReceptionResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DataCallRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DataCallResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.Disconnect;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.DisconnectRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.HeaderDelay;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.RemoteControlRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.RemoteControlRequestWithESN;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.RemoteControlResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.RemoteControlResponseWithESN;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.ShortDataCallBlock;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.ShortDataCallRequestHeader;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.ShortDataCallResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.ShortDataInitializationVector;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.StatusInquiryRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.StatusInquiryResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.StatusRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.StatusResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.TransmissionRelease;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.TransmissionReleaseExtension;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCall;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallAssignment;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallAssignmentDuplicateControl;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallAssignmentDuplicateTraffic;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallConnectionRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallConnectionResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallInitializationVector;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallReceptionRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallReceptionResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.call.VoiceCallResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.data.GPS;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.data.NXDNPacketMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.data.PacketSequence;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.AuthenticationInquiryRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.AuthenticationInquiryRequestMultiSystem;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.AuthenticationInquiryResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.AuthenticationInquiryResponseMultiSystem;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.AuthenticationParameterInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.AuthenticationResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.DataWriteAcknowledge;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.DataWriteBlock;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.DataWriteHeader;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.GroupRegistrationRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.GroupRegistrationResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.GroupRegistrationResponseTypeD;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.RegistrationClearRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.RegistrationClearResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.RegistrationCommand;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.RegistrationRequest;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.RegistrationResponse;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.mobility.RegistrationResponseTypeD;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.proprietary.ProprietaryForm;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.proprietary.TalkerAlias;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.CallCompleteDestinationInfo4;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.CallInProgressDestinationInfo2;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.CallInProgressDestinationInfo4;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.CallInProgressSourceID;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.CallInfo;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.InitializationVectorPart1;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.InitializationVectorPart2;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.RepeaterFree;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.RepeaterHaltCWID;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.RepeaterIdle;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.SiteID;
|
||||
|
||||
/**
|
||||
* Factory for creating NXDN messages
|
||||
*/
|
||||
public class NXDNMessageFactory
|
||||
{
|
||||
/**
|
||||
* Creates an NXDN message parser for the packet sequence.
|
||||
* @param packetSequence to wrap in a message parser
|
||||
* @return message parser or null
|
||||
*/
|
||||
public static NXDNPacketMessage get(PacketSequence packetSequence)
|
||||
{
|
||||
if(GPS.isGPS(packetSequence))
|
||||
{
|
||||
return new GPS(packetSequence);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an NXDN message parser for a message on the outbound control channel (CC)
|
||||
* @param message content
|
||||
* @param timestamp of the message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
* @return message parser
|
||||
*/
|
||||
public static NXDNLayer3Message get(NXDNMessageType type, CorrectedBinaryMessage message, long timestamp, int ran, LICH lich)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case CONTROL_IN_01_CC_VOICE_CALL_REQUEST:
|
||||
case TYPE_D_IN_00_CC_CALL_REQUEST: //Plain call request is handled as voice call request
|
||||
case TYPE_D_IN_04_CC_VOICE_CALL_REQUEST:
|
||||
return new VoiceCallRequest(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_01_CC_VOICE_CALL_RESPONSE:
|
||||
case TYPE_D_OUT_00_CC_CALL_RESPONSE:
|
||||
return new VoiceCallResponse(message, timestamp, type, ran, lich);
|
||||
case TRAFFIC_IN_01_CC_VOICE_CALL:
|
||||
case TRAFFIC_OUT_01_CC_VOICE_CALL:
|
||||
case TYPE_D_IN_01_CC_VOICE_CALL:
|
||||
case TYPE_D_OUT_01_CC_VOICE_CALL:
|
||||
return new VoiceCall(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_02_CC_VOICE_CALL_RECEPTION_REQUEST:
|
||||
case TRAFFIC_OUT_02_CC_VOICE_CALL_RECEPTION_REQUEST:
|
||||
return new VoiceCallReceptionRequest(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_02_CC_VOICE_CALL_RECEPTION_RESPONSE:
|
||||
case TRAFFIC_IN_02_CC_VOICE_CALL_RECEPTION_RESPONSE:
|
||||
return new VoiceCallReceptionResponse(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_03_CC_VOICE_CALL_CONNECTION_REQUEST:
|
||||
return new VoiceCallConnectionRequest(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_03_CC_VOICE_CALL_CONNECTION_RESPONSE:
|
||||
return new VoiceCallConnectionResponse(message, timestamp, type, ran, lich);
|
||||
case TRAFFIC_IN_03_CC_VOICE_CALL_INITIALIZATION_VECTOR:
|
||||
case TRAFFIC_OUT_03_CC_VOICE_CALL_INITIALIZATION_VECTOR:
|
||||
case TYPE_D_IN_03_CC_VOICE_CALL_INITIALIZATION_VECTOR:
|
||||
case TYPE_D_OUT_03_CC_VOICE_CALL_INITIALIZATION_VECTOR:
|
||||
return new VoiceCallInitializationVector(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_04_CC_VOICE_CALL_ASSIGNMENT:
|
||||
case TRAFFIC_OUT_04_CC_VOICE_CALL_ASSIGNMENT:
|
||||
case TYPE_D_OUT_04_CC_VOICE_CALL_ASSIGNMENT:
|
||||
return new VoiceCallAssignment(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_05_CC_VOICE_CALL_ASSIGNMENT_DUPLICATE:
|
||||
return new VoiceCallAssignmentDuplicateControl(message, timestamp, type, ran, lich);
|
||||
case TRAFFIC_OUT_05_CC_VOICE_CALL_ASSIGNMENT_DUPLICATE:
|
||||
return new VoiceCallAssignmentDuplicateTraffic(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_OUT_06_CC_CALL_CONNECTION_RESPONSE:
|
||||
return new CallConnectionResponse(message, timestamp, type, ran, lich);
|
||||
case TRAFFIC_OUT_07_CC_TRANSMISSION_RELEASE_EXTENSION:
|
||||
case TYPE_D_OUT_07_CC_TRANSMISSION_RELEASE_EXTENSION:
|
||||
return new TransmissionReleaseExtension(message, timestamp, type, ran, lich);
|
||||
case TRAFFIC_IN_08_CC_TRANSMISSION_RELEASE:
|
||||
case TRAFFIC_OUT_08_CC_TRANSMISSION_RELEASE:
|
||||
case TYPE_D_IN_08_CC_TRANSMISSION_RELEASE:
|
||||
case TYPE_D_OUT_08_CC_TRANSMISSION_RELEASE:
|
||||
return new TransmissionRelease(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_09_CC_DATA_CALL_REQUEST:
|
||||
return new DataCallRequest(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_09_CC_DATA_CALL_RESPONSE:
|
||||
return new DataCallResponse(message, timestamp, type, ran, lich);
|
||||
case TRAFFIC_IN_09_CC_DATA_CALL_HEADER:
|
||||
case TRAFFIC_OUT_09_CC_DATA_CALL_HEADER:
|
||||
case TYPE_D_IN_09_CC_DATA_CALL_HEADER:
|
||||
case TYPE_D_OUT_09_CC_DATA_CALL_HEADER:
|
||||
return new DataCallHeader(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_10_CC_DATA_CALL_RECEPTION_REQUEST:
|
||||
case TRAFFIC_OUT_10_CC_DATA_CALL_RECEPTION_REQUEST:
|
||||
return new DataCallReceptionRequest(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_10_CC_DATA_CALL_RECEPTION_RESPONSE:
|
||||
return new DataCallReceptionResponse(message, timestamp, type, ran, lich);
|
||||
case TRAFFIC_IN_11_CC_DATA_CALL_BLOCK:
|
||||
case TRAFFIC_OUT_11_CC_DATA_CALL_BLOCK:
|
||||
case TYPE_D_IN_11_CC_DATA_CALL_BLOCK:
|
||||
case TYPE_D_OUT_11_CC_DATA_CALL_BLOCK:
|
||||
return new DataCallBlock(message, timestamp, type, ran, lich);
|
||||
case TRAFFIC_IN_12_CC_DATA_CALL_ACKNOWLEDGE:
|
||||
case TRAFFIC_OUT_12_CC_DATA_CALL_ACKNOWLEDGE:
|
||||
case TYPE_D_IN_12_CC_DATA_CALL_ACKNOWLEDGE:
|
||||
case TYPE_D_OUT_12_CC_DATA_CALL_ACKNOWLEDGE:
|
||||
return new DataCallAcknowledge(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_13_CC_DATA_CALL_ASSIGNMENT_DUPLICATE:
|
||||
return new DataCallAssignmentDuplicateControl(message, timestamp, type, ran, lich);
|
||||
case TRAFFIC_OUT_13_CC_DATA_CALL_ASSIGNMENT_DUPLICATE:
|
||||
return new DataCallAssignmentDuplicateTraffic(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_14_CC_DATA_CALL_ASSIGNMENT:
|
||||
case TRAFFIC_OUT_14_CC_DATA_CALL_ASSIGNMENT:
|
||||
return new DataCallAssignment(message, timestamp, type, ran, lich);
|
||||
case TRAFFIC_IN_15_CC_HEADER_DELAY:
|
||||
case TRAFFIC_OUT_15_CC_HEADER_DELAY:
|
||||
case TYPE_D_IN_15_CC_HEADER_DELAY:
|
||||
case TYPE_D_OUT_15_CC_HEADER_DELAY:
|
||||
return new HeaderDelay(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_16_CC_IDLE:
|
||||
case TRAFFIC_OUT_16_CC_IDLE:
|
||||
case TYPE_D_IN_16_CC_IDLE:
|
||||
case TYPE_D_OUT_16_CC_IDLE:
|
||||
return new Idle(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_17_CC_DISCONNECT:
|
||||
case TRAFFIC_OUT_17_CC_DISCONNECT:
|
||||
case TYPE_D_OUT_17_CC_DISCONNECT:
|
||||
return new Disconnect(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_17_CC_DISCONNECT_REQUEST:
|
||||
case TRAFFIC_IN_17_CC_DISCONNECT_REQUEST:
|
||||
case TYPE_D_IN_17_CC_DISCONNECT_REQUEST:
|
||||
return new DisconnectRequest(message, timestamp, type, ran, lich);
|
||||
|
||||
//Broadcast Messages
|
||||
case CONTROL_OUT_23_BC_DIGITAL_STATION_ID_INFORMATION:
|
||||
case TRAFFIC_OUT_23_BC_DIGITAL_STATION_ID_INFORMATION:
|
||||
case TYPE_D_OUT_23_BC_DIGITAL_STATION_ID:
|
||||
return new DigitalStationIDInformation(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_24_BC_SITE_INFORMATION:
|
||||
case TRAFFIC_OUT_24_BC_SITE_INFORMATION:
|
||||
return new SiteInformation(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_25_BC_SERVICE_INFORMATION:
|
||||
case TRAFFIC_OUT_25_BC_SERVICE_INFORMATION:
|
||||
return new ServiceInformation(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_OUT_25_BC_SERVICE_INFORMATION:
|
||||
return new ServiceInformationTypeD(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_26_BC_CONTROL_CHANNEL_INFORMATION:
|
||||
case TRAFFIC_OUT_26_BC_CONTROL_CHANNEL_INFORMATION:
|
||||
return new ControlChannelInformation(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_27_BC_ADJACENT_SITE_INFORMATION:
|
||||
case TRAFFIC_OUT_27_BC_ADJACENT_SITE_INFORMATION:
|
||||
return new AdjacentSiteInformation(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_OUT_27_BC_ADJACENT_SITE_INFORMATION:
|
||||
return new AdjacentSiteInformationTypeD(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_28_BC_FAILURE_STATUS_INFORMATION:
|
||||
case TRAFFIC_OUT_28_BC_FAILURE_STATUS_INFORMATION:
|
||||
return new FailureStatusInformation(message, timestamp, type, ran, lich);
|
||||
|
||||
//Mobility Management Messages
|
||||
case CONTROL_IN_32_MM_REGISTRATION_REQUEST:
|
||||
case TYPE_D_IN_32_MM_REGISTRATION_REQUEST:
|
||||
return new RegistrationRequest(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_32_MM_REGISTRATION_RESPONSE:
|
||||
return new RegistrationResponse(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_OUT_32_MM_REGISTRATION_RESPONSE:
|
||||
return new RegistrationResponseTypeD(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_34_MM_REGISTRATION_CLEAR_REQUEST:
|
||||
case TYPE_D_IN_34_MM_REGISTRATION_CLEAR_REQUEST:
|
||||
return new RegistrationClearRequest(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_34_MM_REGISTRATION_CLEAR_RESPONSE:
|
||||
case TYPE_D_OUT_34_MM_REGISTRATION_CLEAR_RESPONSE:
|
||||
return new RegistrationClearResponse(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_35_MM_REGISTRATION_COMMAND:
|
||||
return new RegistrationCommand(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_36_MM_GROUP_REGISTRATION_REQUEST:
|
||||
case TYPE_D_IN_36_MM_GROUP_REGISTRATION_REQUEST:
|
||||
return new GroupRegistrationRequest(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_36_MM_GROUP_REGISTRATION_RESPONSE:
|
||||
return new GroupRegistrationResponse(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_OUT_36_MM_GROUP_REGISTRATION_RESPONSE:
|
||||
return new GroupRegistrationResponseTypeD(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_OUT_38_MM_AUTHENTICATION_PARAMETER_INFORMATION:
|
||||
return new AuthenticationParameterInformation(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_IN_39_MM_AUTHENTICATION_RESPONSE:
|
||||
return new AuthenticationResponse(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_40_MM_AUTHENTICATION_INQUIRY_REQUEST:
|
||||
case TYPE_D_IN_40_MM_AUTHENTICATION_INQUIRY_REQUEST:
|
||||
case TYPE_D_OUT_40_MM_AUTHENTICATION_INQUIRY_REQUEST:
|
||||
return new AuthenticationInquiryRequest(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_41_MM_AUTHENTICATION_INQUIRY_RESPONSE:
|
||||
case TYPE_D_IN_41_MM_AUTHENTICATION_INQUIRY_RESPONSE:
|
||||
case TYPE_D_OUT_41_MM_AUTHENTICATION_INQUIRY_RESPONSE:
|
||||
return new AuthenticationInquiryResponse(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM:
|
||||
case TRAFFIC_IN_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM:
|
||||
case TRAFFIC_OUT_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM:
|
||||
return new AuthenticationInquiryRequestMultiSystem(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_43_MM_AUTHENTICATION_INQUIRY_RESPONSE_MULTI_SYSTEM:
|
||||
case TRAFFIC_IN_43_MM_AUTHORIZATION_INQUIRY_RESPONSE_MULTI_SYSTEM:
|
||||
case TRAFFIC_OUT_43_MM_AUTHORIZATION_INQUIRY_RESPONSE_MULTI_SYSTEM:
|
||||
return new AuthenticationInquiryResponseMultiSystem(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_OUT_43_MM_DATA_WRITE_HEADER:
|
||||
return new DataWriteHeader(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_OUT_44_MM_DATA_WRITE_DATA:
|
||||
return new DataWriteBlock(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_IN_45_MM_DATA_WRITE_ACKNOWLEDGE:
|
||||
return new DataWriteAcknowledge(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_48_CC_STATUS_INQUIRY_REQUEST:
|
||||
case CONTROL_OUT_48_CC_STATUS_INQUIRY_REQUEST:
|
||||
case TRAFFIC_IN_48_CC_STATUS_INQUIRY_REQUEST:
|
||||
case TRAFFIC_OUT_48_CC_STATUS_INQUIRY_REQUEST:
|
||||
case TYPE_D_IN_48_CC_STATUS_INQUIRY_REQUEST:
|
||||
case TYPE_D_OUT_48_CC_STATUS_INQUIRY_REQUEST:
|
||||
return new StatusInquiryRequest(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_49_CC_STATUS_INQUIRY_RESPONSE:
|
||||
case CONTROL_OUT_49_CC_STATUS_INQUIRY_RESPONSE:
|
||||
case TRAFFIC_IN_49_CC_STATUS_INQUIRY_RESPONSE:
|
||||
case TRAFFIC_OUT_49_CC_STATUS_INQUIRY_RESPONSE:
|
||||
case TYPE_D_IN_49_CC_STATUS_INQUIRY_RESPONSE:
|
||||
case TYPE_D_OUT_49_CC_STATUS_INQUIRY_RESPONSE:
|
||||
return new StatusInquiryResponse(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_50_CC_STATUS_REQUEST:
|
||||
case CONTROL_OUT_50_CC_STATUS_REQUEST:
|
||||
case TRAFFIC_IN_50_CC_STATUS_REQUEST:
|
||||
case TRAFFIC_OUT_50_CC_STATUS_REQUEST:
|
||||
case TYPE_D_IN_50_CC_STATUS_REQUEST:
|
||||
case TYPE_D_OUT_50_CC_STATUS_REQUEST:
|
||||
return new StatusRequest(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_51_CC_STATUS_RESPONSE:
|
||||
case CONTROL_OUT_51_CC_STATUS_RESPONSE:
|
||||
case TRAFFIC_IN_51_CC_STATUS_RESPONSE:
|
||||
case TRAFFIC_OUT_51_CC_STATUS_RESPONSE:
|
||||
case TYPE_D_IN_51_CC_STATUS_RESPONSE:
|
||||
case TYPE_D_OUT_51_CC_STATUS_RESPONSE:
|
||||
return new StatusResponse(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_52_CC_REMOTE_CONTROL_REQUEST:
|
||||
case CONTROL_OUT_52_CC_REMOTE_CONTROL_REQUEST:
|
||||
case TRAFFIC_IN_52_CC_REMOTE_CONTROL_REQUEST:
|
||||
case TRAFFIC_OUT_52_CC_REMOTE_CONTROL_REQUEST:
|
||||
case TYPE_D_IN_52_CC_REMOTE_CONTROL_REQUEST:
|
||||
case TYPE_D_OUT_52_CC_REMOTE_CONTROL_REQUEST:
|
||||
return new RemoteControlRequest(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_53_CC_REMOTE_CONTROL_RESPONSE:
|
||||
case CONTROL_OUT_53_CC_REMOTE_CONTROL_RESPONSE:
|
||||
case TRAFFIC_IN_53_CC_REMOTE_CONTROL_RESPONSE:
|
||||
case TRAFFIC_OUT_53_CC_REMOTE_CONTROL_RESPONSE:
|
||||
case TYPE_D_IN_53_CC_REMOTE_CONTROL_RESPONSE:
|
||||
case TYPE_D_OUT_53_CC_REMOTE_CONTROL_RESPONSE:
|
||||
return new RemoteControlResponse(message, timestamp, type, ran, lich);
|
||||
case CONTROL_OUT_54_CC_REMOTE_CONTROL_REQUEST_WITH_ESN:
|
||||
return new RemoteControlRequestWithESN(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_55_CC_REMOTE_CONTROL_RESPONSE_WITH_ESN:
|
||||
return new RemoteControlResponseWithESN(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_56_CC_SHORT_DATA_CALL_REQUEST_HEADER:
|
||||
case CONTROL_OUT_56_CC_SHORT_DATA_CALL_REQUEST_HEADER:
|
||||
case TRAFFIC_IN_56_CC_SHORT_DATA_CALL_REQUEST_HEADER:
|
||||
case TRAFFIC_OUT_56_CC_SHORT_DATA_CALL_REQUEST_HEADER:
|
||||
case TYPE_D_IN_56_CC_SHORT_DATA_CALL_REQUEST_HEADER:
|
||||
case TYPE_D_OUT_56_CC_SHORT_DATA_CALL_REQUEST_HEADER:
|
||||
return new ShortDataCallRequestHeader(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_57_CC_SHORT_DATA_CALL_REQUEST_USER_DATA:
|
||||
case CONTROL_OUT_57_CC_SHORT_DATA_CALL_REQUEST_USER_DATA:
|
||||
case TRAFFIC_IN_57_CC_SHORT_DATA_CALL_BLOCK:
|
||||
case TRAFFIC_OUT_57_CC_SHORT_DATA_CALL_BLOCK:
|
||||
case TYPE_D_IN_57_CC_SHORT_DATA_CALL_REQUEST_USER_DATA:
|
||||
case TYPE_D_OUT_57_CC_SHORT_DATA_CALL_REQUEST_USER_DATA:
|
||||
return new ShortDataCallBlock(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR:
|
||||
case CONTROL_OUT_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR:
|
||||
case TRAFFIC_IN_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR:
|
||||
case TRAFFIC_OUT_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR:
|
||||
case TYPE_D_IN_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR:
|
||||
case TYPE_D_OUT_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR:
|
||||
return new ShortDataInitializationVector(message, timestamp, type, ran, lich);
|
||||
case CONTROL_IN_59_CC_SHORT_DATA_CALL_RESPONSE:
|
||||
case CONTROL_OUT_59_CC_SHORT_DATA_CALL_RESPONSE:
|
||||
case TRAFFIC_OUT_59_CC_SHORT_DATA_CALL_RESPONSE:
|
||||
case TRAFFIC_IN_59_CC_SHORT_DATA_CALL_RESPONSE:
|
||||
case TYPE_D_IN_59_CC_SHORT_DATA_CALL_RESPONSE:
|
||||
case TYPE_D_OUT_59_CC_SHORT_DATA_CALL_RESPONSE:
|
||||
return new ShortDataCallResponse(message, timestamp, type, ran, lich);
|
||||
|
||||
case PROPRIETARY_FORM:
|
||||
return new ProprietaryForm(message, timestamp, type, ran, lich);
|
||||
case TALKER_ALIAS:
|
||||
return new TalkerAlias(message, timestamp, type, ran, lich);
|
||||
|
||||
//NXDN Tpe-D SCCH messages
|
||||
case TYPE_D_SCCH_IN_INFO_4_CALL_IN_PROGRESS_DESTINATION:
|
||||
case TYPE_D_SCCH_OUT_INFO_4_CALL_IN_PROGRESS_DESTINATION:
|
||||
return new CallInProgressDestinationInfo4(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_SCCH_IN_INFO_4_CALL_COMPLETE_DESTINATION:
|
||||
case TYPE_D_SCCH_OUT_INFO_4_CALL_COMPLETE_DESTINATION:
|
||||
return new CallCompleteDestinationInfo4(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_SCCH_OUT_INFO_4_REPEATER_IDLE:
|
||||
return new RepeaterIdle(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_SCCH_OUT_INFO_4_REPEATER_FREE:
|
||||
return new RepeaterFree(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_SCCH_OUT_INFO_4_REPEATER_HALT:
|
||||
return new RepeaterHaltCWID(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_SCCH_OUT_INFO_4_SITE_ID:
|
||||
return new SiteID(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_SCCH_IN_INFO_3_INITIALIZATION_VECTOR_PART2:
|
||||
case TYPE_D_SCCH_OUT_INFO_3_INITIALIZATION_VECTOR_PART2:
|
||||
return new InitializationVectorPart2(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_SCCH_IN_INFO_3_CALL_IN_PROGRESS_SOURCE:
|
||||
case TYPE_D_SCCH_OUT_INFO_3_CALL_IN_PROGRESS_SOURCE:
|
||||
return new CallInProgressSourceID(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_SCCH_IN_INFO_2_CALL_IN_PROGRESS_DESTINATION:
|
||||
case TYPE_D_SCCH_OUT_INFO_2_CALL_IN_PROGRESS_DESTINATION:
|
||||
return new CallInProgressDestinationInfo2(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_SCCH_IN_INFO_1_CALL_INFO:
|
||||
case TYPE_D_SCCH_OUT_INFO_1_CALL_INFO:
|
||||
return new CallInfo(message, timestamp, type, ran, lich);
|
||||
case TYPE_D_SCCH_IN_INFO_1_INITIALIZATION_VECTOR_PART1:
|
||||
case TYPE_D_SCCH_OUT_INFO_1_INITIALIZATION_VECTOR_PART1:
|
||||
return new InitializationVectorPart1(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
return new UnknownMessage(message, timestamp, type, ran, lich);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,707 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.proprietary.ProprietaryForm;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.Information1;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.Information3;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.Information4;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.scch.SCCH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.Vendor;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Enumeration of NXDN message types.
|
||||
*/
|
||||
public enum NXDNMessageType
|
||||
{
|
||||
//NOTE: do not modify entry order. We use EnumSet.range() below that expects the current ordering.
|
||||
|
||||
//Call Control
|
||||
CONTROL_IN_01_CC_VOICE_CALL_REQUEST(1, "VOICE CALL REQUEST"),
|
||||
CONTROL_IN_02_CC_VOICE_CALL_RECEPTION_RESPONSE(2, "VOICE CALL RECEPTION RESPONSE"),
|
||||
CONTROL_IN_03_CC_VOICE_CALL_CONNECTION_REQUEST(3, "VOICE CALL CONNECTION REQUEST"),
|
||||
CONTROL_IN_09_CC_DATA_CALL_REQUEST(9, "DATA CALL REQUEST"),
|
||||
CONTROL_IN_10_CC_DATA_CALL_RECEPTION_RESPONSE(10, "DATA CALL RECEPTION RESPONSE"),
|
||||
CONTROL_IN_17_CC_DISCONNECT_REQUEST(17, "DISCONNECT REQUEST"),
|
||||
CONTROL_IN_48_CC_STATUS_INQUIRY_REQUEST(48, "STATUS INQUIRY REQUEST"),
|
||||
CONTROL_IN_49_CC_STATUS_INQUIRY_RESPONSE(49, "STATUS INQUIRY RESPONSE"),
|
||||
CONTROL_IN_50_CC_STATUS_REQUEST(50, "STATUS REQUEST"),
|
||||
CONTROL_IN_51_CC_STATUS_RESPONSE(51, "STATUS RESPONSE"),
|
||||
CONTROL_IN_52_CC_REMOTE_CONTROL_REQUEST(52, "REMOTE CONTROL REQUEST"),
|
||||
CONTROL_IN_53_CC_REMOTE_CONTROL_RESPONSE(53, "REMOTE CONTROL RESPONSE"),
|
||||
CONTROL_IN_55_CC_REMOTE_CONTROL_RESPONSE_WITH_ESN(55, "REMOTE CONTROL E RESPONSE"),
|
||||
CONTROL_IN_56_CC_SHORT_DATA_CALL_REQUEST_HEADER(56, "SHORT DATA CALL REQUEST HEADER"),
|
||||
CONTROL_IN_57_CC_SHORT_DATA_CALL_REQUEST_USER_DATA(57, "SHORT DATA CALL REQUEST USER DATA"),
|
||||
CONTROL_IN_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR(58, "SHORT DATA CALL INITIALIZATION VECTOR"),
|
||||
CONTROL_IN_59_CC_SHORT_DATA_CALL_RESPONSE(59, "SHORT DATA CALL RESPONSE"),
|
||||
|
||||
//Mobility Management
|
||||
CONTROL_IN_32_MM_REGISTRATION_REQUEST(32, "REGISTRATION REQUEST"),
|
||||
CONTROL_IN_34_MM_REGISTRATION_CLEAR_REQUEST(34, "REGISTRATION C REQUEST"),
|
||||
CONTROL_IN_36_MM_GROUP_REGISTRATION_REQUEST(36, "GROUP REGISTRATION REQUEST"),
|
||||
CONTROL_IN_41_MM_AUTHENTICATION_INQUIRY_RESPONSE(41, "AUTHORIZATION INQUIRY RESPONSE"),
|
||||
CONTROL_IN_43_MM_AUTHENTICATION_INQUIRY_RESPONSE_MULTI_SYSTEM(43, "AUTHORIZATION INQUIRY RESPONSE MULTI-SYSTEM"),
|
||||
|
||||
//Call Control
|
||||
CONTROL_OUT_01_CC_VOICE_CALL_RESPONSE(1, "VOICE CALL RESPONSE"),
|
||||
CONTROL_OUT_02_CC_VOICE_CALL_RECEPTION_REQUEST(2, "VOICE CALL RECEPTION REQUEST"),
|
||||
CONTROL_OUT_03_CC_VOICE_CALL_CONNECTION_RESPONSE(3, "VOICE CALL CONNECTION RESPONSE"),
|
||||
CONTROL_OUT_04_CC_VOICE_CALL_ASSIGNMENT(4, "VOICE CALL ASSIGNMENT"),
|
||||
CONTROL_OUT_05_CC_VOICE_CALL_ASSIGNMENT_DUPLICATE(5, "VOICE CALL ASSIGNMENT DUPLICATE"),
|
||||
CONTROL_OUT_09_CC_DATA_CALL_RESPONSE(9, "DATA CALL RESPONSE"),
|
||||
CONTROL_OUT_10_CC_DATA_CALL_RECEPTION_REQUEST(10, "DATA CALL RECEPTION REQUEST"),
|
||||
CONTROL_OUT_13_CC_DATA_CALL_ASSIGNMENT_DUPLICATE(13, "DATA CALL ASSIGNMENT DUPLICATE"),
|
||||
CONTROL_OUT_14_CC_DATA_CALL_ASSIGNMENT(14, "DATA CALL ASSIGNMENT"),
|
||||
CONTROL_OUT_16_CC_IDLE(16, "IDLE"),
|
||||
CONTROL_OUT_17_CC_DISCONNECT(17, "DISCONNECT"),
|
||||
CONTROL_OUT_48_CC_STATUS_INQUIRY_REQUEST(48, "STATUS INQUIRY REQUEST"),
|
||||
CONTROL_OUT_49_CC_STATUS_INQUIRY_RESPONSE(49, "STATUS INQUIRY RESPONSE"),
|
||||
CONTROL_OUT_50_CC_STATUS_REQUEST(50, "STATUS REQUEST"),
|
||||
CONTROL_OUT_51_CC_STATUS_RESPONSE(51, "STATUS RESPONSE"),
|
||||
CONTROL_OUT_52_CC_REMOTE_CONTROL_REQUEST(52, "REMOTE CONTROL REQUEST"),
|
||||
CONTROL_OUT_53_CC_REMOTE_CONTROL_RESPONSE(53, "REMOTE CONTROL RESPONSE"),
|
||||
CONTROL_OUT_54_CC_REMOTE_CONTROL_REQUEST_WITH_ESN(54, "REMOTE CONTROL E REQUEST"),
|
||||
CONTROL_OUT_56_CC_SHORT_DATA_CALL_REQUEST_HEADER(56, "SHORT DATA CALL REQUEST HEADER"),
|
||||
CONTROL_OUT_57_CC_SHORT_DATA_CALL_REQUEST_USER_DATA(57, "SHORT DATA CALL REQUEST USER DATA"),
|
||||
CONTROL_OUT_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR(58, "SHORT DATA CALL INITIALIZATION VECTOR"),
|
||||
CONTROL_OUT_59_CC_SHORT_DATA_CALL_RESPONSE(59, "SHORT DATA CALL RESPONSE"),
|
||||
|
||||
//Broadcast
|
||||
CONTROL_OUT_23_BC_DIGITAL_STATION_ID_INFORMATION(23, "DIGITAL STATION ID"),
|
||||
CONTROL_OUT_24_BC_SITE_INFORMATION(24, "SITE INFORMATION"),
|
||||
CONTROL_OUT_25_BC_SERVICE_INFORMATION(25, "SERVICE INFORMATION"),
|
||||
CONTROL_OUT_26_BC_CONTROL_CHANNEL_INFORMATION(26, "CONTROL CHANNEL INFORMATION"),
|
||||
CONTROL_OUT_27_BC_ADJACENT_SITE_INFORMATION(27, "ADJACENT SITE INFORMATION"),
|
||||
CONTROL_OUT_28_BC_FAILURE_STATUS_INFORMATION(28, "FAILURE STATUS INFORMATION"),
|
||||
|
||||
//Mobility Management
|
||||
CONTROL_OUT_32_MM_REGISTRATION_RESPONSE(32, "REGISTRATION RESPONSE"),
|
||||
CONTROL_OUT_34_MM_REGISTRATION_CLEAR_RESPONSE(34, "REGISTRATION C RESPONSE"),
|
||||
CONTROL_OUT_35_MM_REGISTRATION_COMMAND(35, "REG C RESPONSE"),
|
||||
CONTROL_OUT_36_MM_GROUP_REGISTRATION_RESPONSE(36, "GROUP REGISTRATION RESPONSE"),
|
||||
CONTROL_OUT_40_MM_AUTHENTICATION_INQUIRY_REQUEST(40, "AUTHORIZATION INQUIRY REQUEST"),
|
||||
CONTROL_OUT_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM(42, "AUTHORIZATION INQUIRY REQUEST MULTI-SYSTEM"),
|
||||
|
||||
//Call Control
|
||||
TRAFFIC_IN_01_CC_VOICE_CALL(1, "VOICE CALL"),
|
||||
TRAFFIC_IN_02_CC_VOICE_CALL_RECEPTION_RESPONSE(2, "VOICE CALL RECEPTION RESPONSE"),
|
||||
TRAFFIC_IN_03_CC_VOICE_CALL_INITIALIZATION_VECTOR(3, "VOICE CALL INITIALIZATION VECTOR"),
|
||||
TRAFFIC_IN_08_CC_TRANSMISSION_RELEASE(8, "TRANSMISSION RELEASE"),
|
||||
TRAFFIC_IN_09_CC_DATA_CALL_HEADER(9, "DATA CALL HEADER"),
|
||||
TRAFFIC_IN_11_CC_DATA_CALL_BLOCK(11, "DATA CALL BLOCK"),
|
||||
TRAFFIC_IN_12_CC_DATA_CALL_ACKNOWLEDGE(12, "DATA CALL ACKNOWLEDGE"),
|
||||
TRAFFIC_IN_15_CC_HEADER_DELAY(15, "HEADER DELAY"),
|
||||
TRAFFIC_IN_16_CC_IDLE(16, "IDLE"),
|
||||
TRAFFIC_IN_17_CC_DISCONNECT_REQUEST(17, "DISCONNECT REQUEST"),
|
||||
TRAFFIC_IN_48_CC_STATUS_INQUIRY_REQUEST(48, "STATUS INQUIRY REQUEST"),
|
||||
TRAFFIC_IN_49_CC_STATUS_INQUIRY_RESPONSE(49, "STATUS INQUIRY RESPONSE"),
|
||||
TRAFFIC_IN_50_CC_STATUS_REQUEST(50, "STATUS REQUEST"),
|
||||
TRAFFIC_IN_51_CC_STATUS_RESPONSE(51, "STATUS RESPONSE"),
|
||||
TRAFFIC_IN_52_CC_REMOTE_CONTROL_REQUEST(52, "REMOTE CONTROL REQUEST"),
|
||||
TRAFFIC_IN_53_CC_REMOTE_CONTROL_RESPONSE(53, "REMOTE CONTROL RESPONSE"),
|
||||
TRAFFIC_IN_56_CC_SHORT_DATA_CALL_REQUEST_HEADER(56, "SHORT DATA CALL REQUEST HEADER"),
|
||||
TRAFFIC_IN_57_CC_SHORT_DATA_CALL_BLOCK(57, "SHORT DATA CALL BLOCK"),
|
||||
TRAFFIC_IN_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR(58, "SHORT DATA CALL INITIALIZATION VECTOR"),
|
||||
TRAFFIC_IN_59_CC_SHORT_DATA_CALL_RESPONSE(59, "SHORT DATA CALL RESPONSE"),
|
||||
|
||||
//Mobility Management
|
||||
TRAFFIC_IN_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM(42, "AUTHORIZATION INQUIRY REQUEST MULTI-SYSTEM"),
|
||||
TRAFFIC_IN_43_MM_AUTHORIZATION_INQUIRY_RESPONSE_MULTI_SYSTEM(43, "AUTHORIZATION INQUIRY RESPONSE MULTI-SYSTEM"),
|
||||
|
||||
//Call Control
|
||||
TRAFFIC_OUT_01_CC_VOICE_CALL(1, "VOICE CALL"),
|
||||
TRAFFIC_OUT_02_CC_VOICE_CALL_RECEPTION_REQUEST(2, "VOICE CALL RECEPTION REQUEST"),
|
||||
TRAFFIC_OUT_03_CC_VOICE_CALL_INITIALIZATION_VECTOR(3, "VOICE CALL INITIALIZATION VECTOR"),
|
||||
TRAFFIC_OUT_04_CC_VOICE_CALL_ASSIGNMENT(4, "VOICE CALL ASSIGNMENT"),
|
||||
TRAFFIC_OUT_05_CC_VOICE_CALL_ASSIGNMENT_DUPLICATE(5, "VOICE CALL ASSIGNMENT DUP"),
|
||||
TRAFFIC_OUT_07_CC_TRANSMISSION_RELEASE_EXTENSION(7, "TRANSMISSION RELEASE EXTENSION"),
|
||||
TRAFFIC_OUT_08_CC_TRANSMISSION_RELEASE(8, "TRANSMISSION RELEASE"),
|
||||
TRAFFIC_OUT_09_CC_DATA_CALL_HEADER(9, "DATA CALL HEADER"),
|
||||
TRAFFIC_OUT_10_CC_DATA_CALL_RECEPTION_REQUEST(10, "DATA CALL RECEPTION REQUEST"),
|
||||
TRAFFIC_OUT_11_CC_DATA_CALL_BLOCK(11, "DATA CALL USER DATA"),
|
||||
TRAFFIC_OUT_12_CC_DATA_CALL_ACKNOWLEDGE(12, "DATA CALL ACKNOWLEDGE"),
|
||||
TRAFFIC_OUT_13_CC_DATA_CALL_ASSIGNMENT_DUPLICATE(13, "DATA CALL ASSIGNMENT DUPLICATE"),
|
||||
TRAFFIC_OUT_14_CC_DATA_CALL_ASSIGNMENT(14, "DATA CALL ASSIGNMENT DUPLICATE"),
|
||||
TRAFFIC_OUT_15_CC_HEADER_DELAY(15, "HEADER DELAY"),
|
||||
TRAFFIC_OUT_16_CC_IDLE(16, "IDLE"),
|
||||
TRAFFIC_OUT_17_CC_DISCONNECT(17, "DISCONNECT"),
|
||||
TRAFFIC_OUT_48_CC_STATUS_INQUIRY_REQUEST(48, "STATUS INQUIRY REQUEST"),
|
||||
TRAFFIC_OUT_49_CC_STATUS_INQUIRY_RESPONSE(49, "STATUS INQUIRY RESPONSE"),
|
||||
TRAFFIC_OUT_50_CC_STATUS_REQUEST(50, "STATUS REQUEST"),
|
||||
TRAFFIC_OUT_51_CC_STATUS_RESPONSE(51, "STATUS RESPONSE"),
|
||||
TRAFFIC_OUT_52_CC_REMOTE_CONTROL_REQUEST(52, "REMOTE CONTROL REQUEST"),
|
||||
TRAFFIC_OUT_53_CC_REMOTE_CONTROL_RESPONSE(53, "REMOTE CONTROL RESPONSE"),
|
||||
TRAFFIC_OUT_56_CC_SHORT_DATA_CALL_REQUEST_HEADER(56, "SHORT DATA CALL REQUEST HEADER"),
|
||||
TRAFFIC_OUT_57_CC_SHORT_DATA_CALL_BLOCK(57, "SHORT DATA CALL REQUEST USER"),
|
||||
TRAFFIC_OUT_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR(58, "SHORT DATA CALL INITIALIZATION VECTOR"),
|
||||
TRAFFIC_OUT_59_CC_SHORT_DATA_CALL_RESPONSE(59, "SHORT DATA CALL RESPONSE"),
|
||||
|
||||
//Broadcast
|
||||
TRAFFIC_OUT_23_BC_DIGITAL_STATION_ID_INFORMATION(23, "DIGITAL STATION ID"),
|
||||
TRAFFIC_OUT_24_BC_SITE_INFORMATION(24, "SITE INFORMATION"),
|
||||
TRAFFIC_OUT_25_BC_SERVICE_INFORMATION(25, "SERVICE INFORMATION"),
|
||||
TRAFFIC_OUT_26_BC_CONTROL_CHANNEL_INFORMATION(26, "CONTROL CHANNEL INFORMATION"),
|
||||
TRAFFIC_OUT_27_BC_ADJACENT_SITE_INFORMATION(27, "ADJACENT SITE INFORMATION"),
|
||||
TRAFFIC_OUT_28_BC_FAILURE_STATUS_INFORMATION(28, "FAILURE STATUS INFORMATION"),
|
||||
|
||||
//Mobility Management
|
||||
TRAFFIC_OUT_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM(42, "AUTHORIZATION REQUEST MULTI-SYSTEM"),
|
||||
TRAFFIC_OUT_43_MM_AUTHORIZATION_INQUIRY_RESPONSE_MULTI_SYSTEM(43, "AUTHORIZATION RESPONSE MULTI-SYSTEM"),
|
||||
|
||||
//Miscellanous
|
||||
PROPRIETARY_FORM(63, "PROPRIETARY FORM"),
|
||||
TALKER_ALIAS(0x82, "TALKER ALIAS"),
|
||||
TALKER_ALIAS_COMPLETE(0x0, "TALKER ALIAS COMPLETE"),
|
||||
|
||||
//Type D SCCH messages
|
||||
TYPE_D_SCCH_IN_INFO_4_CALL_IN_PROGRESS_DESTINATION(-1, "CALL IN PROGRESS - DESTINATION INFO 4"),
|
||||
TYPE_D_SCCH_IN_INFO_4_CALL_COMPLETE_DESTINATION(-1, "CALL COMPLETE - DESTINATION INFO 4"),
|
||||
TYPE_D_SCCH_IN_INFO_3_CALL_IN_PROGRESS_SOURCE(-1, "CALL IN PROGRESS - SOURCE ID"),
|
||||
TYPE_D_SCCH_IN_INFO_3_INITIALIZATION_VECTOR_PART2(-1, "INITIALIZATION VECTOR PART 2"),
|
||||
TYPE_D_SCCH_IN_INFO_2_CALL_IN_PROGRESS_DESTINATION(-1, "CALL IN PROGRESS DESTINATION INFO 2"),
|
||||
TYPE_D_SCCH_IN_INFO_1_CALL_INFO(-1, "CALL INFO"),
|
||||
TYPE_D_SCCH_IN_INFO_1_INITIALIZATION_VECTOR_PART1(-1, "INITIALIZATION VECTOR PART 1"),
|
||||
TYPE_D_SCCH_IN_UNKNOWN(-1, "UNKNOWN"),
|
||||
|
||||
TYPE_D_SCCH_OUT_INFO_4_CALL_IN_PROGRESS_DESTINATION(-1, "CALL IN PROGRESS - DESTINATION INFO 4"),
|
||||
TYPE_D_SCCH_OUT_INFO_4_CALL_COMPLETE_DESTINATION(-1, "CALL COMPLETE - DESTINATION INFO 4"),
|
||||
TYPE_D_SCCH_OUT_INFO_4_REPEATER_IDLE(-1, "REPEATER IDLE"),
|
||||
TYPE_D_SCCH_OUT_INFO_4_REPEATER_HALT(-1, "REPEATER HALT"),
|
||||
TYPE_D_SCCH_OUT_INFO_4_REPEATER_FREE(-1, "REPEATER FREE"),
|
||||
TYPE_D_SCCH_OUT_INFO_4_SITE_ID(-1, "SITE ID"),
|
||||
TYPE_D_SCCH_OUT_INFO_3_CALL_IN_PROGRESS_SOURCE(-1, "CALL IN PROGRESS - SOURCE ID"),
|
||||
TYPE_D_SCCH_OUT_INFO_3_INITIALIZATION_VECTOR_PART2(-1, "INITIALIZATION VECTOR PART 2"),
|
||||
TYPE_D_SCCH_OUT_INFO_2_CALL_IN_PROGRESS_DESTINATION(-1, "CALL IN PROGRESS DESTINATION INFO 2"),
|
||||
TYPE_D_SCCH_OUT_INFO_1_CALL_INFO(-1, "CALL INFO"),
|
||||
TYPE_D_SCCH_OUT_INFO_1_INITIALIZATION_VECTOR_PART1(-1, "INITIALIZATION VECTOR PART 1"),
|
||||
TYPE_D_SCCH_OUT_UNKNOWN(-1, "UNKNOWN"),
|
||||
|
||||
//Call Control
|
||||
TYPE_D_IN_00_CC_CALL_REQUEST(0x00, "CALL REQUEST"),
|
||||
TYPE_D_IN_01_CC_VOICE_CALL(0x01, "VOICE CALL"),
|
||||
TYPE_D_IN_03_CC_VOICE_CALL_INITIALIZATION_VECTOR(0x03, "VOICE CALL INITIALIZATION VECTOR"),
|
||||
TYPE_D_IN_04_CC_VOICE_CALL_REQUEST(0x04, "VOICE CALL REQUEST"),
|
||||
TYPE_D_IN_08_CC_TRANSMISSION_RELEASE(0x08, "TRANSMISSION RELEASE"),
|
||||
TYPE_D_IN_09_CC_DATA_CALL_HEADER(0x09, "DATA CALL HEADER"),
|
||||
TYPE_D_IN_11_CC_DATA_CALL_BLOCK(0x0B, "DATA CALL BLOCK"),
|
||||
TYPE_D_IN_12_CC_DATA_CALL_ACKNOWLEDGE(0x0C, "DATA CALL ACKNOWLEDGE"),
|
||||
TYPE_D_IN_15_CC_HEADER_DELAY(0x0F, "HEADER DELAY"),
|
||||
TYPE_D_IN_16_CC_IDLE(0x10, "IDLE"),
|
||||
TYPE_D_IN_17_CC_DISCONNECT_REQUEST(0x11, "DISCONNECT REQUEST"),
|
||||
TYPE_D_IN_48_CC_STATUS_INQUIRY_REQUEST(0x30, "STATUS INQUIRY REQUEST"),
|
||||
TYPE_D_IN_49_CC_STATUS_INQUIRY_RESPONSE(0x31, "STATUS INQUIRY RESPONSE"),
|
||||
TYPE_D_IN_50_CC_STATUS_REQUEST(0x32, "STATUS REQUEST"),
|
||||
TYPE_D_IN_51_CC_STATUS_RESPONSE(0x33, "STATUS RESPONSE"),
|
||||
TYPE_D_IN_52_CC_REMOTE_CONTROL_REQUEST(0x34, "STATUS REQUEST"),
|
||||
TYPE_D_IN_53_CC_REMOTE_CONTROL_RESPONSE(0x35, "STATUS RESPONSE"),
|
||||
TYPE_D_IN_56_CC_SHORT_DATA_CALL_REQUEST_HEADER(0x38, "SHORT DATA CALL REQUEST HEADER FORMAT"),
|
||||
TYPE_D_IN_57_CC_SHORT_DATA_CALL_REQUEST_USER_DATA(0x39, "SHORT DATA CALL REQUEST USER DATA FORMAT"),
|
||||
TYPE_D_IN_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR(0x3A, "SHORT DATA CALL INITIALIZATION VECTOR"),
|
||||
TYPE_D_IN_59_CC_SHORT_DATA_CALL_RESPONSE(0x3B, "SHORT DATA CALL RESPONSE"),
|
||||
|
||||
//Mobility Management
|
||||
TYPE_D_IN_32_MM_REGISTRATION_REQUEST(0x20, "REGISTRATION REQUEST"),
|
||||
TYPE_D_IN_34_MM_REGISTRATION_CLEAR_REQUEST(0x22, "REGISTRATION CLEAR REQUEST"),
|
||||
TYPE_D_IN_36_MM_GROUP_REGISTRATION_REQUEST(0x24, "GROUP REGISTRATION REQUEST"),
|
||||
TYPE_D_IN_39_MM_AUTHENTICATION_RESPONSE(0x27, "AUTHENTICATION RESPONSE"),
|
||||
TYPE_D_IN_40_MM_AUTHENTICATION_INQUIRY_REQUEST(0x28, "AUTHENTICATION INQUIRY REQUEST"),
|
||||
TYPE_D_IN_41_MM_AUTHENTICATION_INQUIRY_RESPONSE(0x29, "AUTHENTICATION INQUIRY RESPONSE"),
|
||||
TYPE_D_IN_45_MM_DATA_WRITE_ACKNOWLEDGE(0x2D, "DATA WRITE ACKNOWLEDGE"),
|
||||
TYPE_D_IN_UNKNOWN(-1, "UNKNOWN"),
|
||||
|
||||
//Call Control
|
||||
TYPE_D_OUT_00_CC_CALL_RESPONSE(0x00, "CALL RESPONSE"),
|
||||
TYPE_D_OUT_01_CC_VOICE_CALL(0x01, "VOICE CALL"),
|
||||
TYPE_D_OUT_03_CC_VOICE_CALL_INITIALIZATION_VECTOR(0x03, "VOICE CALL INITIALIZATION VECTOR"),
|
||||
TYPE_D_OUT_04_CC_VOICE_CALL_ASSIGNMENT(0x04, "VOICE CALL ASSIGNMENT"),
|
||||
TYPE_D_OUT_06_CC_CALL_CONNECTION_RESPONSE(0x06, "CALL CONNECTION RESPONSE"),
|
||||
TYPE_D_OUT_07_CC_TRANSMISSION_RELEASE_EXTENSION(0x07, "TRANSMISSION RELEASE EXTENSION"),
|
||||
TYPE_D_OUT_08_CC_TRANSMISSION_RELEASE(0x08, "TRANSMISSION RELEASE"),
|
||||
TYPE_D_OUT_09_CC_DATA_CALL_HEADER(0x09, "DATA CALL HEADER"),
|
||||
TYPE_D_OUT_11_CC_DATA_CALL_BLOCK(0x0B, "DATA CALL BLOCK"),
|
||||
TYPE_D_OUT_12_CC_DATA_CALL_ACKNOWLEDGE(0x0C, "DATA CALL ACKNOWLEDGE"),
|
||||
TYPE_D_OUT_15_CC_HEADER_DELAY(0x0F, "HEADER DELAY"),
|
||||
TYPE_D_OUT_16_CC_IDLE(0x10, "IDLE"),
|
||||
TYPE_D_OUT_17_CC_DISCONNECT(0x11, "DISCONNECT"),
|
||||
TYPE_D_OUT_48_CC_STATUS_INQUIRY_REQUEST(0x30, "STATUS INQUIRY REQUEST"),
|
||||
TYPE_D_OUT_49_CC_STATUS_INQUIRY_RESPONSE(0x31, "STATUS INQUIRY RESPONSE"),
|
||||
TYPE_D_OUT_50_CC_STATUS_REQUEST(0x32, "STATUS REQUEST"),
|
||||
TYPE_D_OUT_51_CC_STATUS_RESPONSE(0x33, "STATUS RESPONSE"),
|
||||
TYPE_D_OUT_52_CC_REMOTE_CONTROL_REQUEST(0x34, "STATUS REQUEST"),
|
||||
TYPE_D_OUT_53_CC_REMOTE_CONTROL_RESPONSE(0x35, "STATUS RESPONSE"),
|
||||
TYPE_D_OUT_56_CC_SHORT_DATA_CALL_REQUEST_HEADER(0x38, "SHORT DATA CALL REQUEST HEADER FORMAT"),
|
||||
TYPE_D_OUT_57_CC_SHORT_DATA_CALL_REQUEST_USER_DATA(0x39, "SHORT DATA CALL REQUEST USER DATA FORMAT"),
|
||||
TYPE_D_OUT_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR(0x3A, "SHORT DATA CALL INITIALIZATION VECTOR"),
|
||||
TYPE_D_OUT_59_CC_SHORT_DATA_CALL_RESPONSE(0x3B, "SHORT DATA CALL RESPONSE"),
|
||||
|
||||
//Broadcast
|
||||
TYPE_D_OUT_23_BC_DIGITAL_STATION_ID(0x17, "DIGITAL STATION ID"),
|
||||
TYPE_D_OUT_25_BC_SERVICE_INFORMATION(0x19, "SERVICE INFORMATION"),
|
||||
TYPE_D_OUT_27_BC_ADJACENT_SITE_INFORMATION(0x1B, "ADJACENT SITE INFORMATION"),
|
||||
|
||||
//Mobility Management
|
||||
TYPE_D_OUT_32_MM_REGISTRATION_RESPONSE(0x20, "REGISTRATION RESPONSE"),
|
||||
TYPE_D_OUT_34_MM_REGISTRATION_CLEAR_RESPONSE(0x22, "REGISTRATION CLEAR RESPONSE"),
|
||||
TYPE_D_OUT_36_MM_GROUP_REGISTRATION_RESPONSE(0x24, "GROUP REGISTRATION RESPONSE"),
|
||||
TYPE_D_OUT_38_MM_AUTHENTICATION_PARAMETER_INFORMATION(0x26, "AUTHENTICATION PARAMETER INFORMATION"),
|
||||
TYPE_D_OUT_40_MM_AUTHENTICATION_INQUIRY_REQUEST(0x28, "AUTHENTICATION INQUIRY REQUEST"),
|
||||
TYPE_D_OUT_41_MM_AUTHENTICATION_INQUIRY_RESPONSE(0x29, "AUTHENTICATION INQUIRY RESPONSE"),
|
||||
TYPE_D_OUT_43_MM_DATA_WRITE_HEADER(0x2B, "DATA WRITE HEADER FORMAT"),
|
||||
TYPE_D_OUT_44_MM_DATA_WRITE_DATA(0x2C, "DATA WRITE DATA FORMAT"),
|
||||
TYPE_D_OUT_UNKNOWN(-1, "UNKNOWN"),
|
||||
|
||||
UNKNOWN(-1, "UNKNOWN");
|
||||
|
||||
private final int mValue;
|
||||
private final String mLabel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param value for the entry
|
||||
* @param label to display
|
||||
*/
|
||||
NXDNMessageType(int value, String label)
|
||||
{
|
||||
mValue = value;
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Control channel message types
|
||||
*/
|
||||
public static final EnumSet<NXDNMessageType> CONTROL = EnumSet.range(CONTROL_IN_01_CC_VOICE_CALL_REQUEST, CONTROL_OUT_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM);
|
||||
|
||||
/**
|
||||
* Traffic channel message types
|
||||
*/
|
||||
public static final EnumSet<NXDNMessageType> TRAFFIC = EnumSet.range(TRAFFIC_IN_01_CC_VOICE_CALL, TRAFFIC_OUT_43_MM_AUTHORIZATION_INQUIRY_RESPONSE_MULTI_SYSTEM);
|
||||
|
||||
/**
|
||||
* Type-D message types
|
||||
*/
|
||||
public static final EnumSet<NXDNMessageType> TYPE_D = EnumSet.range(TYPE_D_SCCH_IN_INFO_4_CALL_IN_PROGRESS_DESTINATION, TYPE_D_OUT_UNKNOWN);
|
||||
|
||||
/**
|
||||
* Broadcast message types
|
||||
*/
|
||||
public static final EnumSet<NXDNMessageType> BROADCAST = EnumSet.of(CONTROL_OUT_23_BC_DIGITAL_STATION_ID_INFORMATION,
|
||||
CONTROL_OUT_24_BC_SITE_INFORMATION, CONTROL_OUT_25_BC_SERVICE_INFORMATION,
|
||||
CONTROL_OUT_26_BC_CONTROL_CHANNEL_INFORMATION, CONTROL_OUT_27_BC_ADJACENT_SITE_INFORMATION,
|
||||
CONTROL_OUT_28_BC_FAILURE_STATUS_INFORMATION, TRAFFIC_OUT_23_BC_DIGITAL_STATION_ID_INFORMATION,
|
||||
TRAFFIC_OUT_24_BC_SITE_INFORMATION, TRAFFIC_OUT_25_BC_SERVICE_INFORMATION,
|
||||
TRAFFIC_OUT_26_BC_CONTROL_CHANNEL_INFORMATION, TRAFFIC_OUT_27_BC_ADJACENT_SITE_INFORMATION,
|
||||
TRAFFIC_OUT_28_BC_FAILURE_STATUS_INFORMATION, TYPE_D_OUT_23_BC_DIGITAL_STATION_ID,
|
||||
TYPE_D_OUT_25_BC_SERVICE_INFORMATION, TYPE_D_OUT_27_BC_ADJACENT_SITE_INFORMATION);
|
||||
public static final EnumSet<NXDNMessageType> CONTROL_IN_CALL_CONTROL = EnumSet.range(CONTROL_IN_01_CC_VOICE_CALL_REQUEST, CONTROL_IN_59_CC_SHORT_DATA_CALL_RESPONSE);
|
||||
public static final EnumSet<NXDNMessageType> CONTROL_IN_MOBILITY_MANAGEMENT = EnumSet.range(CONTROL_IN_32_MM_REGISTRATION_REQUEST, CONTROL_IN_43_MM_AUTHENTICATION_INQUIRY_RESPONSE_MULTI_SYSTEM);
|
||||
public static final EnumSet<NXDNMessageType> CONTROL_OUT_BROADCAST = EnumSet.range(CONTROL_OUT_23_BC_DIGITAL_STATION_ID_INFORMATION, CONTROL_OUT_28_BC_FAILURE_STATUS_INFORMATION);
|
||||
public static final EnumSet<NXDNMessageType> CONTROL_OUT_CALL_CONTROL = EnumSet.range(CONTROL_OUT_01_CC_VOICE_CALL_RESPONSE, CONTROL_OUT_59_CC_SHORT_DATA_CALL_RESPONSE);
|
||||
public static final EnumSet<NXDNMessageType> CONTROL_OUT_MOBILITY_MANAGEMENT = EnumSet.range(CONTROL_OUT_32_MM_REGISTRATION_RESPONSE, CONTROL_OUT_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM);
|
||||
public static final EnumSet<NXDNMessageType> TRAFFIC_IN_CALL_CONTROL = EnumSet.range(TRAFFIC_IN_01_CC_VOICE_CALL, TRAFFIC_IN_59_CC_SHORT_DATA_CALL_RESPONSE);
|
||||
public static final EnumSet<NXDNMessageType> TRAFFIC_IN_MOBILITY_MANAGEMENT = EnumSet.range(TRAFFIC_IN_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM, TRAFFIC_IN_43_MM_AUTHORIZATION_INQUIRY_RESPONSE_MULTI_SYSTEM);
|
||||
public static final EnumSet<NXDNMessageType> TRAFFIC_OUT_BROADCAST = EnumSet.range(TRAFFIC_OUT_23_BC_DIGITAL_STATION_ID_INFORMATION, TRAFFIC_OUT_28_BC_FAILURE_STATUS_INFORMATION);
|
||||
public static final EnumSet<NXDNMessageType> TRAFFIC_OUT_CALL_CONTROL = EnumSet.range(TRAFFIC_OUT_01_CC_VOICE_CALL, TRAFFIC_OUT_59_CC_SHORT_DATA_CALL_RESPONSE);
|
||||
public static final EnumSet<NXDNMessageType> TRAFFIC_OUT_MOBILITY_MANAGEMENT = EnumSet.range(TRAFFIC_OUT_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM, TRAFFIC_OUT_43_MM_AUTHORIZATION_INQUIRY_RESPONSE_MULTI_SYSTEM);
|
||||
public static final EnumSet<NXDNMessageType> TYPE_D_IN_SCCH = EnumSet.range(TYPE_D_SCCH_IN_INFO_4_CALL_IN_PROGRESS_DESTINATION, TYPE_D_SCCH_IN_UNKNOWN);
|
||||
public static final EnumSet<NXDNMessageType> TYPE_D_OUT_SCCH = EnumSet.range(TYPE_D_SCCH_OUT_INFO_4_CALL_IN_PROGRESS_DESTINATION, TYPE_D_SCCH_OUT_UNKNOWN);
|
||||
public static final EnumSet<NXDNMessageType> TYPE_D_IN_CALL_CONTROL = EnumSet.range(TYPE_D_IN_00_CC_CALL_REQUEST, TYPE_D_IN_59_CC_SHORT_DATA_CALL_RESPONSE);
|
||||
public static final EnumSet<NXDNMessageType> TYPE_D_IN_MOBILITY_MANAGEMENT = EnumSet.range(TYPE_D_IN_32_MM_REGISTRATION_REQUEST, TYPE_D_IN_UNKNOWN);
|
||||
public static final EnumSet<NXDNMessageType> TYPE_D_OUT_BROADCAST = EnumSet.range(TYPE_D_OUT_23_BC_DIGITAL_STATION_ID, TYPE_D_OUT_27_BC_ADJACENT_SITE_INFORMATION);
|
||||
public static final EnumSet<NXDNMessageType> TYPE_D_OUT_CALL_CONTROL = EnumSet.range(TYPE_D_OUT_00_CC_CALL_RESPONSE, TYPE_D_OUT_59_CC_SHORT_DATA_CALL_RESPONSE);
|
||||
public static final EnumSet<NXDNMessageType> TYPE_D_OUT_MOBILITY_MANAGEMENT = EnumSet.range(TYPE_D_OUT_32_MM_REGISTRATION_RESPONSE, TYPE_D_OUT_UNKNOWN);
|
||||
public static final EnumSet<NXDNMessageType> OTHER = EnumSet.of(PROPRIETARY_FORM, TALKER_ALIAS, TALKER_ALIAS_COMPLETE);
|
||||
|
||||
/**
|
||||
* Call control message types
|
||||
*/
|
||||
public static final EnumSet<NXDNMessageType> CALL_CONTROL = EnumSet.of(TRAFFIC_IN_01_CC_VOICE_CALL);
|
||||
|
||||
/**
|
||||
* Indicates if this message type is a broadcast message
|
||||
*/
|
||||
public boolean isBroadcast()
|
||||
{
|
||||
return BROADCAST.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message type is a control channel message
|
||||
*/
|
||||
public boolean isControl()
|
||||
{
|
||||
return CONTROL.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message type is a traffic channel message
|
||||
*/
|
||||
public boolean isTraffic()
|
||||
{
|
||||
return TRAFFIC.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the message type is a Type-D message
|
||||
*/
|
||||
public boolean isTypeD()
|
||||
{
|
||||
return TYPE_D.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Value for the enumeration type
|
||||
*
|
||||
* @return value
|
||||
*/
|
||||
public int getValue()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display label
|
||||
*
|
||||
* @return label
|
||||
*/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to lookup a vendor proprietary message type
|
||||
*
|
||||
* @param vendor for the message
|
||||
* @param value of the message type
|
||||
* @return message type or UNKNOWN
|
||||
*/
|
||||
public static NXDNMessageType getProprietary(Vendor vendor, int value)
|
||||
{
|
||||
switch(vendor)
|
||||
{
|
||||
case JVC:
|
||||
switch(value)
|
||||
{
|
||||
case 0x82:
|
||||
return TALKER_ALIAS;
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
return PROPRIETARY_FORM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to lookup the message type for the proprietary form message
|
||||
*
|
||||
* @param message in proprietary form
|
||||
* @return message type or UNKNOWN
|
||||
*/
|
||||
public static NXDNMessageType getProprietary(CorrectedBinaryMessage message)
|
||||
{
|
||||
Vendor vendor = ProprietaryForm.getVendor(message);
|
||||
int type = ProprietaryForm.getTypeValue(message);
|
||||
return getProprietary(vendor, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to look up the inbound control channel message type.
|
||||
*
|
||||
* @param message containing the type value to lookup
|
||||
* @param lich to determine inbound versus outbound
|
||||
* @return matching entry or UNKNOWN if the value is not recognized.
|
||||
*/
|
||||
public static NXDNMessageType getControl(CorrectedBinaryMessage message, LICH lich)
|
||||
{
|
||||
int value = NXDNLayer3Message.getTypeValue(message);
|
||||
|
||||
if(lich.isOutbound())
|
||||
{
|
||||
return switch(value)
|
||||
{
|
||||
case 1 -> CONTROL_OUT_01_CC_VOICE_CALL_RESPONSE;
|
||||
case 2 -> CONTROL_OUT_02_CC_VOICE_CALL_RECEPTION_REQUEST;
|
||||
case 3 -> CONTROL_OUT_03_CC_VOICE_CALL_CONNECTION_RESPONSE;
|
||||
case 4 -> CONTROL_OUT_04_CC_VOICE_CALL_ASSIGNMENT;
|
||||
case 5 -> CONTROL_OUT_05_CC_VOICE_CALL_ASSIGNMENT_DUPLICATE;
|
||||
case 9 -> CONTROL_OUT_09_CC_DATA_CALL_RESPONSE;
|
||||
case 10 -> CONTROL_OUT_10_CC_DATA_CALL_RECEPTION_REQUEST;
|
||||
case 13 -> CONTROL_OUT_13_CC_DATA_CALL_ASSIGNMENT_DUPLICATE;
|
||||
case 14 -> CONTROL_OUT_14_CC_DATA_CALL_ASSIGNMENT;
|
||||
case 16 -> CONTROL_OUT_16_CC_IDLE;
|
||||
case 17 -> CONTROL_OUT_17_CC_DISCONNECT;
|
||||
case 23 -> CONTROL_OUT_23_BC_DIGITAL_STATION_ID_INFORMATION;
|
||||
case 24 -> CONTROL_OUT_24_BC_SITE_INFORMATION;
|
||||
case 25 -> CONTROL_OUT_25_BC_SERVICE_INFORMATION;
|
||||
case 26 -> CONTROL_OUT_26_BC_CONTROL_CHANNEL_INFORMATION;
|
||||
case 27 -> CONTROL_OUT_27_BC_ADJACENT_SITE_INFORMATION;
|
||||
case 32 -> CONTROL_OUT_32_MM_REGISTRATION_RESPONSE;
|
||||
case 34 -> CONTROL_OUT_34_MM_REGISTRATION_CLEAR_RESPONSE;
|
||||
case 35 -> CONTROL_OUT_35_MM_REGISTRATION_COMMAND;
|
||||
case 36 -> CONTROL_OUT_36_MM_GROUP_REGISTRATION_RESPONSE;
|
||||
case 40 -> CONTROL_OUT_40_MM_AUTHENTICATION_INQUIRY_REQUEST;
|
||||
case 42 -> CONTROL_OUT_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM;
|
||||
case 48 -> CONTROL_OUT_48_CC_STATUS_INQUIRY_REQUEST;
|
||||
case 49 -> CONTROL_OUT_49_CC_STATUS_INQUIRY_RESPONSE;
|
||||
case 50 -> CONTROL_OUT_50_CC_STATUS_REQUEST;
|
||||
case 51 -> CONTROL_OUT_51_CC_STATUS_RESPONSE;
|
||||
case 52 -> CONTROL_OUT_52_CC_REMOTE_CONTROL_REQUEST;
|
||||
case 53 -> CONTROL_OUT_53_CC_REMOTE_CONTROL_RESPONSE;
|
||||
case 54 -> CONTROL_OUT_54_CC_REMOTE_CONTROL_REQUEST_WITH_ESN;
|
||||
case 56 -> CONTROL_OUT_56_CC_SHORT_DATA_CALL_REQUEST_HEADER;
|
||||
case 57 -> CONTROL_OUT_57_CC_SHORT_DATA_CALL_REQUEST_USER_DATA;
|
||||
case 58 -> CONTROL_OUT_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR;
|
||||
case 59 -> CONTROL_OUT_59_CC_SHORT_DATA_CALL_RESPONSE;
|
||||
case 63 -> getProprietary(message);
|
||||
default -> UNKNOWN;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return switch(value)
|
||||
{
|
||||
case 1 -> CONTROL_IN_01_CC_VOICE_CALL_REQUEST;
|
||||
case 2 -> CONTROL_IN_02_CC_VOICE_CALL_RECEPTION_RESPONSE;
|
||||
case 3 -> CONTROL_IN_03_CC_VOICE_CALL_CONNECTION_REQUEST;
|
||||
case 9 -> CONTROL_IN_09_CC_DATA_CALL_REQUEST;
|
||||
case 10 -> CONTROL_IN_10_CC_DATA_CALL_RECEPTION_RESPONSE;
|
||||
case 17 -> CONTROL_IN_17_CC_DISCONNECT_REQUEST;
|
||||
case 32 -> CONTROL_IN_32_MM_REGISTRATION_REQUEST;
|
||||
case 34 -> CONTROL_IN_34_MM_REGISTRATION_CLEAR_REQUEST;
|
||||
case 36 -> CONTROL_IN_36_MM_GROUP_REGISTRATION_REQUEST;
|
||||
case 41 -> CONTROL_IN_41_MM_AUTHENTICATION_INQUIRY_RESPONSE;
|
||||
case 43 -> CONTROL_IN_43_MM_AUTHENTICATION_INQUIRY_RESPONSE_MULTI_SYSTEM;
|
||||
case 48 -> CONTROL_IN_48_CC_STATUS_INQUIRY_REQUEST;
|
||||
case 49 -> CONTROL_IN_49_CC_STATUS_INQUIRY_RESPONSE;
|
||||
case 50 -> CONTROL_IN_50_CC_STATUS_REQUEST;
|
||||
case 51 -> CONTROL_IN_51_CC_STATUS_RESPONSE;
|
||||
case 52 -> CONTROL_IN_52_CC_REMOTE_CONTROL_REQUEST;
|
||||
case 53 -> CONTROL_IN_53_CC_REMOTE_CONTROL_RESPONSE;
|
||||
case 55 -> CONTROL_IN_55_CC_REMOTE_CONTROL_RESPONSE_WITH_ESN;
|
||||
case 56 -> CONTROL_IN_56_CC_SHORT_DATA_CALL_REQUEST_HEADER;
|
||||
case 57 -> CONTROL_IN_57_CC_SHORT_DATA_CALL_REQUEST_USER_DATA;
|
||||
case 58 -> CONTROL_IN_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR;
|
||||
case 59 -> CONTROL_IN_59_CC_SHORT_DATA_CALL_RESPONSE;
|
||||
case 63 -> getProprietary(message);
|
||||
default -> UNKNOWN;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to look up the traffic channel message type.
|
||||
*
|
||||
* @param message containing the type value to lookup
|
||||
* @param lich to determine inbound versus outbout
|
||||
* @return matching entry or UNKNOWN if the value is not recognized.
|
||||
*/
|
||||
public static NXDNMessageType getTraffic(CorrectedBinaryMessage message, LICH lich)
|
||||
{
|
||||
int value = NXDNLayer3Message.getTypeValue(message);
|
||||
|
||||
if(lich.isOutbound())
|
||||
{
|
||||
return switch(value)
|
||||
{
|
||||
case 1 -> TRAFFIC_OUT_01_CC_VOICE_CALL;
|
||||
case 2 -> TRAFFIC_OUT_02_CC_VOICE_CALL_RECEPTION_REQUEST;
|
||||
case 3 -> TRAFFIC_OUT_03_CC_VOICE_CALL_INITIALIZATION_VECTOR;
|
||||
case 4 -> TRAFFIC_OUT_04_CC_VOICE_CALL_ASSIGNMENT;
|
||||
case 5 -> TRAFFIC_OUT_05_CC_VOICE_CALL_ASSIGNMENT_DUPLICATE;
|
||||
case 7 -> TRAFFIC_OUT_07_CC_TRANSMISSION_RELEASE_EXTENSION;
|
||||
case 8 -> TRAFFIC_OUT_08_CC_TRANSMISSION_RELEASE;
|
||||
case 9 -> TRAFFIC_OUT_09_CC_DATA_CALL_HEADER;
|
||||
case 10 -> TRAFFIC_OUT_10_CC_DATA_CALL_RECEPTION_REQUEST;
|
||||
case 11 -> TRAFFIC_OUT_11_CC_DATA_CALL_BLOCK;
|
||||
case 12 -> TRAFFIC_OUT_12_CC_DATA_CALL_ACKNOWLEDGE;
|
||||
case 13 -> TRAFFIC_OUT_13_CC_DATA_CALL_ASSIGNMENT_DUPLICATE;
|
||||
case 14 -> TRAFFIC_OUT_14_CC_DATA_CALL_ASSIGNMENT;
|
||||
case 15 -> TRAFFIC_OUT_15_CC_HEADER_DELAY;
|
||||
case 16 -> TRAFFIC_OUT_16_CC_IDLE;
|
||||
case 17 -> TRAFFIC_OUT_17_CC_DISCONNECT;
|
||||
case 23 -> TRAFFIC_OUT_23_BC_DIGITAL_STATION_ID_INFORMATION;
|
||||
case 25 -> TRAFFIC_OUT_25_BC_SERVICE_INFORMATION;
|
||||
case 26 -> TRAFFIC_OUT_26_BC_CONTROL_CHANNEL_INFORMATION;
|
||||
case 27 -> TRAFFIC_OUT_27_BC_ADJACENT_SITE_INFORMATION;
|
||||
case 28 -> TRAFFIC_OUT_28_BC_FAILURE_STATUS_INFORMATION;
|
||||
case 42 -> TRAFFIC_OUT_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM;
|
||||
case 43 -> TRAFFIC_OUT_43_MM_AUTHORIZATION_INQUIRY_RESPONSE_MULTI_SYSTEM;
|
||||
case 48 -> TRAFFIC_OUT_48_CC_STATUS_INQUIRY_REQUEST;
|
||||
case 49 -> TRAFFIC_OUT_49_CC_STATUS_INQUIRY_RESPONSE;
|
||||
case 50 -> TRAFFIC_OUT_50_CC_STATUS_REQUEST;
|
||||
case 51 -> TRAFFIC_OUT_51_CC_STATUS_RESPONSE;
|
||||
case 52 -> TRAFFIC_OUT_52_CC_REMOTE_CONTROL_REQUEST;
|
||||
case 53 -> TRAFFIC_OUT_53_CC_REMOTE_CONTROL_RESPONSE;
|
||||
case 56 -> TRAFFIC_OUT_56_CC_SHORT_DATA_CALL_REQUEST_HEADER;
|
||||
case 57 -> TRAFFIC_OUT_57_CC_SHORT_DATA_CALL_BLOCK;
|
||||
case 58 -> TRAFFIC_OUT_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR;
|
||||
case 59 -> TRAFFIC_OUT_59_CC_SHORT_DATA_CALL_RESPONSE;
|
||||
case 63 -> getProprietary(message);
|
||||
default -> UNKNOWN;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return switch(value)
|
||||
{
|
||||
case 1 -> TRAFFIC_IN_01_CC_VOICE_CALL;
|
||||
case 3 -> TRAFFIC_IN_03_CC_VOICE_CALL_INITIALIZATION_VECTOR;
|
||||
case 8 -> TRAFFIC_IN_08_CC_TRANSMISSION_RELEASE;
|
||||
case 9 -> TRAFFIC_IN_09_CC_DATA_CALL_HEADER;
|
||||
case 11 -> TRAFFIC_IN_11_CC_DATA_CALL_BLOCK;
|
||||
case 12 -> TRAFFIC_IN_12_CC_DATA_CALL_ACKNOWLEDGE;
|
||||
case 15 -> TRAFFIC_IN_15_CC_HEADER_DELAY;
|
||||
case 16 -> TRAFFIC_IN_16_CC_IDLE;
|
||||
case 17 -> TRAFFIC_IN_17_CC_DISCONNECT_REQUEST;
|
||||
case 23 -> CONTROL_OUT_23_BC_DIGITAL_STATION_ID_INFORMATION;
|
||||
case 42 -> TRAFFIC_IN_42_MM_AUTHENTICATION_INQUIRY_REQUEST_MULTI_SYSTEM;
|
||||
case 43 -> TRAFFIC_IN_43_MM_AUTHORIZATION_INQUIRY_RESPONSE_MULTI_SYSTEM;
|
||||
case 48 -> TRAFFIC_IN_48_CC_STATUS_INQUIRY_REQUEST;
|
||||
case 49 -> TRAFFIC_IN_49_CC_STATUS_INQUIRY_RESPONSE;
|
||||
case 50 -> TRAFFIC_IN_50_CC_STATUS_REQUEST;
|
||||
case 51 -> TRAFFIC_IN_51_CC_STATUS_RESPONSE;
|
||||
case 52 -> TRAFFIC_IN_52_CC_REMOTE_CONTROL_REQUEST;
|
||||
case 53 -> TRAFFIC_IN_53_CC_REMOTE_CONTROL_RESPONSE;
|
||||
case 56 -> TRAFFIC_IN_56_CC_SHORT_DATA_CALL_REQUEST_HEADER;
|
||||
case 57 -> TRAFFIC_IN_57_CC_SHORT_DATA_CALL_BLOCK;
|
||||
case 58 -> TRAFFIC_IN_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR;
|
||||
case 59 -> TRAFFIC_IN_59_CC_SHORT_DATA_CALL_RESPONSE;
|
||||
case 63 -> getProprietary(message);
|
||||
default -> UNKNOWN;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NXDN Type-D SCCH message type lookup
|
||||
*
|
||||
* @param message that is SCCH
|
||||
* @return message type
|
||||
*/
|
||||
public static NXDNMessageType getSCCH(CorrectedBinaryMessage message, LICH lich)
|
||||
{
|
||||
int structure = SCCH.getStructure(message);
|
||||
|
||||
return switch(SCCH.getStructure(message))
|
||||
{
|
||||
case 0 -> Information4.getMessageType(message, lich);
|
||||
case 1 -> Information3.getMessageType(message, lich);
|
||||
case 2 -> lich.isOutbound() ? NXDNMessageType.TYPE_D_SCCH_OUT_INFO_2_CALL_IN_PROGRESS_DESTINATION : NXDNMessageType.TYPE_D_SCCH_IN_INFO_2_CALL_IN_PROGRESS_DESTINATION;
|
||||
case 3 -> Information1.getMessageType(message, lich);
|
||||
default -> lich.isOutbound() ? NXDNMessageType.TYPE_D_SCCH_OUT_UNKNOWN : NXDNMessageType.TYPE_D_SCCH_IN_UNKNOWN;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* NXDN Type-D message type lookup
|
||||
*
|
||||
* @param message containing a message type value
|
||||
* @param lich to determine inbound vs outbound
|
||||
* @return message type
|
||||
*/
|
||||
public static NXDNMessageType getTypeD(CorrectedBinaryMessage message, LICH lich)
|
||||
{
|
||||
int value = NXDNLayer3Message.getTypeValue(message);
|
||||
|
||||
if(lich.isOutbound())
|
||||
{
|
||||
return switch(value)
|
||||
{
|
||||
case 0 -> TYPE_D_OUT_00_CC_CALL_RESPONSE;
|
||||
case 1 -> TYPE_D_OUT_01_CC_VOICE_CALL;
|
||||
case 2 -> TYPE_D_OUT_03_CC_VOICE_CALL_INITIALIZATION_VECTOR;
|
||||
case 3 -> TYPE_D_OUT_04_CC_VOICE_CALL_ASSIGNMENT;
|
||||
case 6 -> TYPE_D_OUT_06_CC_CALL_CONNECTION_RESPONSE;
|
||||
case 7 -> TYPE_D_OUT_07_CC_TRANSMISSION_RELEASE_EXTENSION;
|
||||
case 8 -> TYPE_D_OUT_08_CC_TRANSMISSION_RELEASE;
|
||||
case 9 -> TYPE_D_OUT_09_CC_DATA_CALL_HEADER;
|
||||
case 11 -> TYPE_D_OUT_11_CC_DATA_CALL_BLOCK;
|
||||
case 12 -> TYPE_D_OUT_12_CC_DATA_CALL_ACKNOWLEDGE;
|
||||
case 15 -> TYPE_D_OUT_15_CC_HEADER_DELAY;
|
||||
case 16 -> TYPE_D_OUT_16_CC_IDLE;
|
||||
case 17 -> TYPE_D_OUT_17_CC_DISCONNECT;
|
||||
case 23 -> TYPE_D_OUT_23_BC_DIGITAL_STATION_ID;
|
||||
case 25 -> TYPE_D_OUT_25_BC_SERVICE_INFORMATION;
|
||||
case 27 -> TYPE_D_OUT_27_BC_ADJACENT_SITE_INFORMATION;
|
||||
case 32 -> TYPE_D_OUT_32_MM_REGISTRATION_RESPONSE;
|
||||
case 34 -> TYPE_D_OUT_34_MM_REGISTRATION_CLEAR_RESPONSE;
|
||||
case 36 -> TYPE_D_OUT_36_MM_GROUP_REGISTRATION_RESPONSE;
|
||||
case 38 -> TYPE_D_OUT_38_MM_AUTHENTICATION_PARAMETER_INFORMATION;
|
||||
case 40 -> TYPE_D_OUT_40_MM_AUTHENTICATION_INQUIRY_REQUEST;
|
||||
case 41 -> TYPE_D_OUT_41_MM_AUTHENTICATION_INQUIRY_RESPONSE;
|
||||
case 43 -> TYPE_D_OUT_43_MM_DATA_WRITE_HEADER;
|
||||
case 44 -> TYPE_D_OUT_44_MM_DATA_WRITE_DATA;
|
||||
case 48 -> TYPE_D_OUT_48_CC_STATUS_INQUIRY_REQUEST;
|
||||
case 49 -> TYPE_D_OUT_49_CC_STATUS_INQUIRY_RESPONSE;
|
||||
case 50 -> TYPE_D_OUT_50_CC_STATUS_REQUEST;
|
||||
case 51 -> TYPE_D_OUT_51_CC_STATUS_RESPONSE;
|
||||
case 52 -> TYPE_D_OUT_52_CC_REMOTE_CONTROL_REQUEST;
|
||||
case 53 -> TYPE_D_OUT_53_CC_REMOTE_CONTROL_RESPONSE;
|
||||
case 56 -> TYPE_D_OUT_56_CC_SHORT_DATA_CALL_REQUEST_HEADER;
|
||||
case 57 -> TYPE_D_OUT_57_CC_SHORT_DATA_CALL_REQUEST_USER_DATA;
|
||||
case 58 -> TYPE_D_OUT_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR;
|
||||
case 59 -> TYPE_D_OUT_59_CC_SHORT_DATA_CALL_RESPONSE;
|
||||
case 63 -> getProprietary(message);
|
||||
default -> TYPE_D_OUT_UNKNOWN;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return switch(value)
|
||||
{
|
||||
case 0 -> TYPE_D_IN_00_CC_CALL_REQUEST;
|
||||
case 1 -> TYPE_D_IN_01_CC_VOICE_CALL;
|
||||
case 3 -> TYPE_D_IN_03_CC_VOICE_CALL_INITIALIZATION_VECTOR;
|
||||
case 4 -> TYPE_D_IN_04_CC_VOICE_CALL_REQUEST;
|
||||
case 8 -> TYPE_D_IN_08_CC_TRANSMISSION_RELEASE;
|
||||
case 9 -> TYPE_D_IN_09_CC_DATA_CALL_HEADER;
|
||||
case 11 -> TYPE_D_IN_11_CC_DATA_CALL_BLOCK;
|
||||
case 12 -> TYPE_D_IN_12_CC_DATA_CALL_ACKNOWLEDGE;
|
||||
case 15 -> TYPE_D_IN_15_CC_HEADER_DELAY;
|
||||
case 16 -> TYPE_D_IN_16_CC_IDLE;
|
||||
case 17 -> TYPE_D_IN_17_CC_DISCONNECT_REQUEST;
|
||||
case 32 -> TYPE_D_IN_32_MM_REGISTRATION_REQUEST;
|
||||
case 34 -> TYPE_D_IN_34_MM_REGISTRATION_CLEAR_REQUEST;
|
||||
case 36 -> TYPE_D_IN_36_MM_GROUP_REGISTRATION_REQUEST;
|
||||
case 39 -> TYPE_D_IN_39_MM_AUTHENTICATION_RESPONSE;
|
||||
case 40 -> TYPE_D_IN_40_MM_AUTHENTICATION_INQUIRY_REQUEST;
|
||||
case 41 -> TYPE_D_IN_41_MM_AUTHENTICATION_INQUIRY_RESPONSE;
|
||||
case 45 -> TYPE_D_IN_45_MM_DATA_WRITE_ACKNOWLEDGE;
|
||||
case 48 -> TYPE_D_IN_48_CC_STATUS_INQUIRY_REQUEST;
|
||||
case 49 -> TYPE_D_IN_49_CC_STATUS_INQUIRY_RESPONSE;
|
||||
case 50 -> TYPE_D_IN_50_CC_STATUS_REQUEST;
|
||||
case 51 -> TYPE_D_IN_51_CC_STATUS_RESPONSE;
|
||||
case 52 -> TYPE_D_IN_52_CC_REMOTE_CONTROL_REQUEST;
|
||||
case 53 -> TYPE_D_IN_53_CC_REMOTE_CONTROL_RESPONSE;
|
||||
case 56 -> TYPE_D_IN_56_CC_SHORT_DATA_CALL_REQUEST_HEADER;
|
||||
case 57 -> TYPE_D_IN_57_CC_SHORT_DATA_CALL_REQUEST_USER_DATA;
|
||||
case 58 -> TYPE_D_IN_58_CC_SHORT_DATA_CALL_INITIALIZATION_VECTOR;
|
||||
case 59 -> TYPE_D_IN_59_CC_SHORT_DATA_CALL_RESPONSE;
|
||||
case 63 -> getProprietary(message);
|
||||
default -> TYPE_D_IN_UNKNOWN;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Unknown message type
|
||||
*/
|
||||
public class UnknownMessage extends NXDNLayer3Message
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public UnknownMessage(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = getMessageBuilder();
|
||||
sb.append("UNKNOWN TYPE ").append(getMessageType());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
sb.append(" LICH:").append(getLICH());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.broadcast;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.ChannelFrequency;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.NXDNChannel;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.NXDNChannelDFA;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.NXDNChannelLookup;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNLayer3Message;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.Bandwidth;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ChannelAccessInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.LocationID;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Adjacent (ie neighbor) site information
|
||||
*/
|
||||
public class AdjacentSiteInformation extends NXDNLayer3Message implements IChannelInformationReceiver
|
||||
{
|
||||
private static final int LOCATION_ID_1 = OCTET_1;
|
||||
private static final IntField NEIGHBOR_ID_1 = IntField.length4(OCTET_4 + 2);
|
||||
|
||||
//Channel Mode Fields
|
||||
private static final IntField CHANNEL_1 = IntField.length10(OCTET_4 + 6);
|
||||
private static final int LOCATION_ID_2 = OCTET_6;
|
||||
private static final IntField NEIGHBOR_ID_2 = IntField.length4(OCTET_9 + 2);
|
||||
private static final IntField CHANNEL_2 = IntField.length10(OCTET_9 + 6);
|
||||
private static final int LOCATION_ID_3 = OCTET_11;
|
||||
private static final IntField NEIGHBOR_ID_3 = IntField.length4(OCTET_14 + 2);
|
||||
private static final IntField CHANNEL_3 = IntField.length10(OCTET_14 + 6);
|
||||
private static final int LOCATION_ID_4 = OCTET_16;
|
||||
private static final IntField NEIGHBOR_ID_4 = IntField.length4(OCTET_19 + 2);
|
||||
private static final IntField CHANNEL_4 = IntField.length10(OCTET_19 + 6);
|
||||
|
||||
//DFA Mode Fields
|
||||
private static final IntField DFA_BANDWIDTH_1 = IntField.length2(OCTET_4 + 6);
|
||||
private static final IntField DFA_CHANNEL_1 = IntField.length16(OCTET_5);
|
||||
private static final int DFA_LOCATION_ID_2 = OCTET_7;
|
||||
private static final IntField DFA_NEIGHBOR_ID_2 = IntField.length4(OCTET_10 + 2);
|
||||
private static final IntField DFA_BANDWIDTH_2 = IntField.length2(OCTET_10 + 6);
|
||||
private static final IntField DFA_CHANNEL_2 = IntField.length16(OCTET_11);
|
||||
private static final int DFA_LOCATION_ID_3 = OCTET_13;
|
||||
private static final IntField DFA_NEIGHBOR_ID_3 = IntField.length4(OCTET_16 + 2);
|
||||
private static final IntField DFA_BANDWIDTH_3 = IntField.length2(OCTET_16 + 6);
|
||||
private static final IntField DFA_CHANNEL_3 = IntField.length16(OCTET_17);
|
||||
|
||||
private LocationID mLocationID1;
|
||||
private LocationID mLocationID2;
|
||||
private LocationID mLocationID3;
|
||||
private LocationID mLocationID4;
|
||||
|
||||
private NXDNChannel mChannel1;
|
||||
private NXDNChannel mChannel2;
|
||||
private NXDNChannel mChannel3;
|
||||
private NXDNChannel mChannel4;
|
||||
|
||||
private int mNeighborId2;
|
||||
private int mNeighborId3;
|
||||
private int mNeighborId4;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
* @param type of message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public AdjacentSiteInformation(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = getMessageBuilder();
|
||||
sb.append("ADJACENT SITE ").append(getNeighborId1()).append(" [").append(getLocation1());
|
||||
if(hasChannel1())
|
||||
{
|
||||
sb.append(" CHAN:").append(getChannel1());
|
||||
|
||||
if(hasChannel2())
|
||||
{
|
||||
sb.append("] SITE ").append(getNeighborId2()).append(" [").append(getLocation2());
|
||||
sb.append(" CHAN:").append(getChannel2());
|
||||
|
||||
if(hasChannel3())
|
||||
{
|
||||
sb.append("] SITE ").append(getNeighborId3()).append(" [").append(getLocation3());
|
||||
sb.append(" CHAN:").append(getChannel3());
|
||||
|
||||
if(hasChannel4())
|
||||
{
|
||||
sb.append("] SITE ").append(getNeighborId4()).append(" [").append(getLocation4());
|
||||
sb.append(" CHAN:").append(getChannel4());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("]");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("]");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("]");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ChannelAccessInformation channelAccessInformation, Map<Integer, ChannelFrequency> channelFrequencyMap)
|
||||
{
|
||||
if(channelAccessInformation != null)
|
||||
{
|
||||
if(channelAccessInformation.isChannel()) //Channel Mode
|
||||
{
|
||||
mChannel1 = new NXDNChannelLookup(getMessage().getInt(CHANNEL_1));
|
||||
mChannel1.receive(channelAccessInformation, channelFrequencyMap);
|
||||
|
||||
mNeighborId2 = getMessage().getInt(NEIGHBOR_ID_2);
|
||||
|
||||
if(mNeighborId2 > 0)
|
||||
{
|
||||
mLocationID2 = new LocationID(getMessage(), LOCATION_ID_2);
|
||||
mChannel2 = new NXDNChannelLookup(getMessage().getInt(CHANNEL_2));
|
||||
mChannel2.receive(channelAccessInformation, channelFrequencyMap);
|
||||
|
||||
mNeighborId3 = getMessage().getInt(NEIGHBOR_ID_3);
|
||||
|
||||
if(mNeighborId3 > 0)
|
||||
{
|
||||
mLocationID3 = new LocationID(getMessage(), LOCATION_ID_3);
|
||||
mChannel3 = new NXDNChannelLookup(getMessage().getInt(CHANNEL_3));
|
||||
mChannel3.receive(channelAccessInformation, channelFrequencyMap);
|
||||
|
||||
mNeighborId4 = getMessage().getInt(NEIGHBOR_ID_4);
|
||||
|
||||
if(mNeighborId4 > 0)
|
||||
{
|
||||
mLocationID4 = new LocationID(getMessage(), LOCATION_ID_4);
|
||||
mChannel4 = new NXDNChannelLookup(getMessage().getInt(CHANNEL_4));
|
||||
mChannel4.receive(channelAccessInformation, channelFrequencyMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else //DFA Mode
|
||||
{
|
||||
mChannel1 = new NXDNChannelDFA(getMessage().getInt(DFA_CHANNEL_1), 0,
|
||||
Bandwidth.fromValue(getMessage().getInt(DFA_BANDWIDTH_1)));
|
||||
mChannel1.receive(channelAccessInformation, channelFrequencyMap);
|
||||
|
||||
mNeighborId2 = getMessage().getInt(DFA_NEIGHBOR_ID_2);
|
||||
|
||||
if(mNeighborId2 > 0)
|
||||
{
|
||||
mLocationID2 = new LocationID(getMessage(), DFA_LOCATION_ID_2);
|
||||
mChannel2 = new NXDNChannelDFA(getMessage().getInt(DFA_CHANNEL_2), 0,
|
||||
Bandwidth.fromValue(getMessage().getInt(DFA_BANDWIDTH_2)));
|
||||
mChannel2.receive(channelAccessInformation, channelFrequencyMap);
|
||||
|
||||
mNeighborId3 = getMessage().getInt(DFA_NEIGHBOR_ID_3);
|
||||
|
||||
if(mNeighborId3 > 0)
|
||||
{
|
||||
mLocationID3 = new LocationID(getMessage(), DFA_LOCATION_ID_3);
|
||||
mChannel3 = new NXDNChannelDFA(getMessage().getInt(DFA_CHANNEL_3), 0,
|
||||
Bandwidth.fromValue(getMessage().getInt(DFA_BANDWIDTH_3)));
|
||||
mChannel3.receive(channelAccessInformation, channelFrequencyMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Neighbor getNeighbor1()
|
||||
{
|
||||
if(hasChannel1())
|
||||
{
|
||||
return new Neighbor(getNeighborId1(), getLocation1(), getChannel1());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Neighbor getNeighbor2()
|
||||
{
|
||||
if(hasChannel2())
|
||||
{
|
||||
return new Neighbor(getNeighborId2(), getLocation2(), getChannel2());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Neighbor getNeighbor3()
|
||||
{
|
||||
if(hasChannel3())
|
||||
{
|
||||
return new Neighbor(getNeighborId3(), getLocation3(), getChannel3());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Neighbor getNeighbor4()
|
||||
{
|
||||
if(hasChannel4())
|
||||
{
|
||||
return new Neighbor(getNeighborId4(), getLocation4(), getChannel4());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Neighbor ID for neighbor 1
|
||||
*/
|
||||
public int getNeighborId1()
|
||||
{
|
||||
return getMessage().getInt(NEIGHBOR_ID_1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Neighbor ID for neighbor 2
|
||||
*/
|
||||
public int getNeighborId2()
|
||||
{
|
||||
return mNeighborId2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Neighbor ID for neighbor 3
|
||||
*/
|
||||
public int getNeighborId3()
|
||||
{
|
||||
return mNeighborId3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Neighbor ID for neighbor 4
|
||||
*/
|
||||
public int getNeighborId4()
|
||||
{
|
||||
return mNeighborId4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Location 1
|
||||
* @return location
|
||||
*/
|
||||
public LocationID getLocation1()
|
||||
{
|
||||
if(mLocationID1 == null)
|
||||
{
|
||||
mLocationID1 = new LocationID(getMessage(), LOCATION_ID_1);
|
||||
}
|
||||
|
||||
return mLocationID1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Location 2
|
||||
* @return location or null
|
||||
*/
|
||||
public LocationID getLocation2()
|
||||
{
|
||||
return mLocationID2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Location 3
|
||||
* @return location or null
|
||||
*/
|
||||
public LocationID getLocation3()
|
||||
{
|
||||
return mLocationID3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Location 4
|
||||
* @return location or null
|
||||
*/
|
||||
public LocationID getLocation4()
|
||||
{
|
||||
return mLocationID4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the channel is available. Note: channel info isn't available until the channel access info is set.
|
||||
*/
|
||||
public boolean hasChannel1()
|
||||
{
|
||||
return mChannel1 != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel 1
|
||||
* @return channel or null.
|
||||
*/
|
||||
public NXDNChannel getChannel1()
|
||||
{
|
||||
return mChannel1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel 2
|
||||
* @return channel or null.
|
||||
*/
|
||||
public NXDNChannel getChannel2()
|
||||
{
|
||||
return mChannel2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel 3
|
||||
* @return channel or null.
|
||||
*/
|
||||
public NXDNChannel getChannel3()
|
||||
{
|
||||
return mChannel3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel 4
|
||||
* @return channel or null.
|
||||
*/
|
||||
public NXDNChannel getChannel4()
|
||||
{
|
||||
return mChannel4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the channel is available. Note: channel info isn't available until the channel access info is set.
|
||||
*/
|
||||
public boolean hasChannel2()
|
||||
{
|
||||
return mChannel2 != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the channel is available. Note: channel info isn't available until the channel access info is set.
|
||||
*/
|
||||
public boolean hasChannel3()
|
||||
{
|
||||
return mChannel3 != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the channel is available. Note: channel info isn't available until the channel access info is set.
|
||||
*/
|
||||
public boolean hasChannel4()
|
||||
{
|
||||
return mChannel4 != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.broadcast;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.identifier.NXDNSite;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNLayer3Message;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.AdjacentSiteOption;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.SystemID;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Type-D adjacent (ie neighbor) site information
|
||||
*/
|
||||
public class AdjacentSiteInformationTypeD extends NXDNLayer3Message
|
||||
{
|
||||
private static final int INDEX = OCTET_1 + 7;
|
||||
private static final IntField ADJACENT_SITE_OPTION_1 = IntField.length5(OCTET_2);
|
||||
private static final int ADJACENT_SITE_SYSTEM_ID_1 = OCTET_2 + 5;
|
||||
private static final IntField ADJACENT_SITE_CODE_1 = IntField.length8(OCTET_5);
|
||||
private static final IntField ADJACENT_SITE_OPTION_2 = IntField.length5(OCTET_6);
|
||||
private static final int ADJACENT_SITE_SYSTEM_ID_2 = OCTET_6 + 5;
|
||||
private static final IntField ADJACENT_SITE_CODE_2 = IntField.length8(OCTET_9);
|
||||
|
||||
private AdjacentSiteOption mSiteOption1;
|
||||
private AdjacentSiteOption mSiteOption2;
|
||||
private SystemID mSystemID1;
|
||||
private SystemID mSystemID2;
|
||||
private NXDNSite mSite1;
|
||||
private NXDNSite mSite2;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
* @param type message
|
||||
* @param ran is always 0
|
||||
* @param lich for the fragment
|
||||
*/
|
||||
public AdjacentSiteInformationTypeD(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = getMessageBuilder();
|
||||
sb.append("TYPE-D NEIGHBOR-").append(isIndex() ? "3:" : "1:");
|
||||
sb.append(getSystemID1()).append(" SITE:").append(getSite1());
|
||||
|
||||
if(getSiteOption1().isIsolatedSite())
|
||||
{
|
||||
sb.append(" (ISOLATED)");
|
||||
}
|
||||
|
||||
if(hasSite2())
|
||||
{
|
||||
sb.append(" NEIGHBOR-").append(isIndex() ? "4:" : "2:");
|
||||
sb.append(getSystemID2()).append(" SITE:").append(getSite2());
|
||||
|
||||
if(getSiteOption2().isIsolatedSite())
|
||||
{
|
||||
sb.append(" (ISOLATED)");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the site 2 field is empty (false) or not (true)
|
||||
*/
|
||||
public boolean hasSite2()
|
||||
{
|
||||
return getSystemID2().getSystem().getValue() != 0 && getSite2().getValue() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this is adjacent site information message 1 (false) or 2 (true) when transmitting up
|
||||
* to four adjacent sites with two sites carried in message 1 and two carried in message 2.
|
||||
*/
|
||||
public boolean isIndex()
|
||||
{
|
||||
return getMessage().get(INDEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjacent site option 1
|
||||
*/
|
||||
public AdjacentSiteOption getSiteOption1()
|
||||
{
|
||||
if(mSiteOption1 == null)
|
||||
{
|
||||
mSiteOption1 = new AdjacentSiteOption(getMessage().getInt(ADJACENT_SITE_OPTION_1));
|
||||
}
|
||||
|
||||
return mSiteOption1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjacent site option 2
|
||||
*/
|
||||
public AdjacentSiteOption getSiteOption2()
|
||||
{
|
||||
if(mSiteOption2 == null)
|
||||
{
|
||||
mSiteOption2 = new AdjacentSiteOption(getMessage().getInt(ADJACENT_SITE_OPTION_2));
|
||||
}
|
||||
|
||||
return mSiteOption2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjacent site system ID 1
|
||||
*/
|
||||
public SystemID getSystemID1()
|
||||
{
|
||||
if(mSystemID1 == null)
|
||||
{
|
||||
mSystemID1 = new SystemID(getMessage(), ADJACENT_SITE_SYSTEM_ID_1);
|
||||
}
|
||||
|
||||
return mSystemID1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjacent site system ID 2
|
||||
*/
|
||||
public SystemID getSystemID2()
|
||||
{
|
||||
if(mSystemID2 == null)
|
||||
{
|
||||
mSystemID2 = new SystemID(getMessage(), ADJACENT_SITE_SYSTEM_ID_2);
|
||||
}
|
||||
|
||||
return mSystemID2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjacent site 1 ID
|
||||
*/
|
||||
public NXDNSite getSite1()
|
||||
{
|
||||
if(mSite1 == null)
|
||||
{
|
||||
mSite1 = NXDNSite.create(getMessage().getInt(ADJACENT_SITE_CODE_1));
|
||||
}
|
||||
|
||||
return mSite1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjacent site 2 ID
|
||||
*/
|
||||
public NXDNSite getSite2()
|
||||
{
|
||||
if(mSite2 == null)
|
||||
{
|
||||
mSite2 = NXDNSite.create(getMessage().getInt(ADJACENT_SITE_CODE_2));
|
||||
}
|
||||
|
||||
return mSite2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.broadcast;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.ChannelFrequency;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.NXDNChannel;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.NXDNChannelDFA;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.NXDNChannelLookup;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNLayer3Message;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.Bandwidth;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ChannelAccessInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ChannelNotification;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.LocationID;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Control Channel information for a site
|
||||
*/
|
||||
public class ControlChannelInformation extends NXDNLayer3Message implements IChannelInformationReceiver
|
||||
{
|
||||
private static final int LOCATION_ID = OCTET_1;
|
||||
private LocationID mLocationID;
|
||||
private static final IntField FLAGS = IntField.length4(OCTET_4);
|
||||
|
||||
//Channel Access = Channel Mode Fields
|
||||
private static final IntField CONTROL_CHANNEL_1 = IntField.length10(OCTET_4 + 6);
|
||||
private static final IntField CONTROL_CHANNEL_2 = IntField.length10(OCTET_6 + 6);
|
||||
|
||||
//Channel Access = DFA Fields
|
||||
private static final int HAS_CHANNEL_1 = OCTET_4 + 4;
|
||||
private static final int HAS_CHANNEL_2 = OCTET_4 + 5;
|
||||
private static final IntField BANDWIDTH_1 = IntField.length2(OCTET_4 + 6);
|
||||
private static final IntField CONTROL_CHANNEL_1_OFN = IntField.length16(OCTET_5);
|
||||
private static final IntField CONTROL_CHANNEL_1_IFN = IntField.length16(OCTET_7);
|
||||
private static final IntField BANDWIDTH_2 = IntField.length2(OCTET_9 + 6);
|
||||
private static final IntField CONTROL_CHANNEL_2_OFN = IntField.length16(OCTET_10);
|
||||
private static final IntField CONTROL_CHANNEL_2_IFN = IntField.length16(OCTET_12);
|
||||
private NXDNChannel mChannel1;
|
||||
private NXDNChannel mChannel2;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param message content
|
||||
* @param timestamp of the message
|
||||
* @param type of message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public ControlChannelInformation(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = getMessageBuilder();
|
||||
sb.append(getLocationID());
|
||||
sb.append(" ").append(getFlags()).append(" CONTROL");
|
||||
|
||||
if(hasChannel1())
|
||||
{
|
||||
sb.append(" 1 ").append(getChannel1());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" 1:NOT YET CONFIGURED");
|
||||
}
|
||||
|
||||
if(hasChannel2())
|
||||
{
|
||||
sb.append(" 2 ").append(getChannel2());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message has a channel 1 value.
|
||||
*/
|
||||
public boolean hasChannel1()
|
||||
{
|
||||
return mChannel1 != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message has a channel 2 value.
|
||||
*/
|
||||
public boolean hasChannel2()
|
||||
{
|
||||
return mChannel2 != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel 1
|
||||
* @return null if channel access info hasn't been received, or the channel 1 instance.
|
||||
*/
|
||||
public NXDNChannel getChannel1()
|
||||
{
|
||||
if(mChannel1 == null)
|
||||
{
|
||||
return new NXDNChannelLookup(0);
|
||||
}
|
||||
|
||||
return mChannel1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel 2
|
||||
* @return null if channel access info hasn't been received, message doesn't have a channel 2, or the channel 2 instance.
|
||||
*/
|
||||
public NXDNChannel getChannel2()
|
||||
{
|
||||
if(mChannel2 == null)
|
||||
{
|
||||
return new NXDNChannelLookup(0);
|
||||
}
|
||||
|
||||
return mChannel2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ChannelAccessInformation channelAccessInformation, Map<Integer, ChannelFrequency> channelFrequencyMap)
|
||||
{
|
||||
if(channelAccessInformation != null)
|
||||
{
|
||||
if(channelAccessInformation.isDFA())
|
||||
{
|
||||
if(getMessage().get(HAS_CHANNEL_1))
|
||||
{
|
||||
int ofn = getMessage().getInt(CONTROL_CHANNEL_1_OFN);
|
||||
int ifn = getMessage().getInt(CONTROL_CHANNEL_1_IFN);
|
||||
Bandwidth bandwidth = Bandwidth.fromValue(getMessage().getInt(BANDWIDTH_1));
|
||||
mChannel1 = new NXDNChannelDFA(ofn, ifn, bandwidth);
|
||||
mChannel1.receive(channelAccessInformation, channelFrequencyMap);
|
||||
}
|
||||
|
||||
if(getMessage().get(HAS_CHANNEL_2))
|
||||
{
|
||||
int ofn = getMessage().getInt(CONTROL_CHANNEL_2_OFN);
|
||||
int ifn = getMessage().getInt(CONTROL_CHANNEL_2_IFN);
|
||||
Bandwidth bandwidth = Bandwidth.fromValue(getMessage().getInt(BANDWIDTH_2));
|
||||
mChannel2 = new NXDNChannelDFA(ofn, ifn, bandwidth);
|
||||
mChannel2.receive(channelAccessInformation, channelFrequencyMap);
|
||||
}
|
||||
}
|
||||
else //Channel mode
|
||||
{
|
||||
int channel1 = getMessage().getInt(CONTROL_CHANNEL_1);
|
||||
mChannel1 = new NXDNChannelLookup(channel1);
|
||||
mChannel1.receive(channelAccessInformation, channelFrequencyMap);
|
||||
int channel2 = getMessage().getInt(CONTROL_CHANNEL_2);
|
||||
|
||||
if(channel2 > 0)
|
||||
{
|
||||
mChannel2 = new NXDNChannelLookup(channel2);
|
||||
mChannel2.receive(channelAccessInformation, channelFrequencyMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Location ID field
|
||||
*/
|
||||
public LocationID getLocationID()
|
||||
{
|
||||
if(mLocationID == null)
|
||||
{
|
||||
mLocationID = new LocationID(getMessage(), LOCATION_ID);
|
||||
}
|
||||
|
||||
return mLocationID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags that amplify the meaning of the control channel information.
|
||||
*/
|
||||
public ChannelNotification getFlags()
|
||||
{
|
||||
return ChannelNotification.fromValue(getMessage().getInt(FLAGS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return getLocationID().getIdentifiers();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.broadcast;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNLayer3Message;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.coding.NXDNCRC;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.StationIDOption;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Digital Station ID information (e.g. U.S. FCC Call sign)
|
||||
*/
|
||||
public class DigitalStationIDInformation extends NXDNLayer3Message
|
||||
{
|
||||
private static final IntField OPTION = IntField.length8(OCTET_1);
|
||||
private StationIDOption mStationIDOption;
|
||||
private String mCharacters;
|
||||
private Boolean mCharacterCRC;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
* @param type of message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public DigitalStationIDInformation(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = getMessageBuilder();
|
||||
sb.append("DIGITAL STATION ID");
|
||||
if(getStationIDOption().isComplete())
|
||||
{
|
||||
sb.append(":").append(getCharacters());
|
||||
sb.append(" CRC:").append(isValidCharacterCRC() ? "PASS" : "FAIL");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" PARTIAL:").append(getCharacters());
|
||||
}
|
||||
|
||||
sb.append(" ").append(getStationIDOption());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public boolean isValidCharacterCRC()
|
||||
{
|
||||
if(mCharacterCRC == null)
|
||||
{
|
||||
mCharacterCRC = NXDNCRC.checkMessage(getMessage().getSubMessage(OCTET_2, getMessage().size()), getStationIDOption().getValue() * 8);
|
||||
}
|
||||
|
||||
return mCharacterCRC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Station ID option that specifies message sequencing and character length.
|
||||
*/
|
||||
public StationIDOption getStationIDOption()
|
||||
{
|
||||
if(mStationIDOption == null)
|
||||
{
|
||||
mStationIDOption = new StationIDOption(getMessage().getInt(OPTION));
|
||||
}
|
||||
|
||||
return mStationIDOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* Character fragment from this message.
|
||||
*/
|
||||
public String getCharacters()
|
||||
{
|
||||
if(mCharacters == null)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
int pointer = OCTET_2;
|
||||
int length = OCTET_2 + (8 * getStationIDOption().getValue());
|
||||
length = Math.min(length, getMessage().size());
|
||||
|
||||
while(pointer < length)
|
||||
{
|
||||
IntField field = IntField.length8(pointer);
|
||||
sb.append((char)(0xFF & getMessage().getInt(field)));
|
||||
pointer += 8;
|
||||
}
|
||||
|
||||
mCharacters = sb.toString().trim();
|
||||
}
|
||||
|
||||
return mCharacters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.broadcast;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNLayer3Message;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.CallTimer;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.LocationID;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Site failure status information
|
||||
*/
|
||||
public class FailureStatusInformation extends NXDNLayer3Message
|
||||
{
|
||||
private static final int LOCATION_ID = OCTET_1;
|
||||
private static final IntField CALL_TIMER = IntField.length6(OCTET_5);
|
||||
private LocationID mLocationID;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
* @param type of message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public FailureStatusInformation(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = getMessageBuilder();
|
||||
sb.append("FAIL-SOFT MODE ").append(getLocationID()).append(" CALL TIMER:").append(getCallTimer());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Location that is in failsoft
|
||||
*/
|
||||
public LocationID getLocationID()
|
||||
{
|
||||
if(mLocationID == null)
|
||||
{
|
||||
mLocationID = new LocationID(getMessage(), LOCATION_ID);
|
||||
}
|
||||
|
||||
return mLocationID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call timer.
|
||||
*/
|
||||
public CallTimer getCallTimer()
|
||||
{
|
||||
return CallTimer.fromValue(getMessage().getInt(CALL_TIMER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.broadcast;
|
||||
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.ChannelFrequency;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ChannelAccessInformation;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Interface for messages that require a channel access information structure when calculating channel frequency values.
|
||||
*
|
||||
* Note: the channel access information object is from the SiteInformation broadcast message.
|
||||
*/
|
||||
public interface IChannelInformationReceiver
|
||||
{
|
||||
/**
|
||||
* Provides the latest channel access information and any optional channel:frequency mapping entries.
|
||||
* @param channelAccessInformation from the control channel
|
||||
* @param channelFrequencyMap provided by the user that maps each channel number to a frequency.
|
||||
*/
|
||||
void receive(ChannelAccessInformation channelAccessInformation, Map<Integer, ChannelFrequency> channelFrequencyMap);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.broadcast;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNLayer3Message;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Channel idle message
|
||||
*/
|
||||
public class Idle extends NXDNLayer3Message
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
* @param type
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public Idle(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = getMessageBuilder();
|
||||
if(isTypeD())
|
||||
{
|
||||
sb.append("TYPE-D ");
|
||||
}
|
||||
sb.append("IDLE");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.broadcast;
|
||||
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.NXDNChannel;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.LocationID;
|
||||
|
||||
/**
|
||||
* Neighbor site summary
|
||||
* @param locationID
|
||||
* @param channel
|
||||
*/
|
||||
public record Neighbor(int id, LocationID locationID, NXDNChannel channel)
|
||||
{
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("ID: ").append(id).append(" ").append(locationID).append(" ").append(channel);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.broadcast;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNLayer3Message;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.LocationID;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.RestrictionInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ServiceInfo;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Service information
|
||||
*/
|
||||
public class ServiceInformation extends NXDNLayer3Message
|
||||
{
|
||||
private static final int LOCATION_ID = OCTET_1;
|
||||
private static final IntField SERVICE_INFORMATION = IntField.length16(OCTET_4);
|
||||
private static final int RESTRICTION_INFORMATION = OCTET_6;
|
||||
private LocationID mLocationID;
|
||||
private ServiceInfo mServiceInfo;
|
||||
private RestrictionInformation mRestrictionInformation;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param message content
|
||||
* @param timestamp of the message
|
||||
* @param type of message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public ServiceInformation(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = getMessageBuilder();
|
||||
sb.append(getLocationID());
|
||||
sb.append(" ").append(getServiceInformation());
|
||||
sb.append(" ").append(getRestrictionInformation());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Location ID field
|
||||
*/
|
||||
public LocationID getLocationID()
|
||||
{
|
||||
if(mLocationID == null)
|
||||
{
|
||||
mLocationID = new LocationID(getMessage(), LOCATION_ID);
|
||||
}
|
||||
|
||||
return mLocationID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service information field.
|
||||
*/
|
||||
public ServiceInfo getServiceInformation()
|
||||
{
|
||||
if(mServiceInfo == null)
|
||||
{
|
||||
mServiceInfo = new ServiceInfo(getMessage().getInt(SERVICE_INFORMATION));
|
||||
}
|
||||
|
||||
return mServiceInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restriction information field.
|
||||
*/
|
||||
public RestrictionInformation getRestrictionInformation()
|
||||
{
|
||||
if(mRestrictionInformation == null)
|
||||
{
|
||||
mRestrictionInformation = new RestrictionInformation(getMessage(), RESTRICTION_INFORMATION);
|
||||
}
|
||||
|
||||
return mRestrictionInformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.broadcast;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.identifier.NXDNSite;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNLayer3Message;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.RestrictionInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ServiceInfo;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.SystemID;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Service information for Type-D system
|
||||
*/
|
||||
public class ServiceInformationTypeD extends NXDNLayer3Message
|
||||
{
|
||||
private static final int SYSTEM_ID = OCTET_1 + 5;
|
||||
private static final IntField SITE_CODE = IntField.length8(OCTET_4);
|
||||
private static final IntField SERVICE_INFORMATION = IntField.length16(OCTET_5);
|
||||
private static final int RESTRICTION_INFORMATION = OCTET_7;
|
||||
private NXDNSite mSite;
|
||||
private SystemID mSystemID;
|
||||
private ServiceInfo mServiceInfo;
|
||||
private RestrictionInformation mRestrictionInformation;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param message content
|
||||
* @param timestamp of the message
|
||||
* @param type of message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public ServiceInformationTypeD(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = getMessageBuilder();
|
||||
sb.append("TYPE-D ").append(getSystemID());
|
||||
sb.append(" SITE:").append(getSite());
|
||||
sb.append(" ").append(getServiceInformation());
|
||||
sb.append(" ").append(getRestrictionInformation());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Site information
|
||||
*/
|
||||
public NXDNSite getSite()
|
||||
{
|
||||
if(mSite == null)
|
||||
{
|
||||
mSite = NXDNSite.create(getMessage().getInt(SITE_CODE));
|
||||
}
|
||||
|
||||
return mSite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Location ID field
|
||||
*/
|
||||
public SystemID getSystemID()
|
||||
{
|
||||
if(mSystemID == null)
|
||||
{
|
||||
mSystemID = new SystemID(getMessage(), SYSTEM_ID);
|
||||
}
|
||||
|
||||
return mSystemID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service information field.
|
||||
*/
|
||||
public ServiceInfo getServiceInformation()
|
||||
{
|
||||
if(mServiceInfo == null)
|
||||
{
|
||||
mServiceInfo = new ServiceInfo(getMessage().getInt(SERVICE_INFORMATION));
|
||||
}
|
||||
|
||||
return mServiceInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restriction information field.
|
||||
*/
|
||||
public RestrictionInformation getRestrictionInformation()
|
||||
{
|
||||
if(mRestrictionInformation == null)
|
||||
{
|
||||
mRestrictionInformation = new RestrictionInformation(getMessage(), RESTRICTION_INFORMATION);
|
||||
}
|
||||
|
||||
return mRestrictionInformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.broadcast;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.ChannelFrequency;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.NXDNChannelLookup;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNLayer3Message;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ChannelAccessInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ChannelStructure;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.LocationID;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.RestrictionInformation;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ServiceInfo;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Site information
|
||||
*/
|
||||
public class SiteInformation extends NXDNLayer3Message implements IChannelInformationReceiver
|
||||
{
|
||||
private static final int LOCATION_ID = OCTET_1;
|
||||
private static final int CHANNEL_STRUCTURE = OCTET_4;
|
||||
private static final IntField SERVICE_INFORMATION = IntField.length16(OCTET_6);
|
||||
private static final int RESTRICTION_INFORMATION = OCTET_8;
|
||||
private static final int CHANNEL_ACCESS_INFORMATION = OCTET_11;
|
||||
private static final IntField VERSION_NUMBER = IntField.length8(OCTET_14);
|
||||
private static final IntField ADJACENT_SITE_ALLOCATION = IntField.length4(OCTET_15);
|
||||
private static final IntField CONTROL_CHANNEL_1 = IntField.range(OCTET_15 + 4, OCTET_16 + 5);
|
||||
private static final IntField CONTROL_CHANNEL_2 = IntField.range(OCTET_16 + 6, OCTET_17 + 7);
|
||||
|
||||
private LocationID mLocationID;
|
||||
private ChannelStructure mChannelStructure;
|
||||
private ServiceInfo mServiceInfo;
|
||||
private RestrictionInformation mRestrictionInformation;
|
||||
private ChannelAccessInformation mChannelAccessInformation;
|
||||
private NXDNChannelLookup mChannel1;
|
||||
private NXDNChannelLookup mChannel2;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param message content
|
||||
* @param timestamp of the message
|
||||
* @param type of message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public SiteInformation(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = getMessageBuilder();
|
||||
sb.append(getLocationID());
|
||||
if(hasChannel1())
|
||||
{
|
||||
sb.append(" CONTROL CHANNEL 1:").append(getChannel1());
|
||||
|
||||
if(hasChannel2())
|
||||
{
|
||||
sb.append(" CONTROL CHANNEL 2:").append(getChannel2());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" SITE USES DIRECT FREQUENCY ALLOCATION (DFA) CHANNELS");
|
||||
}
|
||||
sb.append(" ADJACENT SITES:").append(getAdjacentSiteAllocation());
|
||||
sb.append(" NXDN VER:").append(getVersionNumber());
|
||||
sb.append(" ").append(getServiceInformation());
|
||||
sb.append(" ").append(getRestrictionInformation());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Control channel 1
|
||||
* @return channel instance or null if the site is configured for DFA
|
||||
*/
|
||||
public NXDNChannelLookup getChannel1()
|
||||
{
|
||||
if(mChannel1 == null && getChannelAccessInformation().isChannel())
|
||||
{
|
||||
mChannel1 = new NXDNChannelLookup(getMessage().getInt(CONTROL_CHANNEL_1));
|
||||
}
|
||||
|
||||
return mChannel1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message has a first control channel value.
|
||||
*/
|
||||
public boolean hasChannel1()
|
||||
{
|
||||
return getChannel1() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Control channel 2
|
||||
* @return channel instance or null if the site is configured for DFA
|
||||
*/
|
||||
public NXDNChannelLookup getChannel2()
|
||||
{
|
||||
if(mChannel2 == null && getChannelAccessInformation().isChannel())
|
||||
{
|
||||
int channelNumber = getMessage().getInt(CONTROL_CHANNEL_2);
|
||||
|
||||
if(channelNumber > 0)
|
||||
{
|
||||
mChannel2 = new NXDNChannelLookup(channelNumber);
|
||||
}
|
||||
}
|
||||
|
||||
return mChannel2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message has a first control channel value.
|
||||
*/
|
||||
public boolean hasChannel2()
|
||||
{
|
||||
return getChannel2() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Location ID field
|
||||
*/
|
||||
public LocationID getLocationID()
|
||||
{
|
||||
if(mLocationID == null)
|
||||
{
|
||||
mLocationID = new LocationID(getMessage(), LOCATION_ID);
|
||||
}
|
||||
|
||||
return mLocationID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel structure field.
|
||||
*/
|
||||
public ChannelStructure getChannelStructure()
|
||||
{
|
||||
if(mChannelStructure == null)
|
||||
{
|
||||
mChannelStructure = new ChannelStructure(getMessage(), CHANNEL_STRUCTURE);
|
||||
}
|
||||
|
||||
return mChannelStructure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service information field.
|
||||
*/
|
||||
public ServiceInfo getServiceInformation()
|
||||
{
|
||||
if(mServiceInfo == null)
|
||||
{
|
||||
mServiceInfo = new ServiceInfo(getMessage().getInt(SERVICE_INFORMATION));
|
||||
}
|
||||
|
||||
return mServiceInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restriction information field.
|
||||
*/
|
||||
public RestrictionInformation getRestrictionInformation()
|
||||
{
|
||||
if(mRestrictionInformation == null)
|
||||
{
|
||||
mRestrictionInformation = new RestrictionInformation(getMessage(), RESTRICTION_INFORMATION);
|
||||
}
|
||||
|
||||
return mRestrictionInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel access information field.
|
||||
*/
|
||||
public ChannelAccessInformation getChannelAccessInformation()
|
||||
{
|
||||
if(mChannelAccessInformation == null)
|
||||
{
|
||||
mChannelAccessInformation = new ChannelAccessInformation(getMessage(), CHANNEL_ACCESS_INFORMATION);
|
||||
}
|
||||
|
||||
return mChannelAccessInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* NXDN version number. Note: version numbers are not further defined.
|
||||
*/
|
||||
public int getVersionNumber()
|
||||
{
|
||||
return getMessage().getInt(VERSION_NUMBER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of adjacent trunking sites.
|
||||
*/
|
||||
public int getAdjacentSiteAllocation()
|
||||
{
|
||||
return getMessage().getInt(ADJACENT_SITE_ALLOCATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return getLocationID().getIdentifiers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the latest channel access information and any optional channel:frequency mapping entries.
|
||||
* @param channelAccessInformation from the control channel
|
||||
* @param channelFrequencyMap provided by the user that maps each channel number to a frequency.
|
||||
*/
|
||||
public void receive(ChannelAccessInformation channelAccessInformation, Map<Integer, ChannelFrequency> channelFrequencyMap)
|
||||
{
|
||||
if(hasChannel1())
|
||||
{
|
||||
getChannel1().receive(channelAccessInformation, channelFrequencyMap);
|
||||
}
|
||||
|
||||
if(hasChannel2())
|
||||
{
|
||||
getChannel2().receive(channelAccessInformation, channelFrequencyMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.call;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.FragmentedIntField;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.edac.Golay23;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.NXDNMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.SACCHFragment;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.AudioCodec;
|
||||
import io.github.dsheirer.util.ByteUtil;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* NXDN audio frame data
|
||||
*/
|
||||
public class Audio extends NXDNMessage
|
||||
{
|
||||
private static final FragmentedIntField VECTOR_C0 = FragmentedIntField.of(0, 4, 8, 12, 16, 20, 24, 28, 32,
|
||||
36, 40, 44, 48, 52, 56, 60, 64, 68, 1, 5, 9, 13, 17, 21);
|
||||
private static final FragmentedIntField VECTOR_C1 = FragmentedIntField.of(25, 29, 33, 37, 41, 45, 49, 53,
|
||||
57, 61, 65, 69, 2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42);
|
||||
private static final IntField VECTOR_U0 = IntField.length12(0);
|
||||
|
||||
private final AudioCodec mAudioCodec;
|
||||
private final List<byte[]> mAudioFrames;
|
||||
private SACCHFragment mSACCHFragment;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param audioCodec code used for the channel
|
||||
* @param frames of raw AMBE audio
|
||||
* @param timestamp for the message
|
||||
* @param ran from the SR field
|
||||
* @param lich from the SR field
|
||||
*/
|
||||
public Audio(AudioCodec audioCodec, List<byte[]> frames, long timestamp, int ran, LICH lich)
|
||||
{
|
||||
super(new CorrectedBinaryMessage(0), timestamp, ran, lich);
|
||||
mAudioCodec = audioCodec;
|
||||
mAudioFrames = frames;
|
||||
|
||||
boolean valid = false;
|
||||
|
||||
//Check that at least 1 audio frame is valid.
|
||||
for(byte[] frame: frames)
|
||||
{
|
||||
valid |= isValid(frame);
|
||||
}
|
||||
|
||||
setValid(valid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional SACCH fragment
|
||||
*/
|
||||
public SACCHFragment getSACCHFragment()
|
||||
{
|
||||
return mSACCHFragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this audio has an optional SACCH fragment
|
||||
*/
|
||||
public boolean hasSACCHFragment()
|
||||
{
|
||||
return mSACCHFragment != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional SACCH fragment
|
||||
*/
|
||||
public void setSACCHFragment(SACCHFragment fragment)
|
||||
{
|
||||
mSACCHFragment = fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = getMessageBuilder().append("AUDIO FRAMES");
|
||||
|
||||
for(byte[] frame: mAudioFrames)
|
||||
{
|
||||
sb.append(" ").append(ByteUtil.toHexString(frame));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio frame data
|
||||
*/
|
||||
public List<byte[]> getAudioFrames()
|
||||
{
|
||||
return mAudioFrames;
|
||||
}
|
||||
|
||||
/**
|
||||
* AMBE+ audio codec format
|
||||
* @return full or half rate
|
||||
*/
|
||||
public AudioCodec getAudioCodec()
|
||||
{
|
||||
return mAudioCodec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs error detection and correction to determine if the frame is valid
|
||||
*/
|
||||
public static boolean isValid(byte[] audio)
|
||||
{
|
||||
CorrectedBinaryMessage cbm = CorrectedBinaryMessage.from(audio, ByteOrder.LITTLE_ENDIAN);
|
||||
CorrectedBinaryMessage vc0 = new CorrectedBinaryMessage(24);
|
||||
vc0.load(0, 24, cbm.getInt(VECTOR_C0));
|
||||
|
||||
//TODO: use Golay24 here, but the Golay24 implementation isn't as robust as the Golay23 implementation
|
||||
int error1 = Golay23.checkAndCorrect(vc0, 0);
|
||||
|
||||
if(error1 < 4)
|
||||
{
|
||||
CorrectedBinaryMessage vc1 = new CorrectedBinaryMessage(23);
|
||||
vc1.load(0, 23, cbm.getInt(VECTOR_C1));
|
||||
CorrectedBinaryMessage modulationVector = getModulationVector(vc0.getInt(VECTOR_U0));
|
||||
vc1.xor(modulationVector);
|
||||
int error2 = Golay23.checkAndCorrect(vc1, 0);
|
||||
return error2 < 4;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the bit modulation vector that is xor'd to vector C1 using U0 as the seed.
|
||||
*
|
||||
* @param seed for the modulation vector
|
||||
* @return modulation vector bit sequence to descramble vector C1
|
||||
*/
|
||||
private static CorrectedBinaryMessage getModulationVector(int seed)
|
||||
{
|
||||
CorrectedBinaryMessage modulationVector = new CorrectedBinaryMessage(23);
|
||||
|
||||
//alg 52
|
||||
int prX = 16 * seed;
|
||||
|
||||
for(int x = 0; x < 23; x++)
|
||||
{
|
||||
//alg 53 - simplified [... - 65536 * floor((173 * pr(n-1) + 13849) / 65536)] to modulus operation
|
||||
prX = (173 * prX + 13849) % 65536;
|
||||
|
||||
//alg 54 - values 32768 and above are a 1 and below is a 0 (default)
|
||||
if(prX >= 32768)
|
||||
{
|
||||
modulationVector.set(x);
|
||||
}
|
||||
}
|
||||
|
||||
return modulationVector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.call;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.identifier.NXDNEncryptionKey;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.CallOption;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* Base call message
|
||||
*/
|
||||
public abstract class Call extends CallControl
|
||||
{
|
||||
protected static final IntField CALL_OPTION = IntField.length5(OCTET_2 + 3);
|
||||
private static final IntField CIPHER_TYPE = IntField.length2(OCTET_7);
|
||||
private static final IntField KEY_ID = IntField.length6(OCTET_7 + 2);
|
||||
|
||||
private EncryptionKeyIdentifier mEncryptionKeyIdentifier;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
* @param type of message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public Call(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call options for this call.
|
||||
*/
|
||||
public abstract CallOption getCallOption();
|
||||
|
||||
/**
|
||||
* Encryption algorithm and key ID.
|
||||
*/
|
||||
public EncryptionKeyIdentifier getEncryptionKeyIdentifier()
|
||||
{
|
||||
if(mEncryptionKeyIdentifier == null)
|
||||
{
|
||||
mEncryptionKeyIdentifier = EncryptionKeyIdentifier.create(Protocol.NXDN, NXDNEncryptionKey.create(getMessage().getInt(CIPHER_TYPE), getMessage().getInt(KEY_ID)));
|
||||
}
|
||||
|
||||
return mEncryptionKeyIdentifier;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.call;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.CallOption;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.VoiceCallOption;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Type-D call connection response to respond to an SU call connection across sites.
|
||||
*/
|
||||
public class CallConnectionResponse extends CallWithOptionalLocation
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
* @param type of message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public CallConnectionResponse(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = getMessageBuilder();
|
||||
sb.append("CALL CONNECTION RESPONSE FROM:").append(getSource());
|
||||
sb.append(" TO:").append(getDestination());
|
||||
|
||||
if(getEncryptionKeyIdentifier().isEncrypted())
|
||||
{
|
||||
sb.append(" ").append(getEncryptionKeyIdentifier());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLocationOffset()
|
||||
{
|
||||
return OCTET_8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CallOption getCallOption()
|
||||
{
|
||||
return new VoiceCallOption(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return List.of(getSource(), getDestination());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.call;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.identifier.NXDNRadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.identifier.NXDNTalkgroupIdentifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNLayer3Message;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.CallControlOption;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.CallType;
|
||||
|
||||
/**
|
||||
* Base call control message
|
||||
*/
|
||||
public abstract class CallControl extends NXDNLayer3Message
|
||||
{
|
||||
private static final IntField CC_OPTION = IntField.length8(OCTET_1);
|
||||
private static final IntField CALL_TYPE = IntField.length3(OCTET_2);
|
||||
private CallControlOption mCallControlOption;
|
||||
protected NXDNRadioIdentifier mSourceIdentifier;
|
||||
protected IntegerIdentifier mDestinationIdentifier;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
* @param type of message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public CallControl(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call control options for the call
|
||||
* @return options
|
||||
*/
|
||||
public CallControlOption getCallControlOption()
|
||||
{
|
||||
if(mCallControlOption == null)
|
||||
{
|
||||
mCallControlOption = new CallControlOption(getMessage().getInt(CC_OPTION));
|
||||
}
|
||||
|
||||
return mCallControlOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call type for this voice call.
|
||||
* @return type
|
||||
*/
|
||||
public CallType getCallType()
|
||||
{
|
||||
return CallType.fromValue(getMessage().getInt(CALL_TYPE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Source radio ID
|
||||
* @return source identifier.
|
||||
*/
|
||||
public NXDNRadioIdentifier getSource()
|
||||
{
|
||||
if(mSourceIdentifier == null)
|
||||
{
|
||||
if(isTypeD())
|
||||
{
|
||||
mSourceIdentifier = NXDNRadioIdentifier.createTypeDFrom(getMessage().getInt(IDENTIFIER_OCTET_3));
|
||||
}
|
||||
else
|
||||
{
|
||||
mSourceIdentifier = NXDNRadioIdentifier.createFrom(getMessage().getInt(IDENTIFIER_OCTET_3));
|
||||
}
|
||||
}
|
||||
|
||||
return mSourceIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destination identifier, either talkgroup or radio.
|
||||
* @return destination identifier
|
||||
*/
|
||||
public IntegerIdentifier getDestination()
|
||||
{
|
||||
if(mDestinationIdentifier == null)
|
||||
{
|
||||
if(isTypeD())
|
||||
{
|
||||
mDestinationIdentifier = switch (getCallType())
|
||||
{
|
||||
case GROUP_BROADCAST, GROUP_CONFERENCE -> NXDNTalkgroupIdentifier
|
||||
.createTypeDTo(getMessage().getInt(IDENTIFIER_OCTET_5));
|
||||
default -> NXDNRadioIdentifier.createTypeDTo(getMessage().getInt(IDENTIFIER_OCTET_5));
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
mDestinationIdentifier = switch (getCallType())
|
||||
{
|
||||
case GROUP_BROADCAST, GROUP_CONFERENCE -> NXDNTalkgroupIdentifier
|
||||
.createTo(getMessage().getInt(IDENTIFIER_OCTET_5));
|
||||
default -> NXDNRadioIdentifier.createTo(getMessage().getInt(IDENTIFIER_OCTET_5));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return mDestinationIdentifier;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.call;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.identifier.NXDNRadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.LocationID;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.LocationOrSystemIDOption;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.SystemID;
|
||||
|
||||
/**
|
||||
* Call message with optional location field for fully qualified source/destination identifiers.
|
||||
*/
|
||||
public abstract class CallWithOptionalLocation extends Call
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
* @param type of message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public CallWithOptionalLocation(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to the location ID and option field within the message. This is implemented by the subclass.
|
||||
*/
|
||||
protected abstract int getLocationOffset();
|
||||
|
||||
/**
|
||||
* Source identifier. Overrides the default method to optionally create a fully qualified identifier that includes
|
||||
* the visitor source system ID.
|
||||
*/
|
||||
@Override
|
||||
public NXDNRadioIdentifier getSource()
|
||||
{
|
||||
//Disabling fully qualified identifier until we're sure we need this.
|
||||
// if(mSourceIdentifier == null && getCallControlOption().hasLocationId() && getLocationIDOption().isSource())
|
||||
// {
|
||||
// mSourceIdentifier = NXDNFullyQualifiedRadioIdentifier.createFrom(getLocationID().getSystem().getValue(),
|
||||
// getMessage().getInt(IDENTIFIER_OCTET_3));
|
||||
// mSourceIdentifier = NXDNRadioIdentifier.createFrom(getMessage().getInt(IDENTIFIER_OCTET_3));
|
||||
// }
|
||||
|
||||
return super.getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destination identifier. Overrides the default method to optionally create a fully qualified identifier that
|
||||
* includes the visitor source system ID.
|
||||
*/
|
||||
@Override
|
||||
public IntegerIdentifier getDestination()
|
||||
{
|
||||
//Disabling fully qualified identifier until we're sure we need this.
|
||||
// if(mDestinationIdentifier == null && getCallControlOption().hasLocationId() && getLocationIDOption().isDestination())
|
||||
// {
|
||||
// mDestinationIdentifier = switch (getCallType())
|
||||
// {
|
||||
// case GROUP_BROADCAST, GROUP_CONFERENCE -> NXDNFullyQualifiedTalkgroupIdentifier
|
||||
// .createTo(getLocationID().getSystem().getValue(), getMessage().getInt(IDENTIFIER_OCTET_5));
|
||||
// default -> NXDNFullyQualifiedRadioIdentifier.createTo(getLocationID().getSystem().getValue(),
|
||||
// getMessage().getInt(IDENTIFIER_OCTET_5));
|
||||
// };
|
||||
// }
|
||||
|
||||
return super.getDestination();
|
||||
}
|
||||
|
||||
/**
|
||||
* Location ID Option field.
|
||||
*/
|
||||
public LocationOrSystemIDOption getLocationIDOption()
|
||||
{
|
||||
IntField field = IntField.length2(getLocationOffset());
|
||||
return LocationOrSystemIDOption.fromValue(getMessage().getInt(field));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a location ID field parser.
|
||||
*/
|
||||
public LocationID getLocationID()
|
||||
{
|
||||
//In Type-D systems, this is a System ID field
|
||||
if(isTypeD())
|
||||
{
|
||||
return new SystemID(getMessage(), getLocationOffset() + 5);
|
||||
}
|
||||
|
||||
return new LocationID(getMessage(), getLocationOffset() + 5, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.call;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.DataCallOption;
|
||||
|
||||
/**
|
||||
* Data call base implementation
|
||||
*/
|
||||
public abstract class DataCall extends Call
|
||||
{
|
||||
private DataCallOption mDataCallOption;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
* @param type of message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public DataCall(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data call options
|
||||
*/
|
||||
public DataCallOption getCallOption()
|
||||
{
|
||||
if(mDataCallOption == null)
|
||||
{
|
||||
mDataCallOption = new DataCallOption(getMessage().getInt(CALL_OPTION));
|
||||
}
|
||||
|
||||
return mDataCallOption;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.call;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ErrorBlockFlag;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ResponseInformation;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Response to confirmed data call
|
||||
*/
|
||||
public class DataCallAcknowledge extends DataCall
|
||||
{
|
||||
private static final IntField RESPONSE_INFORMATION = IntField.length16(OCTET_7);
|
||||
private static final IntField ERROR_BLOCK_FLAG = IntField.length16(OCTET_9);
|
||||
private ResponseInformation mResponseInformation;
|
||||
private ErrorBlockFlag mErrorBlockFlag;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
* @param type of message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public DataCallAcknowledge(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = getMessageBuilder();
|
||||
sb.append("DATA CALL ACKNOWLEDGE");
|
||||
sb.append(" FROM:").append(getSource());
|
||||
sb.append(" TO:").append(getDestination());
|
||||
sb.append(" ").append(getResponseInformation());
|
||||
sb.append(" ").append(getErrorBlockFlag());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Response with acknowledge or no-acknowledge.
|
||||
* @return response information.
|
||||
*/
|
||||
public ResponseInformation getResponseInformation()
|
||||
{
|
||||
if(mResponseInformation == null)
|
||||
{
|
||||
mResponseInformation = new ResponseInformation(getMessage().getInt(RESPONSE_INFORMATION));
|
||||
}
|
||||
|
||||
return mResponseInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags indicating error state with any received blocks.
|
||||
* @return flags
|
||||
*/
|
||||
public ErrorBlockFlag getErrorBlockFlag()
|
||||
{
|
||||
if(mErrorBlockFlag == null)
|
||||
{
|
||||
mErrorBlockFlag = new ErrorBlockFlag(getMessage().getInt(ERROR_BLOCK_FLAG));
|
||||
}
|
||||
|
||||
return mErrorBlockFlag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return List.of(getSource(), getDestination(), getEncryptionKeyIdentifier());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.nxdn.layer3.call;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.ChannelFrequency;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.NXDNChannel;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.NXDNChannelDFA;
|
||||
import io.github.dsheirer.module.decode.nxdn.channel.NXDNChannelLookup;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer2.LICH;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.broadcast.IChannelInformationReceiver;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.Bandwidth;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.CallTimer;
|
||||
import io.github.dsheirer.module.decode.nxdn.layer3.type.ChannelAccessInformation;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Data call assignment
|
||||
*/
|
||||
public class DataCallAssignment extends DataCallWithOptionalLocation implements IChannelInformationReceiver
|
||||
{
|
||||
private static final IntField CALL_TIMER = IntField.length6(OCTET_7);
|
||||
private static final IntField CHANNEL_NUMBER = IntField.length10(OCTET_7 + 6);
|
||||
private static final IntField BANDWIDTH = IntField.length2(OCTET_7 + 6);
|
||||
private static final IntField OFN = IntField.length16(OCTET_8);
|
||||
private static final IntField IFN = IntField.length16(OCTET_10);
|
||||
private static final int LOCATION_ID_OFFSET = OCTET_12;
|
||||
private NXDNChannel mChannel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message with binary data
|
||||
* @param timestamp for the message
|
||||
* @param type of message
|
||||
* @param ran value
|
||||
* @param lich info
|
||||
*/
|
||||
public DataCallAssignment(CorrectedBinaryMessage message, long timestamp, NXDNMessageType type, int ran, LICH lich)
|
||||
{
|
||||
super(message, timestamp, type, ran, lich);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLocationOffset()
|
||||
{
|
||||
return LOCATION_ID_OFFSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = getMessageBuilder();
|
||||
|
||||
if(getCallControlOption().isEmergency())
|
||||
{
|
||||
sb.append("EMERGENCY ");
|
||||
}
|
||||
|
||||
if(getCallControlOption().isPriorityPaging())
|
||||
{
|
||||
sb.append("PRIORITY PAGING ");
|
||||
}
|
||||
|
||||
sb.append(getCallType()).append(" DATA CALL ASSIGNMENT");
|
||||
sb.append(" FROM:").append(getSource());
|
||||
sb.append(" TO:").append(getDestination());
|
||||
sb.append(" ").append(getEncryptionKeyIdentifier());
|
||||
sb.append(getCallOption());
|
||||
if(hasChannel())
|
||||
{
|
||||
sb.append(" CHANNEL:").append(getChannel());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" CHANNEL: NOT CONFIGURED");
|
||||
}
|
||||
sb.append(" TIMER:").append(getCallTimer());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ChannelAccessInformation channelAccessInformation, Map<Integer, ChannelFrequency> channelFrequencyMap)
|
||||
{
|
||||
if(mChannel == null)
|
||||
{
|
||||
if(channelAccessInformation.isChannel())
|
||||
{
|
||||
mChannel = new NXDNChannelLookup(getMessage().getInt(CHANNEL_NUMBER));
|
||||
}
|
||||
else
|
||||
{
|
||||
mChannel = new NXDNChannelDFA(getMessage().getInt(OFN), getMessage().getInt(IFN),
|
||||
Bandwidth.fromValue(getMessage().getInt(BANDWIDTH)));
|
||||
}
|
||||
}
|
||||
|
||||
mChannel.receive(channelAccessInformation, channelFrequencyMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the channel information is configured.
|
||||
*/
|
||||
public boolean hasChannel()
|
||||
{
|
||||
return getChannel() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel for the call
|
||||
*/
|
||||
public NXDNChannel getChannel()
|
||||
{
|
||||
return mChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call timer.
|
||||
*/
|
||||
public CallTimer getCallTimer()
|
||||
{
|
||||
return CallTimer.fromValue(getMessage().getInt(CALL_TIMER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return List.of(getSource(), getDestination(), getEncryptionKeyIdentifier());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user