#1633 DMR Decoder redesign. Uses new DQPSK vector/scalar demodulator implementations and soft sync detectors for more accurate detections and symbol timing and symbol period calculations. Includes two new calibrations with scalar and vector implementations for DQPSK demodulator and Interpolator. Enhances DMR error detection and correction algorithms, specifically the block product turbo code algorithms. (#2115)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2024-12-24 06:53:58 -05:00
committed by GitHub
parent 11abb87177
commit 9ffa02e2e5
188 changed files with 8066 additions and 3192 deletions

View File

@@ -479,7 +479,7 @@ public class DecoderFactory
AliasList aliasList, DecodeConfigDMR decodeConfig,
TrafficChannelManager trafficChannelManager, IChannelDescriptor channelDescriptor)
{
modules.add(new DMRDecoder(decodeConfig));
modules.add(new DMRDecoder(decodeConfig, channel.isTrafficChannel()));
DMRTrafficChannelManager dmrTrafficChannelManager = null;

View File

@@ -1,550 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.dsp.symbol.DibitDelayBuffer;
import io.github.dsheirer.dsp.symbol.QPSKCarrierLock;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Processes a stream of Dibit symbols and performs burst detection and timeslot framing. This framer
* also detects abnormal PLL phase locks and issues PLL phase lock corrections.
*
* Timeslot Map:
* 0: Common Association Channel (CACH) and Slow Link Control (SLC)
* 1. Timeslot 1
* 2. Timeslot 2
*/
public class DMRBurstFramer implements Listener<Dibit>
{
private final static Logger mLog = LoggerFactory.getLogger(DMRBurstFramer.class);
private static final int DMR_SYMBOL_RATE = 4800;
private static final double PLL_PHASE_CORRECTION_90_DEGREES = (double)DMR_SYMBOL_RATE / 4.0;
private static final double PLL_PHASE_CORRECTION_180_DEGREES = (double)DMR_SYMBOL_RATE / 2.0;
private static final int BURST_DIBIT_START_TS1 = 0;
private static final int BURST_DIBIT_START_TS2 = 144;
private static final int BURST_DIBIT_LENGTH = 144;
private static final int TWO_TIMESLOT_DIBIT_LENGTH = BURST_DIBIT_LENGTH * 2;
private static final int SYNC_DIBIT_OFFSET_TS1 = 66;
private static final int SYNC_DIBIT_OFFSET_TS2 = 210;
private static final int SYNC_DIBIT_LENGTH = 24;
private static final int MAX_STREAMING_SYNC_DETECT_BIT_ERRORS = 4;
private static final int MAX_EXPLICIT_SYNC_DETECT_BIT_ERRORS = 6;
/**
* Threshold for issuing a sync loss message. This is set to trigger once the dibit count exceeds one second of
* dibits (4800 baud/dibits) plus two bursts dibit length. The two burst length padding is so that we don't reset
* the dibit counter below the burst sync check threshold.
*/
private static final int SYNC_LOSS_MESSAGE_THRESHOLD = DMR_SYMBOL_RATE + TWO_TIMESLOT_DIBIT_LENGTH;
/**
* The message buffer is sized to hold two DMR bursts of 144 dibits (288 bits) each.
*/
private DibitDelayBuffer mMessageBuffer = new DibitDelayBuffer(BURST_DIBIT_LENGTH * 2);
/**
* The sync delay buffer is sized to align sync detection with two bursts being fully loaded into the message and
* timeslot 1 region of the message buffer is aligned with the sync delay buffer.
*
* This delay equals the number of dibits in the second half of the burst payload: 54 dibits (108 bits).
*/
private DibitDelayBuffer mSyncDelayBuffer = new DibitDelayBuffer(54);
/**
* Synchronized indicates that either the primary or secondary sync tracker is currently synchronized to
* one of the timeslot bursts. Once either sync tracker obtains a valid sync pattern, this flag is set
* to true. The synchronized state will remain true as long as at least one of the two sync trackers
* maintains sync state. Once both sync trackers lose sync, the framer will fallback into sync search/detection
* mode to inspect each dibit until a sync match can be (re)discovered.
*/
private boolean mSynchronized = false;
/**
* Tracks the number of dibits received to trigger burst or sync loss message processing
*/
private int mDibitCounter = 0;
/**
* The Sync detection is used to gain initial lock to a sync pattern or to regain sync if lost. The sync detector
* is used to align a DMR burst into the timeslot 1 region of the message dibit delay buffer so that it can be
* inspected to ensure the timeslots are aligned.
*/
private DMRSyncDetector mSyncDetectorTimeslot1 = new DMRSyncDetector(MAX_STREAMING_SYNC_DETECT_BIT_ERRORS,
MAX_EXPLICIT_SYNC_DETECT_BIT_ERRORS);
private DMRSyncDetector mSyncDetectorTimeslot2 = new DMRSyncDetector(MAX_STREAMING_SYNC_DETECT_BIT_ERRORS,
MAX_EXPLICIT_SYNC_DETECT_BIT_ERRORS);
/**
* Timeslot sync trackers track the synchronization state for each of the two timeslots.
*/
private SyncTracker mSyncTrackerTimeslot1 = new SyncTracker();
private SyncTracker mSyncTrackerTimeslot2 = new SyncTracker();
/**
* Timeslot alignment tracker tracks that the timeslots in the delay buffer are aligned.
*/
private TimeslotAlignmentTracker mTimeslotAlignmentTracker = new TimeslotAlignmentTracker();
private IDMRBurstDetectListener mBurstDetectListener;
private IPhaseLockedLoop mPhaseLockedLoop;
/**
* Constructs an instance
* @param listener to be notified of framed burst detections and/or sync loss bits processed
* @param phaseLockedLoop to receive symbol alignment corrections
*/
public DMRBurstFramer(IDMRBurstDetectListener listener, IPhaseLockedLoop phaseLockedLoop)
{
mBurstDetectListener = listener;
mPhaseLockedLoop = phaseLockedLoop;
}
/**
* Primary dibit symbol input method
* @param dibit to process
*/
@Override
public void receive(Dibit dibit)
{
mDibitCounter++;
//Feed the message buffer first to ensure buffer contains two full bursts when a sync is detected
mMessageBuffer.put(dibit);
//Feed the sync delay buffer and reassign the delayed dibit to feed the sync detector if we're not synchronized
dibit = mSyncDelayBuffer.getAndPut(dibit);
if(mSynchronized)
{
if(mDibitCounter >= TWO_TIMESLOT_DIBIT_LENGTH)
{
if(mDibitCounter > TWO_TIMESLOT_DIBIT_LENGTH)
{
processSyncLossDibits(mDibitCounter - TWO_TIMESLOT_DIBIT_LENGTH, 0);
}
dispatch();
}
}
else
{
mSyncDetectorTimeslot2.add(dibit);
if(mSyncDetectorTimeslot2.hasSync())
{
dispatch();
}
else if(mDibitCounter >= SYNC_LOSS_MESSAGE_THRESHOLD)
{
processSyncLossDibits(DMR_SYMBOL_RATE, 0);
}
}
}
/**
* Dispatches burst 1 and/or burst 2 that is currently in the message buffer
*/
private void dispatch()
{
if(mBurstDetectListener != null)
{
if(mDibitCounter > TWO_TIMESLOT_DIBIT_LENGTH)
{
processSyncLossDibits(mDibitCounter - TWO_TIMESLOT_DIBIT_LENGTH, 0);
}
mSyncDetectorTimeslot1.setCurrentSyncValue(getSyncValue(SYNC_DIBIT_OFFSET_TS1));
mSyncDetectorTimeslot2.setCurrentSyncValue(getSyncValue(SYNC_DIBIT_OFFSET_TS2));
mSyncTrackerTimeslot1.update(mSyncDetectorTimeslot1.getSyncPattern(), mSyncDetectorTimeslot1.getCarrierLock());
mSyncTrackerTimeslot2.update(mSyncDetectorTimeslot2.getSyncPattern(), mSyncDetectorTimeslot2.getCarrierLock());
CorrectedBinaryMessage burst1 = null;
CorrectedBinaryMessage burst2 = null;
CACH cach1 = null;
CACH cach2 = null;
//If we have valid sync but PLL tracker misalignment, correct both timeslots before we extract messages
if((mSyncTrackerTimeslot1.hasSync() && mSyncTrackerTimeslot1.hasCarrierMisAlignment()) ||
(mSyncTrackerTimeslot2.hasSync() && mSyncTrackerTimeslot2.hasCarrierMisAlignment()))
{
// mLog.debug("Repairing Message Bits - PLL Mis-Alignment Detected");
if(mSyncTrackerTimeslot1.hasCarrierMisAlignment())
{
repairPLLMisalignment(BURST_DIBIT_START_TS1, mSyncTrackerTimeslot1.getCarrierLock());
repairPLLMisalignment(BURST_DIBIT_START_TS2, mSyncTrackerTimeslot1.getCarrierLock());
}
else
{
repairPLLMisalignment(BURST_DIBIT_START_TS1, mSyncTrackerTimeslot2.getCarrierLock());
repairPLLMisalignment(BURST_DIBIT_START_TS2, mSyncTrackerTimeslot2.getCarrierLock());
}
}
if(mSyncTrackerTimeslot1.hasSync())
{
burst1 = mMessageBuffer.getMessage(BURST_DIBIT_START_TS1, BURST_DIBIT_LENGTH);
burst1.incrementCorrectedBitCount(mSyncDetectorTimeslot1.getPatternMatchBitErrorCount());
if(mSyncTrackerTimeslot1.getSyncPattern().hasCACH())
{
cach1 = CACH.getCACH(burst1);
}
}
if(mSyncTrackerTimeslot2.hasSync())
{
burst2 = mMessageBuffer.getMessage(BURST_DIBIT_START_TS2, BURST_DIBIT_LENGTH);
burst2.incrementCorrectedBitCount(mSyncDetectorTimeslot2.getPatternMatchBitErrorCount());
if(mSyncTrackerTimeslot2.getSyncPattern().hasCACH())
{
cach2 = CACH.getCACH(burst2);
}
}
//Update the timeslot alignment tracker using the sync patterns and optional CACHes from each timeslot
mTimeslotAlignmentTracker.update(cach1, cach2, mSyncDetectorTimeslot1.getSyncPattern(),
mSyncDetectorTimeslot2.getSyncPattern());
if(mTimeslotAlignmentTracker.hasSufficientData())
{
if(mTimeslotAlignmentTracker.isAligned())
{
if(burst1 != null)
{
mBurstDetectListener.burstDetected(burst1, mSyncTrackerTimeslot1.getSyncPattern(), 1);
mDibitCounter -= BURST_DIBIT_LENGTH;
}
else
{
processSyncLossDibits(BURST_DIBIT_LENGTH, 1);
}
if(burst2 != null)
{
mBurstDetectListener.burstDetected(burst2, mSyncTrackerTimeslot2.getSyncPattern(), 2);
mDibitCounter -= BURST_DIBIT_LENGTH;
}
else
{
processSyncLossDibits(BURST_DIBIT_LENGTH, 2);
}
mSynchronized = mSyncTrackerTimeslot1.hasSync() || mSyncTrackerTimeslot2.hasSync();
}
else
{
if(burst1 != null)
{
mBurstDetectListener.burstDetected(burst1, mSyncTrackerTimeslot1.getSyncPattern(), 2);
mDibitCounter -= BURST_DIBIT_LENGTH;
//Transfer the sync pattern from tracker 1 to tracker 2 so we don't lose voice framing
mSyncTrackerTimeslot2.setSyncPattern(mSyncTrackerTimeslot1.getSyncPattern());
}
else
{
processSyncLossDibits(BURST_DIBIT_LENGTH, 2);
mSyncTrackerTimeslot2.reset();
}
mTimeslotAlignmentTracker.reset();
mSyncTrackerTimeslot1.reset();
mDibitCounter = BURST_DIBIT_LENGTH;
mSynchronized = true;
}
}
else
{
//When we don't have sufficient timeslot tracking data, we either have misaligned timeslots from a
//repeater or we have some form of mobile burst. If mobile, process to the correct timeslto, otherwise
//dump the messages to timeslot 0 so that the burst doesn't corrupt the decoder states for either
// timeslot, until we positively regain timeslot tracking
if(burst1 != null)
{
DMRSyncPattern sync1 = mSyncTrackerTimeslot1.getSyncPattern();
if(sync1.isMobileStationSyncPattern() || sync1.isDirectModeTS1())
{
mBurstDetectListener.burstDetected(burst1, sync1, 1);
}
else if(sync1.isDirectModeTS2())
{
mBurstDetectListener.burstDetected(burst1, sync1, 2);
}
else
{
mBurstDetectListener.burstDetected(burst1, sync1, 0);
}
mDibitCounter -= BURST_DIBIT_LENGTH;
}
else
{
processSyncLossDibits(BURST_DIBIT_LENGTH, 0);
}
if(burst2 != null)
{
DMRSyncPattern sync2 = mSyncTrackerTimeslot2.getSyncPattern();
if(sync2.isMobileStationSyncPattern() || sync2.isDirectModeTS1())
{
mBurstDetectListener.burstDetected(burst2, sync2, 1);
}
else if(sync2.isDirectModeTS2())
{
mBurstDetectListener.burstDetected(burst2, sync2, 2);
}
else
{
mBurstDetectListener.burstDetected(burst2, sync2, 0);
}
mDibitCounter -= BURST_DIBIT_LENGTH;
}
else
{
processSyncLossDibits(BURST_DIBIT_LENGTH, 0);
}
mSynchronized = mSyncTrackerTimeslot1.hasSync() || mSyncTrackerTimeslot2.hasSync();
}
if(!mSynchronized)
{
mTimeslotAlignmentTracker.reset();
mSyncTrackerTimeslot1.reset();
mSyncTrackerTimeslot2.reset();
}
if(mDibitCounter < 0)
{
mDibitCounter = 0;
}
}
}
/**
* Repairs the captured dibits in the message buffer when a PLL lock misalignment is detected.
* @param offset to start of burst
* @param carrierLock that indicates the mis-alignment
*/
private void repairPLLMisalignment(int offset, QPSKCarrierLock carrierLock)
{
int end = offset + BURST_DIBIT_LENGTH;
for(int x = offset; x < end; x++)
{
Dibit misalignedDibit = mMessageBuffer.get(x);
mMessageBuffer.set(x, carrierLock.correct(misalignedDibit));
}
if(mPhaseLockedLoop != null)
{
// mLog.warn("Repairing PLL Phase Lock Mis-Alignment - Detected: " + carrierLock);
switch(carrierLock)
{
case PLUS_90:
mPhaseLockedLoop.correctInversion(PLL_PHASE_CORRECTION_90_DEGREES);
break;
case MINUS_90:
mPhaseLockedLoop.correctInversion(-PLL_PHASE_CORRECTION_90_DEGREES);
break;
case INVERTED:
mPhaseLockedLoop.correctInversion(PLL_PHASE_CORRECTION_180_DEGREES);
break;
}
}
}
/**
* Resets the message and sync delay buffers and resets the dibit counter to zero.
*/
public void reset()
{
mDibitCounter = 0;
mMessageBuffer.reset();
mSyncDelayBuffer.reset();
mTimeslotAlignmentTracker.reset();
}
/**
* Calculates the value of the sync field currently in the message buffer as specified by the offset value.
*/
private long getSyncValue(int dibitOffset)
{
Dibit[] syncDibits = mMessageBuffer.getBuffer(dibitOffset, SYNC_DIBIT_LENGTH);
long value = 0;
for(Dibit dibit: syncDibits)
{
value = Long.rotateLeft(value, 2);
value += dibit.getValue();
}
return value;
}
/**
* Processes dibits received without a sync condition
* @param dibitCount with sync loss
*/
private void processSyncLossDibits(int dibitCount, int timeslot)
{
mDibitCounter -= dibitCount;
if(mBurstDetectListener != null)
{
mBurstDetectListener.syncLost(dibitCount * 2, timeslot);
}
}
/**
* Tracks the sync state for a timeslot to account for voice framing that does not transmit a sync pattern in each
* frame.
*/
public class SyncTracker
{
private DMRSyncPattern mSyncPattern = DMRSyncPattern.UNKNOWN;
private QPSKCarrierLock mCarrierLock = QPSKCarrierLock.NORMAL;
/**
* Updates the sync pattern for the most recently detected burst to the specified pattern. When the previous
* sync pattern is one of either base or mobile voice frame and the detected pattern is unknown, automatically
* advances the sync pattern to the next voice frame in the super frame sequence to account for voice
* frames B - F that do not have a sync pattern.
*/
public void update(DMRSyncPattern pattern, QPSKCarrierLock carrierLock)
{
if(pattern == DMRSyncPattern.UNKNOWN)
{
switch(mSyncPattern)
{
case BASE_STATION_VOICE: //Voice frame A
mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_B;
break;
case BS_VOICE_FRAME_B:
mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_C;
break;
case BS_VOICE_FRAME_C:
mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_D;
break;
case BS_VOICE_FRAME_D:
mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_E;
break;
case BS_VOICE_FRAME_E:
mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_F;
break;
case MOBILE_STATION_VOICE: //Voice frame A
case DIRECT_MODE_VOICE_TIMESLOT_1:
case DIRECT_MODE_VOICE_TIMESLOT_2:
mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_B;
break;
case MS_VOICE_FRAME_B:
mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_C;
break;
case MS_VOICE_FRAME_C:
mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_D;
break;
case MS_VOICE_FRAME_D:
mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_E;
break;
case MS_VOICE_FRAME_E:
mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_F;
break;
default:
mSyncPattern = pattern;
mCarrierLock = carrierLock;
break;
}
}
else
{
mSyncPattern = pattern;
mCarrierLock = carrierLock;
}
}
/**
* Most recently detected sync pattern
*/
public DMRSyncPattern getSyncPattern()
{
return mSyncPattern;
}
/**
* Explicitly sets the pattern for this tracker
*/
public void setSyncPattern(DMRSyncPattern pattern)
{
mSyncPattern = pattern;
}
/**
* Carrier lock as indicated by the sync detector.
*/
public QPSKCarrierLock getCarrierLock()
{
return mCarrierLock;
}
/**
* Indicates if the burst has a QPSK constellation mis-alignment that has to be corrected
*/
public boolean hasCarrierMisAlignment()
{
return getCarrierLock() != QPSKCarrierLock.NORMAL;
}
/**
* Checks the contents of the message buffer to determine if the message has a valid sync pattern, or if the
* message is a continuation voice frame from a voice super frame. The identified sync pattern and number of
* bit errors for that pattern are stored in the sync detector so that they can be accessed for dispatching
* a message.
*/
public boolean hasSync()
{
return getSyncPattern() != DMRSyncPattern.UNKNOWN;
}
/**
* Resets the sync pattern to unknown
*/
public void reset()
{
mSyncPattern = DMRSyncPattern.UNKNOWN;
}
}
}

View File

@@ -21,18 +21,13 @@ package io.github.dsheirer.module.decode.dmr;
import io.github.dsheirer.edac.CRCDMR;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,9 +44,9 @@ import org.slf4j.LoggerFactory;
* values against CSBK messages and some other function is involved in determining which CRC mask value to use when
* checking the CRC of the transmitted CSBK ... fun and games!
*/
public class DmrCrcMaskManager
public class DMRCrcMaskManager
{
private Logger LOGGER = LoggerFactory.getLogger(DmrCrcMaskManager.class);
private Logger LOGGER = LoggerFactory.getLogger(DMRCrcMaskManager.class);
private Map<Integer, MaskTracker> mCsbkTrackerMapTS1 = new TreeMap<>();
private Map<Integer, MaskTracker> mCsbkTrackerMapTS2 = new TreeMap<>();
private Set<MaskTracker> mSortedTrackersTS1 = new TreeSet<>();
@@ -60,7 +55,7 @@ public class DmrCrcMaskManager
/**
* Constructs an instance
*/
public DmrCrcMaskManager()
public DMRCrcMaskManager()
{
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 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,22 +19,26 @@
package io.github.dsheirer.module.decode.dmr;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
import io.github.dsheirer.dsp.gain.complex.ComplexGainFactory;
import io.github.dsheirer.dsp.gain.complex.IComplexGainControl;
import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulator;
import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer;
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
import io.github.dsheirer.dsp.psk.pll.FrequencyCorrectionSyncMonitor;
import io.github.dsheirer.dsp.psk.pll.PLLBandwidth;
import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulator;
import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulatorFactory;
import io.github.dsheirer.dsp.squelch.PowerMonitor;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.SyncLossMessage;
import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.FeedbackDecoder;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.module.decode.dmr.audio.DMRAudioModule;
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
import io.github.dsheirer.module.decode.dmr.message.data.DataMessageWithLinkControl;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ShortLCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IByteBufferProvider;
import io.github.dsheirer.sample.complex.ComplexSamples;
@@ -44,7 +48,9 @@ import io.github.dsheirer.source.ISourceEventProvider;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.wave.ComplexWaveSource;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@@ -52,108 +58,82 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* DMR decoder module.
* DMR DQPSK decoder.
*
* Processes the sample stream using an optimal (scalar or vector) DQPSK demodulator. The demodulated sample stream
* is processed by the DMRSoftSymbolProcessor at each symbol time interval using a primary and two lagging soft sync
* detectors. The sync detectors use correlation of the demodulated soft symbols with all known sync patterns to
* detect the sync and align the symbol timing and symbol period to the optimal location and symbol period as
* calculated across each detected burst. Once the symbol processor has fingerprinted the channel type, it optimizes
* sync detection for one of three detected modes (BASE, MOBILE or DIRECT) to reduce sync correlation workload.
*
* The DMRSoftSymbolMessageFramer receives sync detections from the soft symbol processor and buffers symbols from the
* start of the burst through the sync so that the symbols can be resampled when the sync is detected, ensuring that
* the burst symbols are accurate using the symbol timing and period values calculated on the sync detection.
*
* For Direct Mode where one timeslot is empty, the symbol message framer uses two message burst buffers that alternate
* for each burst so that one buffer tracks the active timeslot and the other buffer tracks the empty timeslot to
* ensure accurate burst detection and tracking.
*
* 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 ISourceEventListener, ISourceEventProvider,
IComplexSamplesListener, Listener<ComplexSamples>, IByteBufferProvider
public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplexSamplesListener, ISourceEventListener,
ISourceEventProvider, Listener<ComplexSamples>
{
private final static Logger mLog = LoggerFactory.getLogger(DMRDecoder.class);
protected static final float SAMPLE_COUNTER_GAIN = 0.4f;
private static final double SYMBOL_RATE = 4800.0;
private double mSampleRate;
private Broadcaster<Dibit> mDibitBroadcaster = new Broadcaster<>();
private DibitToByteBufferAssembler mByteBufferAssembler = new DibitToByteBufferAssembler(300);
private static final Logger LOGGER = LoggerFactory.getLogger(DMRDecoder.class);
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##");
private static final int SYMBOL_RATE = 4800;
private static final Map<Double,float[]> BASEBAND_FILTERS = new HashMap<>();
private DQPSKDemodulator mDemodulator;
private DMRMessageFramer mMessageFramer = new DMRMessageFramer();
private DMRSoftSymbolProcessor mSymbolProcessor = new DMRSoftSymbolProcessor(mMessageFramer);
private DMRMessageProcessor mMessageProcessor;
protected IComplexGainControl mAGC = ComplexGainFactory.getComplexGainControl();
private Map<Double,float[]> mBasebandFilters = new HashMap<>();
protected IRealFilter mIBasebandFilter;
protected IRealFilter mQBasebandFilter;
protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
protected DQPSKDecisionDirectedDemodulator mQPSKDemodulator;
protected CostasLoop mCostasLoop;
protected FrequencyCorrectionSyncMonitor mFrequencyCorrectionSyncMonitor;
protected DMRMessageFramer mMessageFramer;
protected PowerMonitor mPowerMonitor = new PowerMonitor();
private IRealFilter mIBasebandFilter;
private IRealFilter mQBasebandFilter;
private PowerMonitor mPowerMonitor = new PowerMonitor();
/**
* Constructs an instance
* @param config for the DMR decoder
*/
public DMRDecoder(DecodeConfigDMR config)
public DMRDecoder(DecodeConfigDMR config, boolean isTrafficChannel)
{
mMessageProcessor = new DMRMessageProcessor(config);
mMessageProcessor.setMessageListener(getMessageListener());
getDibitBroadcaster().addListener(mByteBufferAssembler);
setSampleRate(25000.0);
if(isTrafficChannel)
{
mSymbolProcessor.setBaseStationMode(true);
}
}
@Override
public void start()
{
super.start();
mQPSKDemodulator.start();
}
@Override
public void stop()
{
super.stop();
mQPSKDemodulator.stop();
}
/**
* DMR decoder type.
*/
@Override
public DecoderType getDecoderType()
{
return DecoderType.DMR;
}
/**
* Message processor
*/
protected DMRMessageProcessor getMessageProcessor()
{
return mMessageProcessor;
}
/**
* Sets the sample rate and configures internal decoder components.
* @param sampleRate of the channel to decode
*/
public void setSampleRate(double sampleRate)
{
if(sampleRate <= getSymbolRate() * 2)
if(sampleRate <= SYMBOL_RATE * 2)
{
throw new IllegalArgumentException("Sample rate [" + sampleRate + "] must be >9600 (2 * " +
getSymbolRate() + " symbol rate)");
SYMBOL_RATE + " symbol rate)");
}
mPowerMonitor.setSampleRate((int)sampleRate);
mSampleRate = sampleRate;
mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter());
mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter());
mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate());
mCostasLoop.setPLLBandwidth(PLLBandwidth.BW_300);
mFrequencyCorrectionSyncMonitor = new FrequencyCorrectionSyncMonitor(mCostasLoop, this);
mInterpolatingSampleBuffer = new InterpolatingSampleBuffer(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN);
mQPSKDemodulator = new DQPSKDecisionDirectedDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
if(mMessageFramer != null)
{
getDibitBroadcaster().removeListener(mMessageFramer);
}
//The Costas Loop receives symbol-inversion correction requests when detected.
//The PLL gain monitor receives sync detect/loss signals from the message framer
mMessageFramer = new DMRMessageFramer(mCostasLoop);
mMessageFramer.setSyncDetectListener(mFrequencyCorrectionSyncMonitor);
mMessageFramer.setListener(getMessageProcessor());
mQPSKDemodulator.setSymbolListener(getDibitBroadcaster());
getDibitBroadcaster().addListener(mMessageFramer);
mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter(sampleRate));
mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter(sampleRate));
mDemodulator = DQPSKDemodulatorFactory.getDemodulator(sampleRate, SYMBOL_RATE);
mSymbolProcessor.setSamplesPerSymbol(mDemodulator.getSamplesPerSymbol());
mMessageFramer.setListener(mMessageProcessor);
mMessageProcessor.setMessageListener(getMessageListener());
}
/**
@@ -163,142 +143,86 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener,
@Override
public void receive(ComplexSamples samples)
{
mMessageFramer.setCurrentTime(samples.timestamp());
mMessageFramer.setTimestamp(samples.timestamp());
float[] i = mIBasebandFilter.filter(samples.i());
float[] q = mQBasebandFilter.filter(samples.q());
//Process buffer for power measurements
mPowerMonitor.process(i, q);
ComplexSamples amplified = mAGC.process(i, q, samples.timestamp());
mQPSKDemodulator.receive(amplified);
float[] demodulated = mDemodulator.demodulate(i, q);
mSymbolProcessor.receive(demodulated);
}
/**
* Constructs a baseband filter for this decoder using the current sample rate
*/
private float[] getBasebandFilter()
private float[] getBasebandFilter(double sampleRate)
{
//Attempt to reuse a cached (ie already-designed) filter if available
float[] filter = mBasebandFilters.get(getSampleRate());
if(filter == null)
if(BASEBAND_FILTERS.containsKey(sampleRate))
{
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate((int)getSampleRate())
return BASEBAND_FILTERS.get(sampleRate);
}
FIRFilterSpecification specification = FIRFilterSpecification
.lowPassBuilder()
.sampleRate(sampleRate)
.passBandCutoff(5100)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandAmplitude(0.0)
.stopBandStart(6500)
.stopBandRipple(0.01)
.build();
.passBandAmplitude(1.0).passBandRipple(0.01) //.01
.stopBandAmplitude(0.0).stopBandStart(6500) //6500
.stopBandRipple(0.01).build();
try
{
filter = FilterFactory.getTaps(specification);//
}
catch(Exception fde) //FilterDesignException
{
mLog.error("Couldn't design low pass baseband filter for sample rate: " + getSampleRate());
}
float[] coefficients = null;
if(filter != null)
{
mBasebandFilters.put(getSampleRate(), filter);
}
else
{
throw new IllegalStateException("Couldn't design a DMR baseband filter for sample rate: " + getSampleRate());
}
}
return filter;
}
/**
* Process source events
*/
protected void process(SourceEvent sourceEvent)
{
switch(sourceEvent.getEvent())
try
{
case NOTIFICATION_SAMPLE_RATE_CHANGE:
mCostasLoop.reset();
setSampleRate(sourceEvent.getValue().doubleValue());
break;
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
//Reset the PLL if/when the tuner PPM changes so that we can re-lock
mCostasLoop.reset();
break;
coefficients = FilterFactory.getTaps(specification);
BASEBAND_FILTERS.put(sampleRate, 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;
}
/**
* Resets this decoder to prepare for processing a new channel
*/
@Override
public void reset()
{
mCostasLoop.reset();
mFrequencyCorrectionSyncMonitor.reset();
}
/**
* Assembler for packaging Dibit stream into reusable byte buffers.
*/
protected Broadcaster<Dibit> getDibitBroadcaster()
{
return mDibitBroadcaster;
}
/**
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
* Implements the IByteBufferProvider interface - delegates to the symbol processor
*/
@Override
public void setBufferListener(Listener<ByteBuffer> listener)
{
mByteBufferAssembler.setBufferListener(listener);
mSymbolProcessor.setBufferListener(listener);
}
/**
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
* Implements the IByteBufferProvider interface - delegates to the symbol processor
*/
@Override
public void removeBufferListener(Listener<ByteBuffer> listener)
{
mByteBufferAssembler.removeBufferListener(listener);
mSymbolProcessor.setBufferListener(null);
}
/**
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
* Implements the IByteBufferProvider interface - delegates to the symbol processor
*/
@Override
public boolean hasBufferListeners()
{
return mByteBufferAssembler.hasBufferListeners();
return mSymbolProcessor.hasBufferListener();
}
protected double getSymbolRate()
@Override
public Listener<SourceEvent> getSourceEventListener()
{
return SYMBOL_RATE;
}
/**
* Current sample rate for this decoder
*/
protected double getSampleRate()
{
return mSampleRate;
}
/**
* Samples per symbol based on current sample rate and symbol rate.
*/
public float getSamplesPerSymbol()
{
return (float)(getSampleRate() / getSymbolRate());
return this::process;
}
/**
@@ -307,66 +231,194 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener,
@Override
public void setSourceEventListener(Listener<SourceEvent> listener)
{
super.setSourceEventListener(listener);
mPowerMonitor.setSourceEventListener(listener);
}
/**
* Removes a registered source event listener from receiving source events from this decoder
*/
@Override
public void removeSourceEventListener()
{
super.removeSourceEventListener();
mPowerMonitor.setSourceEventListener(null);
}
@Override
public Listener<SourceEvent> getSourceEventListener()
public void start()
{
return sourceEvent -> process(sourceEvent);
super.start();
mMessageFramer.start();
}
@Override
public void stop()
{
super.stop();
mMessageFramer.stop();
}
/**
* Listener interface to receive reusable complex buffers
* Process source events
*/
private void process(SourceEvent sourceEvent)
{
switch(sourceEvent.getEvent())
{
case NOTIFICATION_SAMPLE_RATE_CHANGE:
setSampleRate(sourceEvent.getValue().doubleValue());
break;
}
}
@Override
public Listener<ComplexSamples> getComplexSamplesListener()
{
return DMRDecoder.this;
return this;
}
public static void main(String[] args)
{
File file = new File("/home/denny/Downloads/TIII,HYT414.585000,.wav");
LOGGER.info("Starting ...");
try
// String directory = "D:\\DQPSK Equalizer Research\\"; //Windows
String directory = "/media/denny/T9/DQPSK Equalizer Research/"; //Linux
// String file = directory + "DMR_1_CAPPLUS.wav";
// String file = directory + "DMR_2_CAPPLUS.wav";
// String file = directory + "DMR_3_CAPPLUS.wav";
// String file = directory + "20230819_064211_451250000_SaiaNet_Syracuse_Control_29_baseband.wav";
// String file = directory + "20230819_064344_454575000_JPJ_Communications_(DMR)_Madison_Control_28_baseband.wav";
// String file = directory + "DMR_DCDM_4_4_baseband_20220318_142716.wav";
// String file = directory + "DMR_4_20241213_Saianet.wav";
String file = directory + "DMR_5_20241217_031219_451425000_SaiaNet_Onondaga_SaiaNet_Control_1_baseband.wav";
// String file = directory + "DMR_6_20241217_031511_451425000_SaiaNet_Onondaga_SaiaNet_Control_50_baseband.wav";
// String file = directory + "DMR_7_20241217_031651_451250000_SaiaNet_Onondaga_SaiaNet_Control_50_baseband.wav";
// String file = directory + "DMR_8_20241217_031845_461662500_SaiaNet_(Tier_III)_Onondaga_Control_25_baseband.wav";
// String file = directory + "DMR_9_CAPPLUS_encrypted_American_Airlines_Maricopa_Control_29_baseband.wav";
// String file = directory + "DMR_10_CAP_ENCRYPTED_20241222_035408_935487500_American_Airlines_Maricopa_Control_1_baseband.wav";
boolean autoReplay = false;
DMRDecoder decoder = new DMRDecoder(new DecodeConfigDMR(), false);
decoder.start();
UserPreferences userPreferences = new UserPreferences();
DMRAudioModule audio1 = new DMRAudioModule(userPreferences, new AliasList(""), DMRMessage.TIMESLOT_1);
DMRAudioModule audio2 = new DMRAudioModule(userPreferences, new AliasList(""), DMRMessage.TIMESLOT_2);
decoder.setMessageListener(new Listener<>()
{
private long mBitCounter = 1;
private int mBitErrorCounter;
private int mValidMessageCounter;
private int mTotalMessageCounter;
@Override
public void receive(IMessage iMessage)
{
int errors = 0;
if(iMessage.getTimeslot() == DMRMessage.TIMESLOT_1)
{
audio1.receive(iMessage);
}
if(iMessage.getTimeslot() == DMRMessage.TIMESLOT_2)
{
audio2.receive(iMessage);
}
if(iMessage instanceof DMRBurst burst)
{
mBitCounter += 288;
errors = burst.getMessage().getCorrectedBitCount();
mTotalMessageCounter++;
if(mTotalMessageCounter == 492)
{
int a = 0;
}
if(burst.isValid())
{
mBitErrorCounter += Math.max(errors, 0);
mValidMessageCounter++;
}
}
else if(iMessage instanceof LCMessage lcw)
{
mTotalMessageCounter++;
errors = lcw.getMessage().getCorrectedBitCount();
if(lcw.isValid())
{
mBitErrorCounter += errors;
mValidMessageCounter++;
}
}
double bitErrorRate = (double)mBitErrorCounter / (double)mBitCounter * 100.0;
boolean logFLC = false;
boolean logSLC = false;
boolean logCACH = false;
boolean logIdles = false;
boolean logEverything = true;
if(!logEverything && logFLC)
{
if(iMessage instanceof FullLCMessage)
{
System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]");
}
else if(iMessage instanceof Terminator terminator)
{
System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]");
}
else if(iMessage instanceof DataMessageWithLinkControl)
{
System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]");
}
}
if(!logEverything && logCACH && iMessage instanceof DMRBurst burst && burst.hasCACH())
{
System.out.println("CACH:" + burst.getCACH());
}
if(!logEverything && logSLC && iMessage instanceof ShortLCMessage)
{
System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]");
}
if(logEverything)
{
System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]");
}
if(!logEverything && logIdles && iMessage instanceof SyncLossMessage)
{
System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]");
}
}
});
try(ComplexWaveSource source = new ComplexWaveSource(new File(file), autoReplay))
{
ComplexWaveSource source = new ComplexWaveSource(file);
source.open();
System.out.println("Source Sample Rate: " + source.getSampleRate());
DMRDecoder decoder = new DMRDecoder(new DecodeConfigDMR());
decoder.setMessageListener(message -> System.out.println("TS:" + message.getTimeslot() + " " + message));
source.setListener(iNativeBuffer -> {
Iterator<ComplexSamples> it = iNativeBuffer.iterator();
while(it.hasNext())
{
ComplexSamples samples = it.next();
decoder.receive(samples);
decoder.receive(it.next());
}
});
decoder.setSampleRate(source.getSampleRate());
decoder.start();
source.start();
decoder.setSampleRate(source.getSampleRate());
while(true)
{
source.next(65535);
source.next(2048, true);
}
}
catch(Exception e)
catch(IOException ioe)
{
e.printStackTrace();
LOGGER.error("Error", ioe);
}
LOGGER.info("Finished");
}
}

