P25 Work-in-progress check-in.

This commit is contained in:
dennis.sheirer
2014-11-10 09:53:03 +00:00
parent 7b89c614e7
commit 46e3594c07
31 changed files with 2271 additions and 227 deletions

View File

@@ -87,6 +87,13 @@ public class BitSetBuffer extends BitSet
this.or( toCopyFrom );
this.mPointer = toCopyFrom.pointer();
}
public BitSetBuffer( BitSet bitset, int size )
{
this( size );
this.or( bitset );
this.mPointer = size - 1;
}
/**
* Current pointer index
@@ -95,6 +102,11 @@ public class BitSetBuffer extends BitSet
{
return mPointer;
}
public void setPointer( int index )
{
mPointer = index;
}
/**
* Static method to construct a new BitSetBuffer, preloaded with the bits
@@ -153,6 +165,11 @@ public class BitSetBuffer extends BitSet
{
return mSize;
}
public void setSize( int size )
{
mSize = size;
}
/**
* Clears (sets to false or 0) the bits in this bitset and resets the

View File

@@ -48,6 +48,28 @@ public class SyncPatternMatcher
}
}
public SyncPatternMatcher( long sync )
{
mSync = sync;
mMask = getMask( sync );
}
/**
* Generates an all ones mask corresponding to the sync pattern
*/
private static long getMask( long sync )
{
long mask = sync;
for( int x = 1; x < 64; x *= 2 )
{
mask |= ( mask >> x );
}
return mask;
}
public void receive( boolean bit )
{
//Left shift the previous value

13
src/crc/BCH.java Normal file
View File

@@ -0,0 +1,13 @@
package crc;
import java.util.BitSet;
public class BCH
{
public BCH( int n, int k, int t, boolean[] polynomial, boolean sys )
{
int[] exponents = new int[ n - k + 1 ];
}
}

View File

@@ -0,0 +1,19 @@
package decode.p25;
public enum FrameSync
{
P25_PHASE1( 0x5575F5FF77FFl ),
P25_PHASE1_INVERTED( 0xFFDF5F55DD55l );
private long mSync;
private FrameSync( long sync )
{
mSync = sync;
}
public long getSync()
{
return mSync;
}
}

View File

@@ -20,8 +20,12 @@ package decode.p25;
import instrument.Instrumentable;
import instrument.tap.Tap;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import alias.AliasList;
@@ -29,19 +33,26 @@ import decode.Decoder;
import decode.DecoderType;
import dsp.filter.ComplexFIRFilter;
import dsp.filter.FilterFactory;
import dsp.filter.FloatFIRFilter;
import dsp.filter.Window.WindowType;
import dsp.fsk.C4FMDecoder;
import dsp.fsk.P25MessageFramer;
import dsp.fsk.C4FMSlicer;
import dsp.fsk.C4FMSymbolFilter;
import dsp.nbfm.FMDiscriminator;
public class P25Decoder extends Decoder implements Instrumentable
{
private final static Logger mLog = LoggerFactory.getLogger( P25Decoder.class );
private ComplexFIRFilter mBasebandFilter = new ComplexFIRFilter(
FilterFactory.getLowPass( 48000, 6250, 73, WindowType.HAMMING ), 1.0 );
private FMDiscriminator mFMDiscriminator = new FMDiscriminator( 1 );
// private FloatFIRFilter mAudioFilter = new FloatFIRFilter( FilterFactory
// .getLowPass( 48000, 6000, 73, WindowType.HAMMING ), 1.0 );
private C4FMDecoder mC4FMDecoder = new C4FMDecoder();
FilterFactory.getLowPass( 48000, 5000, 31, WindowType.HAMMING ), 1.0 );
private FMDiscriminator mDemodulator = new FMDiscriminator( 1 );
private C4FMSymbolFilter mSymbolFilter = new C4FMSymbolFilter();
private C4FMSlicer mSlicer = new C4FMSlicer();
private P25MessageFramer mNormalFramer;
private P25MessageFramer mInvertedFramer;
private P25MessageProcessor mMessageProcessor;
private AliasList mAliasList;
public P25Decoder( SampleType sampleType, AliasList aliasList )
@@ -65,13 +76,25 @@ public class P25Decoder extends Decoder implements Instrumentable
* to process
*/
this.addComplexListener( mBasebandFilter );
mBasebandFilter.setListener( mFMDiscriminator );
mFMDiscriminator.setListener( getRealReceiver() );
// mFMDiscriminator.setListener( mAudioFilter );
// mAudioFilter.setListener( getRealReceiver() );
mBasebandFilter.setListener( mDemodulator );
mDemodulator.setListener( getRealReceiver() );
}
addRealSampleListener( mC4FMDecoder );
addRealSampleListener( mSymbolFilter );
mSymbolFilter.setListener( mSlicer );
mMessageProcessor = new P25MessageProcessor( mAliasList );
mNormalFramer = new P25MessageFramer(
FrameSync.P25_PHASE1.getSync(), 64, false );
mSlicer.addListener( mNormalFramer );
mNormalFramer.setListener( mMessageProcessor );
mInvertedFramer = new P25MessageFramer(
FrameSync.P25_PHASE1_INVERTED.getSync(), 64, true );
mSlicer.addListener( mInvertedFramer );
mInvertedFramer.setListener( mMessageProcessor );
}
@Override
@@ -83,24 +106,23 @@ public class P25Decoder extends Decoder implements Instrumentable
@Override
public List<Tap> getTaps()
{
return mC4FMDecoder.getTaps();
return new ArrayList<Tap>();
}
@Override
public void addTap( Tap tap )
{
mC4FMDecoder.addTap( tap );
}
@Override
public void removeTap( Tap tap )
{
mC4FMDecoder.removeTap( tap );
}
@Override
public void addUnfilteredRealSampleListener( RealSampleListener listener )
{
throw new IllegalArgumentException( "unfiltered real sample provider not implemented in P25 Decoder" );
throw new IllegalArgumentException( "unfiltered real sample provider "
+ "not implemented in P25 Decoder" );
}
}

View File

@@ -94,4 +94,30 @@ public class P25Interleave
return message;
}
public static void main( String[] args )
{
int start = 0;
int end = 196;
BitSetBuffer b = new BitSetBuffer( end );
for( int x = 0; x < end; x += 3 )
{
if( x > 20 )
{
b.set( x );
}
}
mLog.debug( "Origi:" + b.toString() );
P25Interleave.interleave( b, start, end );
mLog.debug( "Inter:" + b.toString() );
P25Interleave.deinterleave( b, start, end );
mLog.debug( "Donee:" + b.toString() );
}
}

View File

@@ -0,0 +1,28 @@
package decode.p25;
import message.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import alias.AliasList;
public class P25MessageProcessor implements Listener<Message>
{
private final static Logger mLog =
LoggerFactory.getLogger( P25MessageProcessor.class );
private AliasList mAliasList;
public P25MessageProcessor( AliasList aliasList )
{
mAliasList = aliasList;
}
@Override
public void receive( Message message )
{
mLog.debug( "P25: " + message.toString() + "\t" + message.getBinaryMessage() );
}
}

View File

