mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
535 lines
13 KiB
Java
535 lines
13 KiB
Java
package module.decode.p25;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Random;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import bits.BinaryMessage;
|
|
import dsp.symbol.Dibit;
|
|
|
|
public class Trellis_1_2_Rate
|
|
{
|
|
public final static int MAX_ERROR_THRESHOLD = 7;
|
|
|
|
private final static Logger mLog =
|
|
LoggerFactory.getLogger( Trellis_1_2_Rate.class );
|
|
|
|
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 Trellis_1_2_Rate()
|
|
{
|
|
/* 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();
|
|
}
|
|
|
|
// private String constellationsToString( String label )
|
|
// {
|
|
// StringBuilder sb = new StringBuilder();
|
|
//
|
|
// sb.append( label );
|
|
//
|
|
// for( ConstellationNode node: mConstellationNodes )
|
|
// {
|
|
// sb.append( " " + node.getConstellation().name() );
|
|
// }
|
|
//
|
|
// return sb.toString();
|
|
// }
|
|
//
|
|
// private String inputsToString()
|
|
// {
|
|
// StringBuilder sb = new StringBuilder();
|
|
//
|
|
// sb.append( " INPUTS: " );
|
|
//
|
|
// for( ConstellationNode node: mConstellationNodes )
|
|
// {
|
|
// sb.append( " " );
|
|
// sb.append( ( node.getConstellation().getInput().getBit1() ? "1" : "0" ) );
|
|
// sb.append( ( node.getConstellation().getInput().getBit2() ? "1" : "0" ) );
|
|
// }
|
|
//
|
|
// return sb.toString();
|
|
// }
|
|
|
|
public boolean decode( BinaryMessage message, int start, int end )
|
|
{
|
|
/* load each of the nodes with de-interleaved constellations */
|
|
for( int index = 0; index < 49; index++ )
|
|
{
|
|
Constellation c = getConstellation( message, start + index * 4 );
|
|
|
|
mConstellationNodes.get( index ).setConstellation( c );
|
|
}
|
|
|
|
/* test to see if constellations are correct - otherwise correct them */
|
|
ConstellationNode firstNode = mConstellationNodes.get( 0 );
|
|
|
|
int errorCount = firstNode.getErrorCount();
|
|
|
|
if( errorCount > 0 )
|
|
{
|
|
if( errorCount < MAX_ERROR_THRESHOLD )
|
|
{
|
|
/* repair the errors */
|
|
firstNode.correctTo( Dibit.D00_PLUS_1 );
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* clear constellations from original message */
|
|
message.clear( start, end );
|
|
|
|
/* replace with decoded values from the constellation nodes */
|
|
for( int index = 0; index < 49; index++ )
|
|
{
|
|
ConstellationNode node = mConstellationNodes.get( index );
|
|
|
|
Dibit input = node.getInputDibit();
|
|
|
|
if( input.getBit1() )
|
|
{
|
|
message.set( start + ( index * 2 ) );
|
|
}
|
|
if( input.getBit2() )
|
|
{
|
|
message.set( start + ( index * 2 ) + 1 );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private Constellation getConstellation( BinaryMessage message, int index )
|
|
{
|
|
int transmittedValue = 0;
|
|
|
|
for( int x = 0; x < 4; x++ )
|
|
{
|
|
if( message.get( index + x ) )
|
|
{
|
|
transmittedValue += ( 1 << ( 3 - x ) );
|
|
}
|
|
}
|
|
|
|
return Constellation.fromTransmittedValue( transmittedValue );
|
|
}
|
|
|
|
public class ConstellationNode
|
|
{
|
|
private ConstellationNode mConnectedNode;
|
|
private Constellation mConstellation;
|
|
private boolean mCorrect;
|
|
|
|
public ConstellationNode()
|
|
{
|
|
}
|
|
|
|
public void dispose()
|
|
{
|
|
mConnectedNode = null;
|
|
}
|
|
|
|
public Constellation getConstellation()
|
|
{
|
|
return mConstellation;
|
|
}
|
|
|
|
public Dibit getInputDibit()
|
|
{
|
|
return mConstellation.getInput();
|
|
}
|
|
|
|
public boolean hasStateDibit( Dibit dibit )
|
|
{
|
|
return mConstellation.getState() == dibit;
|
|
}
|
|
|
|
/**
|
|
* 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 stateDibit to use for the left side.
|
|
*/
|
|
public void correctTo( Dibit stateDibit )
|
|
{
|
|
if( mCorrect && mConstellation.getState() == stateDibit )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( isCurrentConnectionCorrect() )
|
|
{
|
|
mConstellation = Constellation.fromStateAndInputDibits( stateDibit,
|
|
mConstellation.getInput() );
|
|
|
|
mCorrect = true;
|
|
|
|
if( mConnectedNode != null )
|
|
{
|
|
/* the next node's state was this node's input */
|
|
mConnectedNode.correctTo( mConstellation.getInput() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Constellation cheapestConstellation = mConstellation;
|
|
|
|
int cheapestCost = 100; //arbitrary
|
|
|
|
for( Dibit testInput: Dibit.values() )
|
|
{
|
|
Constellation testConstellation = Constellation
|
|
.fromStateAndInputDibits( stateDibit, testInput );
|
|
|
|
int testCost = mConstellation.costTo( testConstellation ) +
|
|
mConnectedNode.costTo( testInput );
|
|
|
|
if( testCost < cheapestCost )
|
|
{
|
|
cheapestCost = testCost;
|
|
cheapestConstellation = testConstellation;
|
|
}
|
|
}
|
|
|
|
mConstellation = cheapestConstellation;
|
|
|
|
mConnectedNode.correctTo( mConstellation.getInput() );
|
|
|
|
mCorrect = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculates the cost (hamming distance) of using the argument as the
|
|
* state dibit for the current node, and recursively finding the
|
|
* cheapest corresponding input dibit.
|
|
*
|
|
* @param stateTest
|
|
* @return
|
|
*/
|
|
public int costTo( Dibit stateTest )
|
|
{
|
|
if( isCurrentConnectionCorrect() )
|
|
{
|
|
Constellation c = Constellation.fromStateAndInputDibits(
|
|
stateTest, mConstellation.getInput() );
|
|
|
|
return mConstellation.costTo( c );
|
|
}
|
|
else
|
|
{
|
|
int cheapestCost = 100; //arbitrary
|
|
|
|
for( Dibit inputTest: Dibit.values() )
|
|
{
|
|
Constellation constellationTest =
|
|
Constellation.fromStateAndInputDibits( stateTest, inputTest );
|
|
|
|
int cost = mConnectedNode.costTo( inputTest ) +
|
|
mConstellation.costTo( constellationTest );
|
|
|
|
if( cost < cheapestCost )
|
|
{
|
|
cheapestCost = cost;
|
|
}
|
|
}
|
|
|
|
return cheapestCost;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Indicates if the immediate connection to the right is correct
|
|
*/
|
|
public boolean isCurrentConnectionCorrect()
|
|
{
|
|
return ( mConnectedNode == null ||
|
|
mConstellation.getInput() == mConnectedNode.getStateDibit() );
|
|
}
|
|
|
|
/**
|
|
* 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 int getErrorCount()
|
|
{
|
|
if( mConnectedNode == null )
|
|
{
|
|
mCorrect = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
mCorrect = mConstellation.getInput() == mConnectedNode.getStateDibit();
|
|
|
|
return mConnectedNode.getErrorCount() + ( mCorrect ? 0 : 1 );
|
|
}
|
|
|
|
public Dibit getStateDibit()
|
|
{
|
|
return mConstellation.getState();
|
|
}
|
|
|
|
public void setConstellation( Constellation constellation )
|
|
{
|
|
mConstellation = constellation;
|
|
mCorrect = false;
|
|
}
|
|
|
|
public void connect( ConstellationNode node )
|
|
{
|
|
mConnectedNode = node;
|
|
}
|
|
}
|
|
|
|
public enum Constellation
|
|
{
|
|
/* State (previous), Input (next), Transmit 1, Transmit 2 */
|
|
CB( Dibit.D01_PLUS_3, Dibit.D01_PLUS_3, Dibit.D00_PLUS_1, Dibit.D00_PLUS_1, 0 ),
|
|
CC( Dibit.D00_PLUS_1, Dibit.D10_MINUS_1, Dibit.D00_PLUS_1, Dibit.D01_PLUS_3, 1 ),
|
|
C0( Dibit.D00_PLUS_1, Dibit.D00_PLUS_1, Dibit.D00_PLUS_1, Dibit.D10_MINUS_1, 2 ),
|
|
C7( Dibit.D01_PLUS_3, Dibit.D11_MINUS_3, Dibit.D00_PLUS_1, Dibit.D11_MINUS_3, 3 ),
|
|
CE( Dibit.D10_MINUS_1, Dibit.D11_MINUS_3, Dibit.D01_PLUS_3, Dibit.D00_PLUS_1, 4 ),
|
|
C9( Dibit.D11_MINUS_3, Dibit.D00_PLUS_1, Dibit.D01_PLUS_3, Dibit.D01_PLUS_3, 5 ),
|
|
C5( Dibit.D11_MINUS_3, Dibit.D10_MINUS_1, Dibit.D01_PLUS_3, Dibit.D10_MINUS_1, 6 ),
|
|
C2( Dibit.D10_MINUS_1, Dibit.D01_PLUS_3, Dibit.D01_PLUS_3, Dibit.D11_MINUS_3, 7 ),
|
|
CA( Dibit.D11_MINUS_3, Dibit.D11_MINUS_3, Dibit.D10_MINUS_1, Dibit.D00_PLUS_1, 8 ),
|
|
CD( Dibit.D10_MINUS_1, Dibit.D00_PLUS_1, Dibit.D10_MINUS_1, Dibit.D01_PLUS_3, 9 ),
|
|
C1( Dibit.D10_MINUS_1, Dibit.D10_MINUS_1, Dibit.D10_MINUS_1, Dibit.D10_MINUS_1, 10 ),
|
|
C6( Dibit.D11_MINUS_3, Dibit.D01_PLUS_3, Dibit.D10_MINUS_1, Dibit.D11_MINUS_3, 11 ),
|
|
CF( Dibit.D00_PLUS_1, Dibit.D01_PLUS_3, Dibit.D11_MINUS_3, Dibit.D00_PLUS_1, 12 ),
|
|
C8( Dibit.D01_PLUS_3, Dibit.D10_MINUS_1, Dibit.D11_MINUS_3, Dibit.D01_PLUS_3, 13 ),
|
|
C4( Dibit.D01_PLUS_3, Dibit.D00_PLUS_1, Dibit.D11_MINUS_3, Dibit.D10_MINUS_1, 14 ),
|
|
C3( Dibit.D00_PLUS_1, Dibit.D11_MINUS_3, Dibit.D11_MINUS_3, Dibit.D11_MINUS_3, 15 );
|
|
|
|
private Dibit mStateDibit;
|
|
private Dibit mInputDibit;
|
|
private Dibit mTransmitDibit1;
|
|
private Dibit mTransmitDibit2;
|
|
private int mTransmittedValue;
|
|
|
|
private Constellation( Dibit stateDibit,
|
|
Dibit inputDibit,
|
|
Dibit transmitDibit1,
|
|
Dibit transmitDibit2,
|
|
int transmittedValue )
|
|
{
|
|
mStateDibit = stateDibit;
|
|
mInputDibit = inputDibit;
|
|
mTransmitDibit1 = transmitDibit1;
|
|
mTransmitDibit2 = transmitDibit2;
|
|
mTransmittedValue = transmittedValue;
|
|
}
|
|
|
|
public Dibit getState()
|
|
{
|
|
return mStateDibit;
|
|
}
|
|
|
|
public Dibit getInput()
|
|
{
|
|
return mInputDibit;
|
|
}
|
|
|
|
public Dibit getTransmitDibit1()
|
|
{
|
|
return mTransmitDibit1;
|
|
}
|
|
|
|
public Dibit getTransmitDibit2()
|
|
{
|
|
return mTransmitDibit2;
|
|
}
|
|
|
|
public int getTransmittedValue()
|
|
{
|
|
return mTransmittedValue;
|
|
}
|
|
|
|
public static Constellation fromTransmittedValue( int value )
|
|
{
|
|
if( 0 <= value && value <= 15 )
|
|
{
|
|
return values()[ value ];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static Constellation fromTransmittedDibits( Dibit left, Dibit right )
|
|
{
|
|
return fromTransmittedValue( left.getHighValue() + right.getLowValue() );
|
|
}
|
|
|
|
public static Constellation fromStateAndInputDibits( Dibit state, Dibit input )
|
|
{
|
|
switch( state )
|
|
{
|
|
case D00_PLUS_1:
|
|
switch( input )
|
|
{
|
|
case D00_PLUS_1:
|
|
return C0;
|
|
case D01_PLUS_3:
|
|
return CF;
|
|
case D10_MINUS_1:
|
|
return CC;
|
|
case D11_MINUS_3:
|
|
return C3;
|
|
}
|
|
break;
|
|
case D01_PLUS_3:
|
|
switch( input )
|
|
{
|
|
case D00_PLUS_1:
|
|
return C4;
|
|
case D01_PLUS_3:
|
|
return CB;
|
|
case D10_MINUS_1:
|
|
return C8;
|
|
case D11_MINUS_3:
|
|
return C7;
|
|
}
|
|
break;
|
|
case D10_MINUS_1:
|
|
switch( input )
|
|
{
|
|
case D00_PLUS_1:
|
|
return CD;
|
|
case D01_PLUS_3:
|
|
return C2;
|
|
case D10_MINUS_1:
|
|
return C1;
|
|
case D11_MINUS_3:
|
|
return CE;
|
|
}
|
|
break;
|
|
case D11_MINUS_3:
|
|
switch( input )
|
|
{
|
|
case D00_PLUS_1:
|
|
return C9;
|
|
case D01_PLUS_3:
|
|
return C6;
|
|
case D10_MINUS_1:
|
|
return C5;
|
|
case D11_MINUS_3:
|
|
return CA;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Should never get to here */
|
|
return C0;
|
|
}
|
|
|
|
/**
|
|
* 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[ getTransmittedValue() ][ other.getTransmittedValue() ];
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Test harness
|
|
*/
|
|
public static void main( String[] args )
|
|
{
|
|
Random random = new Random();
|
|
|
|
BinaryMessage buffer = new BinaryMessage( 196 );
|
|
|
|
try
|
|
{
|
|
for( int x = 0; x < 196; x++ )
|
|
{
|
|
int number = random.nextInt( 2 );
|
|
|
|
if( number == 1 )
|
|
{
|
|
buffer.add( false );
|
|
}
|
|
else
|
|
{
|
|
buffer.add( true );
|
|
}
|
|
}
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
e.printStackTrace();
|
|
}
|
|
|
|
mLog.debug( " BUFFER: " + buffer.toString() );
|
|
|
|
Trellis_1_2_Rate t = new Trellis_1_2_Rate();
|
|
|
|
t.decode( buffer, 0, 196 );
|
|
mLog.debug( " DECODED: " + buffer.toString() );
|
|
mLog.debug( "Finished!" );
|
|
}
|
|
}
|