mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
P25 - adds 3/4 rate convolutional decoder for confirmed packet data units.
This commit is contained in:
@@ -233,40 +233,47 @@ public class P25ChannelState extends ChannelState
|
||||
//TODO: process call here
|
||||
break;
|
||||
case GROUP_VOICE_CHANNEL_USER:
|
||||
decode.p25.message.tdu.lc.GroupVoiceChannelUser gvcuser =
|
||||
(decode.p25.message.tdu.lc.GroupVoiceChannelUser)tdulc;
|
||||
|
||||
if( isActiveCall( CURRENT_CHANNEL, gvcuser.getGroupAddress() ) )
|
||||
if( !( tdulc instanceof decode.p25.message.tdu.lc.GroupVoiceChannelUser ) )
|
||||
{
|
||||
if( gvcuser.getFromID() != null &&
|
||||
!gvcuser.getFromID().contentEquals( "0000" ) &&
|
||||
!gvcuser.getFromID().contentEquals( "000000" ) )
|
||||
{
|
||||
updateCall( CURRENT_CHANNEL, gvcuser.getSourceAddress(),
|
||||
gvcuser.getGroupAddress() );
|
||||
}
|
||||
else
|
||||
{
|
||||
updateCall( CURRENT_CHANNEL, null, gvcuser.getGroupAddress() );
|
||||
}
|
||||
mLog.error( "TDULC message has opcode of group voice channel user but is not instance " + tdulc.toString() + " " + tdulc.getBinaryMessage().toString() );
|
||||
}
|
||||
else
|
||||
{
|
||||
CallEvent gvcuserEvent =
|
||||
new P25CallEvent.Builder( CallEventType.CALL )
|
||||
.aliasList( mAliasList )
|
||||
.channel( CURRENT_CHANNEL )
|
||||
.details( ( gvcuser.isEncrypted() ? "ENCRYPTED" : "" ) +
|
||||
( gvcuser.isEmergency() ? " EMERGENCY" : "") )
|
||||
.from( gvcuser.getSourceAddress() )
|
||||
.to( gvcuser.getGroupAddress() )
|
||||
.build();
|
||||
decode.p25.message.tdu.lc.GroupVoiceChannelUser gvcuser =
|
||||
(decode.p25.message.tdu.lc.GroupVoiceChannelUser)tdulc;
|
||||
|
||||
mCallEventModel.add( gvcuserEvent );
|
||||
|
||||
addCall( gvcuserEvent );
|
||||
|
||||
setState( State.CALL );
|
||||
if( isActiveCall( CURRENT_CHANNEL, gvcuser.getGroupAddress() ) )
|
||||
{
|
||||
if( gvcuser.getFromID() != null &&
|
||||
!gvcuser.getFromID().contentEquals( "0000" ) &&
|
||||
!gvcuser.getFromID().contentEquals( "000000" ) )
|
||||
{
|
||||
updateCall( CURRENT_CHANNEL, gvcuser.getSourceAddress(),
|
||||
gvcuser.getGroupAddress() );
|
||||
}
|
||||
else
|
||||
{
|
||||
updateCall( CURRENT_CHANNEL, null, gvcuser.getGroupAddress() );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CallEvent gvcuserEvent =
|
||||
new P25CallEvent.Builder( CallEventType.CALL )
|
||||
.aliasList( mAliasList )
|
||||
.channel( CURRENT_CHANNEL )
|
||||
.details( ( gvcuser.isEncrypted() ? "ENCRYPTED" : "" ) +
|
||||
( gvcuser.isEmergency() ? " EMERGENCY" : "") )
|
||||
.from( gvcuser.getSourceAddress() )
|
||||
.to( gvcuser.getGroupAddress() )
|
||||
.build();
|
||||
|
||||
mCallEventModel.add( gvcuserEvent );
|
||||
|
||||
addCall( gvcuserEvent );
|
||||
|
||||
setState( State.CALL );
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MESSAGE_UPDATE:
|
||||
|
||||
652
src/decode/p25/Trellis_3_4_Rate.java
Normal file
652
src/decode/p25/Trellis_3_4_Rate.java
Normal file
@@ -0,0 +1,652 @@
|
||||
package decode.p25;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import bits.BitSetBuffer;
|
||||
import dsp.fsk.Dibit;
|
||||
|
||||
public class Trellis_3_4_Rate
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( Trellis_3_4_Rate.class );
|
||||
|
||||
/* Hamming distance (bit match count) between constellation pairs */
|
||||
private static final int[][] CONSTELLATION_METRICS =
|
||||
{ { 4,3,3,2,3,2,2,1,3,2,2,1,2,1,1,0 },
|
||||
{ 3,4,2,3,2,3,1,2,2,3,1,2,1,2,0,1 },
|
||||
{ 3,2,4,3,2,1,3,2,2,1,3,2,1,0,2,1 },
|
||||
{ 2,3,3,4,1,2,2,3,1,2,2,3,0,1,1,2 },
|
||||
{ 3,2,2,1,4,3,3,2,2,1,1,0,3,2,2,1 },
|
||||
{ 2,3,1,2,3,4,2,3,1,2,0,1,2,3,1,2 },
|
||||
{ 2,1,3,2,3,2,4,3,1,0,2,1,2,1,3,2 },
|
||||
{ 1,2,2,3,2,3,3,4,0,1,1,2,1,2,2,3 },
|
||||
{ 3,2,2,1,2,1,1,0,4,3,3,2,3,2,2,1 },
|
||||
{ 2,3,1,2,1,2,0,1,3,4,2,3,2,3,1,2 },
|
||||
{ 2,1,3,2,1,0,2,1,3,2,4,3,2,1,3,2 },
|
||||
{ 1,2,2,3,0,1,1,2,2,3,3,4,1,2,2,3 },
|
||||
{ 2,1,1,0,3,2,2,1,3,2,2,1,4,3,3,2 },
|
||||
{ 1,2,0,1,2,3,1,2,2,3,1,2,3,4,2,3 },
|
||||
{ 1,0,2,1,2,1,3,2,2,1,3,2,3,2,4,3 },
|
||||
{ 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 } };
|
||||
|
||||
/* Even valued state tribits can only produce even valued constellations
|
||||
* and odd valued state tribits can only produce odd constellations. */
|
||||
public static Con[] EVEN_CONSTELLATIONS = new Con[] { Con.C0,Con.C2,Con.C4,
|
||||
Con.C6,Con.C8,Con.CA,Con.CC,Con.CE };
|
||||
public static Con[] ODD_CONSTELLATIONS = new Con[] { Con.C1,Con.C3,Con.C5,
|
||||
Con.C7,Con.C9,Con.CB,Con.CD,Con.CF };
|
||||
|
||||
/* Constellation and state tribit lookup map to find the input tribit */
|
||||
public static HashMap<Con,Tribit[]> INPUT_FROM_CONSTELLATION_MAP;
|
||||
|
||||
private ArrayList<Con> mTransmittedConstellations = new ArrayList<Con>();
|
||||
private ArrayList<Path> mSurvivorPaths = new ArrayList<Path>();
|
||||
private ArrayList<Path> mNewPaths = new ArrayList<Path>();
|
||||
private PathMetrics mPathMetrics = new PathMetrics();
|
||||
|
||||
/**
|
||||
* Implements the Viterbi algorithm to decode 3/4 rate trellis encoded 196-bit
|
||||
* packet data messages.
|
||||
*/
|
||||
public Trellis_3_4_Rate()
|
||||
{
|
||||
createConstellationToTribitMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a lookup map for state to input tribit values for a given
|
||||
* constellation. Input tribits are contained in the array using the state
|
||||
* tribit's value as the lookup index. Null values indicate illegal state
|
||||
* and input combinations for the specified constellation.
|
||||
*/
|
||||
private void createConstellationToTribitMap()
|
||||
{
|
||||
INPUT_FROM_CONSTELLATION_MAP = new HashMap<Con,Tribit[]>();
|
||||
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.CB, new Tribit[] { null, null, Tribit.T5, Tribit.T3, Tribit.T1, Tribit.T7, null, null } );
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.CC, new Tribit[] { Tribit.T3, Tribit.T1, null, null, null, null, Tribit.T7, Tribit.T5 } );
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.C0, new Tribit[] { Tribit.T0, Tribit.T6, null, null, null, null, Tribit.T4, Tribit.T2 } );
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.C7, new Tribit[] { null, null, Tribit.T6, Tribit.T4, Tribit.T2, Tribit.T0, null, null } );
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.CE, new Tribit[] { Tribit.T7, Tribit.T5, null, null, null, null, Tribit.T3, Tribit.T1 } );
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.C9, new Tribit[] { null, null, Tribit.T1, Tribit.T7, Tribit.T5, Tribit.T3, null, null } );
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.C5, new Tribit[] { null, null, Tribit.T2, Tribit.T0, Tribit.T6, Tribit.T4, null, null } );
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.C2, new Tribit[] { Tribit.T4, Tribit.T2, null, null, null, null, Tribit.T0, Tribit.T6 } );
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.CA, new Tribit[] { Tribit.T5, Tribit.T3, null, null, null, null, Tribit.T1, Tribit.T7 } );
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.CD, new Tribit[] { null, null, Tribit.T3, Tribit.T1, Tribit.T7, Tribit.T5, null, null } );
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.C1, new Tribit[] { null, null, Tribit.T0, Tribit.T6, Tribit.T4, Tribit.T2, null, null } );
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.C6, new Tribit[] { Tribit.T6, Tribit.T4, null, null, null, null, Tribit.T2, Tribit.T0 } );
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.CF, new Tribit[] { null, null, Tribit.T7, Tribit.T5, Tribit.T3, Tribit.T1, null, null } );
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.C8, new Tribit[] { Tribit.T1, Tribit.T7, null, null, null, null, Tribit.T5, Tribit.T3 } );
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.C4, new Tribit[] { Tribit.T2, Tribit.T0, null, null, null, null, Tribit.T6, Tribit.T4 } );
|
||||
INPUT_FROM_CONSTELLATION_MAP.put( Con.C3, new Tribit[] { null, null, Tribit.T4, Tribit.T2, Tribit.T0, Tribit.T6, null, null } );;
|
||||
}
|
||||
|
||||
/**
|
||||
* Member object cleanup prior to deleting
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a 196-bit 3/4 rate convolutional encoded message located between
|
||||
* start and end indexes and returns the decoded 144-bit message overlayed
|
||||
* upon the original message with the remaining 52 bits cleared to zero.
|
||||
*
|
||||
* @return - original message with decoded message bits..
|
||||
*/
|
||||
public boolean decode( BitSetBuffer message, int start, int end )
|
||||
{
|
||||
reset();
|
||||
|
||||
/* Load and decode each of the transmitted constellations */
|
||||
for( int index = 0; index < 49; index++ )
|
||||
{
|
||||
Con c = getConstellation( message, start + index * 4 );
|
||||
|
||||
add( c );
|
||||
}
|
||||
|
||||
/* The final decoded survivor path should be at index 0 */
|
||||
Path path = mSurvivorPaths.get( 0 );
|
||||
|
||||
/* We should have a path with 50 nodes, counting the starting 000 node
|
||||
* and the flushing 000 node, otherwise there was an error */
|
||||
if( path != null && path.getNodes().size() == 50 )
|
||||
{
|
||||
/* Clear the original message bits */
|
||||
message.clear( start, end );
|
||||
|
||||
List<Node> nodes = path.getNodes();
|
||||
|
||||
/* Load each of the nodes' state tribit values into the original message */
|
||||
for( int x = 1; x < 50; x++ )
|
||||
{
|
||||
message.load( start + ( ( x - 1 ) * 3 ), 3,
|
||||
nodes.get( x ).getState().getValue() );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the decoder before decoding a new data packet.
|
||||
*/
|
||||
private void reset()
|
||||
{
|
||||
mTransmittedConstellations.clear();
|
||||
mNewPaths.clear();
|
||||
|
||||
mPathMetrics.reset();
|
||||
|
||||
mSurvivorPaths.clear();
|
||||
|
||||
/* Tribit 000 is the only legal start point */
|
||||
mSurvivorPaths.add( new Path( new Node( 0, Tribit.T0, Con.C0 ) ) );
|
||||
}
|
||||
|
||||
private void add( Con con )
|
||||
{
|
||||
mTransmittedConstellations.add( con );
|
||||
|
||||
/* Add in any newly created survivor paths */
|
||||
mSurvivorPaths.addAll( mNewPaths );
|
||||
mNewPaths.clear();
|
||||
|
||||
/* Set a culling threshold for any path metrics 3 below the best path */
|
||||
int survivorThreshold = mPathMetrics.getSurvivorThreshold();
|
||||
|
||||
/* Reset path metrics */
|
||||
mPathMetrics.reset();
|
||||
|
||||
/* Add constellation to each survivor path. If a survivor path metric
|
||||
* falls below the culling threshold, remove it */
|
||||
Iterator<Path> it = mSurvivorPaths.iterator();
|
||||
|
||||
Path path;
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
path = it.next();
|
||||
|
||||
if( path.isDead() || path.getPathMetric() < survivorThreshold )
|
||||
{
|
||||
it.remove();
|
||||
}
|
||||
else
|
||||
{
|
||||
path.add( con );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Con getConstellation( BitSetBuffer message, int index )
|
||||
{
|
||||
int value = message.getInt( index, index + 3 );
|
||||
return Con.fromTransmittedValue( value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Path metrics maintains a map of the current best path metrics that end
|
||||
* at each of the 8 valid tribit values. Each new path is evaluated against
|
||||
* the current best path for retention (survivor) or disposal (dead). A
|
||||
* best metric is maintained to evaluate the Walking Dead for disposal.
|
||||
* Walking dead is a path lagging the best path metric by more than 3.
|
||||
*/
|
||||
public class PathMetrics
|
||||
{
|
||||
private HashMap<Tribit,Path> mMetrics = new HashMap<Tribit,Path>();
|
||||
|
||||
private int mBestMetric = 0;
|
||||
|
||||
public PathMetrics()
|
||||
{
|
||||
}
|
||||
|
||||
public int getSurvivorThreshold()
|
||||
{
|
||||
return mBestMetric - 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all path metrics
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
mMetrics.clear();
|
||||
mBestMetric = 0;
|
||||
}
|
||||
|
||||
public int getMetric( Tribit tribit )
|
||||
{
|
||||
if( mMetrics.containsKey( tribit ) )
|
||||
{
|
||||
return mMetrics.get( tribit ).getPathMetric();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the path's metric against the current best path metric for
|
||||
* the final state tribit of the path. If the path's metric is higher
|
||||
* than the current best path metric, the current path is marked for
|
||||
* termination and the new path is promoted to best path. If the new
|
||||
* path's metric is less than the best path metric, then the new path
|
||||
* metric is marked for termination. If the metrics are equal, the new
|
||||
* path is allowed to survive.
|
||||
*
|
||||
* @param path/metric to evaluate
|
||||
*/
|
||||
public void evalute( Path path )
|
||||
{
|
||||
Node lastNode = path.getLastNode();
|
||||
|
||||
Path bestPath = mMetrics.get( lastNode.getState() );
|
||||
|
||||
if( bestPath == null )
|
||||
{
|
||||
mMetrics.put( lastNode.getState(), path );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( path.getPathMetric() < bestPath.getPathMetric() )
|
||||
{
|
||||
path.setSurvivor( false );
|
||||
}
|
||||
else if( path.getPathMetric() > bestPath.getPathMetric() )
|
||||
{
|
||||
mMetrics.put( lastNode.getState(), path );
|
||||
|
||||
bestPath.setSurvivor( false );
|
||||
}
|
||||
|
||||
/* Equal metric paths are allowed to survive */
|
||||
}
|
||||
|
||||
/* Capture the best path metric */
|
||||
if( path.getPathMetric() > mBestMetric )
|
||||
{
|
||||
mBestMetric = path.getPathMetric();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Path is a sequence of nodes where the starting node always contains
|
||||
* tribit 0 and constellation 0. Path maintains a running path metric
|
||||
* derived from the sum of each of the node's branch metrics.
|
||||
*/
|
||||
public class Path
|
||||
{
|
||||
private boolean mSurvivor = true;
|
||||
private int mPathMetric = 0;
|
||||
private ArrayList<Node> mNodes = new ArrayList<Node>();
|
||||
|
||||
public Path( Node first )
|
||||
{
|
||||
mNodes.add( first );
|
||||
|
||||
mPathMetric = first.getBranchMetric();
|
||||
|
||||
mPathMetrics.evalute( this );
|
||||
}
|
||||
|
||||
public Path( ArrayList<Node> nodes, int metric )
|
||||
{
|
||||
mNodes = nodes;
|
||||
mPathMetric = metric;
|
||||
}
|
||||
|
||||
public List<Node> getNodes()
|
||||
{
|
||||
return mNodes;
|
||||
}
|
||||
|
||||
public int getBitErrorCount()
|
||||
{
|
||||
return ( getLastNode().getIndex() * 4 ) - mPathMetric;
|
||||
}
|
||||
|
||||
public void setSurvivor( boolean survivor )
|
||||
{
|
||||
mSurvivor = survivor;
|
||||
}
|
||||
|
||||
public boolean isSurvivor()
|
||||
{
|
||||
return mSurvivor;
|
||||
}
|
||||
|
||||
public boolean isDead()
|
||||
{
|
||||
return !mSurvivor;
|
||||
}
|
||||
|
||||
@SuppressWarnings( "unchecked" )
|
||||
public Path copyOf()
|
||||
{
|
||||
return new Path( (ArrayList<Node>)mNodes.clone(), mPathMetric );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the constellation and returns any new paths created as a result
|
||||
* of the constellation being detected as an errant constellation.
|
||||
*/
|
||||
public void add( Con con )
|
||||
{
|
||||
Node current = getLastNode();
|
||||
|
||||
Tribit input = INPUT_FROM_CONSTELLATION_MAP
|
||||
.get( con )[ current.getState().getValue() ];
|
||||
|
||||
/* A non-null input tribit from the lookup table is valid, otherwise
|
||||
* create 8 alternate branches from the original path to explore
|
||||
* where the error occurred. */
|
||||
if( input != null )
|
||||
{
|
||||
add( new Node( mNodes.size(), input, con ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Create new paths for each of the valid constellations that
|
||||
* correspond to the parity of the current state tribit */
|
||||
Con[] constellations = current.getState().getType() ==
|
||||
Type.EVEN ? EVEN_CONSTELLATIONS : ODD_CONSTELLATIONS;
|
||||
|
||||
for( int x = 0; x < 8; x++ )
|
||||
{
|
||||
/* Get the input tribit corresponding to the state tribit
|
||||
* and constellation combination */
|
||||
Tribit tribit = INPUT_FROM_CONSTELLATION_MAP
|
||||
.get( constellations[ x ] )[ current.getState().getValue() ];
|
||||
|
||||
Node candidate = new Node( mNodes.size(), tribit, constellations[ x ] );
|
||||
|
||||
/* If the current path metric plus the candidate node's
|
||||
* branch metric is equal or better than the current best
|
||||
* metric for the given tribit, add it as a potential path,
|
||||
* otherwise discard it */
|
||||
if( this.getPathMetric() + candidate.getBranchMetric() >=
|
||||
mPathMetrics.getMetric( tribit ) )
|
||||
{
|
||||
/* If this is the final constellation of the set, simply
|
||||
* add it to the existing path ... */
|
||||
if( x == 7 )
|
||||
{
|
||||
add( candidate );
|
||||
}
|
||||
/* ... otherwise create a copy of the current path */
|
||||
else
|
||||
{
|
||||
Path path = this.copyOf();
|
||||
|
||||
path.add( candidate );
|
||||
|
||||
mNewPaths.add( path );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void add( Node node )
|
||||
{
|
||||
mNodes.add( node );
|
||||
|
||||
mPathMetric += node.getBranchMetric();
|
||||
|
||||
mPathMetrics.evalute( this );
|
||||
}
|
||||
|
||||
public int getPathMetric()
|
||||
{
|
||||
return mPathMetric;
|
||||
}
|
||||
|
||||
public Node getLastNode()
|
||||
{
|
||||
return mNodes.get( mNodes.size() - 1 );
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "PATH [" );
|
||||
sb.append( mPathMetric );
|
||||
sb.append( "] " );
|
||||
|
||||
for( Node node: mNodes )
|
||||
{
|
||||
sb.append( node.getState().name() );
|
||||
sb.append( "/" );
|
||||
sb.append( node.getConstellation().name() );
|
||||
sb.append( " " );
|
||||
}
|
||||
|
||||
sb.append( mSurvivor ? "SURVIVOR" : "DEAD" );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trellis node containing the constellation used to produce the node and
|
||||
* the input tribit for the constellation.
|
||||
*/
|
||||
public class Node
|
||||
{
|
||||
private int mIndex;
|
||||
private int mBranchMetric;
|
||||
private Tribit mTribit;
|
||||
private Con mConstellation;
|
||||
|
||||
public Node( int index, Tribit tribit, Con constellation )
|
||||
{
|
||||
mIndex = index;
|
||||
mTribit = tribit;
|
||||
mConstellation = constellation;
|
||||
|
||||
if( mIndex > 0 )
|
||||
{
|
||||
Con transmitted = mTransmittedConstellations.get( index - 1 );
|
||||
|
||||
if( transmitted == null )
|
||||
{
|
||||
throw new IllegalArgumentException( "Cannot calculate "
|
||||
+ "metric - transmitted constellation symbol is null" );
|
||||
}
|
||||
|
||||
mBranchMetric = transmitted.getMetricTo( constellation );
|
||||
}
|
||||
}
|
||||
|
||||
public int getIndex()
|
||||
{
|
||||
return mIndex;
|
||||
}
|
||||
|
||||
public Tribit getState()
|
||||
{
|
||||
return mTribit;
|
||||
}
|
||||
|
||||
public Con getConstellation()
|
||||
{
|
||||
return mConstellation;
|
||||
}
|
||||
|
||||
public int getBranchMetric()
|
||||
{
|
||||
return mBranchMetric;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "Node " );
|
||||
sb.append( mIndex );
|
||||
sb.append( " Metric:" );
|
||||
sb.append( mBranchMetric );
|
||||
sb.append( " Con:" );
|
||||
sb.append( mConstellation.name() );
|
||||
sb.append( " State:" );
|
||||
if( mTribit == null )
|
||||
{
|
||||
sb.append( "null" );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( mTribit.name() );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constellation Type. Constellations are classified as even or add based
|
||||
* on the the composition/comparison of the transmitted (representative)
|
||||
* dibits:
|
||||
*
|
||||
* EVEN: - first dibit differs from second dibit in exactly one bit position
|
||||
*
|
||||
* ODD: - first dibit differs from second dibit in zero or two bit positions
|
||||
* following three patterns:
|
||||
* 1) repeat (10 10), mirror (01 10), or invert(00 11)
|
||||
*/
|
||||
public enum Type { EVEN, ODD };
|
||||
|
||||
/**
|
||||
* Constellations, ordered by transmitted value. Transmitted value is the
|
||||
* value of the dibit pair that represents the constellation when transmitted.
|
||||
*/
|
||||
public enum Con
|
||||
{
|
||||
CB( 0 ),
|
||||
CC( 1 ),
|
||||
C0( 2 ),
|
||||
C7( 3 ),
|
||||
CE( 4 ),
|
||||
C9( 5 ),
|
||||
C5( 6 ),
|
||||
C2( 7 ),
|
||||
CA( 8 ),
|
||||
CD( 9 ),
|
||||
C1( 10 ),
|
||||
C6( 11 ),
|
||||
CF( 12 ),
|
||||
C8( 13 ),
|
||||
C4( 14 ),
|
||||
C3( 15 );
|
||||
|
||||
private int mTransmittedValue;
|
||||
|
||||
private Con( int transmittedValue )
|
||||
{
|
||||
mTransmittedValue = transmittedValue;
|
||||
}
|
||||
|
||||
public int getTransmittedValue()
|
||||
{
|
||||
return mTransmittedValue;
|
||||
}
|
||||
|
||||
public static Con fromTransmittedValue( int value )
|
||||
{
|
||||
if( 0 <= value && value <= 15 )
|
||||
{
|
||||
return values()[ value ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Con fromTransmittedDibits( Dibit left, Dibit right )
|
||||
{
|
||||
return fromTransmittedValue( left.getHighValue() + right.getLowValue() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the metric or hamming distance to the other constellation
|
||||
* using the values from the constellation costs table. A perfect match
|
||||
* has a metric of 4 and a complete miss has a value of 0.
|
||||
*/
|
||||
public int getMetricTo( Con other )
|
||||
{
|
||||
return CONSTELLATION_METRICS[ getTransmittedValue() ][ other.getTransmittedValue() ];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tribit. Used to define both the state and the input bit sequences that
|
||||
* produce the transmitted constellation bit sequences. Each tribit is
|
||||
* either even or odd valued producing a direct correlation to the types
|
||||
* of valid constellations containing the tribit as the state tribit.
|
||||
*/
|
||||
public enum Tribit
|
||||
{
|
||||
T0( 0, Type.EVEN ),
|
||||
T1( 1, Type.EVEN ),
|
||||
T2( 2, Type.ODD ),
|
||||
T3( 3, Type.ODD ),
|
||||
T4( 4, Type.ODD ),
|
||||
T5( 5, Type.ODD ),
|
||||
T6( 6, Type.EVEN ),
|
||||
T7( 7, Type.EVEN );
|
||||
|
||||
private int mValue;
|
||||
private Type mType;
|
||||
|
||||
private Tribit( int value, Type type )
|
||||
{
|
||||
mValue = value;
|
||||
mType = type;
|
||||
}
|
||||
|
||||
public Type getType()
|
||||
{
|
||||
return mType;
|
||||
}
|
||||
|
||||
public int getValue()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test harness
|
||||
*/
|
||||
public static void main( String[] args )
|
||||
{
|
||||
String raw = "0010001000010110011110101111110101110101110111010110011100000111011001011101111011110111011001000110001000100010001011011110001000100010101101110010001000101101010010100000101000001010000010101010";
|
||||
// String raw = "0010001100010110011110100111110101110101110111110110011100000111011001011101111011110111011001000110001000100010101011011110001000100010101101110010001000101101010010100000101000001010000010101010";
|
||||
// x x x x
|
||||
BitSetBuffer message = BitSetBuffer.load( raw );
|
||||
|
||||
mLog.debug( "MSG: " + message.toString() );
|
||||
|
||||
Trellis_3_4_Rate t = new Trellis_3_4_Rate();
|
||||
t.decode( message, 0, 196 );
|
||||
|
||||
mLog.debug( "DEC: " + message.toString() );
|
||||
|
||||
mLog.debug( "Finished!" );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -19,6 +19,60 @@ public enum Dibit
|
||||
mLowValue = lowValue;
|
||||
mHighValue = highValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowable transition state 1 for trellis coded modulation. Indicates
|
||||
* the dibit that differs from this dibit in the LSB bit position.
|
||||
*/
|
||||
public Dibit getAllowableTransition1()
|
||||
{
|
||||
switch( this )
|
||||
{
|
||||
case D00_PLUS_1:
|
||||
return D01_PLUS_3;
|
||||
case D01_PLUS_3:
|
||||
return D00_PLUS_1;
|
||||
case D10_MINUS_1:
|
||||
return D11_MINUS_3;
|
||||
case D11_MINUS_3:
|
||||
return D10_MINUS_1;
|
||||
default:
|
||||
/* We'll never get here */
|
||||
return D00_PLUS_1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowable transition state 2 for trellis coded modulation. Indicates
|
||||
* the dibit that differs from this dibit in the MSB bit position.
|
||||
*/
|
||||
public Dibit getAllowableTransition2()
|
||||
{
|
||||
switch( this )
|
||||
{
|
||||
case D00_PLUS_1:
|
||||
return D10_MINUS_1;
|
||||
case D01_PLUS_3:
|
||||
return D11_MINUS_3;
|
||||
case D10_MINUS_1:
|
||||
return D00_PLUS_1;
|
||||
case D11_MINUS_3:
|
||||
return D01_PLUS_3;
|
||||
default:
|
||||
/* We'll never get here */
|
||||
return D00_PLUS_1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the trellis coded modulation dibit is allowed to transition
|
||||
* to the dibit argument.
|
||||
*/
|
||||
public boolean isAllowableTransition( Dibit dibit )
|
||||
{
|
||||
return dibit == getAllowableTransition1() ||
|
||||
dibit == getAllowableTransition2();
|
||||
}
|
||||
|
||||
public boolean getBit1()
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ import bits.BitSetFullException;
|
||||
import bits.SyncPatternMatcher;
|
||||
import decode.p25.P25Interleave;
|
||||
import decode.p25.Trellis_1_2_Rate;
|
||||
import decode.p25.Trellis_3_4_Rate;
|
||||
import decode.p25.message.P25Message;
|
||||
import decode.p25.message.hdu.HDUMessage;
|
||||
import decode.p25.message.ldu.LDU1Message;
|
||||
@@ -61,6 +62,7 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
private SyncPatternMatcher mMatcher;
|
||||
private boolean mInverted = false;
|
||||
private Trellis_1_2_Rate mHalfRate = new Trellis_1_2_Rate();
|
||||
private Trellis_3_4_Rate mThreeQuarterRate = new Trellis_3_4_Rate();
|
||||
private BCH_63_16_11 mNIDDecoder = new BCH_63_16_11();
|
||||
|
||||
/**
|
||||
@@ -290,7 +292,6 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
if( confirmed )
|
||||
{
|
||||
setDUID( DataUnitID.PDUC );
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -390,14 +391,34 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
mComplete = true;
|
||||
break;
|
||||
case PDUC:
|
||||
int blocks = mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW );
|
||||
|
||||
int current = ( mMessage.size() - 260 ) / 196;
|
||||
|
||||
if( current < blocks )
|
||||
/* De-interleave the latest block*/
|
||||
P25Interleave.deinterleaveData( mMessage,
|
||||
mMessage.size() - 196, mMessage.size() );
|
||||
|
||||
/* Decode 3/4 rate convolutional encoding from latest block */
|
||||
if( mThreeQuarterRate.decode( mMessage,
|
||||
mMessage.size() - 196, mMessage.size() ) )
|
||||
{
|
||||
mMessageLength += 196;
|
||||
mMessage.setSize( mMessage.size() + 196 );
|
||||
/* Resize the message to account for removing 48 + 4 parity bits */
|
||||
mMessage.setSize( mMessage.size() - 52 );
|
||||
|
||||
int blocks = mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW );
|
||||
|
||||
int current = ( mMessage.size() - 160 ) / 144;
|
||||
|
||||
if( current < blocks )
|
||||
{
|
||||
mMessageLength += 196;
|
||||
mMessage.setSize( mMessage.size() + 196 );
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatch( new PDUConfirmedMessage( mMessage.copy(),
|
||||
mDUID, mAliasList ) );
|
||||
|
||||
mComplete = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user