Updated LTR Standard decoder state to new architecture to support

per-call recording and accurate state tracking.

Resolved issue with ThreadPoolManager limiting total number of scheduled
tasks (removed limiter - original intent is now OBE)

Updated ChannelState to better handle auxiliary decoder decode events,
so that when a fleetsync ident is at the end of the transmission, the
decode doesn't kick the channel back into a call state after it has
faded, causing static bursts and additional empty recordings.

Updated recording file name to include millisecond level precision, to
further avoid duplicate recording names
This commit is contained in:
denny
2015-09-26 07:37:40 -04:00
parent 9c69733034
commit 507ae5255e
11 changed files with 142 additions and 146 deletions

View File

@@ -32,7 +32,6 @@ public class ThreadPoolManager
private final static Logger mLog = LoggerFactory.getLogger( ThreadPoolManager.class );
private int THREAD_POOL_SIZE = 2;
private int MAX_TASK_COUNT = 24;
private HashMap<ScheduledFuture<?>,ThreadType> mTasks =
new HashMap<ScheduledFuture<?>,ThreadType>();
@@ -55,21 +54,12 @@ public class ThreadPoolManager
new NamingThreadFactory( "sdrtrunk" ) );
}
if( mTasks.size() <= MAX_TASK_COUNT )
{
ScheduledFuture<?> task =
mExecutor.scheduleAtFixedRate( command, 0, period, unit );
mTasks.put( task, type );
return task;
}
else
{
throw new RejectedExecutionException( "Cannot schedule task - at "
+ "max task count [" + mTasks.size() + "]" );
}
ScheduledFuture<?> task =
mExecutor.scheduleAtFixedRate( command, 0, period, unit );
mTasks.put( task, type );
return task;
}
public void scheduleOnce( Runnable command, long delay, TimeUnit unit )
@@ -81,22 +71,14 @@ public class ThreadPoolManager
new NamingThreadFactory( "sdrtrunk" ) );
}
if( mTasks.size() <= MAX_TASK_COUNT )
{
mExecutor.schedule( command, delay, unit );
}
else
{
throw new RejectedExecutionException( "Cannot schedule task - at "
+ "max task count [" + mTasks.size() + "]" );
}
mExecutor.schedule( command, delay, unit );
}
public void cancel( ScheduledFuture<?> task )
{
task.cancel( true );
ThreadType type = mTasks.remove( task );
mTasks.remove( task );
}
public int getTaskCount( ThreadType type )

View File

@@ -66,8 +66,6 @@ public class CallEventModel extends AbstractTableModel implements Listener<CallE
private SimpleDateFormat mSDFTime = new SimpleDateFormat( "HH:mm:ss" );
private boolean mNewMessagesFirst = true;
public CallEventModel()
{
}
@@ -102,10 +100,6 @@ public class CallEventModel extends AbstractTableModel implements Listener<CallE
*/
public void receive( final CallEvent event )
{
//TODO: reverse this so that the new events go to the end and the oldest
//events are at the beginning ... use the getObjectAt() to control
//correct access to the ordering imposed.
if( event.isValid() )
{
if( !mEvents.contains( event ) )

View File

@@ -43,6 +43,8 @@ public class Fleetsync2DecoderState extends DecoderState
private String mMessage;
private String mMessageType;
private long mFrequency;
public Fleetsync2DecoderState( AliasList aliasList )
{
@@ -158,14 +160,14 @@ public class Fleetsync2DecoderState extends DecoderState
}
FleetsyncCallEvent fsCallEvent =
FleetsyncCallEvent.getFleetsync2Event( fleetsync );
FleetsyncCallEvent.getFleetsync2Event( fleetsync, mFrequency );
fsCallEvent.setAliasList( getAliasList() );
broadcast( fsCallEvent );
/* Broadcast decode event so that the channel state will
* kick in and reset everything */
* kick in and reset everything after a short delay */
broadcast( new DecoderStateEvent( this, Event.DECODE, state ) );
}
}
@@ -248,6 +250,9 @@ public class Fleetsync2DecoderState extends DecoderState
case RESET:
resetState();
break;
case SOURCE_FREQUENCY:
mFrequency = event.getFrequency();
break;
default:
break;
}

View File