@@ -0,0 +1,502 @@
package decode.p25;
import java.util.ArrayList;
import bits.BitSetBuffer;
public class TrellisHalfRate
{
private ArrayList<ConstellationNode> mConstellationNodes =
new ArrayList<ConstellationNode>();
private static final int[][] CONSTELLATION_COSTS =
{ { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 },
{ 1,0,2,1,2,1,3,2,2,1,3,2,3,2,4,3 },
{ 1,2,0,1,2,3,1,2,2,3,1,2,3,4,2,3 },
{ 2,1,1,0,3,2,2,1,3,2,2,1,4,3,3,2 },
{ 1,2,2,3,0,1,1,2,2,3,3,4,1,2,2,3 },
{ 2,1,3,2,1,0,2,1,3,2,4,3,2,1,3,2 },
{ 2,3,1,2,1,2,0,1,3,4,2,3,2,3,1,2 },
{ 3,2,2,1,2,1,1,0,4,3,3,2,3,2,2,1 },
{ 1,2,2,3,2,3,3,4,0,1,1,2,1,2,2,3 },
{ 2,1,3,2,3,2,4,3,1,0,2,1,2,1,3,2 },
{ 2,3,1,2,3,4,2,3,1,2,0,1,2,3,1,2 },
{ 3,2,2,1,4,3,3,2,2,1,1,0,3,2,2,1 },
{ 2,3,3,4,1,2,2,3,1,2,2,3,0,1,1,2 },
{ 3,2,4,3,2,1,3,2,2,1,3,2,1,0,2,1 },
{ 3,4,2,3,2,3,1,2,2,3,1,2,1,2,0,1 },
{ 4,3,3,2,3,2,2,1,3,2,2,1,2,1,1,0 } };
public TrellisHalfRate()
{
/* Create 49 x 4-bit constellation nodes to handle 196-bit blocks */
ConstellationNode previous = null;
for( int x = 0; x < 49; x++ )
{
ConstellationNode node = new ConstellationNode();
if( previous != null )
{
previous.connect( node );
}
previous = node;
mConstellationNodes.add( node );
}
}
public void dispose()
{
for( ConstellationNode node: mConstellationNodes )
{
node.dispose();
}
mConstellationNodes.clear();
}
public void decode( BitSetBuffer message, int start, int end )
{
/* load each of the nodes with deinterleaved constellations */
for( int index = 0; index < 49; index++ )
{
Constellation c = getConstellation( message, index * 4 );
mConstellationNodes.get( index ).setConstellation( c );
}
/* test to see if constellations are correct - otherwise correct them */
ConstellationNode firstNode = mConstellationNodes.get( 0 );
if( !firstNode.startsWith( Dibit.D0 ) || !firstNode.isCorrect() )
{
firstNode.correctTo( Dibit.D0 );
}
/* clear constellations from original message */
message.clear( start, end );
/* replace with decoded values from the nodes */
for( int index = 0; index < 49; index++ )
{
ConstellationNode node = mConstellationNodes.get( index );
if( node.firstBit() )
{
message.set( start + ( index * 2 ) );
}
if( node.secondBit() )
{
message.set( start + ( index * 2 ) + 1 );
}
}
}
private Constellation getConstellation( BitSetBuffer message, int index )
{
int constellation = 0;
for( int x = 0; x < 4; x++ )
{
if( message.get( index + x ) )
{
constellation += ( 1 << ( 3 - x ) );
}
}
return Constellation.fromValue( constellation );
}
public class ConstellationNode
{
private ConstellationNode mConnectedNode;
private Constellation mConstellation;
private boolean mCorrect;
public ConstellationNode()
{
}
public void dispose()
{
mConnectedNode = null;
}
public boolean startsWith( Dibit dibit )
{
return mConstellation.getLeft() == dibit;
}
public boolean firstBit()
{
return mConstellation.getRight().firstBit();
}
public boolean secondBit()
{
return mConstellation.getRight().secondBit();
}
/**
* Executes a correction down the line of connected nodes. Only nodes
* with the mCorrect flag set to false will be corrected.
*
* Note: Assumes that the starting node's value is 0. Initiate the
* corrective sequence by invoking this method with Dibit.D0 on the
* first node.
*
* @param dibit to use for the left side.
*/
public void correctTo( Dibit dibit )
{
if( mCorrect && mConstellation.getLeft() == dibit )
{
return;
}
if( isCurrentConnectionCorrect() )
{
mConstellation = Constellation.
fromDibits( dibit, mConstellation.getRight() );
mCorrect = true;
if( mConnectedNode != null )
{
mConnectedNode.correctTo( mConstellation.getRight() );
}
}
else
{
Constellation cheapest = mConstellation;
int cost = 100; //arbitrary
for( Dibit d: Dibit.values() )
{
Constellation test = Constellation.fromDibits( dibit, d );
int testCost = mConstellation.costTo( test ) +
mConnectedNode.costTo( d );
if( testCost < cost )
{
cost = testCost;
cheapest = test;
}
}
mConstellation = cheapest;
mConnectedNode.correctTo( mConstellation.getRight() );
mCorrect = true;
}
}
/**
* Calculates the cost (hamming distance) of using the argument as the
* left side dibit for the current node, and recursively finding the
* cheapest corresponding right dibit.
*
* @param leftTest
* @return
*/
public int costTo( Dibit leftTest )
{
if( isCurrentConnectionCorrect() )
{
Constellation c = Constellation.
fromDibits( leftTest, mConstellation.getRight() );
return mConstellation.costTo( c );
}
else
{
int cheapestCost = 100; //arbitrary
for( Dibit d: Dibit.values() )
{
Constellation c = Constellation.fromDibits( leftTest, d );
int cost = mConnectedNode.costTo( d ) +
mConstellation.costTo( c );
if( cost < cheapestCost )
{
cheapestCost = cost;
}
}
return cheapestCost;
}
}
/**
* Indicates if the immediate connection is correct
*/
public boolean isCurrentConnectionCorrect()
{
return ( mConnectedNode == null ||
mConstellation.getRight() == mConnectedNode.getLeft() );
}
/**
* Executes a recursive call to all nodes to the right, setting the
* mCorrect flag on all nodes to true, if the node's connection to the
* right node is correct and all nodes to the right are correct. Or,
* false if this node's connection, or any node's connection to the
* right is incorrect.
*
* @return - true - all node connections to the right are correct
* false - one or more nodes to the right are incorrect
*/
public boolean isCorrect()
{
if( mCorrect )
{
return mCorrect;
}
if( mConnectedNode == null )
{
mCorrect = true;
}
else
{
mCorrect = mConnectedNode.isCorrect() &&
mConstellation.getRight() == mConnectedNode.getLeft();
}
return mCorrect;
}
public Dibit getLeft()
{
return mConstellation.getLeft();
}
public void setConstellation( Constellation constellation )
{
mConstellation = constellation;
mCorrect = false;
}
public void connect( ConstellationNode node )
{
mConnectedNode = node;
}
}
public enum Constellation
{
C0( Dibit.D1, Dibit.D1, 0 ),
C1( Dibit.D0, Dibit.D2, 1 ),
C2( Dibit.D0, Dibit.D0, 2 ),
C3( Dibit.D1, Dibit.D3, 3 ),
C4( Dibit.D2, Dibit.D3, 4 ),
C5( Dibit.D3, Dibit.D0, 5 ),
C6( Dibit.D3, Dibit.D2, 6 ),
C7( Dibit.D2, Dibit.D1, 7 ),
C8( Dibit.D3, Dibit.D3, 8 ),
C9( Dibit.D2, Dibit.D0, 9 ),
CA( Dibit.D2, Dibit.D2, 10 ),
CB( Dibit.D3, Dibit.D1, 11 ),
CC( Dibit.D0, Dibit.D1, 12 ),
CD( Dibit.D1, Dibit.D2, 13 ),
CE( Dibit.D1, Dibit.D0, 14 ),
CF( Dibit.D0, Dibit.D3, 15 );
private Dibit mLeftDibit;
private Dibit mRightDibit;
private int mValue;
private Constellation( Dibit leftDibit, Dibit rightDibit, int value )
{
mLeftDibit = leftDibit;
mRightDibit = rightDibit;
mValue = value;
}
public Dibit getLeft()
{
return mLeftDibit;
}
public Dibit getRight()
{
return mRightDibit;
}
public int getValue()
{
return mValue;
}
public static Constellation fromValue( int value )
{
if( 0 <= value && value <= 15 )
{
return Constellation.values()[ value ];
}
return null;
}
/**
* Returns the cost or hamming distance to the other constellation using
* the values from the constellation costs table
*/
public int costTo( Constellation other )
{
return CONSTELLATION_COSTS[ getValue() ][ other.getValue() ];
}
public static Constellation fromDibits( Dibit left, Dibit right )
{
switch( left )
{
case D0:
switch( right )
{
case D0:
return C2;
case D1:
return CC;
case D2:
return C1;
case D3:
return CF;
default:
}
case D1:
switch( right )
{
case D0:
return CE;
case D1:
return C0;
case D2:
return CD;
case D3:
return C3;
default:
}
case D2:
switch( right )
{
case D0:
return C9;
case D1:
return C7;
case D2:
return CA;
case D3:
return C4;
default:
}
case D3:
switch( right )
{
case D0:
return C5;
case D1:
return CB;
case D2:
return C6;
case D3:
return C8;
default:
}
default:
}
/* we should never get to here */
return C0;
}
}
public enum Dibit
{
D0( false, false ),
D1( false, true ),
D2( true, false ),
D3( true, true );
private boolean mFirstBit;
private boolean mSecondBit;
private Dibit( boolean firstBit, boolean secondBit )
{
mFirstBit = firstBit;
mSecondBit = secondBit;
}
public boolean firstBit()
{
return mFirstBit;
}
public boolean secondBit()
{
return mSecondBit;
}
}
/**
* Test harness
*/
public static void main( String[] args )
{
String original = "0010101110101010001011001100111011001000100000101101001000100000110010111010100100100111100100100010000000001110001110010010110000010110001000101101101001110010001001011100001101011110110110011011";
// String original = "0010111110111110001000100010001011000000000000001101101010101001110011011001001000101100000011100010001000101100001101010010001000011010011100001101100111001110000101111101010001101010101001111110";
// String original = "0010001011000000110101111110001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010111110111110110000110101001011110101";
String witherrors = "0010001011000000110101100110001000100010001100100011001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010111110111110110000110101001011110101";
//Injected errors: xx x x
BitSetBuffer bufferOriginal = new BitSetBuffer( 196 );
BitSetBuffer bufferWithErrors = new BitSetBuffer( 196 );
try
{
for( int x = 0; x < 196; x++ )
{
if( witherrors.substring( x, x + 1 ).contentEquals( "0" ) )
{
bufferWithErrors.add( false );
}
else
{
bufferWithErrors.add( true );
}
if( original.substring( x, x + 1 ).contentEquals( "0" ) )
{
bufferOriginal.add( false );
}
else
{
bufferOriginal.add( true );
}
}
}
catch( Exception e )
{
e.printStackTrace();
}
System.out.println( " ORIGINAL: " + original );
System.out.println( " WITH ERRORS: " + witherrors );
TrellisHalfRate t = new TrellisHalfRate();
t.decode( bufferOriginal, 0, 196 );
System.out.println( "ORIG DECODED: " + bufferOriginal.toString() );
t.decode( bufferWithErrors, 0, 196 );
System.out.println( "ERRS DECODED: " + bufferWithErrors.toString() );
}
}

