mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-10 23:57:25 -05:00
721 lines
21 KiB
Java
721 lines
21 KiB
Java
package module.decode.p25;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import message.Message;
|
|
import module.decode.p25.message.P25Message;
|
|
import module.decode.p25.message.hdu.HDUMessage;
|
|
import module.decode.p25.message.ldu.LDU1Message;
|
|
import module.decode.p25.message.ldu.LDU2Message;
|
|
import module.decode.p25.message.ldu.lc.LDULCMessageFactory;
|
|
import module.decode.p25.message.pdu.PDUMessage;
|
|
import module.decode.p25.message.pdu.PDUMessageFactory;
|
|
import module.decode.p25.message.pdu.confirmed.PDUConfirmedMessage;
|
|
import module.decode.p25.message.pdu.confirmed.PDUConfirmedMessageFactory;
|
|
import module.decode.p25.message.tdu.TDUMessage;
|
|
import module.decode.p25.message.tdu.lc.TDULCMessageFactory;
|
|
import module.decode.p25.message.tdu.lc.TDULinkControlMessage;
|
|
import module.decode.p25.message.tsbk.TSBKMessage;
|
|
import module.decode.p25.message.tsbk.TSBKMessageFactory;
|
|
import module.decode.p25.message.vselp.VSELP1Message;
|
|
import module.decode.p25.message.vselp.VSELP2Message;
|
|
import module.decode.p25.reference.DataUnitID;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import sample.Listener;
|
|
import alias.AliasList;
|
|
import bits.BinaryMessage;
|
|
import bits.BitSetFullException;
|
|
import bits.ISyncDetectListener;
|
|
import bits.MultiSyncPatternMatcher;
|
|
import bits.SoftSyncDetector;
|
|
import bits.SyncDetector;
|
|
import dsp.psk.LSMDemodulator;
|
|
import dsp.symbol.Dibit;
|
|
import dsp.symbol.FrameSync;
|
|
import edac.BCH_63_16_11;
|
|
import edac.CRC;
|
|
import edac.CRCP25;
|
|
|
|
public class P25MessageFramer implements Listener<Dibit>
|
|
{
|
|
private final static Logger mLog =
|
|
LoggerFactory.getLogger( P25MessageFramer.class );
|
|
|
|
/* Determines the threshold for sync pattern soft matching */
|
|
private static final int SYNC_MATCH_THRESHOLD = 2;
|
|
private static final int SYNC_IN_CALL_THRESHOLD = 4;
|
|
|
|
/* Costas Loop phase lock error correction values. A phase lock error of
|
|
* 90 degrees requires a correction of 1/4 of the symbol rate (1200Hz). An
|
|
* error of 180 degrees requires a correction of 1/2 of the symbol rate */
|
|
public static final double SYMBOL_FREQUENCY = 2.0d * Math.PI * 4800.0d / 48000.0d;
|
|
public static final double PHASE_CORRECTION_90_DEGREES = SYMBOL_FREQUENCY / 4.0d;
|
|
public static final double PHASE_CORRECTION_180_DEGREES = SYMBOL_FREQUENCY / 2.0d;
|
|
public static final int TSBK_BEGIN = 64;
|
|
public static final int TSBK_CRC_START = 144;
|
|
public static final int TSBK_END = 260;
|
|
public static final int TSBK_DECODED_END = 160;
|
|
|
|
public static final int PDU0_BEGIN = 64;
|
|
public static final int PDU0_CRC_BEGIN = 144;
|
|
public static final int PDU0_END = 260;
|
|
public static final int PDU1_BEGIN = 160;
|
|
public static final int PDU1_END = 356;
|
|
public static final int PDU2_BEGIN = 256;
|
|
public static final int PDU2_END = 452;
|
|
public static final int PDU3_BEGIN = 352;
|
|
public static final int PDU3_END = 548;
|
|
public static final int PDU3_DECODED_END = 448;
|
|
|
|
private SoftSyncDetector mPrimarySyncDetector = new SoftSyncDetector(
|
|
FrameSync.P25_PHASE1_NORMAL.getSync(), SYNC_MATCH_THRESHOLD );
|
|
|
|
private MultiSyncPatternMatcher mMatcher = new MultiSyncPatternMatcher( 48 );
|
|
private ArrayList<P25MessageAssembler> mAssemblers =
|
|
new ArrayList<P25MessageAssembler>();
|
|
|
|
private Listener<Message> mListener;
|
|
private AliasList mAliasList;
|
|
|
|
private Trellis_1_2_Rate mHalfRate = new Trellis_1_2_Rate();
|
|
private Trellis_3_4_Rate mThreeQuarterRate = new Trellis_3_4_Rate();
|
|
private BCH_63_16_11 mNIDDecoder = new BCH_63_16_11();
|
|
|
|
/**
|
|
* Constructs a P25 message framer to receive a stream of symbols and
|
|
* detect the sync pattern then capture the following stream of symbols up
|
|
* to the message length, and then broadcast that bit buffer to the registered
|
|
* listener.
|
|
*/
|
|
public P25MessageFramer( AliasList aliasList )
|
|
{
|
|
mAliasList = aliasList;
|
|
|
|
mPrimarySyncDetector.setListener( new ISyncDetectListener()
|
|
{
|
|
@Override
|
|
public void syncDetected()
|
|
{
|
|
for( P25MessageAssembler assembler: mAssemblers )
|
|
{
|
|
if( !assembler.isActive() )
|
|
{
|
|
assembler.setActive( true );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} );
|
|
|
|
mMatcher.add( mPrimarySyncDetector );
|
|
|
|
/**
|
|
* We use two message assemblers to catch any sync detections, so that
|
|
* if we have a false trigger then we still have a chance to catch the
|
|
* valid message. If we have two false triggers before the arrival of
|
|
* the actual message sync, then the third or subsequent real or false
|
|
* sync pattern will produce a debug message indicating no assemblers
|
|
* are available..
|
|
*/
|
|
mAssemblers.add( new P25MessageAssembler() );
|
|
mAssemblers.add( new P25MessageAssembler() );
|
|
}
|
|
|
|
public P25MessageFramer( AliasList aliasList, LSMDemodulator demodulator )
|
|
{
|
|
this( aliasList );
|
|
|
|
if( demodulator != null )
|
|
{
|
|
/* For CQPSK, we include 3 additional sync detectors to watch for and
|
|
* correct +/-90 and 180 degree costas loop phase lock errors */
|
|
mMatcher.add( new CostasPhaseErrorDetector( FrameSync.P25_PHASE1_ERROR_90_CCW,
|
|
demodulator, PHASE_CORRECTION_90_DEGREES ) );
|
|
|
|
mMatcher.add( new CostasPhaseErrorDetector( FrameSync.P25_PHASE1_ERROR_90_CW,
|
|
demodulator, -PHASE_CORRECTION_90_DEGREES ) );
|
|
|
|
mMatcher.add( new CostasPhaseErrorDetector( FrameSync.P25_PHASE1_ERROR_180,
|
|
demodulator, PHASE_CORRECTION_180_DEGREES ) );
|
|
}
|
|
}
|
|
|
|
public void dispose()
|
|
{
|
|
for( P25MessageAssembler a: mAssemblers )
|
|
{
|
|
a.dispose();
|
|
}
|
|
|
|
mAssemblers.clear();
|
|
|
|
mListener = null;
|
|
mAliasList = null;
|
|
|
|
mMatcher.dispose();
|
|
mMatcher = null;
|
|
|
|
mPrimarySyncDetector.dispose();
|
|
mPrimarySyncDetector = null;
|
|
|
|
}
|
|
|
|
private void dispatch( Message message )
|
|
{
|
|
if( mListener != null )
|
|
{
|
|
mListener.receive( message );
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void receive( Dibit symbol )
|
|
{
|
|
for( P25MessageAssembler assembler: mAssemblers )
|
|
{
|
|
if( assembler.isActive() )
|
|
{
|
|
assembler.receive( symbol );
|
|
|
|
if( assembler.complete() )
|
|
{
|
|
assembler.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
mMatcher.receive( symbol.getBit1(), symbol.getBit2() );
|
|
}
|
|
|
|
public void setListener( Listener<Message> listener )
|
|
{
|
|
mListener = listener;
|
|
}
|
|
|
|
public void removeListener( Listener<Message> listener )
|
|
{
|
|
mListener = null;
|
|
}
|
|
|
|
private class P25MessageAssembler
|
|
{
|
|
/* Starting position of the status symbol counter is 24 symbols to
|
|
* account for the 48-bit sync pattern which is not included in message */
|
|
private int mStatusSymbolPointer = 24;
|
|
private BinaryMessage mMessage;
|
|
private int mMessageLength;
|
|
private boolean mComplete = false;
|
|
private boolean mActive = false;
|
|
private DataUnitID mDUID = DataUnitID.NID;
|
|
|
|
public P25MessageAssembler()
|
|
{
|
|
mMessageLength = mDUID.getMessageLength();
|
|
mMessage = new BinaryMessage( mMessageLength );
|
|
reset();
|
|
}
|
|
|
|
public void receive( Dibit dibit )
|
|
{
|
|
if( mActive )
|
|
{
|
|
if( mStatusSymbolPointer == 35 )
|
|
{
|
|
mStatusSymbolPointer = 0;
|
|
}
|
|
else
|
|
{
|
|
mStatusSymbolPointer++;
|
|
|
|
try
|
|
{
|
|
mMessage.add( dibit.getBit1() );
|
|
mMessage.add( dibit.getBit2() );
|
|
}
|
|
catch( BitSetFullException e )
|
|
{
|
|
mComplete = true;
|
|
}
|
|
|
|
/* Check the message for complete */
|
|
if( mMessage.isFull() )
|
|
{
|
|
checkComplete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void reset()
|
|
{
|
|
mDUID = DataUnitID.NID;
|
|
mMessage.setSize( mDUID.getMessageLength() );
|
|
mMessage.clear();
|
|
mStatusSymbolPointer = 24;
|
|
mComplete = false;
|
|
mActive = false;
|
|
}
|
|
|
|
public void setActive( boolean active )
|
|
{
|
|
mActive = active;
|
|
}
|
|
|
|
private void setDUID( DataUnitID id )
|
|
{
|
|
mDUID = id;
|
|
mMessageLength = id.getMessageLength();
|
|
mMessage.setSize( mMessageLength );
|
|
}
|
|
|
|
private void checkComplete()
|
|
{
|
|
switch( mDUID )
|
|
{
|
|
case NID:
|
|
mMessage = mNIDDecoder.correctNID( mMessage );
|
|
|
|
if( mMessage.getCRC() != CRC.FAILED_CRC )
|
|
{
|
|
int value = mMessage.getInt( P25Message.DUID );
|
|
|
|
DataUnitID duid = DataUnitID.fromValue( value );
|
|
|
|
if( duid != DataUnitID.UNKN )
|
|
{
|
|
setDUID( duid );
|
|
}
|
|
else
|
|
{
|
|
mComplete = true;
|
|
dispatch( new P25Message( mMessage.copy(), mDUID, mAliasList ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mComplete = true;
|
|
}
|
|
break;
|
|
case HDU:
|
|
mComplete = true;
|
|
dispatch( new HDUMessage( mMessage.copy(), mDUID, mAliasList ) );
|
|
|
|
/* We're in a call now, lower the sync match threshold */
|
|
mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD );
|
|
break;
|
|
case LDU1:
|
|
mComplete = true;
|
|
|
|
LDU1Message ldu1 = new LDU1Message( mMessage.copy(),
|
|
mDUID, mAliasList );
|
|
|
|
/* Convert the LDU1 message into a link control LDU1 message */
|
|
dispatch( LDULCMessageFactory.getMessage( ldu1 ) );
|
|
|
|
/* We're in a call now, lower the sync match threshold */
|
|
mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD );
|
|
break;
|
|
case LDU2:
|
|
mComplete = true;
|
|
dispatch( new LDU2Message( mMessage.copy(), mDUID, mAliasList ) );
|
|
|
|
/* We're in a call now, lower the sync match threshold */
|
|
mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD );
|
|
break;
|
|
case PDU0:
|
|
|
|
/* Remove interleaving */
|
|
P25Interleave.deinterleaveData( mMessage, PDU0_BEGIN, PDU0_END );
|
|
|
|
/* Remove trellis encoding - abort processing if we have an
|
|
* unsuccessful decode due to excessive errors */
|
|
if( mHalfRate.decode( mMessage, PDU0_BEGIN, PDU0_END ) )
|
|
{
|
|
mMessage = CRCP25.correctCCITT80( mMessage,
|
|
PDU0_BEGIN, PDU0_CRC_BEGIN );
|
|
|
|
|
|
if( mMessage.getCRC() != CRC.FAILED_CRC )
|
|
{
|
|
boolean confirmed = mMessage.get(
|
|
PDUMessage.CONFIRMATION_REQUIRED_INDICATOR );
|
|
|
|
if( confirmed )
|
|
{
|
|
setDUID( DataUnitID.PDUC );
|
|
}
|
|
else
|
|
{
|
|
setDUID( DataUnitID.PDU1 );
|
|
}
|
|
|
|
mMessage.setPointer( PDU1_BEGIN );
|
|
}
|
|
else
|
|
{
|
|
mComplete = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mComplete = true;
|
|
}
|
|
|
|
/* Set sync match threshold to normal */
|
|
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
|
|
|
|
break;
|
|
case PDU1:
|
|
/* Remove interleaving */
|
|
P25Interleave.deinterleaveData( mMessage, PDU1_BEGIN, PDU1_END );
|
|
|
|
/* Remove trellis encoding - abort processing if we have an
|
|
* unsuccessful decode due to excessive errors */
|
|
if( mHalfRate.decode( mMessage, PDU1_BEGIN, PDU1_END ) )
|
|
{
|
|
if( mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW ) == 1 )
|
|
{
|
|
mMessage.setSize( PDU2_BEGIN );
|
|
|
|
PDUMessage pduMessage1 = PDUMessageFactory.getMessage(
|
|
mMessage.copy(), DataUnitID.PDU1, mAliasList );
|
|
|
|
dispatch( pduMessage1 );
|
|
|
|
mComplete = true;
|
|
}
|
|
else
|
|
{
|
|
setDUID( DataUnitID.PDU2 );
|
|
|
|
mMessage.setPointer( PDU2_BEGIN );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mComplete = true;
|
|
}
|
|
break;
|
|
case PDU2:
|
|
/* Remove interleaving */
|
|
P25Interleave.deinterleaveData( mMessage, PDU2_BEGIN, PDU2_END );
|
|
|
|
/* Remove trellis encoding - abort processing if we have an
|
|
* unsuccessful decode due to excessive errors */
|
|
if( mHalfRate.decode( mMessage, PDU2_BEGIN, PDU2_END ) )
|
|
{
|
|
if( mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW ) == 2 )
|
|
{
|
|
mMessage.setSize( PDU3_BEGIN );
|
|
|
|
PDUMessage pduMessage2 = PDUMessageFactory.getMessage(
|
|
mMessage.copy(), DataUnitID.PDU2, mAliasList );
|
|
|
|
dispatch( pduMessage2 );
|
|
|
|
mComplete = true;
|
|
}
|
|
else
|
|
{
|
|
setDUID( DataUnitID.PDU3 );
|
|
|
|
mMessage.setPointer( PDU3_BEGIN );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mComplete = true;
|
|
}
|
|
|
|
/* Set sync match threshold to normal */
|
|
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
|
|
break;
|
|
case PDU3:
|
|
/* Remove interleaving */
|
|
P25Interleave.deinterleaveData( mMessage, PDU3_BEGIN, PDU3_END );
|
|
|
|
/* Remove trellis encoding - abort processing if we have an
|
|
* unsuccessful decode due to excessive errors */
|
|
if( mHalfRate.decode( mMessage, PDU3_BEGIN, PDU3_END ) )
|
|
{
|
|
mMessage.setSize( PDU3_DECODED_END );
|
|
|
|
PDUMessage pduMessage3 = PDUMessageFactory.getMessage(
|
|
mMessage.copy(), DataUnitID.PDU3, mAliasList );
|
|
|
|
dispatch( pduMessage3 );
|
|
}
|
|
|
|
mComplete = true;
|
|
break;
|
|
case PDUC:
|
|
/* De-interleave the latest block*/
|
|
P25Interleave.deinterleaveData( mMessage,
|
|
mMessage.size() - 196, mMessage.size() );
|
|
|
|
/* Decode 3/4 rate convolutional encoding from latest block */
|
|
if( mThreeQuarterRate.decode( mMessage,
|
|
mMessage.size() - 196, mMessage.size() ) )
|
|
{
|
|
/* Resize the message and adjust the message pointer
|
|
* to account for removing 48 + 4 parity bits */
|
|
mMessage.setSize( mMessage.size() - 52 );
|
|
mMessage.adjustPointer( -52 );
|
|
|
|
int blocks = mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW );
|
|
|
|
int current = ( mMessage.size() - 160 ) / 144;
|
|
|
|
if( current < blocks )
|
|
{
|
|
mMessage.setSize( mMessage.size() + 196 );
|
|
mMessageLength = mMessage.size();
|
|
}
|
|
else
|
|
{
|
|
PDUConfirmedMessage pducm =
|
|
new PDUConfirmedMessage( mMessage.copy(),
|
|
mAliasList );
|
|
|
|
/* Translate into correct subclass */
|
|
dispatch( PDUConfirmedMessageFactory.getMessage( pducm ) );
|
|
|
|
mComplete = true;
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
PDUConfirmedMessage pducm =
|
|
new PDUConfirmedMessage( mMessage.copy(),
|
|
mAliasList );
|
|
|
|
/* Translate into correct subclass */
|
|
dispatch( PDUConfirmedMessageFactory.getMessage( pducm ) );
|
|
|
|
mComplete = true;
|
|
}
|
|
|
|
/* Set sync match threshold to normal */
|
|
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
|
|
break;
|
|
case TDU:
|
|
dispatch( new TDUMessage( mMessage.copy(), mDUID, mAliasList ) );
|
|
mComplete = true;
|
|
|
|
/* Set sync match threshold to normal */
|
|
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
|
|
break;
|
|
case TDULC:
|
|
TDULinkControlMessage tdulc = new TDULinkControlMessage(
|
|
mMessage.copy(), mDUID, mAliasList );
|
|
|
|
/* Convert to an appropriate link control message */
|
|
tdulc = TDULCMessageFactory.getMessage( tdulc );
|
|
|
|
dispatch( tdulc );
|
|
mComplete = true;
|
|
|
|
/* Set sync match threshold to normal */
|
|
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
|
|
break;
|
|
case TSBK1:
|
|
/* Remove interleaving */
|
|
P25Interleave.deinterleaveData( mMessage, TSBK_BEGIN, TSBK_END );
|
|
|
|
/* Remove trellis encoding - abort processing if we have an
|
|
* unsuccessful decode due to excessive errors */
|
|
|
|
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
|
|
{
|
|
mMessage = CRCP25.correctCCITT80( mMessage,
|
|
TSBK_BEGIN, TSBK_CRC_START );
|
|
|
|
if( mMessage.getCRC() != CRC.FAILED_CRC )
|
|
{
|
|
BinaryMessage tsbkBuffer1 = mMessage.copy();
|
|
tsbkBuffer1.setSize( TSBK_DECODED_END );
|
|
|
|
TSBKMessage tsbkMessage1 = TSBKMessageFactory.getMessage(
|
|
tsbkBuffer1, DataUnitID.TSBK1, mAliasList );
|
|
|
|
if( tsbkMessage1.isLastBlock() )
|
|
{
|
|
mComplete = true;
|
|
}
|
|
else
|
|
{
|
|
setDUID( DataUnitID.TSBK2 );
|
|
mMessage.setPointer( TSBK_BEGIN );
|
|
}
|
|
|
|
dispatch( tsbkMessage1 );
|
|
}
|
|
else
|
|
{
|
|
mComplete = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mComplete = true;
|
|
}
|
|
|
|
/* Set sync match threshold to normal */
|
|
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
|
|
break;
|
|
case TSBK2:
|
|
/* Remove interleaving */
|
|
P25Interleave.deinterleaveData( mMessage, TSBK_BEGIN, TSBK_END );
|
|
|
|
/* Remove trellis encoding - abort processing if we have an
|
|
* unsuccessful decode due to excessive errors */
|
|
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
|
|
{
|
|
mMessage = CRCP25.correctCCITT80( mMessage,
|
|
TSBK_BEGIN, TSBK_CRC_START );
|
|
|
|
if( mMessage.getCRC() != CRC.FAILED_CRC )
|
|
{
|
|
BinaryMessage tsbkBuffer2 = mMessage.copy();
|
|
tsbkBuffer2.setSize( TSBK_DECODED_END );
|
|
|
|
TSBKMessage tsbkMessage2 = TSBKMessageFactory.getMessage(
|
|
tsbkBuffer2, DataUnitID.TSBK2, mAliasList );
|
|
|
|
if( tsbkMessage2.isLastBlock() )
|
|
{
|
|
mComplete = true;
|
|
}
|
|
else
|
|
{
|
|
setDUID( DataUnitID.TSBK3 );
|
|
mMessage.setPointer( TSBK_BEGIN );
|
|
}
|
|
|
|
dispatch( tsbkMessage2 );
|
|
}
|
|
else
|
|
{
|
|
mComplete = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mComplete = true;
|
|
}
|
|
break;
|
|
case TSBK3:
|
|
/* Remove interleaving */
|
|
P25Interleave.deinterleaveData( mMessage, TSBK_BEGIN, TSBK_END );
|
|
|
|
/* Remove trellis encoding - abort processing if we have an
|
|
* unsuccessful decode due to excessive errors */
|
|
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
|
|
{
|
|
mMessage = CRCP25.correctCCITT80( mMessage,
|
|
TSBK_BEGIN, TSBK_CRC_START );
|
|
|
|
if( mMessage.getCRC() != CRC.FAILED_CRC )
|
|
{
|
|
BinaryMessage tsbkBuffer3 = mMessage.copy();
|
|
tsbkBuffer3.setSize( TSBK_DECODED_END );
|
|
|
|
TSBKMessage tsbkMessage3 = TSBKMessageFactory.getMessage(
|
|
tsbkBuffer3, DataUnitID.TSBK3, mAliasList );
|
|
|
|
dispatch( tsbkMessage3 );
|
|
}
|
|
}
|
|
|
|
mComplete = true;
|
|
break;
|
|
case VSELP1:
|
|
mComplete = true;
|
|
dispatch( new VSELP1Message( mMessage.copy(), mDUID, mAliasList ) );
|
|
break;
|
|
case VSELP2:
|
|
mComplete = true;
|
|
dispatch( new VSELP2Message( mMessage.copy(), mDUID, mAliasList ) );
|
|
break;
|
|
case UNKN:
|
|
mComplete = true;
|
|
dispatch( new P25Message( mMessage.copy(), mDUID, mAliasList ) );
|
|
break;
|
|
default:
|
|
mComplete = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void dispose()
|
|
{
|
|
mMessage = null;
|
|
mHalfRate.dispose();
|
|
}
|
|
|
|
/**
|
|
* Flag to indicate when this assembler has received all of the bits it
|
|
* is looking for (ie message length), and should then be removed from
|
|
* receiving any more bits
|
|
*/
|
|
public boolean complete()
|
|
{
|
|
return mComplete;
|
|
}
|
|
|
|
public boolean isActive()
|
|
{
|
|
return mActive;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sync pattern detector to listen for costas loop phase lock errors and
|
|
* apply a phase correction to the costas loop so that we don't miss any
|
|
* messages.
|
|
*
|
|
* When the costas loop locks with a +/- 90 degree or 180 degree phase
|
|
* error, the slicer will incorrectly apply the symbol pattern rotated left
|
|
* or right by the phase error. However, we can detect these rotated sync
|
|
* patterns and apply immediate phase correction so that message processing
|
|
* can continue.
|
|
*/
|
|
public class CostasPhaseErrorDetector extends SyncDetector
|
|
{
|
|
private LSMDemodulator mDemodulator;
|
|
private double mCorrection;
|
|
|
|
public CostasPhaseErrorDetector( FrameSync frameSync, LSMDemodulator demodulator, double correction )
|
|
{
|
|
super( frameSync.getSync() );
|
|
|
|
mDemodulator = demodulator;
|
|
|
|
mCorrection = correction;
|
|
|
|
setListener( new ISyncDetectListener()
|
|
{
|
|
@Override
|
|
public void syncDetected()
|
|
{
|
|
mDemodulator.correctPhaseError( mCorrection );
|
|
|
|
/* Since we detected a sync pattern, start a message assembler */
|
|
for( P25MessageAssembler assembler: mAssemblers )
|
|
{
|
|
if( !assembler.isActive() )
|
|
{
|
|
assembler.setActive( true );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} );
|
|
}
|
|
}
|
|
}
|