@@ -24,14 +24,19 @@ import alias.AliasList;
public class FleetsyncCallEvent extends CallEvent
{
private long mFrequency = 0;
public FleetsyncCallEvent( CallEventType callEventType,
AliasList aliasList,
String fromID,
String toID,
String details )
String details,
long frequency )
{
super( DecoderType.FLEETSYNC2, callEventType,
aliasList, fromID, toID, details );
mFrequency = frequency;
}
private Alias getAlias( String ident )
@@ -65,7 +70,7 @@ public class FleetsyncCallEvent extends CallEvent
@Override
public long getFrequency()
{
return 0;
return mFrequency;
}
public static class Builder
@@ -78,6 +83,7 @@ public class FleetsyncCallEvent extends CallEvent
private String mFromID;
private String mToID;
private String mDetails;
private long mFrequency;
public Builder( CallEventType callEventType )
{
@@ -89,6 +95,12 @@ public class FleetsyncCallEvent extends CallEvent
mAliasList = aliasList;
return this;
}
public Builder frequency( long frequency )
{
mFrequency = frequency;
return this;
}
public Builder from( String val )
{
@@ -123,11 +135,12 @@ public class FleetsyncCallEvent extends CallEvent
builder.mAliasList,
builder.mFromID,
builder.mToID,
builder.mDetails );
builder.mDetails,
builder.mFrequency );
}
public static FleetsyncCallEvent
getFleetsync2Event( FleetsyncMessage message )
public static FleetsyncCallEvent getFleetsync2Event(
FleetsyncMessage message, long frequency )
{
CallEventType type = CallEventType.UNKNOWN;
StringBuilder sbDetails = new StringBuilder();
@@ -176,6 +189,7 @@ public class FleetsyncCallEvent extends CallEvent
{
return new FleetsyncCallEvent.Builder( type )
.details( sbDetails.toString() )
.frequency( frequency )
.from( message.getFromID() )
.build();
}
@@ -183,6 +197,7 @@ public class FleetsyncCallEvent extends CallEvent
{
return new FleetsyncCallEvent.Builder( type )
.details( sbDetails.toString() )
.frequency( frequency )
.from( message.getFromID() )
.to( message.getToID() )
.build();

View File

@@ -27,7 +27,6 @@ public class LTRCallEvent extends CallEvent
{
private String mChannel;
private long mFrequency;
private int mValidCallMessages = 1;
public LTRCallEvent( DecoderType type,
CallEventType callEventType,
@@ -44,26 +43,6 @@ public class LTRCallEvent extends CallEvent
mFrequency = frequency;
}
/**
* LTR sends multiple CALL messages during a call. In order to detect noise
* bursts from valid calls, send every valid call message to this method
* and the event will keep a tally of valid call messages in order to
* determine if the call was a valid (and loggable) call event.
*/
public boolean addMessage( LTRStandardMessage message )
{
String talkgroup = message.getTalkgroupID();
if( talkgroup.contentEquals( getToID() ) )
{
mValidCallMessages++;
return true;
}
return false;
}
/**
* Checks if the message's talkgroup matches this call event. Counts the
* number of messages that match this call event to use in determining if
@@ -78,24 +57,12 @@ public class LTRCallEvent extends CallEvent
if( talkgroup != null && getToID() != null &&
talkgroup.contentEquals( getToID() ) )
{
mValidCallMessages++;
return true;
}
return false;
}
public boolean isValid()
{
if( getCallEventType() == CallEventType.CALL )
{
return mValidCallMessages > 1;
}
return true;
}
private Alias getAlias( String talkgroup )
{
if( hasAliasList() )

View File

@@ -675,7 +675,7 @@ public class LTRNetDecoderState extends DecoderState
}
/**
* Resets the decoder state for re-use on another channel
* Performs a full reset
*/
public void reset()
{
@@ -694,7 +694,7 @@ public class LTRNetDecoderState extends DecoderState
}
/**
* Resets the decoder state after a call event
* Resets the decoder state after a call or other decode event
*/
private void resetState()
{

View File

@@ -21,6 +21,7 @@ import java.awt.EventQueue;
import javax.swing.JLabel;
import net.miginfocom.swing.MigLayout;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderPanel;
import settings.ColorSetting;
@@ -53,7 +54,11 @@ public class LTRStandardDecoderPanel extends DecoderPanel
public void init()
{
mProtocol.setFont( mFontDecoder );
super.init();
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]0[]") );
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
mTalkgroup.setFont( mFontDecoder );

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
* Copyright (C) 2014,2015 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
@@ -17,7 +17,6 @@
******************************************************************************/
package module.decode.ltrstandard;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -35,22 +34,19 @@ import module.decode.state.DecoderStateEvent.Event;
import module.decode.state.State;
import alias.Alias;
import alias.AliasList;
import audio.metadata.Metadata;
import audio.metadata.MetadataType;
public class LTRStandardDecoderState extends DecoderState
{
private static final int sINT_NULL_VALUE = -1;
private DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000");
private HashMap<Integer,String> mActiveCalls = new HashMap<Integer,String>();
private HashSet<String> mTalkgroupsFirstHeard = new HashSet<String>();
private TreeSet<String> mTalkgroups = new TreeSet<String>();
private TreeSet<Integer> mActiveLCNs = new TreeSet<Integer>();
private int mMonitoredLCN;
private String mTalkgroup;
private Alias mTalkgroupAlias;
private int mChannelNumber;
private long mFrequency;
public LTRStandardDecoderState( AliasList aliasList )
@@ -111,14 +107,13 @@ public class LTRStandardDecoderState extends DecoderState
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
LTRCallEvent current = getCurrentCallEvent();
/* If we have an ongoing call, then add this message
* to the current event - if addMessage returns
* false, then we have a different talkgroup and a
* new call event, so close out the old one. */
if( current != null && !current.addMessage( ltr ) )
/* Detect when we haven't reset from the previous
* call and now have a new call coming in with
* a different talkgroup */
if( current != null && isDifferentTalkgroup( mTalkgroup ) )
{
mCurrentCallEvent.setEnd( System.currentTimeMillis() );
mCurrentCallEvent.end();
broadcast( mCurrentCallEvent );
mCurrentCallEvent = null;
}
@@ -136,30 +131,35 @@ public class LTRStandardDecoderState extends DecoderState
broadcast( mCurrentCallEvent );
broadcast( new DecoderStateEvent( this, Event.START, State.CALL ) );
broadcast( new Metadata( MetadataType.TO,
mTalkgroup, mTalkgroupAlias, true ) );
broadcast( new DecoderStateEvent( this,
Event.START, State.CALL ) );
}
else
{
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CALL ) );
broadcast( new DecoderStateEvent( this,
Event.CONTINUATION, State.CALL ) );
}
}
}
break;
case CA_ENDD:
mTalkgroup = ltr.getTalkgroupID();
broadcast( ChangedAttribute.FROM_TALKGROUP );
broadcast( ChangedAttribute.TO_TALKGROUP );
mTalkgroupAlias = ltr.getTalkgroupIDAlias();
broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS );
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
if( mCurrentCallEvent != null )
{
mCurrentCallEvent.setEnd( System.currentTimeMillis() );
mCurrentCallEvent.end();
broadcast( mCurrentCallEvent );
broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
mCurrentCallEvent = null;
}
int home = ltr.getHomeRepeater();
if( 0 < home && home <= 20 )
@@ -182,9 +182,9 @@ public class LTRStandardDecoderState extends DecoderState
{
if( channel == home )
{
if( mMonitoredLCN != channel )
if( mChannelNumber != channel )
{
mMonitoredLCN = channel;
mChannelNumber = channel;
broadcast( ChangedAttribute.CHANNEL_NUMBER );
}
@@ -206,15 +206,16 @@ public class LTRStandardDecoderState extends DecoderState
if( idleChannel == ltr.getHomeRepeater() &&
idleChannel == ltr.getFree() )
{
if( mMonitoredLCN != idleChannel )
if( mChannelNumber != idleChannel )
{
mMonitoredLCN = idleChannel;
mChannelNumber = idleChannel;
broadcast( ChangedAttribute.CHANNEL_NUMBER );
}
mActiveLCNs.add( idleChannel );
}
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.IDLE ) );
broadcast( new DecoderStateEvent( this,
Event.CONTINUATION, State.IDLE ) );
break;
case UN_KNWN:
default:
@@ -224,60 +225,49 @@ public class LTRStandardDecoderState extends DecoderState
}
}
// /**
// * Intercept the fade event so that we can generate a call end event
// */
// @Override
// public void fade( final CallEventType type )
// {
// /* We can receive multiple call tear-down messages -- only respond to
// * the message that can change the state to fade */
// if( getState().canChangeTo( State.FADE ) )
// {
// mActiveCalls.clear();
//
// LTRCallEvent current = getCurrentLTRCallEvent();
//
// if( current != null )
// {
// /* Close out the call event. If we only received 1 call
// * message, then flag it as noise and remove the call event */
// if( current.isValid() )
// {
// mCallEventModel.setEnd( current );
// }
// else
// {
// mCallEventModel.remove( current );
// }
// }
//
// setCurrentCallEvent( null );
// }
//
// super.fade( type );
// }
/**
* Indicates if the talkgroup is different than the talkgroup specified in
* the current call event
*/
private boolean isDifferentTalkgroup( String talkgroup )
{
return talkgroup != null &&
mCurrentCallEvent != null &&
mCurrentCallEvent.getToID() != null &&
!mCurrentCallEvent.getToID().contentEquals( talkgroup );
}
/**
* Performs a full reset
*/
public void reset()
{
mActiveCalls.clear();
mTalkgroupsFirstHeard.clear();
mTalkgroups.clear();
mActiveLCNs.clear();
mChannelNumber = 0;
resetState();
}
/**
* Performs a temporal reset following a call or other decode event
*/
private void resetState()
{
if( mCurrentCallEvent != null )
{
mCurrentCallEvent.setEnd( System.currentTimeMillis() );
broadcast( mCurrentCallEvent );
mCurrentCallEvent = null;
}
}
private void resetState()
{
mTalkgroup = null;
broadcast( ChangedAttribute.FROM_TALKGROUP );
broadcast( ChangedAttribute.TO_TALKGROUP );
mTalkgroupAlias = null;
broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS );
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
public String getTalkgroup()
@@ -303,7 +293,6 @@ public class LTRStandardDecoderState extends DecoderState
@Override
public void init()
{
}
@Override
@@ -333,9 +322,9 @@ public class LTRStandardDecoderState extends DecoderState
sb.append( "Monitored LCN: " );
if( mMonitoredLCN > 0 )
if( mChannelNumber > 0 )
{
sb.append( mMonitoredLCN );
sb.append( mChannelNumber );
}
else
{

View File

@@ -568,7 +568,6 @@ public class ChannelState extends Module implements ICallEventProvider,
mSquelchLocked = true;
break;
case CONTINUATION:
case DECODE:
if( isEndingState() )
{
setState( event.getState() );
@@ -578,6 +577,12 @@ public class ChannelState extends Module implements ICallEventProvider,
updateFadeTimeout();
}
break;
case DECODE:
if( getState() == State.IDLE )
{
setState( event.getState() );
}
break;
case END:
setState( State.FADE );
break;

View File

@@ -104,7 +104,7 @@ public class RealBufferWaveRecorder extends Module
StringBuilder sb = new StringBuilder();
sb.append( mFilePrefix );
sb.append( "_" );
sb.append( TimeStamp.getTimeStamp( "_" ) );
sb.append( TimeStamp.getLongTimeStamp( "_" ) );
sb.append( ".wav" );
mFile = Paths.get( sb.toString() );

View File

@@ -24,6 +24,7 @@ public class TimeStamp
{
private static SimpleDateFormat mSDFDate = new SimpleDateFormat( "yyyyMMdd" );
private static SimpleDateFormat mSDFTime = new SimpleDateFormat( "HHmmss" );
private static SimpleDateFormat mSDFTimeWithMilliseconds = new SimpleDateFormat( "HHmmss.SSS" );
/**
* Returns the current system date formatted as yyyy-MM-dd
@@ -57,6 +58,14 @@ public class TimeStamp
return mSDFTime.format( new Date( timestamp ) );
}
/**
* Returns the timestamp formatted as a time of HH:mm:ss
*/
public static String getFormattedTimeWithMilliseconds( long timestamp )
{
return mSDFTime.format( new Date( timestamp ) );
}
/**
* Returns current system time formatted as yyyy-MM-dd*HH:mm:ss
* with the * representing the separator attribute
@@ -80,5 +89,30 @@ public class TimeStamp
return sb.toString();
}
/**
* Returns current system time formatted as yyyy-MM-dd*HH:mm:ss.SSS
* with the * representing the separator attribute
*/
public static String getLongTimeStamp( String separator )
{
return getLongTimeStamp( System.currentTimeMillis(), separator );
}
/**
* Returns timestamp formatted as yyyy-MM-dd*HH:mm:ss.SSS
* with the * representing the separator attribute
*/
public static String getLongTimeStamp( long timestamp, String separator )
{
StringBuilder sb = new StringBuilder();
sb.append( getFormattedDate( timestamp ) );
sb.append( separator );
sb.append( getFormattedTimeWithMilliseconds( timestamp ) );
return sb.toString();
}
}