View File

@@ -1,130 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.dmr;
import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulatorInstrumented;
import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented;
import io.github.dsheirer.dsp.psk.SymbolDecisionData;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.Complex;
import io.github.dsheirer.sample.complex.ComplexSamples;
/**
* Instrumented version of DMR decoder
*/
public class DMRDecoderInstrumented extends DMRDecoder
{
private Listener<Double> mPLLPhaseErrorListener;
private Listener<Double> mPLLFrequencyListener;
private Listener<Double> mSamplesPerSymbolListener;
private Listener<Complex> mComplexSymbolListener;
private Listener<ComplexSamples> mFilteredSymbolListener;
private Listener<SymbolDecisionData> mSymbolDecisionDataListener;
/**
* Instrumented version of the DMR decoder that supports registering listeners to provide access to data as
* it is being processed by the decoder.
*/
public DMRDecoderInstrumented(DecodeConfigDMR config)
{
super(config);
}
/**
* Primary method for processing incoming complex sample buffers
* @param complexSamples containing channelized complex samples
*/
@Override
public void receive(ComplexSamples samples)
{
mMessageFramer.setCurrentTime(samples.timestamp());
float[] i = mIBasebandFilter.filter(samples.i());
float[] q = mQBasebandFilter.filter(samples.q());
if(mFilteredSymbolListener != null)
{
mFilteredSymbolListener.receive(new ComplexSamples(i, q, samples.timestamp()));
}
//Process buffer for power measurements
mPowerMonitor.process(i, q);
ComplexSamples amplified = mAGC.process(i, q, samples.timestamp());
mQPSKDemodulator.receive(amplified);
}
/**
* Overrides this method so we can correctly configure for instrumented operations
*/
public void setSampleRate(double sampleRate)
{
super.setSampleRate(sampleRate);
InterpolatingSampleBufferInstrumented instrumentedBuffer =
new InterpolatingSampleBufferInstrumented(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN);
mInterpolatingSampleBuffer = instrumentedBuffer;
DQPSKDecisionDirectedDemodulatorInstrumented instrumented = new DQPSKDecisionDirectedDemodulatorInstrumented(mCostasLoop, instrumentedBuffer, getSampleRate());
mQPSKDemodulator = instrumented;
instrumented.setComplexSymbolListener(mComplexSymbolListener);
instrumented.setPLLErrorListener(mPLLPhaseErrorListener);
instrumented.setPLLFrequencyListener(mPLLFrequencyListener);
instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener);
instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener);
instrumented.setSymbolListener(getDibitBroadcaster());
getDibitBroadcaster().addListener(mMessageFramer);
}
public void setComplexSymbolListener(Listener<Complex> listener)
{
mComplexSymbolListener = listener;
((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setComplexSymbolListener(listener);
}
public void setPLLPhaseErrorListener(Listener<Double> listener)
{
mPLLPhaseErrorListener = listener;
((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setPLLErrorListener(listener);
}
public void setPLLFrequencyListener(Listener<Double> listener)
{
mPLLFrequencyListener = listener;
((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setPLLFrequencyListener(listener);
}
public void setFilteredBufferListener(Listener<ComplexSamples> listener)
{
mFilteredSymbolListener = listener;
}
public void setSymbolDecisionDataListener(Listener<SymbolDecisionData> listener)
{
mSymbolDecisionDataListener = listener;
((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setSymbolDecisionDataListener(listener);
}
public void setSamplesPerSymbolListener(Listener<Double> listener)
{
mSamplesPerSymbolListener = listener;
((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setSamplesPerSymbolListener(listener);
}
}

View File

@@ -88,8 +88,9 @@ import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedEncryptionParameters;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionParameters;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.module.decode.event.IDecodeEvent;
@@ -546,7 +547,7 @@ public class DMRDecoderState extends TimeslotDecoderState
{
if(message.getSyncPattern().isMobileSyncPattern())
{
if(message.getSyncPattern().isDirectMode())
if(message.getSyncPattern().isDirect())
{
updateCurrentCall(DecodeEventType.CALL, "DIRECT MODE", message.getTimestamp());
}
@@ -569,7 +570,7 @@ public class DMRDecoderState extends TimeslotDecoderState
{
EmbeddedParameters embedded = voiceEmb.getEmbeddedParameters();
if(embedded.getShortBurst() instanceof EncryptionParameters arc4)
if(embedded.getShortBurst() instanceof EmbeddedEncryptionParameters arc4)
{
updateEncryptedCall(arc4, true, voiceEmb.getTimestamp());
}
@@ -1356,10 +1357,10 @@ public class DMRDecoderState extends TimeslotDecoderState
/**
* Updates the current call with encryption information.
* @param encryptionParameters decoded from the Voice Frame F
* @param embeddedEncryptionParameters decoded from the Voice Frame F
* @param isGroup true for group or false for individual call.
*/
private void updateEncryptedCall(EncryptionParameters encryptionParameters, boolean isGroup, long timestamp)
private void updateEncryptedCall(EmbeddedEncryptionParameters embeddedEncryptionParameters, boolean isGroup, long timestamp)
{
if(mCurrentCallEvent != null)
{
@@ -1367,11 +1368,11 @@ public class DMRDecoderState extends TimeslotDecoderState
if(details == null)
{
details = encryptionParameters.toString();
details = embeddedEncryptionParameters.toString();
}
else if(!details.contains(encryptionParameters.toString()) && !details.contains("ENCRYPTION"))
else if(!details.contains(embeddedEncryptionParameters.toString()) && !details.contains("ENCRYPTION"))
{
details += " " + encryptionParameters;
details += " " + embeddedEncryptionParameters;
}
mCurrentCallEvent.setDetails(details);
@@ -1381,7 +1382,7 @@ public class DMRDecoderState extends TimeslotDecoderState
mCurrentCallEvent = DMRDecodeEvent.builder(isGroup ? DecodeEventType.CALL_GROUP_ENCRYPTED :
DecodeEventType.CALL_ENCRYPTED, timestamp)
.channel(getCurrentChannel())
.details(encryptionParameters.toString())
.details(embeddedEncryptionParameters.toString())
.identifiers(getIdentifierCollection().copyOf())
.timeslot(getTimeslot())
.build();

View File

@@ -0,0 +1,78 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.dmr;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.module.decode.dmr.sync.DMRHardSyncDetector;
import java.nio.ByteBuffer;
/**
* Processes a stream of dibit symbol decisions to detect DMR sync patterns and produce decoded DMR messages.
*
* Note: this symbol processor is primarily used for offline analysis of a captured bit stream recording.
*/
public class DMRHardSymbolProcessor
{
private final DMRMessageFramer mMessageFramer;
private final DMRHardSyncDetector mSyncDetector = new DMRHardSyncDetector();
//Dibit delay line sizing: CACH(12) + MESSAGE_PREFIX(54) + SYNC(24)
private DibitDelayLine mDibitDelayLine = new DibitDelayLine(90);
/**
* Constructs an instance
* @param messageFramer to receive dibits and sync notifications.
*/
public DMRHardSymbolProcessor(DMRMessageFramer messageFramer)
{
mMessageFramer = messageFramer;
}
/**
* Processes an individual dibit.
* @param dibit to process
*/
public void process(Dibit dibit)
{
Dibit ejected = mDibitDelayLine.insert(dibit);
mMessageFramer.receive(ejected);
if(mSyncDetector.process(dibit))
{
mMessageFramer.syncDetected(mSyncDetector.getDetectedPattern());
}
}
/**
* Primary method for streaming decoded symbol byte arrays.
*
* @param buffer to process into a stream of dibits for processing.
*/
public void receive(ByteBuffer buffer)
{
for(byte value : buffer.array())
{
for(int x = 0; x <= 3; x++)
{
process(Dibit.parse(value, x));
}
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 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
@@ -16,517 +16,375 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.SyncLossMessage;
import io.github.dsheirer.module.decode.dmr.audio.DMRCallSequenceRecorder;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
import io.github.dsheirer.module.decode.dmr.message.DMRMessageFactory;
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket;
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.token.Identity;
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.token.Token;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.record.binary.BinaryReader;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
/**
* DMR Sync Detector and Message Framer. Includes capability to detect PLL out-of-phase lock errors
* and issue phase corrections.
* DMR message framer. Processes a DMR symbol stream to produce DMR messages/bursts. Employs two symbol buffers to
* track and frame both DMR timeslots. Responds to external sync detections to alternately activate one of the two
* buffers and capture a DMR burst, convert it to the correct message format and send it to a message listener.
*
* This framer is able to detect DMR Direct mode and track one active and one inactive timeslot across each voice
* super frame in addition to framing base and mobile station data and voice bursts.
*/
public class DMRMessageFramer implements Listener<Dibit>, IDMRBurstDetectListener
public class DMRMessageFramer implements Listener<Dibit>
{
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageFramer.class);
private static final double DMR_BIT_RATE = 9600.0;
/**
* Provides sync detection and burst framing
*/
private DMRBurstFramer mBurstFramer;
/**
* External listener for messages produced by this framer
*/
private static final int DIBIT_CACH_START = 0;
private static final int DIBIT_BURST_END = 144;
private Listener<IMessage> mMessageListener;
/**
* External listener for sync state information
*/
private ISyncDetectListener mSyncDetectListener;
/**
* Tracks current time for use with framed bursts/messages. This value is updated externally from sample buffer
* timestamps and internally as each framed message is received or sync loss is processed.
*/
private long mCurrentTime = System.currentTimeMillis();
private Dibit[] mBufferA = new Dibit[144];
private Dibit[] mBufferB = new Dibit[144];
private int mBufferAPointer = 0;
private int mBufferBPointer = 0;
private DMRSyncPattern mBufferAPattern = DMRSyncPattern.UNKNOWN;
private DMRSyncPattern mBufferBPattern = DMRSyncPattern.UNKNOWN;
private int mBufferATimeslot;
private int mBufferBTimeslot;
private boolean mBufferAActive = false;
private boolean mAssemblingBurst = false;
private int mDibitCounter = 0;
private int mDibitSinceTimestampCounter = 0;
private long mReferenceTimestamp = 0;
private boolean mRunning = false;
/**
* Constructs an instance
*
* @param phaseLockedLoop to receive PLL phase lock error corrections that are identified by the DMRBurstFramer
* when known phase misaligned sync patterns are detected.
*/
public DMRMessageFramer(IPhaseLockedLoop phaseLockedLoop)
{
mBurstFramer = new DMRBurstFramer(this, phaseLockedLoop);
}
/**
* Constructs an instance.
*/
public DMRMessageFramer()
{
this(null);
Arrays.fill(mBufferA, Dibit.D00_PLUS_1);
Arrays.fill(mBufferB, Dibit.D00_PLUS_1);
}
/**
* Registers a sync detect listener to be notified each time a sync pattern and NID are detected.
*/
public void setSyncDetectListener(ISyncDetectListener syncDetectListener)
{
mSyncDetectListener = syncDetectListener;
}
/**
* Current timestamp or timestamp of incoming message buffers that is continuously updated to as
* close as possible to the bits processed for the expected baud rate.
*
* @return current time
*/
private long getTimestamp()
{
return mCurrentTime;
}
/**
* Sets the current time. This should be invoked by an incoming message buffer stream.
*
* @param currentTime
*/
public void setCurrentTime(long currentTime)
{
mCurrentTime = currentTime;
}
/**
* Updates the current timestamp based on the number of bits processed versus the bit rate per second
* in order to keep an accurate running timestamp to use for timestamped message creation.
*
* @param bitsProcessed thus far
*/
private void updateBitsProcessed(int bitsProcessed)
{
if(bitsProcessed > 0)
{
mCurrentTime += (long)((double)bitsProcessed / DMR_BIT_RATE * 1000.0);
}
}
/**
* Registers the listener for messages produced by this message framer
*
* @param messageListener to receive framed and decoded messages
*/
public void setListener(Listener<IMessage> messageListener)
{
mMessageListener = messageListener;
}
/**
* Primary method for streaming decoded symbol dibits for message framing.
*
* @param dibit to process
* Primary input method for receiving the stream of demodulated dibit symbols.
* @param dibit to process.
*/
@Override
public void receive(Dibit dibit)
{
mBurstFramer.receive(dibit);
}
mDibitCounter++;
mDibitSinceTimestampCounter++;
/**
* Primary method for streaming decoded symbol byte arrays.
*
* @param buffer to process into a stream of dibits for processing.
*/
public void receive(ByteBuffer buffer)
{
for(byte value : buffer.array())
//Issue a sync loss for each full 1-second interval (ie 4800 dibits) of no sync.
if(!mAssemblingBurst && mDibitCounter >= 4800)
{
for(int x = 0; x <= 3; x++)
dispatch(new SyncLossMessage(getTimestamp(), 9600, Protocol.DMR, DMRMessage.TIMESLOT_0));
mDibitCounter -= 4800;
mBufferAPattern = DMRSyncPattern.UNKNOWN;
mBufferBPattern = DMRSyncPattern.UNKNOWN;
}
if(mAssemblingBurst)
{
if(mBufferAActive)
{
receive(Dibit.parse(value, x));
}
}
}
mBufferA[mBufferAPointer++] = dibit;
/**
* DMR burst detection processing. Processes DMR burst/messages from external burst framer/producer.
*
* @param message bits for the burst/message and any bit errors detected in the sync pattern
* @param syncPattern that was detected for the burst
*/
@Override
public void burstDetected(CorrectedBinaryMessage message, DMRSyncPattern syncPattern, int timeslot)
{
//Each burst is 288 bits
updateBitsProcessed(288);
//This allows the frequency error monitor to be notified when we're in sync
if(mSyncDetectListener != null)
{
mSyncDetectListener.syncDetected(0);
}
CACH cach = null;
if(syncPattern.hasCACH())
{
cach = CACH.getCACH(message);
}
if(mMessageListener != null)
{
DMRMessage dmrMessage = DMRMessageFactory.create(syncPattern, message, cach, getTimestamp(), timeslot);
if(dmrMessage != null)
{
mMessageListener.receive(dmrMessage);
}
}
}
/**
* Processes a sync loss to track how many bits were processed and update message listeners.
*
* @param bitsProcessed since the last sync detect
*/
@Override
public void syncLost(int bitsProcessed, int timeslot)
{
updateBitsProcessed(bitsProcessed);
dispatchSyncLoss(bitsProcessed, timeslot);
if(mSyncDetectListener != null)
{
mSyncDetectListener.syncLost(bitsProcessed);
}
}
/**
* Creates a sync loss message and dispatches it to the message listener
*
* @param bitsProcessed without a sync detection
* @param timeslot that lost the sync
*/
private void dispatchSyncLoss(int bitsProcessed, int timeslot)
{
if(bitsProcessed > 0 && mMessageListener != null)
{
mMessageListener.receive(new SyncLossMessage(getTimestamp(), bitsProcessed, Protocol.DMR, timeslot));
}
}
public static class MessageListener implements Listener<IMessage>
{
private boolean mHasDMRData = false;
public int mTS0Count = 0;
public int mTS1Count = 0;
@Override
public void receive(IMessage message)
{
if(message instanceof DMRBurst && ((DMRBurst)message).hasCACH())
{
CACH cach = ((DMRBurst)message).getCACH();
mLog.info(cach.toString() + " TS:" + message.getTimeslot() + " " + message.toString());
if(mBufferAPointer >= mBufferA.length)
{
dispatchBufferA();
}
}
else
{
mLog.info(" TS:" + message.getTimeslot() + " " + message.toString());
}
mBufferB[mBufferBPointer++] = dibit;
if(message instanceof DMRPacketMessage dpm)
{
if(dpm.getPacket() instanceof LRRPPacket lrrp)
if(mBufferBPointer >= mBufferB.length)
{
mLog.info("LRRP: " + lrrp.toString());
mLog.info("MESSAGE: " + lrrp.getMessage().toHexString());
dispatchBufferB();
}
}
// if(message instanceof UDTHeader)
// {
// mLog.info("TS:" + message.getTimeslot() + " " + message.toString());
// }
// if(message instanceof UnknownCSBKMessage && message.isValid())
// {
// mLog.info("TS:" + ((DMRMessage)message).getTimeslot() + " " + message.toString());
// }
}
public boolean hasData()
{
return mHasDMRData;
}
public void reset()
{
mHasDMRData = true;
}
}
public static class LrrpProcessor implements Listener<IMessage>
private void dispatchBufferA()
{
private Map<Integer, List<LRRPPacket>> mLrrpMap = new HashMap<>();
mDibitCounter -= 144;
public LrrpProcessor()
if(mDibitCounter > 0)
{
}
@Override
public void receive(IMessage message)
{
if(message instanceof DMRPacketMessage dpm)
//If there are 144 dibits then tag the sync loss to other timeslot, otherwise dump it to timeslot 0.
if(mDibitCounter == 144)
{
if(dpm.getPacket() instanceof LRRPPacket lrrp)
//Note: since buffer b got skipped, we'll use buffer a timeslot and then it will get reassigned from
//the CACH to the correct timeslot.
dispatch(new SyncLossMessage(getTimestamp(), 288, Protocol.DMR, mBufferATimeslot));
}
else
{
dispatch(new SyncLossMessage(getTimestamp(), mDibitCounter * 2, Protocol.DMR, DMRMessage.TIMESLOT_0));
if(mDibitCounter > 1 && !mBufferAPattern.isDirect())
{
process(lrrp);
mBufferATimeslot = 0;
mBufferBTimeslot = 0;
mBufferBPattern = DMRSyncPattern.UNKNOWN;
}
}
}
public void process(LRRPPacket lrrpPacket)
mDibitCounter = 0;
CorrectedBinaryMessage message = getMessage(mBufferA, DIBIT_CACH_START, DIBIT_BURST_END);
CACH cach = CACH.getCACH(message);
if(mBufferAPattern.hasCACH() && cach.isValid() && mBufferATimeslot != cach.getTimeslot())
{
Identity identity = getIdentity(lrrpPacket);
if(identity != null)
{
List<LRRPPacket> packets = mLrrpMap.get(identity.getID());
if(packets == null)
{
packets = new ArrayList<>();
}
packets.add(lrrpPacket);
mLrrpMap.put(identity.getID(), packets);
}
mBufferATimeslot = cach.getTimeslot();
mBufferBTimeslot = (mBufferATimeslot == 1 ? 2 : 1);
}
public void log()
dispatch(DMRMessageFactory.create(mBufferAPattern, message, cach, getTimestamp(), mBufferATimeslot));
//Since voice frames only have sync on the first burst, we use pseudo-patterns to track the rest of the bursts
//across the voice super-frame. If the current pattern is a voice frame, set it to the next pseudo voice sync.
if(mBufferAPattern.isVoicePattern())
{
mLog.info("*************** DUMPING LRRP PACKETS****************************");
List<Integer> ids = new ArrayList(mLrrpMap.keySet());
Collections.sort(ids);
for(Integer id: ids)
{
mLog.info("");
mLog.info("###################### ID: " + id + " ###################################");
List<LRRPPacket> packets = mLrrpMap.get(id);
for(LRRPPacket lrrp: packets)
{
mLog.info("PACKET: " + lrrp.toString());
mLog.info("MESSAGE: " + lrrp.getMessage().toHexString());
}
}
mBufferAPattern = DMRSyncPattern.getNextVoice(mBufferAPattern);
}
private static Identity getIdentity(LRRPPacket packet)
//Automatically trigger buffer B burst collection if it is collecting a voice super frame.
if(mBufferBPattern.isVoicePattern())
{
for(Token token: packet.getTokens())
{
if(token instanceof Identity)
{
return (Identity)token;
}
}
return null;
}
}
public static void main(String[] args)
{
String path = "/media/denny/T7 Shield/Recordings/DMR/";
//Con+ Traffic + Voice
// String file = path + "20200513_143340_9600BPS_DMR_SaiaNet_Onondaga_Control.bits"; //Enh GPS Revert Window Annce
// String file = path + "20200514_062135_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //Neighbor only
// String file = path + "20200514_063507_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //GPS Window Grant 2579
// String file = path + "20200514_064224_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //GPS Window Grant 5056035
// String file = path + "20200514_131623_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //GPS Grant: 5074193
// String file = path + "SaiaNet/20200514_133947_9600BPS_DMR_SaiaNet_Onondaga_LCN_4.bits"; //<<<<<<<------ Basic Encryption
// String file = path + "20200514_142249_9600BPS_DMR_SaiaNet_Onondaga_LCN_4.bits";
// String file = path + "20200514_144534_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //Con+ Control w/GPS Window Announce
//Con+ Multi-Site with Network Frequency Map - This file is really General Motors Con+ - Tarrant County site
// String file = path + "20200716_235004_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits";
//Cap-Max Tier III CC
// String file = path + "20200710_053632_9600BPS_DMR_Niles_Radio_Coconino_Control.bits";
//Cap+ Multi-Site 1 - Data Revert LSN1/2 with CSBKO 62 Site status
String file = path + "Texas_Dallas_Aerowave_Capacity_Plus_Multi_Site/20200716_210133_9600BPS_DMR_Aerowave_Technologies_Dallas_LCN_2.bits";
// String file = path + "Texas_Dallas_Aerowave_Capacity_Plus_Multi_Site/20200716_210845_9600BPS_DMR_Aerowave_Technologies_Dallas_LCN_2.bits";
// String file = path + "Texas_Dallas_Aerowave_Capacity_Plus_Multi_Site/20200716_212309_9600BPS_DMR_Aerowave_Technologies_Dallas_LCN_2.bits";
//Cap+ Multi-Site Enhanced Data Revert Channel (no CSBKO 62)
// String file = path + "Texas_Hunt_Farmers_Electric_Capacity_Plus_Multi_Site/20200714_224018_9600BPS_DMR_Farmers_Electric_Cooperative_Hunt_LCN_3.bits"; //This may have PLL mis-align issues
//Cap+ Multi-Site - Dallas Unknown
// String file = path + "Texas_Dallas_Unknown_1_Capacity_Plus_Multi_Site/20200716_211233_9600BPS_DMR_Dallas_Unk_Dallas_Unk.bits";
//Hytera Short Data = Proprietary (encrypted)
// String file = path + "20200716_222839_9600BPS_DMR_SystemUnk_SiteUnk_Unk.bits"; //Hytera short data packets
// String file = path + "20200716_223551_9600BPS_DMR_SystemUnk_SiteUnk_Unk.bits";
//Motorola GPS Revert channel
// String file = path + "20200730_085433_9600BPS_DMR_Otsego_County_Road_Commission_Alpine_Center_Road_Commission.bits";
//Hytera XPT (pseudo trunk) beaconing and voice
// String file = path + "20200729_161450_9600BPS_DMR_JPJ_Communications_(DMR)_Madison_Control.bits"; //PLL lock oscillating
// String file = path + "20200729_162200_9600BPS_DMR_JPJ_Communications_(DMR)_Madison_Control.bits";
// String file = path + "Hytera XPT/20200816_161126_9600BPS_DMR_JPJ_Communications_(DMR)_Madison_Control.bits"; //analzing this <<<<<<<<<<<<<<<<<<<<<<<<<<
//Hytera Tier III - CSBKO 40
// String file = path + "20200716_234510_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits";
// String file = path + "20200812_061410_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits";
// String file = path + "20200716_234510_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits"; //MBC-channel grant
//UDT Header And Blocks
// String file = path + "20200730_090634_9600BPS_DMR_Otsego_County_Road_Commission_Alpine_Center_Road_Commission.bits";
//Cap+ BP Scrambling
// String file = path + "20200829_065610_9600BPS_DMR_Albany_Medical_Center_Albany_LCN_3.bits";
// String file = "/home/denny/SDRTrunk/recordings/20200927_054837_9600BPS_DMR_Blair_Communications_(Capacity_Plus)_Dallas_Control_9.bits";
DecodeConfigDMR config = new DecodeConfigDMR();
Broadcaster<IMessage> messageBroadcaster = new Broadcaster<>();
MessageListener messageListener = new MessageListener();
messageBroadcaster.addListener(messageListener);
LrrpProcessor lrrpProcessor = new LrrpProcessor();
messageBroadcaster.addListener(lrrpProcessor);
DMRCallSequenceRecorder ambeRecorder = new DMRCallSequenceRecorder(new UserPreferences(), 123456789l,
"Denny System", "Denny Site");
messageBroadcaster.addListener(ambeRecorder);
boolean multi = false;
if(multi)
{
try
{
DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(path), "*.bits");
stream.forEach(new Consumer<Path>()
{
@Override
public void accept(Path path)
{
mLog.info("Processing: " + path.toString());
DMRMessageFramer messageFramer = new DMRMessageFramer(null);
DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config);
messageFramer.setListener(messageProcessor);
messageProcessor.setMessageListener(messageBroadcaster);
try(BinaryReader reader = new BinaryReader(path, 200))
{
while(reader.hasNext())
{
ByteBuffer buffer = reader.next();
messageFramer.receive(buffer);
}
}
catch(Exception ioe)
{
ioe.printStackTrace();
}
if(!messageListener.hasData())
{
// mLog.info("Has Data: " + listener.hasData() + " File:" + path.toString());
// try
// {
// Files.delete(path);
// }
// catch(IOException ioe)
// {
// ioe.printStackTrace();
// }
}
messageListener.reset();
System.out.println("TS0 VOICE:" + messageListener.mTS0Count + " TS1 VOICE:" + messageListener.mTS1Count);
messageListener.mTS0Count = 0;
messageListener.mTS1Count = 0;
}
});
}
catch(IOException ioe)
{
ioe.printStackTrace();
}
lrrpProcessor.log();
mAssemblingBurst = true;
mBufferAActive = false;
mBufferBPointer = 0;
}
else
{
DMRMessageFramer messageFramer = new DMRMessageFramer(null);
DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config);
messageFramer.setListener(messageProcessor);
messageProcessor.setMessageListener(messageBroadcaster);
mAssemblingBurst = false;
}
}
try(BinaryReader reader = new BinaryReader(Path.of(file), 200))
private void dispatchBufferB()
{
mDibitCounter -= 144;
if(mDibitCounter > 0)
{
//If there are 144 dibits then tag the sync loss to other timeslot, otherwise dump it to timeslot 0.
if(mDibitCounter == 144)
{
while(reader.hasNext())
//Note: since buffer a got skipped, we'll use buffer b timeslot and then it will get reassigned from
//the CACH to the correct timeslot.
dispatch(new SyncLossMessage(getTimestamp(), 288, Protocol.DMR, mBufferBTimeslot));
}
else
{
dispatch(new SyncLossMessage(getTimestamp(), mDibitCounter * 2, Protocol.DMR, DMRMessage.TIMESLOT_0));
if(mDibitCounter > 1 && !mBufferBPattern.isDirect())
{
ByteBuffer buffer = reader.next();
messageFramer.receive(buffer);
mBufferATimeslot = 0;
mBufferBTimeslot = 0;
mBufferAPattern = DMRSyncPattern.UNKNOWN;
}
}
catch(Exception ioe)
{
ioe.printStackTrace();
}
}
System.out.println("TS0 VOICE:" + messageListener.mTS0Count + " TS1 VOICE:" + messageListener.mTS1Count);
messageListener.mTS0Count = 0;
messageListener.mTS1Count = 0;
mDibitCounter = 0;
CorrectedBinaryMessage burst = getMessage(mBufferB, DIBIT_CACH_START, DIBIT_BURST_END);
CACH cach = CACH.getCACH(burst);
if(mBufferBPattern.hasCACH() && cach.isValid() && mBufferBTimeslot != cach.getTimeslot())
{
mBufferBTimeslot = cach.getTimeslot();
mBufferATimeslot = (mBufferBTimeslot == 1 ? 2 : 1);
}
dispatch(DMRMessageFactory.create(mBufferBPattern, burst, cach, getTimestamp(), mBufferBTimeslot));
//Since voice frames only have sync on the first burst, we use pseudo-patterns to track the rest of the bursts
//across the voice super-frame. If the current pattern is a voice frame, set it to the next pseudo voice sync.
if(mBufferBPattern.isVoicePattern())
{
mBufferBPattern = DMRSyncPattern.getNextVoice(mBufferBPattern);
}
//Automatically trigger buffer A burst collection if it is collecting a voice super frame.
if(mBufferAPattern.isVoicePattern())
{
mAssemblingBurst = true;
mBufferAActive = true;
mBufferAPointer = 0;
}
else
{
mAssemblingBurst = false;
}
}
/**
* Dispatches the message to the registered message listener.
* @param message
*/
private void dispatch(IMessage message)
{
if(mRunning && mMessageListener != null)
{
mMessageListener.receive(message);
}
}
/**
* Starts this framer dispatching messages
*/
public void start()
{
mRunning = true;
}
/**
* Stops this framer from dispatching messages
*/
public void stop()
{
mRunning = false;
}
/**
* Sets the listener to receive framed DMR messages.
* @param listener for messages.
*/
public void setListener(Listener<IMessage> listener)
{
mMessageListener = listener;
}
/**
* Creates a binary message from the dibit buffer.
* @param buffer containing dibits
* @param start index inclusive
* @param end index exclusive.
* @return binary message
*/
private CorrectedBinaryMessage getMessage(Dibit[] buffer, int start, int end)
{
CorrectedBinaryMessage message = new CorrectedBinaryMessage(2 * (end - start));
Dibit dibit = null;
for(int i = start; i < end; i++)
{
dibit = buffer[i];
message.add(dibit.getBit1(), dibit.getBit2());
}
return message;
}
/**
* Externally triggered sync detection to initiate DMR burst capture. Indicates a sync pattern is detected and the
* next received dibit symbol is the first dibit of the DMR burst.
* @param pattern detected
*/
public void syncDetected(DMRSyncPattern pattern)
{
if(mAssemblingBurst)
{
if(mBufferAActive && mBufferAPointer < mBufferA.length)
{
mBufferAPointer = 144;
mBufferAPattern = DMRSyncPattern.UNKNOWN;
}
else if(!mBufferAActive && mBufferBPointer < mBufferB.length)
{
mBufferBPointer = 144;
mBufferBPattern = DMRSyncPattern.UNKNOWN;
}
}
mAssemblingBurst = true;
if(mBufferAActive)
{
mBufferAActive = false;
mBufferBPointer = 0;
mBufferBPattern = pattern;
if(pattern.isDirect())
{
if(pattern.isDirectTS1())
{
mBufferATimeslot = 2;
mBufferBTimeslot = 1;
}
else if(pattern.isDirectTS2())
{
mBufferATimeslot = 1;
mBufferBTimeslot = 2;
}
if(mBufferAPattern == DMRSyncPattern.UNKNOWN)
{
mBufferAPattern = DMRSyncPattern.DIRECT_EMPTY_TIMESLOT;
}
}
}
else
{
mBufferAActive = true;
mBufferAPointer = 0;
mBufferAPattern = pattern;
if(pattern.isDirect())
{
if(pattern.isDirectTS1())
{
mBufferATimeslot = 1;
mBufferBTimeslot = 2;
}
else if(pattern.isDirectTS2())
{
mBufferATimeslot = 2;
mBufferBTimeslot = 1;
}
if(mBufferBPattern == DMRSyncPattern.UNKNOWN)
{
mBufferBPattern = DMRSyncPattern.DIRECT_EMPTY_TIMESLOT;
}
}
}
}
/**
* 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();
}
}
}

View File

@@ -20,6 +20,7 @@ package io.github.dsheirer.module.decode.dmr;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.SyncLossMessage;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
@@ -73,7 +74,7 @@ public class DMRMessageProcessor implements Listener<IMessage>
private TalkerAliasAssembler mTalkerAliasAssembler = new TalkerAliasAssembler();
private Listener<IMessage> mMessageListener;
private Map<Integer,TimeslotFrequency> mTimeslotFrequencyMap = new TreeMap<>();
private DmrCrcMaskManager mCrcMaskManager = new DmrCrcMaskManager();
private DMRCrcMaskManager mCrcMaskManager = new DMRCrcMaskManager();
private boolean mIgnoreCrcChecksums;
/**
@@ -115,6 +116,13 @@ public class DMRMessageProcessor implements Listener<IMessage>
return;
}
if(message instanceof SyncLossMessage)
{
mSLCAssembler.reset();
mFLCAssemblerTimeslot1.reset();
mFLCAssemblerTimeslot2.reset();
}
//Detect and correct messages employing an alternate CRC mask pattern.
if(!message.isValid() && message instanceof CSBKMessage csbk)
{

View File

@@ -0,0 +1,458 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.dmr;
import io.github.dsheirer.dsp.filter.interpolator.PhaseAwareLinearInterpolator;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler;
import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetector;
import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorFactory;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncDetectMode;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncModeMonitor;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import io.github.dsheirer.sample.Listener;
import java.nio.ByteBuffer;
/**
* DMR soft symbol processor. Processes a stream of differentially decoded samples to align to the symbol timing and
* symbol period and extract symbols from the sample stream.
*
* Uses three soft sync correlation detectors to achieve initial sync lock with a primary sync detector and two lagging
* sync detectors sampling at 1/3 and 2/3 of the symbol period to ensure the sync is detected while only calculating at
* each symbol period interval. Once a sync is detected, sync lock is declared and the lagging sync detectors are
* disabled. Sync lock disables whenever the quantity of processed symbols exceeds one DMR burst length and the
* lagging detectors re-enable.
*
* The sync mode monitor processes the sync detections to identify the dominant sync mode for the channel and then
* disables processing of the two unused sync modes (BASE STATION, MOBILE STATION, or DIRECT MODE) for the remainder
* of the channel.
*
* This processor uses a symbol delay buffer (dibit delay line) to retain a partial DMR burst from CACH up through the
* sync field of the burst. This allows the optimize method to resample the burst symbols once the sync is detected and
* optimal symbol timing and symbol period are calculated. This ensures the best symbol decisions are then passed to
* the message framer along with the sync detection.
*
* Buffer Structure & Size = 129 Dibits: [2 pad][12 CACH][54 Message Prefix][24 Sync][24 workspace][1 pad]
*/
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 float MAX_POSITIVE_SOFT_SYMBOL = Dibit.D01_PLUS_3.getIdealPhase();
private static final float MAX_NEGATIVE_SOFT_SYMBOL = Dibit.D11_MINUS_3.getIdealPhase();
private static final float SYMBOL_QUADRANT_BOUNDARY = (float)(Math.PI / 2.0);
private static final float SYNC_DETECTION_THRESHOLD = 60;
private DMRSyncModeMonitor mSyncModeMonitor = new DMRSyncModeMonitor();
private DMRSoftSyncDetector mSyncDetector = DMRSoftSyncDetectorFactory.getDetector();
private DMRSoftSyncDetector mSyncDetectorLag1 = DMRSoftSyncDetectorFactory.getDetector();
private DMRSoftSyncDetector mSyncDetectorLag2 = DMRSoftSyncDetectorFactory.getDetector();
private DibitToByteBufferAssembler mDibitAssembler = new DibitToByteBufferAssembler(300);
//Dibit delay line sizing: CACH(12) + MESSAGE_PREFIX(54) + SYNC(24)
private DibitDelayLine mDibitDelayLine = new DibitDelayLine(90);
private DMRMessageFramer mMessageFramer;
private boolean mSyncLock = false;
private float mLaggingSyncOffset1;
private float mLaggingSyncOffset2;
private float mObservedSamplesPerSymbol;
private float mSamplesPerSymbol;
private float mSamplePoint;
private float[] mBuffer;
private int mBufferLoadPointer;
private int mBufferPointer;
private int mBufferWorkspaceLength;
private int mSymbolsSinceLastSync = 0;
/**
* Constructs an instance
* @param messageFramer to receive demodulated symbols/dibits and sync detect notifications
*/
public DMRSoftSymbolProcessor(DMRMessageFramer messageFramer)
{
if(messageFramer == null)
{
throw new IllegalArgumentException("Message framer cannot be null");
}
mMessageFramer = messageFramer;
mSyncModeMonitor.add(mSyncDetector);
mSyncModeMonitor.add(mSyncDetectorLag1);
mSyncModeMonitor.add(mSyncDetectorLag2);
}
/**
* Sets base station sync detection mode. This disables detection for MOBILE and DIRECT sync patterns. This can
* be used for traffic channels that are known to be part of a base station.
* @param baseStationMode true for base mode only, false otherwise.
*/
public void setBaseStationMode(boolean baseStationMode)
{
if(baseStationMode)
{
mSyncModeMonitor.setMode(DMRSyncDetectMode.BASE_ONLY);
}
}
/**
* Registers the listener to receive demodulated bit stream buffers.
* @param listener to register
*/
public void setBufferListener(Listener<ByteBuffer> listener)
{
mDibitAssembler.setBufferListener(listener);
}
/**
* Indicates if there is a registered buffer listener
*/
public boolean hasBufferListener()
{
return mDibitAssembler.hasBufferListeners();
}
/**
* Sets the reference timestamp from the incoming sample stream to use for assigning a timestamp to framed messages.
* @param timestamp to set for reference
*/
public void setTimestamp(long timestamp)
{
mMessageFramer.setTimestamp(timestamp);
}
/**
* Primary input method for receiving a stream of demodulated samples to process into symbols.
* @param samples to process
*/
public void receive(float[] samples)
{
int samplesPointer = 0;
while(samplesPointer < samples.length)
{
if(mBufferLoadPointer == mBuffer.length)
{
System.arraycopy(mBuffer, mBufferWorkspaceLength, mBuffer, 0, mBuffer.length - mBufferWorkspaceLength);
mBufferLoadPointer -= mBufferWorkspaceLength;
mBufferPointer -= mBufferWorkspaceLength;
}
int copyLength = Math.min(mBuffer.length - mBufferLoadPointer, samples.length - samplesPointer);
System.arraycopy(samples, samplesPointer, mBuffer, mBufferLoadPointer, copyLength);
samplesPointer += copyLength;
mBufferLoadPointer += copyLength;
while(mBufferPointer < (mBufferLoadPointer - 7)) //Interpolator needs 1 and optimizer needs 6 pad spaces
{
mBufferPointer++;
mSamplePoint--;
if(mSamplePoint < 1)
{
mSymbolsSinceLastSync++;
if(mSymbolsSinceLastSync > 144)
{
mSyncLock = false;
}
float softSymbol = PhaseAwareLinearInterpolator.calculate(mBuffer[mBufferPointer],
mBuffer[mBufferPointer + 1], mSamplePoint);
Dibit symbol = toSymbol(softSymbol);
//Store the symbol in the delay line and broadcast the delayed ejected symbol to the message framer
//and the bitstream assembler.
Dibit ejected = mDibitDelayLine.insert(symbol);
mMessageFramer.receive(ejected);
mDibitAssembler.receive(ejected);
float scorePrimary = mSyncDetector.process(softSymbol);
if(mSyncLock && scorePrimary > SYNC_DETECTION_THRESHOLD &&
optimize(mSyncDetector.getDetectedPattern(), 0.0f))
{
mSymbolsSinceLastSync = 0;
mSyncModeMonitor.detected(mSyncDetector.getDetectedPattern());
mMessageFramer.syncDetected(mSyncDetector.getDetectedPattern());
}
else
{
float lag1 = mBufferPointer + mSamplePoint - mLaggingSyncOffset1;
float lag2 = mBufferPointer + mSamplePoint - mLaggingSyncOffset2;
int lagIntegral1 = (int)Math.floor(lag1);
int lagIntegral2 = (int)Math.floor(lag2);
float softSymbolLag1 = PhaseAwareLinearInterpolator.calculate(mBuffer[lagIntegral1],
mBuffer[lagIntegral1 + 1], lag1 - lagIntegral1);
float softSymbolLag2 = PhaseAwareLinearInterpolator.calculate(mBuffer[lagIntegral2],
mBuffer[lagIntegral2 + 1], lag2 - lagIntegral2);
float scoreLag1 = mSyncDetectorLag1.process(softSymbolLag1);
float scoreLag2 = mSyncDetectorLag2.process(softSymbolLag2);
if(mSymbolsSinceLastSync > 1 && scoreLag1 > scorePrimary && scoreLag1 > scoreLag2 &&
scoreLag1 > SYNC_DETECTION_THRESHOLD &&
optimize(mSyncDetectorLag1.getDetectedPattern(), -mLaggingSyncOffset1))
{
mSymbolsSinceLastSync = 0;
mSyncModeMonitor.detected(mSyncDetectorLag1.getDetectedPattern());
mMessageFramer.syncDetected(mSyncDetectorLag1.getDetectedPattern());
}
else if(mSymbolsSinceLastSync > 1 && scoreLag2 > scorePrimary &&
scoreLag2 > SYNC_DETECTION_THRESHOLD &&
optimize(mSyncDetectorLag2.getDetectedPattern(), -mLaggingSyncOffset2))
{
mSymbolsSinceLastSync = 0;
mSyncModeMonitor.detected(mSyncDetectorLag2.getDetectedPattern());
mMessageFramer.syncDetected(mSyncDetectorLag2.getDetectedPattern());
}
else if(scorePrimary > SYNC_DETECTION_THRESHOLD &&
optimize(mSyncDetector.getDetectedPattern(), 0.0f))
{
mSymbolsSinceLastSync = 0;
mSyncLock = true;
mSyncModeMonitor.detected(mSyncDetector.getDetectedPattern());
mMessageFramer.syncDetected(mSyncDetector.getDetectedPattern());
}
}
//Add another symbol's worth of samples to the counter
mSamplePoint += mObservedSamplesPerSymbol;
}
}
}
}
/**
* Adjusts the symbol timing and symbol spacing to identify the best achievable sync correlation score and apply
* those adjustments when the correlation score exceeds a positive sync detection threshold.
* @param pattern that was detected
* @param additionalOffset from current mBufferPointer and mSamplePoint. This can be zero offset for the primary
* sync detector or an offset for the lagging sync detectors.
* @return true if there is a positive sync detection.
*/
private boolean optimize(DMRSyncPattern pattern, float additionalOffset)
{
//Offset is the start of the first sample of the first symbol of the sync pattern calculated from the current
//buffer pointer and sample point which should be the final sample of the final symbol of the detected sync.
float offset = (mBufferPointer + mSamplePoint) + additionalOffset - (mObservedSamplesPerSymbol * 23);
//Find the optimal symbol timing
float stepSize = mObservedSamplesPerSymbol / 10.0f;
float stepSizeMin = 0.03f;
float adjustment = 0.0f;
float adjustmentMax = mObservedSamplesPerSymbol / 2.0f;
float candidate = offset;
int candidateIntegral = (int)Math.floor(candidate);
float candidateFractional = candidate - candidateIntegral;
float scoreCenter = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
candidate = offset - stepSize;
candidateIntegral = (int)Math.floor(candidate);
candidateFractional = candidate - candidateIntegral;
float scoreLeft = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
candidate = offset + stepSize;
candidateIntegral = (int)Math.floor(candidate);
candidateFractional = candidate - candidateIntegral;
float scoreRight = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
while(stepSize > stepSizeMin && Math.abs(adjustment) <= adjustmentMax)
{
if(scoreLeft > scoreRight && scoreLeft > scoreCenter)
{
adjustment -= stepSize;
scoreRight = scoreCenter;
scoreCenter = scoreLeft;
candidate = offset + adjustment - stepSize;
candidateIntegral = (int)Math.floor(candidate);
candidateFractional = candidate - candidateIntegral;
scoreLeft = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
}
else if(scoreRight > scoreLeft && scoreRight > scoreCenter)
{
adjustment += stepSize;
scoreLeft = scoreCenter;
scoreCenter = scoreRight;
candidate = offset + adjustment + stepSize;
candidateIntegral = (int)Math.floor(candidate);
candidateFractional = candidate - candidateIntegral;
scoreRight = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
}
else
{
stepSize *= 0.5f;
if(stepSize > stepSizeMin)
{
candidate = offset + adjustment - stepSize;
candidateIntegral = (int)Math.floor(candidate);
candidateFractional = candidate - candidateIntegral;
scoreLeft = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
candidate = offset + adjustment + stepSize;
candidateIntegral = (int)Math.floor(candidate);
candidateFractional = candidate - candidateIntegral;
scoreRight = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
}
}
}
//If we didn't find an optimal correlation score above the 95 threshold, return a false sync.
if(scoreCenter < 95)
{
return false;
}
if(additionalOffset != 0.0)
{
adjustment += additionalOffset;
}
mSamplePoint += adjustment;
while(mSamplePoint < 0)
{
mSamplePoint++;
mBufferPointer--;
}
while(mSamplePoint > 1)
{
mSamplePoint--;
mBufferPointer++;
}
//Adjust the observed samples per symbol using the timing error measured across one or two bursts when we're in
//fine sync mode and the timing error is not excessive.
if(mSyncLock && adjustment < 0.5 && mSymbolsSinceLastSync > 143 && mSymbolsSinceLastSync < 289)
{
mObservedSamplesPerSymbol += (float)((double)adjustment / (double)mSymbolsSinceLastSync * 0.2);
}
//If the timing error adjustment is high enough, resample the symbols. Otherwise, overwrite the captured sync
//pattern in the delay buffer with the detected sync pattern to eliminate any sync bit errors.
if(Math.abs(adjustment) > 0.05)
{
float resampleStart = mBufferPointer + mSamplePoint;
resampleStart -= (89 * mObservedSamplesPerSymbol); //Start at 89 (+ 1 current = 90) symbols
int resampleStartIntegral;
for(int x = 0; x < 66; x++)
{
resampleStartIntegral = (int)Math.floor(resampleStart);
if(resampleStartIntegral >= 0)
{
float resampledSoftSymbol = PhaseAwareLinearInterpolator.calculate(mBuffer[resampleStartIntegral],
mBuffer[resampleStartIntegral + 1], resampleStart - resampleStartIntegral);
mDibitDelayLine.insert(toSymbol(resampledSoftSymbol));
}
else
{
//This shouldn't happen since there's 2x dibits of padding on the front side, but just in case.
mDibitDelayLine.insert(Dibit.D01_PLUS_3);
}
resampleStart += mObservedSamplesPerSymbol;
}
//We don't need to resample the sync region ... just use the actual sync dibit values.
for(Dibit dibit: pattern.toDibits())
{
mDibitDelayLine.insert(dibit);
}
}
else
{
//Overwrite the most recent 24 dibits with the detected sync so there's no sync bit errors
mDibitDelayLine.update(pattern.toDibits());
}
return true;
}
/**
* Calculates the sync correlation score for sync pattern at the specified offsets and symbol timing.
* @param bufferPointer to the start of the samples in the soft symbol buffer
* @param fractional position to interpolate within the 8 samples starting at the buffer pointer.
* @param samplesPerSymbol spacing to test for.
* @param pattern to correlate against.
* @return correlation score.
*/
public float score(int bufferPointer, float fractional, float samplesPerSymbol, DMRSyncPattern pattern)
{
float score = 0;
float[] symbols = pattern.toSymbols();
float[] softSymbols = new float[24];
int integral;
for(int x = 0; x < 24; x++)
{
float softSymbol = PhaseAwareLinearInterpolator.calculate(mBuffer[bufferPointer], mBuffer[bufferPointer + 1], fractional);
softSymbol = Math.min(softSymbol, MAX_POSITIVE_SOFT_SYMBOL);
softSymbol = Math.max(softSymbol, MAX_NEGATIVE_SOFT_SYMBOL);
softSymbols[x] = softSymbol;
score += softSymbol * symbols[x];
fractional += samplesPerSymbol;
integral = (int)Math.floor(fractional);
bufferPointer += integral;
fractional -= integral;
}
return score;
}
/**
* Sets or updates the samples per symbol
* @param samplesPerSymbol to apply.
*/
public void setSamplesPerSymbol(float samplesPerSymbol)
{
mSamplesPerSymbol = samplesPerSymbol;
mObservedSamplesPerSymbol = mSamplesPerSymbol;
mSamplePoint = mSamplesPerSymbol;
mLaggingSyncOffset1 = mSamplesPerSymbol / 3;
mLaggingSyncOffset2 = mLaggingSyncOffset1 * 2;
mBufferWorkspaceLength = (int)Math.ceil(BUFFER_WORKSPACE_LENGTH_DIBITS * mSamplesPerSymbol);
int bufferLength = (int)(Math.ceil(BUFFER_LENGTH_DIBITS * mSamplesPerSymbol));
mBuffer = new float[bufferLength];
mBufferLoadPointer = (int)Math.ceil(BUFFER_PROTECTED_REGION_DIBITS * mSamplesPerSymbol);
mBufferPointer = mBufferLoadPointer;
}
/**
* Decodes the sample value to determine the correct QPSK quadrant and maps the value to a Dibit symbol.
* @param sample in radians.
* @return symbol decision.
*/
private static Dibit toSymbol(float sample)
{
if(sample > 0)
{
return sample > SYMBOL_QUADRANT_BOUNDARY ? Dibit.D01_PLUS_3 : Dibit.D00_PLUS_1;
}
else
{
return sample < -SYMBOL_QUADRANT_BOUNDARY ? Dibit.D11_MINUS_3 : Dibit.D10_MINUS_1;
}
}
}

View File

@@ -1,178 +0,0 @@
package io.github.dsheirer.module.decode.dmr;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.dsp.symbol.QPSKCarrierLock;
import org.apache.commons.lang3.Validate;
/**
* Detector for DMR sync patterns that also includes support for detecting PLL phase lock errors. There are
* two primary use cases for this class:
*
* The first is to detect a sync pattern from a stream of dibits that are processed via the add(dibit) method. After
* each dibit is added, the sync pattern can be inspected by either the hasSync() method or the getSyncPattern()
* method. The maxStreamBitErrors construction parameter specifies how many bit errors can be present when matching
* patterns to the dibit stream.
*
* The second use case is to set a candidate sync pattern value using the setCurrentSyncValue() method. You can then
* use the hasSync() and getSyncPatter() methods to determine if the set value matches a valid sync pattern. The
* maxExplicitBitErrors specifies how many bit errors can be present when matching patterns to the explicitly set
* sync value.
*
* Note: sync pattern matching maximum bit errors uses a hard-coded value for PLL misalignment sync detection since
* this threshold should be relatively high to avoid false triggering and once detected, a PLL should be immediately
* commanded to correct the issue.
*/
public class DMRSyncDetector
{
private static long SYNC_MASK = 0xFFFFFFFFFFFFl;
private static final int MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT = 1;
private int mMaxStreamBitErrors;
private int mMaxExplicitBitErrors;
private long mCurrentSyncValue;
private long mErrorPattern;
private int mPatternMatchBitErrorCount;
private DMRSyncPattern mSyncPattern = DMRSyncPattern.UNKNOWN;
private QPSKCarrierLock mCarrierLock = QPSKCarrierLock.NORMAL;
/**
* Constructs an instance
* @param maxStreamBitErrors allowed for matching sync patterns when using the dibit streaming use case.
* @param maxExplicitBitErrors allows for matching sync patterns when using the explicitly set sync value use case.
*/
public DMRSyncDetector(int maxStreamBitErrors, int maxExplicitBitErrors)
{
Validate.inclusiveBetween(0, 24, maxStreamBitErrors,
"Max (allowable) stream bit errors for sync match must be between 0 and 24");
Validate.inclusiveBetween(0, 24, maxExplicitBitErrors,
"Max (allowable) explicit bit errors for sync match must be between 0 and 24");
mMaxStreamBitErrors = maxStreamBitErrors;
mMaxExplicitBitErrors = maxExplicitBitErrors;
}
/**
* Sync pattern for the current sync value.
*/
public DMRSyncPattern getSyncPattern()
{
return mSyncPattern;
}
/**
* Indicates if there is a sync pattern in the currently set sync value that was either explicitly set or was
* aggregated from a stream of dibits. When this method returns true, the sync pattern can be accessed via the
* getSyncPattern() method.
*/
public boolean hasSync()
{
return getSyncPattern() != DMRSyncPattern.UNKNOWN;
}
/**
* Carrier lock for the QPSK PLL to detect when the PLL has locked to the signal at +/- 90 degrees or 180 degrees.
* @return carrier lock value.
*/
public QPSKCarrierLock getCarrierLock()
{
return mCarrierLock;
}
/**
* Indicates if the detected sync pattern has normal QPSK carrier lock.
*/
public boolean hasNormalCarrierLock()
{
return getCarrierLock() == QPSKCarrierLock.NORMAL;
}
/**
* Number of bit errors that were present when matching the current sync value to the current sync pattern.
*/
public int getPatternMatchBitErrorCount()
{
return mPatternMatchBitErrorCount;
}
/**
* Processes the streaming dibit by left shifting the current sync value and adding the dibit onto the end and then
* test for sync pattern match.
* @param dibit to process and test for sync pattern.
*/
public void add(Dibit dibit)
{
mCurrentSyncValue = Long.rotateLeft(mCurrentSyncValue, 2);
mCurrentSyncValue &= SYNC_MASK;
mCurrentSyncValue += dibit.getValue();
checkSync(mMaxStreamBitErrors);
}
/**
* Explicitly sets the argument as the current sync value and tests for sync pattern match.
* @param value to load as the current sync value and test
*/
public void setCurrentSyncValue(long value)
{
mCurrentSyncValue = value;
checkSync(mMaxExplicitBitErrors);
}
/**
* Checks the current sync value against each of the sync patterns to determine if the value matches a pattern.
*/
private void checkSync(int maxBitErrors)
{
for(DMRSyncPattern pattern: DMRSyncPattern.SYNC_PATTERNS)
{
mErrorPattern = mCurrentSyncValue ^ pattern.getPattern();
if(mErrorPattern == 0)
{
mSyncPattern = pattern;
mCarrierLock = QPSKCarrierLock.NORMAL;
mPatternMatchBitErrorCount = 0;
return;
}
mPatternMatchBitErrorCount = Long.bitCount(mErrorPattern);
if(mPatternMatchBitErrorCount <= maxBitErrors)
{
mSyncPattern = pattern;
mCarrierLock = QPSKCarrierLock.NORMAL;
return;
}
//For PLL mis-aligned lock patterns, reduce the pattern bit error match threshold
mPatternMatchBitErrorCount = Long.bitCount(mCurrentSyncValue ^ pattern.getPlus90Pattern());
if(mPatternMatchBitErrorCount <= MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT)
{
mSyncPattern = pattern;
mCarrierLock = QPSKCarrierLock.PLUS_90;
return;
}
mPatternMatchBitErrorCount = Long.bitCount(mCurrentSyncValue ^ pattern.getMinus90Pattern());
if(mPatternMatchBitErrorCount <= MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT)
{
mSyncPattern = pattern;
mCarrierLock = QPSKCarrierLock.MINUS_90;
return;
}
mPatternMatchBitErrorCount = Long.bitCount(mCurrentSyncValue ^ pattern.getInvertedPattern());
if(mPatternMatchBitErrorCount <= MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT)
{
mSyncPattern = pattern;
mCarrierLock = QPSKCarrierLock.INVERTED;
return;
}
}
mSyncPattern = DMRSyncPattern.UNKNOWN;
mCarrierLock = QPSKCarrierLock.NORMAL;
mPatternMatchBitErrorCount = 0;
}
}

View File

@@ -1,197 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr;
import java.util.EnumSet;
/**
* DMR burst sync patterns enumeration. In addition to the ETSI defined sync patterns, each enumeration entry also
* contains derived sync patterns that match decoded bit streams where a QPSK Phase-Locked Loop has locked to the signal
* misaligned by +/- 90 degrees, or 180 degrees. These derived patterns can be used to detect PLL misalignment for
* PLL correction purposes.
*/
public enum DMRSyncPattern
{
BASE_STATION_DATA(0xDFF57D75DF5Dl, 0xBAAFEBEFBAFBl, 0x455014104504l, 0x200A828A20A2l, "BS DATA"),
BASE_STATION_VOICE(0x755FD7DF75F7l, 0xEFFABEBAEFAEl, 0x100541451051l, 0x8AA028208A08l, "BS VOICE A"),
MOBILE_STATION_DATA(0xD5D7F77FD757l, 0xBFBEAEEABEFEl, 0x404151154101l, 0x2A28088028A8l, "MS DATA"),
MOBILE_STATION_VOICE(0x7F7D5DD57DFDl, 0xEAEBFBBFEBABl, 0x151404401454l, 0x8082A22A8202l, "MS VOICE A"),
DIRECT_MODE_DATA_TIMESLOT_1(0xF7FDD5DDFD55l, 0xAEABBFBBABFFl, 0x515440445400l, 0x08022A2202AAl, "MS DATA - DUAL CAPACITY DIRECT MODE"),
DIRECT_MODE_DATA_TIMESLOT_2(0xD7557F5FF7F5l, 0xBEFFEAFAAEAFl, 0x410015055150l, 0x28AA80A0080Al, "MS DATA - DUAL CAPACITY DIRECT MODE"),
DIRECT_MODE_VOICE_TIMESLOT_1(0x5D577F7757FFl, 0xFBFEEAEEFEAAl, 0x040115110155l, 0xA2A88088A800l, "MS VOICE A - DUAL CAPACITY DIRECT MODE"),
DIRECT_MODE_VOICE_TIMESLOT_2(0x7DFFD5F55D5Fl, 0xEBAABFAFFBFAl, 0x145540500405l, 0x82002A0AA2A0l, "MS VOICE A - DUAL CAPACITY DIRECT MODE"),
MOBILE_STATION_REVERSE_CHANNEL(0x77D55F7DFD77l, 0xEEBFFAEBABEEl, 0x114005145411l, 0x882AA0820288l, "MS RVRS"),
RESERVED(0xDD7FF5D757DDl, 0xBBEAAFBEFEBBl, 0x441550410144l, 0x22800A28A822l, "RESERVED"),
//These are used to identify the sync-less sub frames of a voice super frame
BS_VOICE_FRAME_B("BS VOICE B"),
BS_VOICE_FRAME_C("BS VOICE C"),
BS_VOICE_FRAME_D("BS VOICE D"),
BS_VOICE_FRAME_E("BS VOICE E"),
BS_VOICE_FRAME_F("BS VOICE F"),
MS_VOICE_FRAME_B("MS VOICE B"),
MS_VOICE_FRAME_C("MS VOICE C"),
MS_VOICE_FRAME_D("MS VOICE D"),
MS_VOICE_FRAME_E("MS VOICE E"),
MS_VOICE_FRAME_F("MS VOICE F"),
UNKNOWN("UNKNOWN");
private long mPattern;
private long mPlus90Pattern;
private long mMinus90Pattern;
private long mInvertedPattern;
private String mLabel;
/**
* DMR Sync Patterns. See TS 102-361-1, paragraph 9.1.1
*/
DMRSyncPattern(long pattern, long plus90Pattern, long minus90Pattern, long invertedPattern, String label)
{
mPattern = pattern;
mPlus90Pattern = plus90Pattern;
mMinus90Pattern = minus90Pattern;
mInvertedPattern = invertedPattern;
mLabel = label;
}
/**
* Alternate constructor for syncs that don't have defined patterns.
*/
DMRSyncPattern(String label)
{
mLabel = label;
}
//Valid sync patterns (excluding the sync-less voice patterns)
public static final EnumSet<DMRSyncPattern> SYNC_PATTERNS = EnumSet.of(BASE_STATION_DATA, BASE_STATION_VOICE,
MOBILE_STATION_DATA, MOBILE_STATION_VOICE, DIRECT_MODE_DATA_TIMESLOT_1, DIRECT_MODE_DATA_TIMESLOT_2,
DIRECT_MODE_VOICE_TIMESLOT_1, DIRECT_MODE_VOICE_TIMESLOT_2, MOBILE_STATION_REVERSE_CHANNEL);
//Sync patterns containing a Common Announcement Channel (CACH)
private static final EnumSet<DMRSyncPattern> CACH_PATTERNS = EnumSet.of(BASE_STATION_DATA, BASE_STATION_VOICE,
BS_VOICE_FRAME_B, BS_VOICE_FRAME_C, BS_VOICE_FRAME_D, BS_VOICE_FRAME_E, BS_VOICE_FRAME_F);
//Direct Mode TS 1 Sync Patterns
private static final EnumSet<DMRSyncPattern> DIRECT_MODE_TS1_PATTERNS = EnumSet.of(DIRECT_MODE_VOICE_TIMESLOT_1,
DIRECT_MODE_DATA_TIMESLOT_1);
//Direct Mode TS 2 Sync Patterns
private static final EnumSet<DMRSyncPattern> DIRECT_MODE_TS2_PATTERNS = EnumSet.of(DIRECT_MODE_VOICE_TIMESLOT_2,
DIRECT_MODE_DATA_TIMESLOT_2);
//Mobile Station Sync Patterns
private static final EnumSet<DMRSyncPattern> MOBILE_STATION_PATTERNS = EnumSet.of(MOBILE_STATION_DATA,
MOBILE_STATION_VOICE, MS_VOICE_FRAME_B, MS_VOICE_FRAME_C, MS_VOICE_FRAME_D, MS_VOICE_FRAME_E, MS_VOICE_FRAME_F);
/**
* Pattern that represents the enum entry
*/
public long getPattern()
{
return mPattern;
}
/**
* Pattern that represents the PLL locking to the carrier misaligned by plus 90 degrees.
*/
public long getPlus90Pattern()
{
return mPlus90Pattern;
}
/**
* Pattern that represents the PLL locking to the carrier misaligned by minus 90 degrees.
*/
public long getMinus90Pattern()
{
return mMinus90Pattern;
}
/**
* Pattern that represents the PLL locking to the carrier misaligned by 180 degrees (inverted).
*/
public long getInvertedPattern()
{
return mInvertedPattern;
}
/**
* Indicates if a message using this sync pattern contains a Common Announcement Channel (CACH).
*/
public boolean hasCACH()
{
return CACH_PATTERNS.contains(this);
}
/**
* Indicates if this is a Direct Mode sync pattern
*/
public boolean isDirectMode()
{
return isDirectModeTS1() || isDirectModeTS2();
}
/**
* Indicates if this is Direct Mode sync pattern for timestot 1
* @return true if DMTS1
*/
public boolean isDirectModeTS1()
{
return DIRECT_MODE_TS1_PATTERNS.contains(this);
}
/**
* Indicates if this is Direct Mode sync pattern for timestot 2
* @return true if DMTS2
*/
public boolean isDirectModeTS2()
{
return DIRECT_MODE_TS2_PATTERNS.contains(this);
}
/**
* Indicates if this is a mobile station sync pattern
* @return true if a mobile station sync pattern.
*/
public boolean isMobileStationSyncPattern()
{
return MOBILE_STATION_PATTERNS.contains(this);
}
/**
* Indicates if this is a mobile sync pattern, either mobile station or direct mode.
* @return true if a mobile sync pattern.
*/
public boolean isMobileSyncPattern()
{
return isMobileStationSyncPattern() || isDirectMode();
}
/**
* String representation of the sync pattern
*/
public String toString()
{
return mLabel;
}
}

View File

@@ -0,0 +1,78 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.dmr;
import io.github.dsheirer.dsp.symbol.Dibit;
import java.util.Arrays;
/**
* Implements a circular buffer delay line for dibits.
*/
public class DibitDelayLine
{
private Dibit[] mDelayLine;
public int mPointer;
private int mLength;
/**
* Constructs an instance
* @param length of the delay line in dibits.
*/
public DibitDelayLine(int length)
{
mLength = length;
mDelayLine = new Dibit[length];
Arrays.fill(mDelayLine, Dibit.D00_PLUS_1);
}
/**
* Inserts the dibit into the delay line and returns the oldest dibit from the delay line.
* @param dibit to insert
* @return oldest dibit
*/
public Dibit insert(Dibit dibit)
{
Dibit ejected = mDelayLine[mPointer];
mDelayLine[mPointer++] = dibit;
mPointer %= mLength;
return ejected;
}
/**
* Overwrites the most recently added dibits in the delay line with the provided sequence. This can be used when
* a sync pattern is detected to overwrite the calculated symbols with the known sync pattern.
* @param dibits to overwrite
*/
public void update(Dibit[] dibits)
{
mPointer = mPointer - dibits.length;
if(mPointer < 0)
{
mPointer += mDelayLine.length;
}
for(Dibit dibit: dibits)
{
mDelayLine[mPointer++] = dibit;
mPointer %= mLength;
}
}
}

View File

@@ -1,44 +0,0 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer, Zhenyu Mao
*
* 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.dmr;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Listener interface to be notified each time a DMR burst has been detected or when sync is or continues to be lost.
*/
public interface IDMRBurstDetectListener
{
/**
* Indicates that a DMR sync has been detected and binary message with the burst contents are ready for processing
* @param binaryMessage burst Binary Message and any bit errors detected in the message
* @param pattern SyncPattern
*/
void burstDetected(CorrectedBinaryMessage binaryMessage, DMRSyncPattern pattern, int timeslot);
/**
* Indicates that sync has been lost on the dibit stream
*
* @param bitsProcessed since the last sync detect
* @param timeslot that was being tracked when the sync loss was detected
*/
void syncLost(int bitsProcessed, int timeslot);
}

View File

@@ -1,155 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tracks proper alignment of timeslots into the two bursts of a burst framer.
*/
public class TimeslotAlignmentTracker
{
private final static Logger mLog = LoggerFactory.getLogger(TimeslotAlignmentTracker.class);
private int mBurst1Ts1 = 0;
private int mBurst1Ts2 = 0;
private int mBurst2Ts1 = 0;
private int mBurst2Ts2 = 0;
/**
* Indicates if the track has accumulated enough cach data to accurately declare timeslot alignment. The test
* checks that both timeslots each have different counts for timeslot 0 and 1 and that both timeslots each indicate
* a different dominant timeslot than the other.
*/
public boolean hasSufficientData()
{
return ((mBurst1Ts1 > mBurst1Ts2 && mBurst2Ts2 > mBurst2Ts1) ||
(mBurst1Ts1 < mBurst1Ts2 && mBurst2Ts2 < mBurst2Ts1) ||
(mBurst1Ts1 == 0 && mBurst1Ts2 == 0 && mBurst2Ts1 != mBurst2Ts2) ||
(mBurst1Ts1 != mBurst1Ts2 && mBurst2Ts1 == 0 && mBurst1Ts2 == 0));
}
/**
* Indicates if the timeslots are aligned by inspection of the CACH value for frame 0 and frame 1
*/
public boolean isAligned()
{
return (mBurst1Ts1 > mBurst1Ts2 && mBurst2Ts2 > mBurst2Ts1) ||
(mBurst1Ts1 == 0 && mBurst1Ts2 == 0 && mBurst2Ts2 > mBurst2Ts1) ||
(mBurst1Ts1 > mBurst1Ts2 && mBurst2Ts1 == 0 && mBurst2Ts2 == 0);
}
/**
* Updates the tracker with the cach fields or sync patterns from the current TDMA burst frames
* @param cach1 optional cach that is currently aligned as timeslot 1
* @param cach2 optional cach that is currently aligned as timeslot 2
* @param pattern1 sync pattern for the burst currently aligned as timeslot 1
* @param pattern2 sync pattern for the burst currently aligned as timeslot 2
*/
public void update(CACH cach1, CACH cach2, DMRSyncPattern pattern1, DMRSyncPattern pattern2)
{
if(cach1 != null && cach1.isValid())
{
if(cach1.isTimeslot0())
{
mBurst1Ts1++;
}
else
{
mBurst1Ts2++;
}
}
if(cach2 != null && cach2.isValid())
{
if(cach2.isTimeslot0())
{
mBurst2Ts1++;
}
else
{
mBurst2Ts2++;
}
}
if(pattern1.isDirectMode())
{
switch((pattern1))
{
case DIRECT_MODE_DATA_TIMESLOT_1:
case DIRECT_MODE_VOICE_TIMESLOT_1:
mBurst1Ts1++;
break;
case DIRECT_MODE_DATA_TIMESLOT_2:
case DIRECT_MODE_VOICE_TIMESLOT_2:
mBurst1Ts2++;
break;
}
}
if(pattern2.isDirectMode())
{
switch((pattern2))
{
case DIRECT_MODE_DATA_TIMESLOT_1:
case DIRECT_MODE_VOICE_TIMESLOT_1:
mBurst2Ts1++;
break;
case DIRECT_MODE_DATA_TIMESLOT_2:
case DIRECT_MODE_VOICE_TIMESLOT_2:
mBurst2Ts2++;
break;
}
}
}
/**
* Resets the tracker values.
*/
public void reset()
{
mBurst1Ts1 = 0;
mBurst1Ts2 = 0;
mBurst2Ts1 = 0;
mBurst2Ts2 = 0;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("BURST 1: ").append(mBurst1Ts1).append("/").append(mBurst1Ts2);
sb.append(" BURST 2:").append(mBurst2Ts1).append("/").append(mBurst2Ts2);
if(hasSufficientData())
{
if(isAligned())
{
sb.append(" **ALIGNED**");
}
else
{
sb.append(" >>MIS-ALIGNED<<");
}
}
return sb.toString();
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 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
@@ -33,13 +33,14 @@ import io.github.dsheirer.identifier.tone.ToneSequence;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.IMessageProvider;
import io.github.dsheirer.module.decode.dmr.identifier.DMRToneIdentifier;
import io.github.dsheirer.module.decode.dmr.message.data.header.PiHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.AbstractVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.EncryptionParameters;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedEncryptionParameters;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.sample.Listener;
@@ -98,7 +99,7 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
}
/**
* Processes DMR AMBE audio frame
* Processes DMR AMBE audio frames and signalling messages.
*/
public void receive(IMessage message)
{
@@ -108,49 +109,69 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
if(!mEncryptedCallStateEstablished)
{
//DCDM doesn't provide FLCs or EMBs ... assume that the call is unencrypted.
if(message instanceof VoiceMessage vm && vm.getSyncPattern().isDirectMode())
if(message instanceof VoiceMessage vm && vm.getSyncPattern().isDirect())
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = false;
}
//Both Motorola and Hytera signal their Basic Privacy (BP) scrambling in some of the Voice B-F frames
//in the EMB field.
else if(message instanceof VoiceEMBMessage voiceMessage && voiceMessage.getEMB().isValid())
else if(message instanceof VoiceEMBMessage voice)
{
if(voice.hasEmbeddedParameters() &&
voice.getEmbeddedParameters().getShortBurst() instanceof EmbeddedEncryptionParameters)
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = true;
}
else if(voice.getEMB().isValid())
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = voice.getEMB().isEncrypted();
}
}
else if(message instanceof VoiceHeader vh && vh.getLCMessage() instanceof AbstractVoiceChannelUser vcu &&
vcu.isValid())
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = voiceMessage.getEMB().isEncrypted();
mEncryptedCall = vcu.getServiceOptions().isEncrypted();
}
else if(message instanceof VoiceHeader voiceHeader)
else if(message instanceof PiHeader pi && pi.getLCMessage() instanceof EncryptionParameters ep &&
ep.isValid())
{
LCMessage lc = voiceHeader.getLCMessage();
if(lc instanceof FullLCMessage flc) //We know it is, but this null checks as well
{
if(flc.isValid())
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = flc.isEncrypted();
}
}
mEncryptedCallStateEstablished = true;
mEncryptedCall = true;
}
//Note: the DMRMessageProcessor extracts Full Link Control messages from Voice Frames B-C and sends them
// independent of any DMR Burst messaging. When encountered, it can be assumed that they are part of
// an ongoing call and can be used to establish encryption state when the FLC is a voice channel user.
else if(message instanceof AbstractVoiceChannelUser avcu)
else if(message instanceof AbstractVoiceChannelUser avcu && avcu.isValid())
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = avcu.getServiceOptions().isEncrypted();
}
if(mEncryptedCall)
{
mQueuedAmbeFrames.clear();
}
}
//Queue or process audio frames. Note: audio frames are held/queued until encrypted state is established
//before any audio is generated for the audio segment.
if(message instanceof VoiceMessage voiceMessage)
{
List<byte[]> frames = voiceMessage.getAMBEFrames();
for(byte[] frame: frames)
if(mEncryptedCallStateEstablished && mEncryptedCall)
{
processAudio(frame, message.getTimestamp());
mQueuedAmbeFrames.clear();
}
else
{
List<byte[]> frames = voiceMessage.getAMBEFrames();
for(byte[] frame: frames)
{
processAudio(frame, message.getTimestamp());
}
}
}
else if(message instanceof Terminator)
@@ -168,23 +189,30 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
{
if(mEncryptedCallStateEstablished)
{
//Process any ambe frames that were queued awaiting encryption state determination
if(!mQueuedAmbeFrames.isEmpty())
if(mEncryptedCall)
{
if(!mEncryptedCall)
mQueuedAmbeFrames.clear();
}
else
{
//Process any ambe frames that were queued awaiting encryption state determination
if(!mQueuedAmbeFrames.isEmpty())
{
for(byte[] queuedFrame: mQueuedAmbeFrames)
if(!mEncryptedCall)
{
produceAudio(queuedFrame, timestamp);
for(byte[] queuedFrame: mQueuedAmbeFrames)
{
produceAudio(queuedFrame, timestamp);
}
mQueuedAmbeFrames.clear();
}
mQueuedAmbeFrames.clear();
}
mQueuedAmbeFrames.clear();
produceAudio(frame, timestamp);
}
produceAudio(frame, timestamp);
}
else
{

View File

@@ -0,0 +1,360 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.dmr.bptc;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.IHamming;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Block Product Turbo Code (BPTC) base implementation for BPTC codes using simple even parity column checks.
*/
public abstract class BPTCBase
{
public static int ERRORS_NOT_CORRECTED = -2;
private IHamming mHamming;
private int mColumnCount;
private int mRowCount;
/**
* Constructs an instance
* @param hamming to provide row checking using the correct Hamming error detection and correction algorithm.
* @param column count
* @param row count
*/
public BPTCBase(IHamming hamming, int columnCount, int rowCount)
{
mHamming = hamming;
mColumnCount = columnCount;
mRowCount = rowCount;
}
/**
* Performs error detection and correction.
* @param message to correct
* @return true if the message was corrected or false if there are uncorrectable errors.
*/
public boolean correct(CorrectedBinaryMessage message)
{
BinaryMessage columns = getColumnErrors(message);
BinaryMessage rows = getRowErrors(message);
if(columns.isEmpty() && rows.isEmpty())
{
return true;
}
List<Integer> correctedIndexes = new ArrayList<>();
//1: fix non-shadowed single bit errors in each row
for(int row = rows.nextSetBit(0); row >= 0 && row < mRowCount; row = rows.nextSetBit(row + 1))
{
int index = correct1BitErrors(row, message, rows);
if(index >= 0)
{
correctedIndexes.add(index);
}
}
//Refresh the column errors
columns = getColumnErrors(message);
if(columns.isEmpty() && rows.isEmpty())
{
message.incrementCorrectedBitCount(correctedIndexes.size());
return true;
}
//2: fix non-shadowed multi bit errors (up to 2) in each row
if(columns.cardinality() <= 2)
{
for(int row = rows.nextSetBit(0); row >= 0 && row < mRowCount; row = rows.nextSetBit(row + 1))
{
List<Integer> solution = correctMultiBitErrors(row, message, columns, rows);
if(solution.size() > 0)
{
correctedIndexes.addAll(solution);
}
}
}
if(columns.isEmpty() && rows.isEmpty())
{
message.incrementCorrectedBitCount(correctedIndexes.size());
return true;
}
message.setCorrectedBitCount(ERRORS_NOT_CORRECTED);
return false;
}
/**
* Indicates if the column passes an even parity check.
* @param column to test
* @return true if the parity check passes
*/
public boolean isColumnCorrect(int column, BinaryMessage message)
{
boolean correct = true;
for(int row = 0; row < mRowCount; row++)
{
correct ^= message.get(row * mColumnCount + column);
}
return correct;
}
/**
* Calculates the column from the bit index/offset
* @param index for a bit
* @return column that contains the index
*/
public int getColumn(int index)
{
return index % mColumnCount;
}
/**
* Calculates the index for a bit that exists at the row and column.
* @param column for the bit.
* @param row for the bit
* @return index for the bit.
*/
public int getIndex(int column, int row)
{
return row * mColumnCount + column;
}
/**
* Checks the row for bit errors.
* @param row to check
* @param message containing bits to check
* @return true if the row passes the Hamming check or false if not.
*/
private boolean isRowCorrect(int row, CorrectedBinaryMessage message)
{
return mHamming.getErrorIndex(message, row * mColumnCount) == IHamming.NO_ERRORS;
}
/**
* Calculates the index for the bit error in the message
* @param row to check
* @param message with bits
* @return error index in the range 0-195, or -1 if the message row has no bit errors.
*/
private int getRowErrorIndex(int row, CorrectedBinaryMessage message)
{
return mHamming.getErrorIndex(message, row * mColumnCount);
}
/**
* Returns a bit map indicating the row errors.
* @param message containing rows.
* @return row error map
*/
public BinaryMessage getRowErrors(CorrectedBinaryMessage message)
{
BinaryMessage rows = new BinaryMessage(mRowCount);
for(int row = 0; row < mRowCount; row++)
{
rows.set(row, !isRowCorrect(row, message));
}
return rows;
}
/**
* Returns a bit map indicating the column errors.
* @param message containing rows.
* @return column error map
*/
public BinaryMessage getColumnErrors(CorrectedBinaryMessage message)
{
BinaryMessage columns = new BinaryMessage(mColumnCount);
for(int column = 0; column < mColumnCount; column++)
{
columns.set(column, !isColumnCorrect(column, message));
}
return columns;
}
/**
* Corrects single bit errors in each row using only the Hamming error index.
* @param row to correct
* @param message with bits
* @param rows error flag set
* @return bit index that was corrected or -2 if the error was not corrected
*/
public int correct1BitErrors(int row, CorrectedBinaryMessage message, BinaryMessage rows)
{
int index = getRowErrorIndex(row, message);
if(index >= 0 && index < IHamming.MULTIPLE_ERRORS)
{
message.flip(index);
if(isRowCorrect(row, message))
{
rows.clear(row);
return index;
}
message.flip(index);
}
return ERRORS_NOT_CORRECTED;
}
/**
* Corrects multiple bit errors in a single row when all other rows are corrected first, by flipping the
* bits in the row that show errors in the columns and testing the solution.
* @param row to correct
* @param message with bits
* @param columns bit error matrix
* @param rows bit error matrix
* @return solution or an empty list if no solution.
*/
public List<Integer> correctMultiBitErrors(int row, CorrectedBinaryMessage message, BinaryMessage columns,
BinaryMessage rows)
{
//Test flipping bits in the row everywhere that we have a column error
List<Integer> testFlips = new ArrayList<>();
for(int column = columns.nextSetBit(0); column >= 0 && column < mColumnCount;
column = columns.nextSetBit(column + 1))
{
int toFlip = getIndex(column, row);
message.flip(toFlip);
testFlips.add(toFlip);
}
if(!isRowCorrect(row, message))
{
int index = getRowErrorIndex(row, message);
if(index >= 0 && index < IHamming.MULTIPLE_ERRORS)
{
message.flip(index);
if(testFlips.contains(index))
{
testFlips.removeIf(value -> value == index);
}
else
{
testFlips.add(index);
}
}
}
if(isRowCorrect(row, message))
{
//Check the columns also
boolean columnsCorrect = true;
for(Integer flip: testFlips)
{
columnsCorrect &= isColumnCorrect(getColumn(flip), message);
}
if(columnsCorrect)
{
rows.clear(row);
for(Integer flip: testFlips)
{
columns.clear(getColumn(flip));
}
return testFlips;
}
}
for(Integer flip: testFlips)
{
message.flip(flip);
}
return Collections.emptyList();
}
public void logErrorMap(CorrectedBinaryMessage message)
{
StringBuilder sb = new StringBuilder();
for(int row = 0; row < mRowCount; row++)
{
int offset = row * mColumnCount;
sb.append("Row ").append(row).append(": ");
sb.append(message.getSubMessage(offset, offset + mColumnCount));
sb.append(" ").append(offset).append(":").append(offset + mColumnCount).append("\n");
}
System.out.println(sb);
}
public void logDiagnostic(CorrectedBinaryMessage message)
{
StringBuilder sb = new StringBuilder();
for(int row = 0; row < mRowCount; row++)
{
int offset = row * mColumnCount;
sb.append(" Row ").append(row).append(": ");
sb.append(message.getSubMessage(offset, offset + mColumnCount));
int index = getRowErrorIndex(row, message);
if(index != IHamming.NO_ERRORS)
{
sb.append(" < Index:").append(index);
if(index == IHamming.MULTIPLE_ERRORS)
{
sb.append(" (2+ bit errors)");
}
}
sb.append("\n");
}
sb.append("Errors: ");
BinaryMessage columnErrors = getColumnErrors(message);
for(int column = 0; column < mColumnCount; column++)
{
if(columnErrors.get(column))
{
sb.append("^");
}
else
{
sb.append(".");
}
}
System.out.println("Diagnostic:\n" + sb + "\n");
}
}

View File

@@ -0,0 +1,132 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.dmr.bptc;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.Hamming16;
/**
* Block product turbo code (BPTC) for decoding Full Link Control messages reassembled from the 32-bit payload fragments
* in each of the Voice B-E messages.
*/
public class BPTC_128_77 extends BPTCBase
{
public BPTC_128_77()
{
super(new Hamming16(), 16, 8);
}
/**
* Performs error detection and correction and extracts the payload from the BPTC encoded message.
* @param message with BPTC encoding.
* @return decoded message.
*/
public CorrectedBinaryMessage extract(BinaryMessage message)
{
CorrectedBinaryMessage deinterleaved = deinterleave(message);
correct(deinterleaved);
CorrectedBinaryMessage extractedMessage = new CorrectedBinaryMessage(77);
int pointer = 0;
//Extract the message. Row 1 and 2 are 11 bits. Rows 3-7 are 10 bits.
for(int row = 0; row < 2; row++)
{
for(int column = 0; column < 11; column++)
{
extractedMessage.set(pointer++, deinterleaved.get(row * 16 + column));
}
}
for(int row = 2; row < 7; row++)
{
for(int column = 0; column < 10; column++)
{
extractedMessage.set(pointer++, deinterleaved.get(row * 16 + column));
}
//Transfer the checksum parity bit from this row
extractedMessage.set(70 + row, deinterleaved.get(row * 16 + 10));
}
extractedMessage.setCorrectedBitCount(deinterleaved.getCorrectedBitCount());
return extractedMessage;
}
/**
* Deinterleaves the transmitted message.
* @param interleaved message
* @return deinterleaved message.
*/
public CorrectedBinaryMessage deinterleave(BinaryMessage interleaved)
{
CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(128);
int index;
deinterleaved.set(127, interleaved.get(127));
for(int i = 0; i < 127; i++)
{
index = (i * 8) % 127;
deinterleaved.set(i, interleaved.get(index));
}
return deinterleaved;
}
public static void main(String[] args)
{
BPTC_128_77 bptc = new BPTC_128_77();
String deinterleavedRaw = "00000000000000000000000000000000001000000010010101011001001000010000000000000000000000000110101000010110111100010000111110011111";
String deinterleavedReference = "00000000000000000000000000000000000000000010011100011001001000110000000000000000000000000110101000010110111100010000111110011111"; //No errors
CorrectedBinaryMessage deinterleavedReferenceMessage = new CorrectedBinaryMessage(BinaryMessage.load(deinterleavedReference));
String interleaved = "00000000000000000000000000010010000100010000001000000010000100010000001100000110001101100000001100000101001000010010010100110011"; //Under test
CorrectedBinaryMessage interleavedMessage = new CorrectedBinaryMessage(BinaryMessage.load(interleaved));
// CorrectedBinaryMessage deinterleaved = deinterleave(interleavedMessage);
CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(BinaryMessage.load(deinterleavedRaw));
String deinterleavedUncorrected = deinterleaved.toString();
bptc.logDiagnostic(deinterleaved);
bptc.correct(deinterleaved);
System.out.println("DEINTERLEAVED: " + deinterleavedUncorrected);
System.out.println(" CORRECTED: " + deinterleaved);
System.out.println(" REFERENCE: " + deinterleavedReferenceMessage);
deinterleaved.xor(deinterleavedReferenceMessage);
System.out.println(" DELTA: " + deinterleaved);
if(deinterleaved.cardinality() > 0)
{
System.out.println("--------------------------------");
System.out.println("Residual Error Map:");
bptc.logErrorMap(deinterleaved);
System.out.println("--------------------------------");
}
else
{
System.out.println("--------------------------------");
System.out.println("Message Was Fully Corrected");
System.out.println("--------------------------------");
}
}
}

View File

@@ -0,0 +1,126 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.dmr.bptc;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.Hamming16;
/**
* Block Product Turbo Code 16/2 for decoding a DMR Voice Frame F payload from the 32-bit EMB field.
* <p>
* See ETSI TS 102 361-1 B.2.2.1 and B.2.2.2
*/
public class BPTC_16_2
{
private static final int[] DEINTERLEAVE = new int[]{0, 24, 1, 25, 2, 26, 3, 27, 4, 28, 5, 29, 6, 30, 7, 31, 8, 16,
9, 17, 10, 18, 11, 19, 12, 20, 13, 21, 14, 22, 15, 23};
/**
* Unscramble and perform FEC checks per paragraph B.2.2.1 for Non-Reverse Channel Single Burst
*
* @param message with 32 interleaved bits.
* @return descrambled and error checked message or null if the process fails or there are too many errors.
*/
public static CorrectedBinaryMessage decodeShortBurst(CorrectedBinaryMessage message)
{
CorrectedBinaryMessage deinterleaved = deinterleave(message);
int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
{
return null;
}
//Check for even parity. Bits 0-15 should be the same as bits 16-31.
for(int x = 0; x < 16; x++)
{
if(deinterleaved.get(x) ^ deinterleaved.get(x + 16))
{
return null;
}
}
return deinterleaved;
}
/**
* Unscramble and perform FEC checks per paragraph B.2.2.2 for Reverse Channel Single Burst
*
* @param binaryMessage with 32 interleaved bits.
* @return descrambled and error checked message or null if the process fails or there are too many errors.
*/
public static CorrectedBinaryMessage decodeReverseChannel(CorrectedBinaryMessage message)
{
CorrectedBinaryMessage deinterleaved = deinterleave(message);
System.out.println(" DEINTER: " + deinterleaved.toHexString());
int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
System.out.println(" DECODED: " + deinterleaved.toHexString());
System.out.println("FEC:" + fec);
if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
{
return null;
}
//Check for odd parity. Bits 0-15 should be opposite of bits 16-31.
for(int x = 0; x < 16; x++)
{
if(deinterleaved.get(x) == deinterleaved.get(x + 16))
{
return null;
}
}
return deinterleaved;
}
/**
* Performs deinterleave of the interleaved message.
*
* @param original to deinterleave
* @return deinterleaved message
*/
public static CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage original)
{
CorrectedBinaryMessage delinterleaved = new CorrectedBinaryMessage(32);
for(int x = 0; x < 32; x++)
{
if(original.get(x))
{
delinterleaved.set(DEINTERLEAVE[x]);
}
}
return delinterleaved;
}
public static void main(String[] args)
{
String[] msgs = new String[]{"05030A03", "35003A00", "1C6D2C9E"};
for(String msg : msgs)
{
CorrectedBinaryMessage original = new CorrectedBinaryMessage(BinaryMessage.loadHex(msg));
System.out.println("ORIGINAL: " + original.toHexString());
CorrectedBinaryMessage decoded = decodeReverseChannel(original);
System.out.println("-------------------------");
}
}
}

View File

@@ -0,0 +1,670 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.dmr.bptc;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.BitSetFullException;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.Hamming13;
import io.github.dsheirer.edac.Hamming15;
import io.github.dsheirer.edac.IHamming;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Block Product Turbo Code (BPTC) 196/96 Support
*/
public class BPTC_196_96
{
public static final int BPTC_LENGTH = 196;
public static final int EXTRACTED_LENGTH = 96; //However, we set the 3x reserved bits in 96, 97, and 98 making the length 99
public static final int MAX_ORIGINAL_INDEX = 136;
public static final int COLUMN_COUNT = 15;
public static final int MESSAGE_COLUMN_COUNT = 12; //Should be 11, but adjusted for the first pad bit
public static final int CHECKSUM_COLUMN_COUNT = 4;
public static final int MESSAGE_START_INDEX = 4;
public static final int MAXIMUM_RECURSION_TURBO_DEPTH = 20;
/**
* DMR De-interleaving indices
*/
public static int[] BPTC_DEINTERLEAVE = new int[]{0, 181, 166, 151, 136, 121, 106, 91, 76, 61, 46, 31, 16, 1, 182,
167, 152, 137, 122, 107, 92, 77, 62, 47, 32, 17, 2, 183, 168, 153, 138, 123, 108, 93, 78, 63, 48, 33, 18, 3,
184, 169, 154, 139, 124, 109, 94, 79, 64, 49, 34, 19, 4, 185, 170, 155, 140, 125, 110, 95, 80, 65, 50, 35, 20,
5, 186, 171, 156, 141, 126, 111, 96, 81, 66, 51, 36, 21, 6, 187, 172, 157, 142, 127, 112, 97, 82, 67, 52, 37,
22, 7, 188, 173, 158, 143, 128, 113, 98, 83, 68, 53, 38, 23, 8, 189, 174, 159, 144, 129, 114, 99, 84, 69, 54,
39, 24, 9, 190, 175, 160, 145, 130, 115, 100, 85, 70, 55, 40, 25, 10, 191, 176, 161, 146, 131, 116, 101, 86, 71,
56, 41, 26, 11, 192, 177, 162, 147, 132, 117, 102, 87, 72, 57, 42, 27, 12, 193, 178, 163, 148, 133, 118, 103,
88, 73, 58, 43, 28, 13, 194, 179, 164, 149, 134, 119, 104, 89, 74, 59, 44, 29, 14, 195, 180, 165, 150, 135, 120,
105, 90, 75, 60, 45, 30, 15};
/**
* DMR Column Indices
*/
public static int[][] COLUMN_INDEXES = new int[][]{
{1, 16, 31, 46, 61, 76, 91, 106, 121, 136, 151, 166, 181},
{2, 17, 32, 47, 62, 77, 92, 107, 122, 137, 152, 167, 182},
{3, 18, 33, 48, 63, 78, 93, 108, 123, 138, 153, 168, 183},
{4, 19, 34, 49, 64, 79, 94, 109, 124, 139, 154, 169, 184},
{5, 20, 35, 50, 65, 80, 95, 110, 125, 140, 155, 170, 185},
{6, 21, 36, 51, 66, 81, 96, 111, 126, 141, 156, 171, 186},
{7, 22, 37, 52, 67, 82, 97, 112, 127, 142, 157, 172, 187},
{8, 23, 38, 53, 68, 83, 98, 113, 128, 143, 158, 173, 188},
{9, 24, 39, 54, 69, 84, 99, 114, 129, 144, 159, 174, 189},
{10, 25, 40, 55, 70, 85, 100, 115, 130, 145, 160, 175, 190},
{11, 26, 41, 56, 71, 86, 101, 116, 131, 146, 161, 176, 191},
{12, 27, 42, 57, 72, 87, 102, 117, 132, 147, 162, 177, 192},
{13, 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, 178, 193},
{14, 29, 44, 59, 74, 89, 104, 119, 134, 149, 164, 179, 194},
{15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195}};
/**
* Performs de-interleave, error detect and correct and extracts a 96-bit message from the DMR BPTC(196,96)
* protected message
* @param original uncorrected BPTC(196,96) message
* @return error corrected and extracted 96-bit message
*/
public static CorrectedBinaryMessage extract(CorrectedBinaryMessage original)
{
//De-Interleave
CorrectedBinaryMessage message = deinterleave(original);
//Perform error detection and correction
boolean pursueShadows = true;
correct(message, pursueShadows);
//Extract the 96-bit message from the corrected 196 bits
CorrectedBinaryMessage extracted = new CorrectedBinaryMessage(EXTRACTED_LENGTH);
//Transfer the corrected bit count
extracted.setCorrectedBitCount(message.getCorrectedBitCount());
int index = MESSAGE_START_INDEX;
while(index < MAX_ORIGINAL_INDEX)
{
if((index % COLUMN_COUNT) < MESSAGE_COLUMN_COUNT)
{
try
{
extracted.add(message.get(index));
}
catch(BitSetFullException bsfe)
{
//Should never get to here
bsfe.printStackTrace();
return extracted;
}
index++;
}
else
{
index += CHECKSUM_COLUMN_COUNT;
}
}
//Transfer bits R2, R1, and R0 to the end - RAS bits
extracted.set(96, message.get(0));
extracted.set(97, message.get(1));
extracted.set(98, message.get(2));
return extracted;
}
/**
* De-interleaves the transmitted bits of a DMR BPTC(196,96) protected message
* @param message that contains interleaved (transmitted) message
* @return de-interleaved message
*/
private static CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage message)
{
CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(BPTC_LENGTH);
for(int x = 0; x < BPTC_DEINTERLEAVE.length; x++)
{
deinterleaved.set(x, message.get(BPTC_DEINTERLEAVE[x]));
}
return deinterleaved;
}
/**
* Identifies the error indices from the row level Hamming(15,11,3) protected words
* @param message containing protected words in rows
* @return list of zero or more error indices
*/
private static List<Integer> getRowErrors(CorrectedBinaryMessage message)
{
List<Integer> rowErrors = new ArrayList<>();
int offset = 0;
int index = -1;
for(int row = 0; row < 13; row++)
{
//The first bit of the message is a pad/reserved bit and is ignored.
offset = (row * 15) + 1;
index = Hamming15.getErrorIndex(message, offset);
if(index >= 0 && index < 1000)
{
rowErrors.add(index);
}
}
//Note: row errors should already be in order and don't need sorted
return rowErrors;
}
/**
* Identifies the error indices from the column level Hamming(13,9,3) protected words
* @param message containing protected words in columns
* @return list of zero or more error indices
*/
private static List<Integer> getColumnErrors(CorrectedBinaryMessage message)
{
List<Integer> columnErrors = new ArrayList<>();
int index = -1;
for(int column = 0; column < 15; column++)
{
index = Hamming13.getErrorIndex(message, COLUMN_INDEXES[column]);
if(index >= 0 && index < 1000)
{
columnErrors.add(index);
}
}
Collections.sort(columnErrors);
return columnErrors;
}
/**
* Creates a list of the values that intersect between list 1 and list 2.
* @param list1 to compare
* @param list2 to compare
* @return intersection
*/
private static List<Integer> intersection(List<Integer> list1, List<Integer> list2)
{
List<Integer> intersection = new ArrayList<>();
for(Integer i : list1)
{
if(list2.contains(i))
{
intersection.add(i);
}
}
return intersection;
}
private static void logDiagnostic(CorrectedBinaryMessage message)
{
StringBuilder sb = new StringBuilder();
for(int row = 0; row < 13; row++)
{
int offset = (row * 15) + 1;
sb.append("Row ");
if(row < 10)
{
sb.append(" ").append(row);
}
else
{
sb.append(row);
}
sb.append(": ");
sb.append(message.getSubMessage(offset, offset + 16));
int index = getRowErrorIndex(row, message);
sb.append(" Index:").append(index == IHamming.NO_ERRORS ? "-": index);
sb.append("\n");
}
sb.append("Column Error Indices: ");
for(int column = 0; column < 15; column++)
{
int index = getColumnErrorIndex(column, message);
String label = index == IHamming.NO_ERRORS ? "-" : (index == 1000 ? "(bad)" : String.valueOf(index));
sb.append(column).append(":").append(label).append(" ");
}
System.out.println("Diagnostic:\n" + sb + "\n");
}
private static void logErrorMap(CorrectedBinaryMessage message)
{
StringBuilder sb = new StringBuilder();
for(int row = 0; row < 13; row++)
{
int offset = (row * 15) + 1;
sb.append("Row ");
if(row < 10)
{
sb.append(" ").append(row);
}
else
{
sb.append(row);
}
sb.append(": ");
sb.append(message.getSubMessage(offset, offset + 15));
sb.append(" ").append(offset).append(":").append(offset + 14).append(" =");
for(int index = offset; index < offset + 15; index++)
{
if(message.get(index))
{
sb.append(" " + index);
}
}
sb.append("\n");
}
System.out.println("Error Map:\n" + sb + "\n");
}
/**
* Calculates the indices that make up the intersections of the row and column errors.
* @param rows bitmap indicating rows with errors
* @param columns bitmap indicating columns with errors
* @return set of intersection indices.
*/
public static List<Integer> getIntersectionIndices(BinaryMessage columns, BinaryMessage rows)
{
List<Integer> intersections = new ArrayList<>();
for(int row = rows.nextSetBit(0); row >= 0 && row < 13; row = rows.nextSetBit(row + 1))
{
for(int column = columns.nextSetBit(0); column >= 0 && column < 15; column = columns.nextSetBit(column + 1))
{
intersections.add(getIndex(column, row));
}
}
return intersections;
}
/**
* Performs turbo decode of the message by testing multiple paths across rows and columns to identify paths
* that resolve errors validated by the Hamming checks. Can also detect and often correct shadowing where the bit
* errors occur in a 2x2, 2x3, 3x2 or 3x3 column/row grid and can't normally be corrected by the Hamming code.
* @param message to correct.
* @param pursueShadows to test intersection flipping for double/triple shadows.
* @return true if the message was corrected, false otherwise.
*/
public static boolean correct(CorrectedBinaryMessage message, boolean pursueShadows)
{
List<Integer> columnErrors = getColumnErrors(message);
List<Integer> rowErrors = getRowErrors(message);
if(columnErrors.isEmpty() && rowErrors.isEmpty())
{
return true;
}
//Fix the easy bits first.
List<Integer> flippedBits = new ArrayList<>();
List<Integer> intersection = intersection(columnErrors, rowErrors);
if(!intersection.isEmpty())
{
for(Integer i : intersection)
{
message.flip(i);
flippedBits.add(i);
}
message.incrementCorrectedBitCount(intersection.size());
}
//Set of flags for columns with errors
BinaryMessage columns = new BinaryMessage(15);
for(int column = 0; column < 15; column++)
{
columns.set(column, !isColumnCorrect(column, message));
}
//Set of flags for rows with errors
BinaryMessage rows = new BinaryMessage(13);
for(int row = 0; row < 13; row++)
{
rows.set(row, !isRowCorrect(row, message));
}
if(columns.isEmpty() && rows.isEmpty())
{
return true; //No (more) errors
}
//1: fix up to 2-bit errors per column or row without pursuing multi-column or multi-row paths
boolean pursue = false;
for(int column = columns.nextSetBit(0); column >= 0 && column < 15; column = columns.nextSetBit(column + 1))
{
List<Integer> solution = correctColumn(column, message, columns, rows, 0, pursue, new ArrayList<>());
flippedBits.addAll(solution);
}
if(!columns.isEmpty() || !rows.isEmpty())
{
for(int row = rows.nextSetBit(0); row >= 0 && row < 13; row = rows.nextSetBit(row + 1))
{
List<Integer> solution = correctRow(row, message, columns, rows, 0, pursue, new ArrayList<>());
flippedBits.addAll(solution);
}
}
if(!columns.isEmpty() || !rows.isEmpty())
{
pursue = true;
for(int column = columns.nextSetBit(0); column >= 0 && column < 15; column = columns.nextSetBit(column + 1))
{
List<Integer> solution = correctColumn(column, message, columns, rows, 0, pursue, new ArrayList<>());
flippedBits.addAll(solution);
}
if(!columns.isEmpty() || !rows.isEmpty())
{
for(int row = rows.nextSetBit(0); row >= 0 && row < 13; row = rows.nextSetBit(row + 1))
{
List<Integer> solution = correctRow(row, message, columns, rows, 0, pursue, new ArrayList<>());
flippedBits.addAll(solution);
}
}
}
//Detect double/triple row/column bit errors that are shadowing each other and preventing the
// Hamming codes from giving accurate bit error locations. Flip all of their intersections and
// then attempt to correct the message again. We use a flag 'pursueShadows' to ensure that we
// only chase the shadow once, not recursively, since this parent method can be called recursively.
if(pursueShadows && ((columns.cardinality() == 2 || columns.cardinality() == 3) &&
(rows.cardinality() == 2 || rows.cardinality() == 3)))
{
List<Integer> intersectionIndices = getIntersectionIndices(columns, rows);
for(Integer intersectionIndex: intersectionIndices)
{
message.flip(intersectionIndex);
}
//Set pursue shadows to false so we don't recursively chase shadows
pursueShadows = false;
if(correct(message, pursueShadows))
{
columns.clear();
rows.clear();
int correctedBits = intersectionIndices.size() - message.getCorrectedBitCount();
message.incrementCorrectedBitCount(correctedBits);
}
else
{
for(Integer intersectionIndex: intersectionIndices)
{
message.flip(intersectionIndex);
}
}
}
if(columns.isEmpty() && rows.isEmpty())
{
Collections.sort(flippedBits);
message.incrementCorrectedBitCount(flippedBits.size());
}
else
{
message.setCorrectedBitCount(BPTCBase.ERRORS_NOT_CORRECTED);
return false;
}
return true;
}
/**
* Recursive method ...
* @param column
* @param message
* @param columns
* @param rows
* @return
*/
public static List<Integer> correctColumn(int column, CorrectedBinaryMessage message, BinaryMessage columns,
BinaryMessage rows, int depth, boolean pursue, List<Integer> path)
{
//Don't let the recursive call exceed threshold
if(depth > MAXIMUM_RECURSION_TURBO_DEPTH)
{
return Collections.emptyList();
}
int index = getColumnErrorIndex(column, message);
int row = getRow(index);
//If the candidate index is in range and the row for the candidate has an error, proceed
if(0 <= index && index <= 195 && rows.get(row) && !path.contains(index))
{
message.flip(index);
if(isColumnCorrect(column, message))
{
path.add(index);
//If the row is now correct, stop here.
if(isRowCorrect(row, message))
{
columns.clear(column);
rows.clear(row);
return path;
}
//Recursively test bit flipping in the row. A non-empty list is the solution set.
List<Integer> solution = correctRow(row, message, columns, rows, depth + 1, pursue, path);
//If our candidate index is part of the discovered solution, fail, otherwise return it as part of
// the solution.
if(solution.size() > 0)
{
columns.clear(column);
return solution;
}
else if(pursue) //Pursue additional column flips
{
for(int column2 = columns.nextSetBit(column + 1); column2 >= 0 && column2 < 15; column2 = columns.nextSetBit(column2 + 1))
{
List<Integer> solution2 = correctColumn(column2, message, columns, rows, depth + 1, pursue, path);
if(solution2.size() > 0)
{
columns.clear(column);
rows.clear(row);
return solution2;
}
}
}
path.removeLast();
}
//Revert the change
message.flip(index);
}
return Collections.emptyList();
}
public static List<Integer> correctRow(int row, CorrectedBinaryMessage message, BinaryMessage columns,
BinaryMessage rows, int depth, boolean pursue, List<Integer> path)
{
//Don't let the recursive call exceed threshold
if(depth > MAXIMUM_RECURSION_TURBO_DEPTH)
{
return Collections.emptyList();
}
int index = getRowErrorIndex(row, message);
int column = getColumn(index);
//If the candidate index is in range and the column for the candidate has an error, proceed
if(0 <= index && index <= 195 && columns.get(column) && !path.contains(index))
{
message.flip(index);
if(isRowCorrect(row, message))
{
path.add(index);
//If the column is now correct, stop here and return the path as the solution
if(isColumnCorrect(column, message))
{
columns.clear(column);
rows.clear(row);
return path;
}
//Recursively test bit flipping in the column. A non-empty list is the solution set.
List<Integer> solution = correctColumn(column, message, columns, rows, depth + 1, pursue, path);
//If our candidate index is part of the discovered solution, fail, otherwise return it as part of
// the solution.
if(solution.size() > 0)
{
rows.clear(row);
return solution;
}
else if(pursue) //Pursue additional row flips
{
for(int row2 = rows.nextSetBit(row + 1); row2 >= 0 && row2 < 13; row2 = rows.nextSetBit(row2 + 1))
{
List<Integer> solution2 = correctRow(row2, message, columns, rows, depth + 1, pursue, path);
if(solution2.size() > 0 && !solution2.contains(index))
{
columns.clear(column);
rows.clear(row);
return solution2;
}
}
}
path.removeLast();
}
//Revert the change
message.flip(index);
}
return Collections.emptyList();
}
public static int getIndex(int column, int row)
{
return row * 15 + column + 1;
}
/**
* Checks the row for bit errors.
* @param row to check
* @param message containing bits to check
* @return true if the row passes the Hamming check or false if not.
*/
private static boolean isRowCorrect(int row, CorrectedBinaryMessage message)
{
return Hamming15.getSyndrome(message, (row * 15) + 1) == 0;
}
/**
* Calculates the index for the bit error in the message
* @param row to check
* @param message with bits
* @return error index in the range 0-195, or -1 if the message row has no bit errors.
*/
private static int getRowErrorIndex(int row, CorrectedBinaryMessage message)
{
return Hamming15.getErrorIndex(message, (row * 15) + 1);
}
/**
* Checks the column for bit errors.
* @param column to check
* @param message containing bits to check
* @return true if the column passes the Hamming check or false if not.
*/
private static boolean isColumnCorrect(int column, CorrectedBinaryMessage message)
{
return Hamming13.getSyndrome(message, COLUMN_INDEXES[column]) == 0;
}
/**
* Calculates the index for the bit error in the message.
* @param column to check
* @param message with bits.
* @return error index in the range 0-195, or -1 if message has no bit errors, or 1000 if message has more than 1 bit error.
*/
private static int getColumnErrorIndex(int column, CorrectedBinaryMessage message)
{
return Hamming13.getErrorIndex(message, COLUMN_INDEXES[column]);
}
public static int getColumn(int index)
{
return (index - 1) % 15;
}
public static int getRow(int index)
{
return (index - 1) / 15;
}
public static void main(String[] args)
{
String raw = "0000101111101000000100001010100001010000001000000011100000000000100000000000001000000000001000000000000010000000001111001010000100001000000001100000100001000111110101011111011111101001111011100010";
String ref = "0000101111100000000100001100100001010000001000000000000000000000000000000000000000000000000000000000000000000000001111001010000100001000000001110000100001011111110101011011011111101011111011100000";
CorrectedBinaryMessage rawMessage = new CorrectedBinaryMessage(BinaryMessage.load(raw));
CorrectedBinaryMessage refMessage = new CorrectedBinaryMessage(BinaryMessage.load(ref));
// correct2(refMessage);
boolean pursueShadows = true;
correct(rawMessage, pursueShadows);
System.out.println("RAW: " + raw);
System.out.println("COR: " + rawMessage);
System.out.println("REF: " + ref);
rawMessage.xor(refMessage);
System.out.println("RES: " + rawMessage);
if(rawMessage.cardinality() > 0)
{
System.out.println("Residual Error Map:");
logErrorMap(rawMessage);
}
rawMessage = new CorrectedBinaryMessage(BinaryMessage.load(raw));
rawMessage.xor(refMessage);
System.out.println("\n\nXOR: " + rawMessage);
logErrorMap(rawMessage);
}
}

View File

@@ -0,0 +1,121 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.dmr.bptc;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.Hamming17;
/**
* Block product turbo code (BPTC) for decoding Short Link Control reassembled from CACH fragments.
*/
public class BPTC_68_36 extends BPTCBase
{
public BPTC_68_36()
{
super(new Hamming17(), 17, 4);
}
/**
* Performs error detection and correction and extracts the payload from the BPTC encoded message.
* @param message with BPTC encoding.
* @return decoded message.
*/
public CorrectedBinaryMessage extract(BinaryMessage message)
{
CorrectedBinaryMessage deinterleaved = deinterleave(message);
correct(deinterleaved);
//Extract the message
CorrectedBinaryMessage extractedMessage = new CorrectedBinaryMessage(36);
for(int row = 0; row < 3; row++)
{
for(int column = 0; column < 12; column++)
{
extractedMessage.set(row * 12 + column, deinterleaved.get(row * 17 + column));
}
}
extractedMessage.setCorrectedBitCount(deinterleaved.getCorrectedBitCount());
return extractedMessage;
}
/**
* Deinterleaves the transmitted message.
* @param interleaved message
* @return deinterleaved message.
*/
public CorrectedBinaryMessage deinterleave(BinaryMessage interleaved)
{
CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(68);
for(int index = 0; index < 67; index++)
{
if(interleaved.get(index))
{
deinterleaved.set((index * 17 % 67));
}
}
if(interleaved.get(67))
{
deinterleaved.set(67);
}
return deinterleaved;
}
public static void main(String[] args)
{
BPTC_68_36 bptc = new BPTC_68_36();
String ref = "10101001100001000000001101101111000000110011001110011111101011001100"; //No errors
String raw = "10101001100001000000001101101111000000110011001110011111101011001100"; //Under test
CorrectedBinaryMessage rawMessage = new CorrectedBinaryMessage(BinaryMessage.load(raw));
CorrectedBinaryMessage refMessage = new CorrectedBinaryMessage(BinaryMessage.load(ref));
bptc.logDiagnostic(rawMessage);
bptc.correct(rawMessage);
System.out.println("RAW: " + raw);
System.out.println("COR: " + rawMessage);
System.out.println("REF: " + ref);
rawMessage.xor(refMessage);
System.out.println("RES: " + rawMessage);
if(rawMessage.cardinality() > 0)
{
System.out.println("--------------------------------");
System.out.println("Residual Error Map:");
bptc.logErrorMap(rawMessage);
System.out.println("--------------------------------");
}
System.out.println("\n\n*********** TRUTH DATA ********************");
rawMessage = new CorrectedBinaryMessage(BinaryMessage.load(raw));
rawMessage.xor(refMessage);
System.out.println("XOR: " + rawMessage);
bptc.logErrorMap(rawMessage);
}
}

View File

@@ -1,14 +1,49 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.dmr.message;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.bits.IntField;
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
import java.util.HashMap;
import java.util.Map;
/**
* Common Announcement Channel is a 24-bit sequence that precedes an outbound DMR frame transmitted by a repeater.
*/
public class CACH
{
//Generated by method: createHamming7_4BitErrorMap(). The value of the uncorrected 7-bit CACH is used as an index
//to lookup the bit error position where:
// 0-6: bit error index location where bits are ordered as transmitted: 0, 1 ..., 6
// -1: valid/correct message
// -2: invalid message with 2 or more bit errors
public static final int[] BIT_ERROR_INDEXES = new int[]{-2, -2, -2, -2, -2, -2, -2, -2, -2, 5, 6, -1, 1, 2, 0, 4,
-2, 1, 4, 0, 5, 3, -1, 6, 0, 4, 1, 2, 6, -1, 3, 5, -2, -2, -2, -2, -2, -2, -2, -2, 4, 0, 2, 1, -1, 6, 5, -2,
6, -1, 3, 5, 0, 4, 1, -2, 5, 3, -1, 6, 2, 1, 4, 0, -2, -2, -2, -2, -2, -2, -2, -2, 2, 1, 4, 0, 5, -2, -1, 6,
3, 5, 6, -1, 1, -2, 0, 4, -1, 6, 5, 3, 4, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2, 6, -1, -2, 5, 0, 4, 1, 2,
4, 0, -2, 1, -1, 6, 5, 3, 1, 2, 0, 4, 3, 5, 6, -1};
private static final IntField VALUE_FIELD = IntField.range(0, 6);
private static final int CACH_MESSAGE_LENGTH = 24;
private static final int[] INTERLEAVE_MATRIX = new int[]{0, 4, 8, 12, 14, 18, 22, 1, 2, 3, 5, 6, 7, 9, 10, 11, 13,
15, 16, 17, 19, 20, 21, 23};
@@ -38,6 +73,23 @@ public class CACH
mValid = valid;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append(getInboundChannelAccessType().name()).append(" ");
sb.append(getLCSS());
sb.append(" TS:").append(getTimeslot());
sb.append(" CRC:").append(getCrcChecksum(getMessage()));
sb.append(" PAYLOAD:").append(getPayload().toHexString());
sb.append(" MSG:").append(getMessage().toHexString()).append(" ");
return sb.toString();
}
/**
* Descrambled message bits for this cach
*/
@@ -64,18 +116,6 @@ public class CACH
return false;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
// sb.append(getMessage().toHexString()).append(" ");
// sb.append(getInboundChannelAccessType().name()).append(" ");
sb.append(getLCSS());
// sb.append(" (").append(getTimeslot()).append("|").append(getCrcChecksum(getMessage())).append(")");
// sb.append(" ").append(getPayload().toHexString());
return sb.toString();
}
/**
* Indicates if this cach passes the FEC error correction and indicates that the inbound channel Access Type,
* Timeslot and LCSS information is valid.
@@ -109,17 +149,17 @@ public class CACH
}
/**
* Indicates the outbound timeslot for the next frame that follows this CACH, 0 or 1
* Indicates the outbound timeslot for the next frame that follows this CACH, 1 or 2
*/
public int getTimeslot()
{
return mMessage.get(OUTBOUND_BURST_TIMESLOT) ? 1 : 0;
return mMessage.get(OUTBOUND_BURST_TIMESLOT) ? 2 : 1;
}
/**
* Indicates if the timeslot is 0
*/
public boolean isTimeslot0()
public boolean isTimeslot1()
{
return !mMessage.get(OUTBOUND_BURST_TIMESLOT);
}
@@ -142,7 +182,6 @@ public class CACH
{
CorrectedBinaryMessage decodedMessage = new CorrectedBinaryMessage(CACH_MESSAGE_LENGTH);
//Deinterleave the transmitted message to create the decoded message
for(int x = 0; x < CACH_MESSAGE_LENGTH; x++)
{
@@ -152,14 +191,15 @@ public class CACH
}
}
int checksum = getCrcChecksum(decodedMessage);
int codeword = decodedMessage.getInt(VALUE_FIELD);
int bitError = BIT_ERROR_INDEXES[codeword];
// //debug - investigating corruption of CACH, SLC and EMB during encrypted Hytera/MotoTRBO calls.
// BinaryMessage test = message.getSubMessage(0, CACH_MESSAGE_LENGTH);
// System.out.println("--> CACH:" + test.toString() + " DE:" + decodedMessage.toString() + " CHK:" + checksum);
boolean valid = (checksum == 0);
if(bitError >= 0)
{
decodedMessage.flip(bitError);
}
boolean valid = bitError != -2;
return new CACH(decodedMessage, valid);
}
@@ -184,4 +224,101 @@ public class CACH
return checksum;
}
public static void createHamming7_4BitErrorMap()
{
IntField valueField = IntField.range(0, 6);
Map<Integer, Integer> map = new HashMap<>();
//Link Control Start/Stop value 00 is not valid for CACH, so we'll mark these as invalid codewords (-2).
int invalidLCSSMask = 0x18; //0011000b
BinaryMessage parity0 = new BinaryMessage(7);
parity0.load(4, 3, 5);
BinaryMessage parity1 = new BinaryMessage(7);
parity1.load(4, 3, 7);
BinaryMessage parity2 = new BinaryMessage(7);
parity2.load(4, 3, 6);
BinaryMessage parity3 = new BinaryMessage(7);
parity3.load(4, 3, 3);
//There are 2^4 = 16 potential valid codewords
for(int x = 0; x < 16; x++)
{
BinaryMessage bm = new BinaryMessage(7);
bm.load(0, 4, x);
if(bm.get(0))
{
bm.xor(parity0);
}
if(bm.get(1))
{
bm.xor(parity1);
}
if(bm.get(2))
{
bm.xor(parity2);
}
if(bm.get(3))
{
bm.xor(parity3);
}
int value = bm.getInt(valueField);
//Ignore any legal codeword that has a value of 00 in the LCSS field.
if(((value & invalidLCSSMask) ^ invalidLCSSMask) == invalidLCSSMask)
{
continue;
}
//Use -1 to indicate no errors and we'll reserve -2 for invalid/non-correctable 2-bit errors.
map.put(value, -1);
//Iteratively induce single bit errors and record them into the map
for(int bitError = 0; bitError < 7; bitError++)
{
bm.flip(bitError);
value = bm.getInt(valueField);
//Ignore any bit combination with a 00 in the LCSS field.
if(((value & invalidLCSSMask) ^ invalidLCSSMask) != invalidLCSSMask)
{
map.put(value, bitError);
}
bm.flip(bitError);
}
}
StringBuilder sb = new StringBuilder();
sb.append("public static final int[] BIT_ERROR_INDEXES = new int[]{");
for(int z = 0; z < 128; z++)
{
if(map.containsKey(z))
{
sb.append(map.get(z));
}
else
{
sb.append("-2");
}
if(z != 127)
{
sb.append(",");
}
}
sb.append("};");
System.out.println(sb);
}
public static void main(String[] args)
{
createHamming7_4BitErrorMap();
}
}

View File

@@ -21,7 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Base DMR Burst Message class
*/

View File

@@ -20,63 +20,21 @@
package io.github.dsheirer.module.decode.dmr.message;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.message.TimeslotMessage;
import io.github.dsheirer.protocol.Protocol;
/**
* Base DMR Message
*/
public abstract class DMRMessage extends Message
public abstract class DMRMessage extends TimeslotMessage
{
public static final int TIMESLOT_1 = 1;
public static final int TIMESLOT_2 = 2;
private CorrectedBinaryMessage mCorrectedBinaryMessage;
private boolean mValid = true;
private int mTimeslot;
/**
* Constructs an instance
* @param timestamp for the message
*/
public DMRMessage(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
super(timestamp);
mCorrectedBinaryMessage = message;
mTimeslot = timeslot;
}
/**
* Message bits
*/
public CorrectedBinaryMessage getMessage()
{
return mCorrectedBinaryMessage;
}
/**
* Indicates if this message is valid
*/
public boolean isValid()
{
return mValid;
}
/**
* Sets the valid flag for this message
*/
public void setValid(boolean valid)
{
mValid = valid;
}
/**
* Timeslot for this message
* @return 0 or 1
*/
public int getTimeslot()
{
return mTimeslot;
super(message, timeslot, timestamp);
}
@Override

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2024 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
@@ -20,11 +20,14 @@
package io.github.dsheirer.module.decode.dmr.message;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.message.EmptyTimeslotPlaceholderMessage;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.message.data.DMRDataMessageFactory;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceAMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import io.github.dsheirer.protocol.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,39 +43,33 @@ public class DMRMessageFactory
* @param timeslot for the burst
* @return DMRMessage instance or null for UNKNOWN sync pattern
*/
public static DMRBurst create(DMRSyncPattern syncPattern, CorrectedBinaryMessage binaryMessage, CACH cach,
public static IMessage create(DMRSyncPattern syncPattern, CorrectedBinaryMessage binaryMessage, CACH cach,
long timestamp, int timeslot)
{
switch(syncPattern)
{
case BASE_STATION_VOICE:
case MOBILE_STATION_VOICE:
case BS_VOICE_FRAME_B:
case BS_VOICE_FRAME_C:
case BS_VOICE_FRAME_D:
case BS_VOICE_FRAME_E:
case BS_VOICE_FRAME_F:
case MS_VOICE_FRAME_B:
case MS_VOICE_FRAME_C:
case MS_VOICE_FRAME_D:
case MS_VOICE_FRAME_E:
case MS_VOICE_FRAME_F:
case BASE_STATION_VOICE, MOBILE_STATION_VOICE:
case BS_VOICE_FRAME_B, BS_VOICE_FRAME_C, BS_VOICE_FRAME_D, BS_VOICE_FRAME_E, BS_VOICE_FRAME_F:
case MS_VOICE_FRAME_B, MS_VOICE_FRAME_C, MS_VOICE_FRAME_D, MS_VOICE_FRAME_E, MS_VOICE_FRAME_F:
case DIRECT_VOICE_FRAME_B, DIRECT_VOICE_FRAME_C, DIRECT_VOICE_FRAME_D, DIRECT_VOICE_FRAME_E,
DIRECT_VOICE_FRAME_F:
return createVoiceMessage(syncPattern, binaryMessage, cach, timestamp, timeslot);
case DIRECT_MODE_VOICE_TIMESLOT_1:
case DIRECT_VOICE_TIMESLOT_1:
return createVoiceMessage(syncPattern, binaryMessage, cach, timestamp, 1);
case DIRECT_MODE_VOICE_TIMESLOT_2:
case DIRECT_VOICE_TIMESLOT_2:
return createVoiceMessage(syncPattern, binaryMessage, cach, timestamp, 2);
case BASE_STATION_DATA:
case MOBILE_STATION_DATA:
return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, timeslot);
case DIRECT_MODE_DATA_TIMESLOT_1:
case DIRECT_DATA_TIMESLOT_1:
return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 1);
case DIRECT_MODE_DATA_TIMESLOT_2:
case DIRECT_DATA_TIMESLOT_2:
return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 2);
case DIRECT_EMPTY_TIMESLOT:
return new EmptyTimeslotPlaceholderMessage(timestamp, 288, Protocol.DMR, timeslot);
case UNKNOWN:
return null;
case MOBILE_STATION_REVERSE_CHANNEL:
case REVERSE_CHANNEL:
case RESERVED:
default:
return new UnknownDMRMessage(syncPattern, binaryMessage, cach, timestamp, timeslot);
@@ -95,14 +92,13 @@ public class DMRMessageFactory
{
case BASE_STATION_VOICE:
case MOBILE_STATION_VOICE:
case DIRECT_MODE_VOICE_TIMESLOT_1:
case DIRECT_MODE_VOICE_TIMESLOT_2:
case DIRECT_VOICE_TIMESLOT_1:
case DIRECT_VOICE_TIMESLOT_2:
return new VoiceAMessage(syncPattern, message, cach, timestamp, timeslot);
case BS_VOICE_FRAME_B:
case BS_VOICE_FRAME_C:
case BS_VOICE_FRAME_D:
case BS_VOICE_FRAME_E:
case BS_VOICE_FRAME_F:
case BS_VOICE_FRAME_B, BS_VOICE_FRAME_C, BS_VOICE_FRAME_D, BS_VOICE_FRAME_E, BS_VOICE_FRAME_F:
case MS_VOICE_FRAME_B, MS_VOICE_FRAME_C, MS_VOICE_FRAME_D, MS_VOICE_FRAME_E, MS_VOICE_FRAME_F:
case DIRECT_VOICE_FRAME_B, DIRECT_VOICE_FRAME_C, DIRECT_VOICE_FRAME_D, DIRECT_VOICE_FRAME_E,
DIRECT_VOICE_FRAME_F:
default:
return new VoiceEMBMessage(syncPattern, message, cach, timestamp, timeslot);
}

View File

@@ -21,8 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 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
@@ -21,10 +21,9 @@ package io.github.dsheirer.module.decode.dmr.message.data;
import io.github.dsheirer.bits.BitSetFullException;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.BPTC_196_96;
import io.github.dsheirer.edac.CRCDMR;
import io.github.dsheirer.edac.trellis.ViterbiDecoder_3_4_DMR;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.bptc.BPTC_196_96;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock1Rate;
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock1_2Rate;
@@ -52,6 +51,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.usb.USBData;
import io.github.dsheirer.module.decode.dmr.message.type.DataPacketFormat;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -79,33 +79,73 @@ public class DMRDataMessageFactory
if(slotType.isValid())
{
switch (slotType.getDataType())
{
case RATE_3_OF_4_DATA:
return new DataBlock3_4Rate(pattern, getTrellisPayload(message), cach, slotType, timestamp, timeslot);
case RATE_1_DATA:
return new DataBlock1Rate(pattern, message, cach, slotType, timestamp, timeslot);
}
//Get the BPTC(196,96) protected payload. If the corrected bit count is -1, the payload/message is invalid
CorrectedBinaryMessage payload = getPayload(message);
switch(slotType.getDataType())
{
case SLOT_IDLE:
return new IDLEMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
DataMessage idle = new IDLEMessage(pattern, payload, cach, slotType, timestamp, timeslot);
if(payload.getCorrectedBitCount() < 0)
{
idle.setValid(false);
}
return idle;
case CSBK:
return CSBKMessageFactory.create(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
DataMessage csbk = CSBKMessageFactory.create(pattern, payload, cach, slotType, timestamp, timeslot);
if(payload.getCorrectedBitCount() < 0)
{
csbk.setValid(false);
}
return csbk;
case USB_DATA:
CorrectedBinaryMessage usbdPayload = getPayload(message);
CorrectedBinaryMessage usbdPayload = payload;
switch(USBData.getServiceType(usbdPayload))
{
case LIP_SHORT_LOCATION_REQUEST:
default:
return new USBData(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
DataMessage usb =new USBData(pattern, payload, cach, slotType, timestamp, timeslot);
if(payload.getCorrectedBitCount() < 0)
{
usb.setValid(false);
}
return usb;
}
case MBC_ENC_HEADER:
case MBC_HEADER:
return new MBCHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
DataMessage mbc = new MBCHeader(pattern, payload, cach, slotType, timestamp, timeslot);
if(payload.getCorrectedBitCount() < 0)
{
mbc.setValid(false);
}
return mbc;
case CHANNEL_CONTROL_ENC_HEADER:
case PI_HEADER:
return new PiHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
DataMessage pi = new PiHeader(pattern, payload, cach, slotType, timestamp, timeslot);
if(payload.getCorrectedBitCount() < 0)
{
pi.setValid(false);
}
return pi;
case VOICE_HEADER:
return new VoiceHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
DataMessage voice = new VoiceHeader(pattern, payload, cach, slotType, timestamp, timeslot);
if(payload.getCorrectedBitCount() < 0)
{
voice.setValid(false);
}
return voice;
case DATA_ENC_HEADER:
case DATA_HEADER:
CorrectedBinaryMessage payload = getPayload(message);
int crcBitCount = CRCDMR.correctCCITT80(payload, 0, 80, 0xCCCC);
boolean valid = crcBitCount < 2;
boolean valid = crcBitCount < 2 && payload.getCorrectedBitCount() != -1;
DataPacketFormat dpf = DataHeader.getDataPacketFormat(payload);
switch(dpf)
@@ -191,22 +231,44 @@ public class DMRDataMessageFactory
return dh;
}
case TLC:
return new Terminator(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
DataMessage tlc = new Terminator(pattern, payload, cach, slotType, timestamp, timeslot);
if(payload.getCorrectedBitCount() < 0)
{
tlc.setValid(false);
}
return tlc;
case RATE_1_OF_2_DATA:
return new DataBlock1_2Rate(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
case RATE_3_OF_4_DATA:
return new DataBlock3_4Rate(pattern, getTrellisPayload(message), cach, slotType, timestamp, timeslot);
case RATE_1_DATA:
return new DataBlock1Rate(pattern, message, cach, slotType, timestamp, timeslot);
DataMessage data = new DataBlock1_2Rate(pattern, payload, cach, slotType, timestamp, timeslot);
if(payload.getCorrectedBitCount() < 0)
{
data.setValid(false);
}
return data;
case MBC_BLOCK:
return new MBCContinuationBlock(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
DataMessage mbcBlock = new MBCContinuationBlock(pattern, payload, cach, slotType, timestamp, timeslot);
if(payload.getCorrectedBitCount() < 0)
{
mbcBlock.setValid(false);
}
return mbcBlock;
case RESERVED_15:
case UNKNOWN:
return new UnknownDataMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
DataMessage unk = new UnknownDataMessage(pattern, payload, cach, slotType, timestamp, timeslot);
if(payload.getCorrectedBitCount() < 0)
{
unk.setValid(false);
}
return unk;
}
}
return new UnknownDataMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
CorrectedBinaryMessage payload = getPayload(message);
DataMessage unk = new UnknownDataMessage(pattern, payload, cach, slotType, timestamp, timeslot);
if(payload.getCorrectedBitCount() < 0)
{
unk.setValid(false);
}
return unk;
}
/**

View File

@@ -19,9 +19,9 @@
package io.github.dsheirer.module.decode.dmr.message.data;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Base DMR Data Message

View File

@@ -21,11 +21,11 @@ package io.github.dsheirer.module.decode.dmr.message.data;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 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
@@ -20,8 +20,8 @@ package io.github.dsheirer.module.decode.dmr.message.data;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;
@@ -54,6 +54,11 @@ public class IDLEMessage extends DataMessage
sb.append(" RAS:").append(getBPTCReservedBits());
}
if(!isValid())
{
sb.append(" [CRC-ERROR]");
}
sb.append(" IDLE");
return sb.toString();
}

View File

@@ -21,8 +21,8 @@ package io.github.dsheirer.module.decode.dmr.message.data;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;

View File

@@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.block;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.DataMessage;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;

View File

@@ -20,9 +20,9 @@
package io.github.dsheirer.module.decode.dmr.message.data.block;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Rate 1/1 Data Block

View File

@@ -20,9 +20,9 @@
package io.github.dsheirer.module.decode.dmr.message.data.block;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Rate 1/2 BPTC 196 Data Block

View File

@@ -20,9 +20,9 @@
package io.github.dsheirer.module.decode.dmr.message.data.block;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Rate 3/4 Trellis Coded Data Block

View File

@@ -20,9 +20,9 @@
package io.github.dsheirer.module.decode.dmr.message.data.block;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Unknown DMR data block

View File

@@ -21,12 +21,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.CRCDMR;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.DataMessage;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Control Signalling Block (CSBK) Message

View File

@@ -20,7 +20,6 @@
package io.github.dsheirer.module.decode.dmr.message.data.csbk;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.Hytera08Acknowledge;
@@ -88,6 +87,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc
import io.github.dsheirer.module.decode.dmr.message.data.mbc.UnknownMultiCSBK;
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceKind;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.List;
/**

View File

@@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;

View File

@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -31,6 +30,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.AcknowledgeType;
import io.github.dsheirer.module.decode.dmr.message.type.Reason;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -31,6 +30,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.AcknowledgeType;
import io.github.dsheirer.module.decode.dmr.message.type.Reason;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -20,11 +20,11 @@
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.AdjacentSiteInformation;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Hytera Neighbor/Adjacent Site Information

View File

@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
@@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import io.github.dsheirer.module.decode.dmr.message.type.Version;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,12 +22,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;

View File

@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
@@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import io.github.dsheirer.module.decode.dmr.message.type.Version;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -30,7 +29,7 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -21,7 +21,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -24,7 +24,6 @@ import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
@@ -34,6 +33,7 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -23,11 +23,11 @@ import com.google.common.base.Joiner;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.site.SiteIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -21,13 +21,13 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPMessageType;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -32,6 +31,7 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
@@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import io.github.dsheirer.module.decode.dmr.message.type.Version;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
@@ -36,6 +35,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
@@ -32,6 +31,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -23,13 +23,13 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ProtectKind;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -31,6 +30,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.AcknowledgeType;
import io.github.dsheirer.module.decode.dmr.message.type.Reason;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -2,11 +2,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowle
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRUnitStatus;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -1,9 +1,9 @@
package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Registration Accepted

View File

@@ -21,13 +21,13 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceKind;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* DMR Tier III - Ahoy!

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.Tier3Gateway;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,10 +22,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,10 +22,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.Tier3Gateway;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.Tier3Gateway;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -21,7 +21,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -21,12 +21,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -21,7 +21,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -29,6 +28,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -21,12 +21,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -21,10 +21,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -21,10 +21,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;

View File

@@ -22,10 +22,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -21,7 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;

View File

@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -20,7 +20,6 @@
package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
@@ -31,6 +30,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.List;
/**

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

View File

@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;

Some files were not shown because too many files have changed in this diff Show More