View File

@@ -0,0 +1,116 @@
package decode.p25.message;
import decode.p25.reference.DataUnitID;
import map.Plottable;
import message.Message;
import alias.Alias;
import bits.BitSetBuffer;
public class P25Message extends Message
{
public static final int[] NAC = { 0,1,2,3,4,5,6,7,8,9,10,11 };
public static final int[] DUID = { 12,13,14,15 };
public static final int[] BCH = { 16,17,18,19,20,21,22,23,24,25,26,27,28,29,
30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,
54,55,56,57,58,59,60,61,62,63 };
protected BitSetBuffer mMessage;
protected DataUnitID mDUID;
public P25Message( BitSetBuffer message, DataUnitID duid )
{
super();
mMessage = message;
mDUID = duid;
}
public String getNAC()
{
return mMessage.getHex( NAC, 3 );
}
public DataUnitID getDUID()
{
return mDUID;
}
@Override
public String getErrorStatus()
{
return "UNK";
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " " );
sb.append( getDUID().name() );
return sb.toString();
}
@Override
public String getBinaryMessage()
{
return mMessage.toString();
}
@Override
public String getProtocol()
{
return "P25 Phase 1";
}
@Override
public String getEventType()
{
return mDUID.name();
}
@Override
public String getFromID()
{
return null;
}
@Override
public Alias getFromIDAlias()
{
return null;
}
@Override
public String getToID()
{
return null;
}
@Override
public Alias getToIDAlias()
{
return null;
}
@Override
public Plottable getPlottable()
{
return null;
}
@Override
public String toString()
{
return getMessage();
}
@Override
public boolean isValid()
{
return true;
}
}

View File

@@ -0,0 +1,157 @@
package decode.p25.message;
import decode.p25.reference.DataUnitID;
import decode.p25.reference.PDUFormat;
import decode.p25.reference.ServiceAccessPoint;
import decode.p25.reference.Vendor;
import bits.BitSetBuffer;
public class PDUMessage extends P25Message
{
public static final int PDU_HEADER_DUMMY1 = 66; //Always 0
public static final int PDU_HEADER_CONFIRMATION_REQUIRED = 67;
public static final int PDU_HEADER_DIRECTION_FLAG = 68;
public static final int[] PDU_HEADER_FORMAT = { 69,70,71,72,73 };
public static final int PDU_HEADER_DUMMY2 = 74; //Always 1
public static final int PDU_HEADER_DUMMY3 = 75; //Always 1
public static final int[] PDU_HEADER_SAP_ID = { 76,77,78,79,80 };
public static final int[] PDU_HEADER_VENDOR_ID = { 81,82,83,84,85,86,87,88 };
public static final int[] PDU_HEADER_LOGICAL_LINK_ID = { 89,90,91,92,93,94,
97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114 };
public static final int PDU_HEADER_FULL_MESSAGE_FLAG = 115;
public static final int[] PDU_HEADER_BLOCKS_TO_FOLLOW = { 116,117,118,119,
120,121,122 };
public static final int PDU_HEADER_DUMMY4 = 123; //Always 0
public static final int PDU_HEADER_DUMMY5 = 124; //Always 0
public static final int PDU_HEADER_DUMMY6 = 125; //Always 0
public static final int[] PDU_HEADER_PAD_BLOCKS = { 126,127,128,129,130 };
public static final int PDU_HEADER_RESYNCHRONIZE_FLAG = 131;
public static final int[] PDU_HEADER_SEQUENCE_NUMBER = { 132,133,134 };
public static final int[] PDU_HEADER_FRAGMENT_SEQUENCE_NUMBER = { 135,136,
137,138 };
public static final int PDU_HEADER_DUMMY7 = 139; //Always 0
public static final int PDU_HEADER_DUMMY8 = 140; //Always 0
public static final int[] PDU_HEADER_DATA_HEADER_OFFSET = { 141,142,143,
144,145,146 };
public static final int[] PDU_HEADER_CRC = { 147,148,149,150,151,152,153,154,
155,156,157,158,159,160,161,162 };
//status 166,167
public PDUMessage( BitSetBuffer message, DataUnitID duid )
{
super( message, duid );
}
public String getConfirmation()
{
return mMessage.get( PDU_HEADER_CONFIRMATION_REQUIRED ) ? "CON" : "UNC";
}
public String getDirection()
{
return mMessage.get( PDU_HEADER_DIRECTION_FLAG ) ? "OSP" : "ISP";
}
public PDUFormat getFormat()
{
return PDUFormat.fromValue( mMessage.getInt( PDU_HEADER_FORMAT ) );
}
public ServiceAccessPoint getServiceAccessPoint()
{
return ServiceAccessPoint.fromValue( mMessage.getInt( PDU_HEADER_SAP_ID ) );
}
public Vendor getVendor()
{
return Vendor.fromValue( mMessage.getInt( PDU_HEADER_VENDOR_ID ) );
}
public String getLogicalLinkID()
{
return mMessage.getHex( PDU_HEADER_LOGICAL_LINK_ID, 6 );
}
public boolean isFullMessage()
{
return mMessage.get( PDU_HEADER_FULL_MESSAGE_FLAG );
}
public int getBlockCount()
{
return mMessage.getInt( PDU_HEADER_BLOCKS_TO_FOLLOW );
}
public int getPadBlockCount()
{
return mMessage.getInt( PDU_HEADER_PAD_BLOCKS );
}
public boolean isResync()
{
return mMessage.get( PDU_HEADER_RESYNCHRONIZE_FLAG );
}
public int getSequenceNumber()
{
return mMessage.getInt( PDU_HEADER_SEQUENCE_NUMBER );
}
public int getFragmentSequenceNumber()
{
return mMessage.getInt( PDU_HEADER_FRAGMENT_SEQUENCE_NUMBER );
}
public int getDataHeaderOffset()
{
return mMessage.getInt( PDU_HEADER_DATA_HEADER_OFFSET );
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " " );
sb.append( getDUID().name() );
sb.append( " " );
sb.append( getConfirmation() );
sb.append( " " );
sb.append( getDirection() );
sb.append( " FMT:" );
sb.append( getFormat().getLabel() );
switch( getFormat() )
{
case ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL:
case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL:
sb.append( " SAP:" );
sb.append( getServiceAccessPoint().getLabel() );
sb.append( " VEND:" );
sb.append( getVendor().getLabel() );
sb.append( " LLID:" );
sb.append( getLogicalLinkID() );
sb.append( " FMF:" );
sb.append( isFullMessage() ? "1" : "0" );
sb.append( " BLKS:" );
sb.append( getBlockCount() );
sb.append( " PAD:" );
sb.append( getPadBlockCount() );
sb.append( " RESYNC:" );
sb.append( isResync() ? "1" : "0" );
sb.append( " SEQ:" );
sb.append( getSequenceNumber() );
sb.append( " FRAG:" );
sb.append( getFragmentSequenceNumber() );
sb.append( " DHO:" );
sb.append( getDataHeaderOffset() );
break;
default:
}
return sb.toString();
}
}

View File

