diff --git a/src/bits/BitSetBuffer.java b/src/bits/BitSetBuffer.java index f690c21d..ed587f5f 100644 --- a/src/bits/BitSetBuffer.java +++ b/src/bits/BitSetBuffer.java @@ -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 diff --git a/src/bits/SyncPatternMatcher.java b/src/bits/SyncPatternMatcher.java index d3c3c1fa..09b77ecc 100644 --- a/src/bits/SyncPatternMatcher.java +++ b/src/bits/SyncPatternMatcher.java @@ -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 diff --git a/src/crc/BCH.java b/src/crc/BCH.java new file mode 100644 index 00000000..470a7ae6 --- /dev/null +++ b/src/crc/BCH.java @@ -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 ]; + + + } +} diff --git a/src/decode/p25/FrameSync.java b/src/decode/p25/FrameSync.java new file mode 100644 index 00000000..ed15e37f --- /dev/null +++ b/src/decode/p25/FrameSync.java @@ -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; + } +} diff --git a/src/decode/p25/P25Decoder.java b/src/decode/p25/P25Decoder.java index f259cf0a..ee8190bf 100644 --- a/src/decode/p25/P25Decoder.java +++ b/src/decode/p25/P25Decoder.java @@ -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 getTaps() { - return mC4FMDecoder.getTaps(); + return new ArrayList(); } @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" ); } } diff --git a/src/decode/p25/P25Interleave.java b/src/decode/p25/P25Interleave.java index 58926efd..9d80d6fb 100644 --- a/src/decode/p25/P25Interleave.java +++ b/src/decode/p25/P25Interleave.java @@ -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() ); + } } diff --git a/src/decode/p25/P25MessageProcessor.java b/src/decode/p25/P25MessageProcessor.java new file mode 100644 index 00000000..40ad6764 --- /dev/null +++ b/src/decode/p25/P25MessageProcessor.java @@ -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 +{ + 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() ); + } +} diff --git a/src/decode/p25/TrellisHalfRate.java b/src/decode/p25/TrellisHalfRate.java new file mode 100644 index 00000000..7c3c18fc --- /dev/null +++ b/src/decode/p25/TrellisHalfRate.java @@ -0,0 +1,502 @@ +package decode.p25; + +import java.util.ArrayList; + +import bits.BitSetBuffer; + +public class TrellisHalfRate +{ + private ArrayList mConstellationNodes = + new ArrayList(); + + 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() ); + + } +} diff --git a/src/decode/p25/message/P25Message.java b/src/decode/p25/message/P25Message.java new file mode 100644 index 00000000..06adc11c --- /dev/null +++ b/src/decode/p25/message/P25Message.java @@ -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; + } +} diff --git a/src/decode/p25/message/PDUMessage.java b/src/decode/p25/message/PDUMessage.java new file mode 100644 index 00000000..4149f741 --- /dev/null +++ b/src/decode/p25/message/PDUMessage.java @@ -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(); + } + +} diff --git a/src/decode/p25/message/TSBKMessage.java b/src/decode/p25/message/TSBKMessage.java new file mode 100644 index 00000000..73aafb16 --- /dev/null +++ b/src/decode/p25/message/TSBKMessage.java @@ -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(); + } +} diff --git a/src/decode/p25/message/TSBKMessageFactory.java b/src/decode/p25/message/TSBKMessageFactory.java new file mode 100644 index 00000000..eec980be --- /dev/null +++ b/src/decode/p25/message/TSBKMessageFactory.java @@ -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 ); + } + } +} diff --git a/src/decode/p25/reference/DataUnitID.java b/src/decode/p25/reference/DataUnitID.java new file mode 100644 index 00000000..08f9ea6b --- /dev/null +++ b/src/decode/p25/reference/DataUnitID.java @@ -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; + } + } +} diff --git a/src/decode/p25/reference/Encryption.java b/src/decode/p25/reference/Encryption.java new file mode 100644 index 00000000..e2a5f22b --- /dev/null +++ b/src/decode/p25/reference/Encryption.java @@ -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; + } +} diff --git a/src/decode/p25/reference/Opcode.java b/src/decode/p25/reference/Opcode.java new file mode 100644 index 00000000..cb0fe55f --- /dev/null +++ b/src/decode/p25/reference/Opcode.java @@ -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; + } +} diff --git a/src/decode/p25/reference/PDUFormat.java b/src/decode/p25/reference/PDUFormat.java new file mode 100644 index 00000000..43a61f09 --- /dev/null +++ b/src/decode/p25/reference/PDUFormat.java @@ -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; + } +} diff --git a/src/decode/p25/reference/ServiceAccessPoint.java b/src/decode/p25/reference/ServiceAccessPoint.java new file mode 100644 index 00000000..566202a6 --- /dev/null +++ b/src/decode/p25/reference/ServiceAccessPoint.java @@ -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; + } +} diff --git a/src/decode/p25/reference/Status.java b/src/decode/p25/reference/Status.java new file mode 100644 index 00000000..de624579 --- /dev/null +++ b/src/decode/p25/reference/Status.java @@ -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; + } +} diff --git a/src/decode/p25/reference/Vendor.java b/src/decode/p25/reference/Vendor.java new file mode 100644 index 00000000..9072a1cc --- /dev/null +++ b/src/decode/p25/reference/Vendor.java @@ -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; + } + +} diff --git a/src/dsp/BooleanZeroCrossingDetector.java b/src/dsp/BooleanZeroCrossingDetector.java deleted file mode 100644 index c04d47f5..00000000 --- a/src/dsp/BooleanZeroCrossingDetector.java +++ /dev/null @@ -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 - ******************************************************************************/ -package dsp; - -import sample.Listener; - -public class BooleanZeroCrossingDetector implements Listener -{ - private int mBaudLength; - private int mCounter = 0; - private int mThreshold; - private Listener 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 listener ) - { - mListener = listener; - } - - public void removeListener( Listener listener ) - { - mListener = null; - } - - public enum Cycle { POSITIVE, NEGATIVE }; -} diff --git a/src/dsp/FloatToBinarySlicer.java b/src/dsp/FloatToBinarySlicer.java deleted file mode 100644 index 97061994..00000000 --- a/src/dsp/FloatToBinarySlicer.java +++ /dev/null @@ -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 - ******************************************************************************/ -package dsp; - -import sample.Listener; -import buffer.FloatCircularAveragingBuffer; - -public class FloatToBinarySlicer implements Listener - -{ - private FloatCircularAveragingBuffer mAveragingBuffer; - private Listener 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 listener ) - { - mListener = listener; - } - - public void clearListener() - { - mListener = null; - } -} diff --git a/src/dsp/fsk/C4FMDecoder.java b/src/dsp/fsk/C4FMDecoder.java index c9eb88de..e7a11af3 100644 --- a/src/dsp/fsk/C4FMDecoder.java +++ b/src/dsp/fsk/C4FMDecoder.java @@ -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 { diff --git a/src/dsp/fsk/C4FMFrameListener.java b/src/dsp/fsk/C4FMFrameListener.java new file mode 100644 index 00000000..8cef52eb --- /dev/null +++ b/src/dsp/fsk/C4FMFrameListener.java @@ -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 ); + +} diff --git a/src/dsp/fsk/C4FMSlicer.java b/src/dsp/fsk/C4FMSlicer.java new file mode 100644 index 00000000..5f2b4f23 --- /dev/null +++ b/src/dsp/fsk/C4FMSlicer.java @@ -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 mBroadcaster = new Broadcaster(); + + /** + * 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 listener ) + { + mBroadcaster.addListener( listener ); + } + + /** + * Removes the listener + */ + public void removeListener( Listener listener ) + { + mBroadcaster.removeListener( listener ); + } +} diff --git a/src/dsp/fsk/C4FMSymbol.java b/src/dsp/fsk/C4FMSymbol.java new file mode 100644 index 00000000..12030083 --- /dev/null +++ b/src/dsp/fsk/C4FMSymbol.java @@ -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; + } + } +} diff --git a/src/dsp/filter/C4FMSymbolFilter.java b/src/dsp/fsk/C4FMSymbolFilter.java similarity index 95% rename from src/dsp/filter/C4FMSymbolFilter.java rename to src/dsp/fsk/C4FMSymbolFilter.java index dcf60443..adbf3c5d 100644 --- a/src/dsp/filter/C4FMSymbolFilter.java +++ b/src/dsp/fsk/C4FMSymbolFilter.java @@ -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 ) { diff --git a/src/dsp/fsk/LTRFSKDecoder.java b/src/dsp/fsk/LTRFSKDecoder.java index 89a06f54..b5603d3c 100644 --- a/src/dsp/fsk/LTRFSKDecoder.java +++ b/src/dsp/fsk/LTRFSKDecoder.java @@ -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 { diff --git a/src/dsp/fsk/P25MessageFramer.java b/src/dsp/fsk/P25MessageFramer.java new file mode 100644 index 00000000..d124142c --- /dev/null +++ b/src/dsp/fsk/P25MessageFramer.java @@ -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 +{ + 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 mAssemblers = + new ArrayList(); + + public static final int TSBK_BEGIN = 64; + public static final int TSBK_END = 260; + public static final int TSBK_DECODED_END = 162; + + private Listener 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 listener ) + { + mListener = listener; + } + + public void removeListener( Listener 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; + } + } +} diff --git a/src/dsp/Slicer.java b/src/dsp/fsk/Slicer.java similarity index 96% rename from src/dsp/Slicer.java rename to src/dsp/fsk/Slicer.java index f8493363..5fb9a83e 100644 --- a/src/dsp/Slicer.java +++ b/src/dsp/fsk/Slicer.java @@ -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; /** diff --git a/src/instrument/tap/stream/FloatTapViewPanel.java b/src/instrument/tap/stream/FloatTapViewPanel.java index 1c3ec9e5..cc32c802 100644 --- a/src/instrument/tap/stream/FloatTapViewPanel.java +++ b/src/instrument/tap/stream/FloatTapViewPanel.java @@ -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; diff --git a/src/sample/adapter/Test.java b/src/sample/adapter/Test.java new file mode 100644 index 00000000..956e4c71 --- /dev/null +++ b/src/sample/adapter/Test.java @@ -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" ); + } + +}