@@ -0,0 +1,67 @@
package decode.p25.message;
import decode.p25.reference.DataUnitID;
import decode.p25.reference.Opcode;
import decode.p25.reference.Vendor;
import bits.BitSetBuffer;
public class TSBKMessage extends P25Message
{
public static final int LAST_BLOCK_FLAG = 0;
public static final int ENCRYPTED_FLAG = 1;
public static final int[] OPCODE = { 2,3,4,5,6,7 };
public static final int[] VENDOR_ID = { 8,9,10,11,12,13,14,15 };
public TSBKMessage( int system, BitSetBuffer message )
{
super( message, DataUnitID.TSBK1 );
}
public boolean isLastBlock()
{
return mMessage.get( LAST_BLOCK_FLAG );
}
public boolean isEncrypted()
{
return mMessage.get( ENCRYPTED_FLAG );
}
public Vendor getVendor()
{
return Vendor.fromValue( mMessage.getInt( VENDOR_ID ) );
}
public Opcode getOpcode()
{
return Opcode.fromValue( mMessage.getInt( OPCODE ) );
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "SYSTEM:" );
sb.append( getNAC() ); /* NAC is the system id for TSBK messages */
sb.append( " VEND:" );
sb.append( getVendor().getLabel() );
if( isEncrypted() )
{
sb.append( " ENCRYPTED" );
}
else
{
Opcode opcode = getOpcode();
sb.append( " OPCODE:" );
sb.append( opcode.name() );
sb.append( " [" );
sb.append( mMessage.getHex( OPCODE, 2 ) );
sb.append( "]" );
}
return sb.toString();
}
}

View File

@@ -0,0 +1,31 @@
package decode.p25.message;
import bits.BitSetBuffer;
import decode.p25.reference.Opcode;
import decode.p25.reference.Vendor;
public class TSBKMessageFactory
{
public static TSBKMessage getMessage( int system, BitSetBuffer buffer )
{
int opcode = buffer.getInt( TSBKMessage.OPCODE );
Vendor vendor = Vendor.fromValue(
buffer.getInt( TSBKMessage.VENDOR_ID ) );
switch( vendor )
{
case STANDARD:
switch( Opcode.fromValue( opcode ) )
{
}
return new TSBKMessage( system, buffer );
case MOTOROLA:
default:
return new TSBKMessage( system, buffer );
}
}
}

View File

@@ -0,0 +1,78 @@
package decode.p25.reference;
/**
* The Data Unit ID (DUID) is part of the Network ID (NID) field and indicates
* the type of message.
*/
public enum DataUnitID
{
NID ( -1, 64, "Network and Data Unit ID", false ),
HDU ( 0, 722, "Header Data Unit", false ),
TDU ( 3, 64, "Simple Terminator Data Unit", false ),
LDU1 ( 5, 1630, "Logical Link Data Unit 1", true ),
TSBK1( 7, 260, "Trunking Signaling Block", false ), //Single Block
TSBK2( 7, 260, "Trunking Signaling Block", false ), //Single Block
TSBK3( 7, 260, "Trunking Signaling Block", false ), //Single Block
LDU2 ( 10, 1632, "Logical Link Data Unit 2", true ),
PDU1 ( 12, 456, "Packet Data Unit", false ),
PDU2 ( 12, 652, "Packet Data Unit", false ),
PDU3 ( 12, 848, "Packet Data Unit", false ),
TDULC( 15, 372, "Terminator Data Unit With Link Control", false ),
UNKN ( -1, 0, "Unknown", false );
private int mValue;
private int mMessageLength;
private String mLabel;
private boolean mParity;
private DataUnitID( int value, int length, String label, boolean parity )
{
mValue = value;
mMessageLength = length;
mLabel = label;
mParity = parity;
}
public int getValue()
{
return mValue;
}
public int getMessageLength()
{
return mMessageLength;
}
public String getLabel()
{
return mLabel;
}
public boolean getParity()
{
return mParity;
}
public static DataUnitID fromValue( int value )
{
switch( value )
{
case 0:
return HDU;
case 3:
return TDU;
case 5:
return LDU1;
case 7:
return TSBK1;
case 10:
return LDU2;
case 12:
return PDU1;
case 15:
return TDULC;
default:
return UNKN;
}
}
}

View File

@@ -0,0 +1,23 @@
package decode.p25.reference;
public enum Encryption
{
ACCORDION_1_3( 0x00 ),
BATON_AUTO_EVEN( 0x01 ),
FIREFLY_TYPE1( 0x02 ),
MAYFLY_TYPE1( 0x03 ),
SAVILLE( 0x04 ),
BATON_AUTO_ODD( 0x41 ),
UNENCRYPTED( 0x80 ),
DES_OFB( 0x81 ),
TRIPLE_DES_2_KEY( 0x82 ),
TRIPLE_DES_3_KEY( 0x83 ),
AES( 0x84 );
private int mValue;
private Encryption( int value )
{
mValue = value;
}
}

View File

@@ -0,0 +1,99 @@
package decode.p25.reference;
public enum Opcode
{
GROUP_VOICE_CHANNEL_GRANT( "Group Voice Channel Grant", 0 ),
RESERVED_01( "Reserved", 1 ),
GROUP_VOICE_CHANNLE_GRANT_UPDATE( "Group Voice Channel Grant Update", 2 ),
GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT( "Group Voice Channel Grant Update - Explicit", 3 ),
UNIT_TO_UNIT_VOICE_CHANNEL_GRANT( "Unit-to-Unit Voice Channel Grant", 4 ),
UNIT_TO_UNIT_ANSWER_REQUEST( "Unit-to-Unit Answer Request", 5 ),
UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE( "Unit-to-Unit Voice Channel Grant Update", 6 ),
RESERVED_07( "Reserved", 7 ),
TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT( "Telephone Interconnect Voice Channel Grant", 8 ),
RESERVED_09( "Reserved", 9 ),
TELEPHONE_INTERCONNECT_ANSWER_REQUEST( "Telephone Interconnect Answer Request", 10 ),
RESERVED_0B( "Reserved", 11 ),
RESERVED_0C( "Reserved", 12 ),
RESERVED_0D( "Reserved", 13 ),
RESERVED_0E( "Reserved", 14 ),
RESERVED_0F( "Reserved", 15 ),
INDIVIDUAL_DATA_CHANNEL_GRANT( "Individual Data Channel Grant", 16 ),
GROUP_DATA_CHANNEL_GRANT( "Group Data Channel Grant", 17 ),
GROUP_DATA_CHANNEL_ANNOUNCEMENT( "Group Data Channel Announcement", 18 ),
GROUP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT( "Group Data Channel Announcement-Explicit", 19 ),
SNDCP_DATA_CHANNEL_GRANT( "SNDCP Data Channel Grant", 20 ),
SNDCP_DATA_PAGE_REQUEST( "SNDCP Data Page Request", 21 ),
SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT( "SNDCP Data Channel Announcement Explicit", 22 ),
RESERVED_17( "Reserved", 23 ),
STATUS_UPDATE( "Status Update", 24 ),
RESERVED_19( "Reserved", 25 ),
STATUS_QUERY( "Status Query", 26 ),
RESERVED_1B( "Reserved", 27 ),
MESSAGE_UPDATE( "Message Update", 28 ),
RADIO_UNIT_MONITOR_COMMAND( "Radio Unit Monitor Command", 29 ),
RESERVED_1E( "Reserved", 30 ),
CALL_ALERT( "Call Alert", 31 ),
ACKNOWLEDGE_RESPONSE_FNE( "Acknowledge Response - FNE", 32 ),
QUEUED_RESPONSE( "Queued Response", 33 ),
RESERVED_22( "Reserved", 34 ),
RESERVED_23( "Reserved", 35 ),
EXTENDED_FUNCTION_COMMAND( "Extended Function Command", 36 ),
RESERVED_25( "Reserved", 37 ),
RESERVED_26( "Reserved", 38 ),
DENY_RESPONSE( "Deny Response", 39 ),
GROUP_AFFILIATION_RESPONSE( "Group Affiliation Response", 40 ),
SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT( "Secondary Control Channel Broadcast-Explicit", 41 ),
GROUP_AFFILIATION_QUERY( "Group Affiliation Query", 42 ),
LOCATION_REGISTRATION_RESPONSE( "Location Registration Response", 43 ),
UNIT_REGISTRATION_RESPONSE( "Unit Registration Response", 44 ),
UNIT_REGISTRATION_COMMAND( "Unit Registration Command", 45 ),
AUTHENTICATION_COMMAND( "Authentication Command", 46 ),
UNIT_DEREGISTRATION_ACKNOWLEDGE( "De-Registration Acknowledge", 47 ),
RESERVED_30( "Reserved", 48 ),
RESERVED_31( "Reserved", 49 ),
RESERVED_32( "Reserved", 50 ),
RESERVED_33( "Reserved", 51 ),
IDENTIFIER_UPDATE_VHF_UHF_BANDS( "Identifier Update for VHF/UHF Bands", 52 ),
TIME_DATE_ANNOUNCEMENT( "Time and Date Announcement", 53 ),
ROAMING_ADDRESS_COMMAND( "Roaming Address Command", 54 ),
ROAMING_ADDRESS_UPDATE( "Roaming Address Update", 55 ),
SYSTEM_SERVICE_BROADCAST( "System Service Broadcast", 56 ),
SECONDARY_CONTROL_CHANNEL_BROADCAST( "Secondary Control Channel Broadcast", 57 ),
RFSS_STATUS_BROADCAST( "RFSS Status Broadcast", 58 ),
NETWORK_STATUS_BROADCAST( "Network Status Broadcast", 59 ),
ADJACENT_STATUS_BROADCAST( "Adjacent Status Broadcast", 60 ),
IDENTIFIER_UPDATE_NON_VHF_UHF( "Identifier Update for non-VHF/UHF Bands", 61 ),
PROTECTION_PARAMETER_BROADCAST( "Protection Parameter Broadcast", 62 ),
PROTECTION_PARAMETER_UPDATE( "Protection Parameter Update", 63 ),
UNKNOWN( "Unknown", -1 );
private String mDescription;
private int mCode;
private Opcode( String description, int code )
{
mDescription = description;
mCode = code;
}
public String getDescription()
{
return mDescription;
}
public int getCode()
{
return mCode;
}
public static Opcode fromValue( int value )
{
if( 0 <= value && value <= 63 )
{
return values()[ value ];
}
return UNKNOWN;
}
}

View File

@@ -0,0 +1,67 @@
package decode.p25.reference;
public enum PDUFormat
{
F00( "F00", 0 ),
F01( "F01", 1 ),
F02( "F02", 2 ),
F03( "F03", 3 ),
F04( "F04", 4 ),
F05( "F05", 5 ),
F06( "F06", 6 ),
F07( "F07", 7 ),
F08( "F08", 8 ),
F09( "F09", 9 ),
F10( "F10", 10 ),
F11( "F11", 11 ),
F12( "F12", 12 ),
F13( "F13", 13 ),
F14( "F14", 14 ),
F15( "F15", 15 ),
F16( "F16", 16 ),
F17( "F17", 17 ),
F18( "F18", 18 ),
F19( "F19", 19 ),
F20( "F20", 20 ),
UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL( "UMBTC", 21 ),
ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL( "AMBTC", 22 ),
F23( "F23", 23 ),
F24( "F24", 24 ),
F25( "F25", 25 ),
F26( "F26", 26 ),
F27( "F27", 27 ),
F28( "F28", 28 ),
F29( "F29", 29 ),
F30( "F30", 30 ),
F31( "F31", 31 ),
UNKNOWN( "UNKN", -1 );
private String mLabel;
private int mValue;
private PDUFormat( String label, int value )
{
mLabel = label;
mValue = value;
}
public String getLabel()
{
return mLabel;
}
public int getValue()
{
return mValue;
}
public static PDUFormat fromValue( int value )
{
if( 0 <= value && value <= 31 )
{
return values()[ value ];
}
return UNKNOWN;
}
}

View File

@@ -0,0 +1,99 @@
package decode.p25.reference;
public enum ServiceAccessPoint
{
SAP_0( "0", 0 ),
SAP_1( "1", 1 ),
SAP_2( "2", 2 ),
SAP_3( "3", 3 ),
SAP_4( "4", 4 ),
SAP_5( "5", 5 ),
SAP_6( "6", 6 ),
SAP_7( "7", 7 ),
SAP_8( "8", 8 ),
SAP_9( "9", 9 ),
SAP_10( "10", 10 ),
SAP_11( "11", 11 ),
SAP_12( "12", 12 ),
SAP_13( "13", 13 ),
SAP_14( "14", 14 ),
SAP_15( "15", 15 ),
SAP_16( "16", 16 ),
SAP_17( "17", 17 ),
SAP_18( "18", 18 ),
SAP_19( "19", 19 ),
SAP_20( "20", 20 ),
SAP_21( "21", 21 ),
SAP_22( "22", 22 ),
SAP_23( "23", 23 ),
SAP_24( "24", 24 ),
SAP_25( "25", 25 ),
SAP_26( "26", 26 ),
SAP_27( "27", 27 ),
SAP_28( "28", 28 ),
SAP_29( "29", 29 ),
SAP_30( "30", 30 ),
SAP_31( "31", 31 ),
SAP_32( "32", 32 ),
SAP_33( "33", 33 ),
SAP_34( "34", 34 ),
SAP_35( "35", 35 ),
SAP_36( "36", 36 ),
SAP_37( "37", 37 ),
SAP_38( "38", 38 ),
SAP_39( "39", 39 ),
SAP_40( "40", 40 ),
SAP_41( "41", 41 ),
SAP_42( "42", 42 ),
SAP_43( "43", 43 ),
SAP_44( "44", 44 ),
SAP_45( "45", 45 ),
SAP_46( "46", 46 ),
SAP_47( "47", 47 ),
SAP_48( "48", 48 ),
SAP_49( "49", 49 ),
SAP_50( "50", 50 ),
SAP_51( "51", 51 ),
SAP_52( "52", 52 ),
SAP_53( "53", 53 ),
SAP_54( "54", 54 ),
SAP_55( "55", 55 ),
SAP_56( "56", 56 ),
SAP_57( "57", 57 ),
SAP_58( "58", 58 ),
SAP_59( "59", 59 ),
SAP_60( "60", 60 ),
UNENCRYPTED_TRUNKING_CONTROL( "OPEN TRUNK", 61 ),
SAP_62( "62", 62 ),
ENCRYPTED_TRUNKING_CONTROL( "ENC TRUNK", 63 ),
UKNOWN( "UNKN", -1 );
private String mLabel;
private int mValue;
private ServiceAccessPoint( String label, int value )
{
mLabel = label;
mValue = value;
}
public String getLabel()
{
return mLabel;
}
public int getValue()
{
return mValue;
}
public static ServiceAccessPoint fromValue( int value )
{
if( 0 <= value && value <= 63 )
{
return values()[ value ];
}
return UKNOWN;
}
}

View File

@@ -0,0 +1,21 @@
package decode.p25.reference;
public enum Status
{
TALKAROUND,
BUSY,
REPEATER,
IDLE,
UNKNOWN;
public Status fromValue( int value )
{
if( 0 <= value && value <= 3 )
{
return values()[ value ];
}
return UNKNOWN;
}
}

View File

@@ -0,0 +1,292 @@
package decode.p25.reference;
public enum Vendor
{
STANDARD( "STANDARD", 0),
V1( "V_01", 1),
V2( "V_02", 2),
V3( "V_03", 3),
V4( "V_04", 4),
V5( "V_05", 5),
V6( "V_06", 6),
V7( "V_07", 7),
V8( "V_08", 8),
V9( "V_09", 9),
V10( "V_0A", 10),
V11( "V_0B", 11),
V12( "V_0C", 12),
V13( "V_0D", 13),
V14( "V_0E", 14),
V15( "V_0F", 15),
RELM( "RELM/BK RADIO", 16),
V17( "V_11", 17),
V18( "V_12", 18),
V19( "V_13", 19),
V20( "V_14", 20),
V21( "V_15", 21),
V22( "V_16", 22),
V23( "V_17", 23),
V24( "V_18", 24),
V25( "V_19", 25),
V26( "V_1A", 26),
V27( "V_1B", 27),
V28( "V_1C", 28),
V29( "V_1D", 29),
V30( "V_1E", 30),
V31( "V_1F", 31),
CYCOMM( "CYCOMM", 32),
V33( "V_21", 33),
V34( "V_22", 34),
V35( "V_23", 35),
V36( "V_24", 36),
V37( "V_25", 37),
V38( "V_26", 38),
V39( "V_27", 39),
EFRATOM( "EFRATOM", 40),
V41( "V_29", 41),
V42( "V_2A", 42),
V43( "V_2B", 43),
V44( "V_2C", 44),
V45( "V_2D", 45),
V46( "V_2E", 46),
V47( "V_2F", 47),
ERICSSON( "ERICSSON", 48),
V49( "V_31", 49),
V50( "V_32", 50),
V51( "V_33", 51),
V52( "V_34", 52),
V53( "V_35", 53),
V54( "V_36", 54),
V55( "V_37", 55),
DATRON( "DATRON", 56),
V57( "V_39", 57),
V58( "V_3A", 58),
V59( "V_3B", 59),
V60( "V_3C", 60),
V61( "V_3D", 61),
V62( "V_3E", 62),
V63( "V_3F", 63),
ICOM( "ICOM", 64),
V65( "V_41", 65),
V66( "V_42", 66),
V67( "V_43", 67),
V68( "V_44", 68),
V69( "V_45", 69),
V70( "V_46", 70),
V71( "V_47", 71),
GARMIN( "GARMIN", 72),
V73( "V_49", 73),
V74( "V_4A", 74),
V75( "V_4B", 75),
V76( "V_4C", 76),
V77( "V_4D", 77),
V78( "V_4E", 78),
V79( "V_4F", 79),
GTE( "GTE", 80),
V81( "V_51", 81),
V82( "V_52", 82),
V83( "V_53", 83),
V84( "V_54", 84),
IFR_SYSTEMS( "IFR SYSTEMS", 85),
V86( "V_56", 86),
V87( "V_57", 87),
V88( "V_58", 88),
V89( "V_59", 89),
V90( "V_5A", 90),
V91( "V_5B", 91),
V92( "V_5C", 92),
V93( "V_5D", 93),
V94( "V_5E", 94),
V95( "V_5F", 95),
GEC_MARCONI( "GEC-MARCONI", 96),
V97( "V_61", 97),
V98( "V_62", 98),
V99( "V_63", 99),
V100( "V_64", 100),
V101( "V_65", 101),
V102( "V_66", 102),
V103( "V_67", 103),
KENWOOD( "KENWOOD", 104),
V105( "V_69", 105),
V106( "V_6A", 106),
V107( "V_6B", 107),
V108( "V_6C", 108),
V109( "V_6D", 109),
V110( "V_6E", 110),
V111( "V_6F", 111),
GLENAYRE( "GLENAYRE ELECTRONICS", 112),
V113( "V_71", 113),
V114( "V_72", 114),
V115( "V_73", 115),
JAPAN_RADIO( "JAPAN RADIO CO", 116),
V117( "V_75", 117),
V118( "V_76", 118),
V119( "V_77", 119),
KOKUSAI( "KOKUSAI", 120),
V121( "V_79", 121),
V122( "V_7A", 122),
V123( "V_7B", 123),
MAXON( "MAXON", 124),
V125( "V_7D", 125),
V126( "V_7E", 126),
V127( "V_7F", 127),
MIDLAND( "MIDLAND", 128),
V129( "V_81", 129),
V130( "V_82", 130),
V131( "V_83", 131),
V132( "V_84", 132),
V133( "V_85", 133),
DANIELS( "DANIELS ELECTRONICS", 134),
V135( "V_87", 135),
V136( "V_88", 136),
V137( "V_89", 137),
V138( "V_8A", 138),
V139( "V_8B", 139),
V140( "V_8C", 140),
V141( "V_8D", 141),
V142( "V_8E", 142),
V143( "V_8F", 143),
MOTOROLA( "MOTOROLA", 144),
V145( "V_91", 145),
V146( "V_92", 146),
V147( "V_93", 147),
V148( "V_94", 148),
V149( "V_95", 149),
V150( "V_96", 150),
V151( "V_97", 151),
V152( "V_98", 152),
V153( "V_99", 153),
V154( "V_9A", 154),
V155( "V_9B", 155),
V156( "V_9C", 156),
V157( "V_9D", 157),
V158( "V_9E", 158),
V159( "V_9F", 159),
THALES( "THALES", 160),
V161( "V_A1", 161),
V162( "V_A2", 162),
V163( "V_A3", 163),
MACOM( "M/A-COM", 164),
V165( "V_A5", 165),
V166( "V_A6", 166),
V167( "V_A7", 167),
V168( "V_A8", 168),
V169( "V_A9", 169),
V170( "V_AA", 170),
V171( "V_AB", 171),
V172( "V_AC", 172),
V173( "V_AD", 173),
V174( "V_AE", 174),
V175( "V_AF", 175),
RATHEON( "RATHEON", 176),
V177( "V_B1", 177),
V178( "V_B2", 178),
V179( "V_B3", 179),
V180( "V_B4", 180),
V181( "V_B5", 181),
V182( "V_B6", 182),
V183( "V_B7", 183),
V184( "V_B8", 184),
V185( "V_B9", 185),
V186( "V_BA", 186),
V187( "V_BB", 187),
V188( "V_BC", 188),
V189( "V_BD", 189),
V190( "V_BE", 190),
V191( "V_BF", 191),
SEA( "SEA", 192),
V193( "V_C1", 193),
V194( "V_C2", 194),
V195( "V_C3", 195),
V196( "V_C4", 196),
V197( "V_C5", 197),
V198( "V_C6", 198),
V199( "V_C7", 199),
SECURICOR( "SECURICOR", 200),
V201( "V_C9", 201),
V202( "V_CA", 202),
V203( "V_CB", 203),
V204( "V_CC", 204),
V205( "V_CD", 205),
V206( "V_CE", 206),
V207( "V_CF", 207),
ADI( "ADI", 208),
V209( "V_D1", 209),
V210( "V_D2", 210),
V211( "V_D3", 211),
V212( "V_D4", 212),
V213( "V_D5", 213),
V214( "V_D6", 214),
V215( "V_D7", 215),
TAIT( "TAIT", 216),
V217( "V_D9", 217),
V218( "V_DA", 218),
V219( "V_DB", 219),
V220( "V_DC", 220),
V221( "V_DD", 221),
V222( "V_DE", 222),
V223( "V_DF", 223),
TELETEC( "TELETEC", 224),
V225( "V_E1", 225),
V226( "V_E2", 226),
V227( "V_E3", 227),
V228( "V_E4", 228),
V229( "V_E5", 229),
V230( "V_E6", 230),
V231( "V_E7", 231),
V232( "V_E8", 232),
V233( "V_E9", 233),
V234( "V_EA", 234),
V235( "V_EB", 235),
V236( "V_EC", 236),
V237( "V_ED", 237),
V238( "V_EE", 238),
V239( "V_EF", 239),
TRANSCRYPT( "TRANSCRYPT", 240),
V241( "V_F1", 241),
V242( "V_F2", 242),
V243( "V_F3", 243),
V244( "V_F4", 244),
V245( "V_F5", 245),
V246( "V_F6", 246),
V247( "V_F7", 247),
V248( "V_F8", 248),
V249( "V_F9", 249),
V250( "V_FA", 250),
V251( "V_FB", 251),
V252( "V_FC", 252),
V253( "V_FD", 253),
V254( "V_FE", 254),
V255( "V_FF", 255),
VUNK( "UNKN", -1 );
private String mLabel;
private int mValue;
private Vendor( String label, int value )
{
mLabel = label;
mValue = value;
}
public String getLabel()
{
return mLabel;
}
public int getValue()
{
return mValue;
}
public static Vendor fromValue( int value )
{
if( 0 <= value && value <= 255 )
{
return values()[ value ];
}
return VUNK;
}
}

View File

@@ -1,117 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 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 dsp;
import sample.Listener;
public class BooleanZeroCrossingDetector implements Listener<Boolean>
{
private int mBaudLength;
private int mCounter = 0;
private int mThreshold;
private Listener<Boolean> mListener;
private Cycle mCycle = Cycle.POSITIVE;
/**
* Implements a zero crossing detector for use with squared, filtered
* binary values. Makes a baud decision at 100% of the baud
* length, or at 80% of the baud length during a zero crossing. Counter
* is reset at each zero crossing.
*
* @param baudLength - estimated number of boolean samples per baud
*/
public BooleanZeroCrossingDetector( int baudLength )
{
mBaudLength = baudLength;
mThreshold = (int)( baudLength * .80 );
}
@Override
public void receive( Boolean sample )
{
mCounter++;
switch( mCycle )
{
case POSITIVE:
if( sample )
{
if( mCounter >= mBaudLength )
{
send( true );
mCounter -= mBaudLength;
}
}
else
{
if( mCounter >= mThreshold )
{
send( true );
mCounter -= mBaudLength;
}
mCycle = Cycle.NEGATIVE;
}
break;
case NEGATIVE:
if( sample )
{
if( mCounter >= mThreshold )
{
send( false );
mCounter -= mBaudLength;
}
mCycle = Cycle.POSITIVE;
}
else
{
if( mCounter >= mBaudLength )
{
send( false );
mCounter -= mBaudLength;
}
}
break;
}
}
private void send( boolean bit )
{
if( mListener != null )
{
mListener.receive( bit );
}
}
public void setListener( Listener<Boolean> listener )
{
mListener = listener;
}
public void removeListener( Listener<Boolean> listener )
{
mListener = null;
}
public enum Cycle { POSITIVE, NEGATIVE };
}

View File

@@ -1,69 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 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 dsp;
import sample.Listener;
import buffer.FloatCircularAveragingBuffer;
public class FloatToBinarySlicer implements Listener<Float>
{
private FloatCircularAveragingBuffer mAveragingBuffer;
private Listener<Boolean> mListener;
/**
* Provides binary slicing of the stream of floats, using a circular buffer
* to calculate a running average, then basing the slice decision based
* on the oldest sample leaving the circular buffer, compared to the running
* average.
*
* Designed to operate with a DC bias inherent in the sample stream.
*
* Tracks a long average and a short average to compensate for drifting
* transmitters.
*
* The long buffer compensates for long strings of ones or
* zeros. Set the long buffer size to a few baud periods longer than the
* longest anticipated string of like symbols.
*
*/
public FloatToBinarySlicer( int bufferSize, int sampleCountForAveraging )
{
mAveragingBuffer = new FloatCircularAveragingBuffer( bufferSize,
sampleCountForAveraging );
}
@Override
public void receive( Float newestSample )
{
float oldestSample = mAveragingBuffer.get( newestSample );
//Dispatch the decision
mListener.receive( oldestSample > mAveragingBuffer.getAverage() );
}
public void setListener( Listener<Boolean> listener )
{
mListener = listener;
}
public void clearListener()
{
mListener = null;
}
}

View File

@@ -8,7 +8,6 @@ import java.util.ArrayList;
import java.util.List;
import sample.real.RealSampleListener;
import dsp.filter.C4FMSymbolFilter;
public class C4FMDecoder implements RealSampleListener, Instrumentable
{

View File

@@ -0,0 +1,15 @@
package dsp.fsk;
import bits.BitSetBuffer;
public interface C4FMFrameListener
{
/**
* Listener interface to receive the output of the C4FMMessageFramer.
*
* @param buffer - framed message without the sync pattern
* @param inverted - flag indicating if the message was received inverted
*/
public void receive( BitSetBuffer buffer, boolean inverted );
}

View File

@@ -0,0 +1,69 @@
package dsp.fsk;
import sample.Broadcaster;
import sample.Listener;
import sample.real.RealSampleListener;
/**
* C4FM slicer to convert the output stream of the C4FMSymbolFilter into a
* stream of C4FM symbols.
*
* Supports registering listener(s) to receive normal and/or inverted symbol
* output streams.
*/
public class C4FMSlicer implements RealSampleListener
{
private static final float THRESHOLD = 2.0f;
private Broadcaster<C4FMSymbol> mBroadcaster = new Broadcaster<C4FMSymbol>();
/**
* Primary method for receiving output from the C4FMSymbolFilter. Slices
* (converts) the filtered sample value into a C4FMSymbol decision.
*/
@Override
public void receive( float sample )
{
if( sample > THRESHOLD )
{
dispatch( C4FMSymbol.SYMBOL_PLUS_3 );
}
else if( sample > 0 )
{
dispatch( C4FMSymbol.SYMBOL_PLUS_1 );
}
else if( sample > -THRESHOLD )
{
dispatch( C4FMSymbol.SYMBOL_MINUS_1 );
}
else
{
dispatch( C4FMSymbol.SYMBOL_MINUS_3 );
}
}
/**
* Dispatches the symbol decision to any registered listeners
*/
private void dispatch( C4FMSymbol symbol )
{
mBroadcaster.receive( symbol );
}
/**
* Registers the listener to receive the normal (non-inverted) C4FM symbol
* stream.
*/
public void addListener( Listener<C4FMSymbol> listener )
{
mBroadcaster.addListener( listener );
}
/**
* Removes the listener
*/
public void removeListener( Listener<C4FMSymbol> listener )
{
mBroadcaster.removeListener( listener );
}
}

View File

@@ -0,0 +1,44 @@
package dsp.fsk;
public enum C4FMSymbol
{
SYMBOL_PLUS_3( false, true ),
SYMBOL_PLUS_1( false, false ),
SYMBOL_MINUS_1( true, false ),
SYMBOL_MINUS_3( true, true );
private boolean mBit1;
private boolean mBit2;
private C4FMSymbol( boolean bit1, boolean bit2 )
{
mBit1 = bit1;
mBit2 = bit2;
}
public boolean getBit1()
{
return mBit1;
}
public boolean getBit2()
{
return mBit2;
}
public static C4FMSymbol inverted( C4FMSymbol symbol )
{
switch( symbol )
{
case SYMBOL_MINUS_1:
return SYMBOL_MINUS_3;
case SYMBOL_MINUS_3:
return SYMBOL_PLUS_3;
case SYMBOL_PLUS_1:
return SYMBOL_MINUS_1;
case SYMBOL_PLUS_3:
default:
return SYMBOL_PLUS_1;
}
}
}

View File

@@ -1,4 +1,4 @@
package dsp.filter;
package dsp.fsk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -147,6 +147,7 @@ public class C4FMSymbolFilter implements RealSampleListener,
private static final int NUMBER_FILTER_TAPS = 8;
private static final int NUMBER_FILTER_STEPS = 128;
private static final int SAMPLE_RATE = 48000;
private static final int SYMBOL_RATE = 4800;
@@ -171,6 +172,7 @@ public class C4FMSymbolFilter implements RealSampleListener,
private float mSymbolSpread = 2.0f;
private float mSymbolClock = 0.0f;
private float mSymbolTime = (float)SYMBOL_RATE / (float)SAMPLE_RATE;
private float mFineFrequencyCorrection = 0.0f;
private float mCoarseFrequencyCorrection = 0.0f;
@@ -178,8 +180,20 @@ public class C4FMSymbolFilter implements RealSampleListener,
private int mHistoryLast = 0;
private RealSampleListener mListener;
/**
* C4FM Symbol Filter
*
* Sample Gain values - the gain of incoming sample values will critically
* impact the performance of this filter. Gain should be adjusted to
* optimize the value of mSymbolSpread toward a value of 2.0. If the value
* is less than 2.0, then increase gain. If the value is over 2.0, then
* decrease gain.
*
* During testing, the incoming samples were within the range of -0.5 <> 0.5
* and applying a gain of 11.0 to those samples achieved the best symbol
* spread, centered about 2.0 with +/- 0.5% deviations on a strong signal.
*/
public C4FMSymbolFilter()
{
}
@@ -189,7 +203,8 @@ public class C4FMSymbolFilter implements RealSampleListener,
{
mSymbolClock += mSymbolTime;
mHistory[ mHistoryLast++ ] = sample;
// mHistory[ mHistoryLast++ ] = sample;
mHistory[ mHistoryLast++ ] = sample * 10.5f;
mHistoryLast %= NUMBER_FILTER_TAPS;
if( mSymbolClock > 1.0f )
@@ -199,14 +214,13 @@ public class C4FMSymbolFilter implements RealSampleListener,
int imu = (int)Math.floor( 0.5 +
( (float)NUMBER_FILTER_STEPS * ( mSymbolClock / mSymbolTime ) ) );
int imu_p1 = imu + 1;
if( imu >= NUMBER_FILTER_STEPS )
{
imu = NUMBER_FILTER_STEPS - 1;
imu_p1 = NUMBER_FILTER_STEPS;
}
int imu_p1 = imu + 1;
int j = mHistoryLast;
double interp = 0.0;
@@ -220,7 +234,7 @@ public class C4FMSymbolFilter implements RealSampleListener,
j = ( j + 1 ) % NUMBER_FILTER_TAPS;
}
/* Ouput symbol will be interpolated value corrected for symbol
/* Output symbol will be interpolated value corrected for symbol
* spread and frequency offset */
interp -= mFineFrequencyCorrection;
interp_p1 -= mFineFrequencyCorrection;
@@ -230,7 +244,7 @@ public class C4FMSymbolFilter implements RealSampleListener,
/* Detect received symbol error: basically use a hard decision and
* subtract off expected position nominal symbol level which will be
* +/= 0.5 * symbol spread and +/- 1.5 symbol spread. Rembmer that
* +/- 0.5 * symbol spread and +/- 1.5 symbol spread. Remember that
* nominal symbol spread will be 2.0 */
double symbolError;
@@ -269,8 +283,6 @@ public class C4FMSymbolFilter implements RealSampleListener,
mSymbolClock -= symbolError * K_SYMBOL_TIMING;
}
mLog.debug( "Symbol Clock Adjusted: " + mSymbolClock );
mSymbolSpread = Math.max( mSymbolSpread, SYMBOL_SPREAD_MIN );
mSymbolSpread = Math.min( mSymbolSpread, SYMBOL_SPREAD_MAX );
@@ -282,11 +294,11 @@ public class C4FMSymbolFilter implements RealSampleListener,
/* send frequency correction */
if( Math.abs( mCoarseFrequencyCorrection ) > COARSE_FREQUENCY_DEADBAND )
{
mLog.debug( "Frequency Correction [" +
( mCoarseFrequencyCorrection > 0 ? "-" : "+" ) +
mCoarseFrequencyCorrection + "]" );
}
mLog.error( "C4FM Filter - output [ " + output + " ] frequency correction: " +
mCoarseFrequencyCorrection );
/* dispatch the interpolated value to the listener */
if( mListener != null )
{

View File

@@ -28,14 +28,13 @@ import java.util.List;
import sample.Listener;
import sample.real.RealSampleListener;
import dsp.Slicer;
import dsp.Slicer.Output;
import dsp.filter.DCRemovalFilter3;
import dsp.filter.Filters;
import dsp.filter.FloatHalfBandFilter;
import dsp.filter.FloatHalfBandNoDecimateFilter;
import dsp.filter.LTRPulseShapingFilter;
import dsp.filter.SquaringFilter;
import dsp.fsk.Slicer.Output;
public class LTRFSKDecoder implements RealSampleListener, Instrumentable
{

View File

@@ -0,0 +1,374 @@
package dsp.fsk;
import java.util.ArrayList;
import java.util.BitSet;
import message.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import bits.BitSetBuffer;
import bits.BitSetFullException;
import bits.SyncPatternMatcher;
import decode.p25.P25Interleave;
import decode.p25.TrellisHalfRate;
import decode.p25.message.P25Message;
import decode.p25.message.PDUMessage;
import decode.p25.message.TSBKMessage;
import decode.p25.message.TSBKMessageFactory;
import decode.p25.reference.DataUnitID;
public class P25MessageFramer implements Listener<C4FMSymbol>
{
private final static Logger mLog =
LoggerFactory.getLogger( P25MessageFramer.class );
private static final int[] STATUS_BITS =
new int[] { 22,92,162,232,302,372,442,512,582,652,722,792,862,932 };
private ArrayList<P25MessageAssembler> mAssemblers =
new ArrayList<P25MessageAssembler>();
public static final int TSBK_BEGIN = 64;
public static final int TSBK_END = 260;
public static final int TSBK_DECODED_END = 162;
private Listener<Message> mListener;
private SyncPatternMatcher mMatcher;
private boolean mInverted = false;
private TrellisHalfRate mHalfRate = new TrellisHalfRate();
/**
* Constructs a C4FM message framer to receive a stream of C4FM 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.
*
* @param sync - sync pattern (maximum of 63 bits)
* @param messageLength - in bits
* @param inverted - optional flag to indicate the symbol stream should be
* uninverted prior to processing
*/
public P25MessageFramer( long sync, int messageLength, boolean inverted )
{
mMatcher = new SyncPatternMatcher( sync );
mInverted = inverted;
/**
* 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() );
}
private void dispatch( Message message )
{
if( mListener != null )
{
mListener.receive( message );
}
}
@Override
public void receive( C4FMSymbol symbol )
{
C4FMSymbol correctedSymbol = symbol;
mMatcher.receive( correctedSymbol.getBit1() );
mMatcher.receive( correctedSymbol.getBit2() );
for( P25MessageAssembler assembler: mAssemblers )
{
if( assembler.isActive() )
{
assembler.receive( correctedSymbol );
if( assembler.complete() )
{
assembler.reset();
}
}
}
/* Check for sync match and activate a message assembler, if we can
* find an inactive assembler. Otherwise, ignore and log the issue */
if( mMatcher.matches() )
{
boolean found = false;
for( P25MessageAssembler assembler: mAssemblers )
{
if( !assembler.isActive() )
{
assembler.setActive( true );
found = true;
break;
}
}
if( !found )
{
mLog.debug( "no inactive C4FM message assemblers available" );
}
}
}
public void setListener( Listener<Message> listener )
{
mListener = listener;
}
public void removeListener( Listener<Message> listener )
{
mListener = null;
}
private class P25MessageAssembler
{
private int mStatusIndicatorPointer = 0;
private BitSetBuffer mMessage;
private int mMessageLength;
private boolean mComplete = false;
private boolean mActive = false;
private DataUnitID mDUID = DataUnitID.NID;
public P25MessageAssembler()
{
mMessageLength = mDUID.getMessageLength();
mMessage = new BitSetBuffer( mMessageLength );
reset();
}
public void receive( C4FMSymbol symbol )
{
if( mActive )
{
/* Throw away status bits that are injected every 70 bits */
if( mMessage.pointer() == STATUS_BITS[ mStatusIndicatorPointer ] )
{
mStatusIndicatorPointer++;
}
else
{
try
{
mMessage.add( symbol.getBit1() );
mMessage.add( symbol.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();
mStatusIndicatorPointer = 0;
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:
int value = mMessage.getInt( P25Message.DUID );
DataUnitID duid = DataUnitID.fromValue( value );
if( duid != DataUnitID.UNKN )
{
setDUID( duid );
}
else
{
mComplete = true;
}
break;
case HDU:
mComplete = true;
dispatch( new P25Message( mMessage.copy(), mDUID ) );
break;
case LDU1:
mComplete = true;
dispatch( new P25Message( mMessage.copy(), mDUID ) );
break;
case LDU2:
mComplete = true;
dispatch( new P25Message( mMessage.copy(), mDUID ) );
break;
case PDU1:
int blocks = mMessage.getInt(
PDUMessage.PDU_HEADER_BLOCKS_TO_FOLLOW );
int padBlocks = mMessage.getInt( PDUMessage.PDU_HEADER_PAD_BLOCKS );
int blockCount = blocks + padBlocks;
if( blockCount == 24 || blockCount == 32 )
{
setDUID( DataUnitID.PDU2 );
}
else if( blockCount == 36 || blockCount == 48 )
{
setDUID( DataUnitID.PDU3 );
}
else
{
dispatch( new PDUMessage( mMessage.copy(), mDUID ) );
mComplete = true;
}
break;
case PDU2:
case PDU3:
mComplete = true;
dispatch( new P25Message( mMessage.copy(), mDUID ) );
break;
case TDU:
mComplete = true;
dispatch( new P25Message( mMessage.copy(), mDUID ) );
break;
case TDULC:
mComplete = true;
dispatch( new P25Message( mMessage.copy(), mDUID ) );
break;
case TSBK1:
/* Remove interleaving */
P25Interleave.deinterleave( mMessage, TSBK_BEGIN, TSBK_END );
/* Remove trellis encoding */
mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END );
/* Construct the message */
int tsbkSystem1 = mMessage.getInt( P25Message.NAC );
BitSetBuffer tsbkBuffer1 = new BitSetBuffer( mMessage.get(
TSBK_BEGIN, TSBK_DECODED_END ), 96 );
TSBKMessage tsbkMessage1 =
TSBKMessageFactory.getMessage( tsbkSystem1, tsbkBuffer1 );
if( tsbkMessage1.isLastBlock() )
{
mComplete = true;
}
else
{
setDUID( DataUnitID.TSBK2 );
mMessage.setPointer( TSBK_BEGIN );
}
dispatch( tsbkMessage1 );
break;
case TSBK2:
/* Remove interleaving */
P25Interleave.deinterleave( mMessage, TSBK_BEGIN, TSBK_END );
/* Remove trellis encoding */
mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END );
/* Construct the message */
int tsbkSystem2 = mMessage.getInt( P25Message.NAC );
BitSetBuffer tsbkBuffer2 = new BitSetBuffer( mMessage.get(
TSBK_BEGIN, TSBK_DECODED_END ), 98 );
TSBKMessage tsbkMessage2 =
TSBKMessageFactory.getMessage( tsbkSystem2, tsbkBuffer2 );
if( tsbkMessage2.isLastBlock() )
{
mComplete = true;
}
else
{
setDUID( DataUnitID.TSBK3 );
mMessage.setPointer( TSBK_BEGIN );
}
dispatch( tsbkMessage2 );
break;
case TSBK3:
/* Remove interleaving */
P25Interleave.deinterleave( mMessage, TSBK_BEGIN, TSBK_END );
/* Remove trellis encoding */
mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END );
/* Construct the message */
int tsbkSystem3 = mMessage.getInt( P25Message.NAC );
BitSetBuffer tsbkBuffer3 = new BitSetBuffer( mMessage.get(
TSBK_BEGIN, TSBK_DECODED_END ), 96 );
TSBKMessage tsbkMessage3 =
TSBKMessageFactory.getMessage( tsbkSystem3, tsbkBuffer3 );
mComplete = true;
dispatch( tsbkMessage3 );
break;
case UNKN:
mComplete = true;
dispatch( new P25Message( mMessage.copy(), mDUID ) );
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;
}
}
}

View File

@@ -1,14 +1,10 @@
package dsp;
package dsp.fsk;
import instrument.tap.Tap;
import instrument.tap.stream.SymbolEventTap;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import sample.Listener;
import dsp.fsk.SymbolEvent;
import dsp.fsk.SymbolEvent.Shift;
/**

View File

@@ -111,10 +111,7 @@ public class FloatTapViewPanel extends TapViewPanel
{
int middle = (int)( getHeight() / 2 );
float scaledValue = value / (float)Short.MAX_VALUE;
// mLog.info( "Float value is:" + scaledValue );
float y = middle - ( middle * scaledValue * mVerticalZoom );
float y = middle - ( middle * value * mVerticalZoom );
/* Clip y to zero as necessary */
y = ( y < 0 ) ? 0 : y;

View File

@@ -0,0 +1,26 @@
package sample.adapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import source.tuner.hackrf.HackRFTunerController;
public class Test
{
private final static Logger mLog =
LoggerFactory.getLogger( HackRFTunerController.class );
public static void main( String[] args )
{
mLog.debug( "Starting" );
ShortToFloatMap map1 = new ShortToFloatMap();
for( short x = -32768; x < -32758; x++ )
{
mLog.debug( "X:" + x + " value:" + map1.get( x ) );
}
mLog.debug( "Finished" );
}
}