Resolves threading issue in ChannelState.

Resolves issue in tuner controller where controller was not releasing
sourced tuner channels, preventing re-tuning to other frequencies.
Resolves issue with channel timeout on MPT1327 monitored traffic
channels
This commit is contained in:
Denny Sheirer
2016-02-01 09:17:56 +00:00
parent 6f76f6fca4
commit debdc39a5f
22 changed files with 568 additions and 342 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/*.class /*.class
/classes* /classes*
/product* /product*
/bin/

View File

@@ -230,12 +230,20 @@ public class AudioChannelPanel extends JPanel
mFrom.setText( from.getValue() ); mFrom.setText( from.getValue() );
updateAlias( mFromAlias, from.getAlias() ); updateAlias( mFromAlias, from.getAlias() );
} }
else
{
mFrom.setText( "-----" );
}
if( to != null ) if( to != null )
{ {
mTo.setText( to.getValue() ); mTo.setText( to.getValue() );
updateAlias( mToAlias, to.getAlias() ); updateAlias( mToAlias, to.getAlias() );
} }
else
{
mTo.setText( "-----" );
}
} }
} ); } );

View File

@@ -58,6 +58,16 @@ public class MessageFramer implements Listener<Boolean>,
mMessageLength = messageLength; mMessageLength = messageLength;
} }
public void reset()
{
for( MessageAssembler assembler: mMessageAssemblers )
{
assembler.dispose();
}
mMessageAssemblers.clear();
}
public void dispose() public void dispose()
{ {
mBroadcaster.dispose(); mBroadcaster.dispose();

View File

@@ -64,11 +64,13 @@ public class ThreadPoolManager
mExecutor.schedule( command, delay, unit ); mExecutor.schedule( command, delay, unit );
} }
public void cancel( ScheduledFuture<?> task ) public boolean cancel( ScheduledFuture<?> task )
{ {
task.cancel( true ); boolean success = task.cancel( true );
mTasks.remove( task ); mTasks.remove( task );
return success;
} }
public int getTaskCount( ThreadType type ) public int getTaskCount( ThreadType type )

View File

@@ -809,8 +809,7 @@ public class Channel extends Configuration
{ {
if( mProcessingChain == null ) if( mProcessingChain == null )
{ {
mProcessingChain = new ProcessingChain( mProcessingChain = new ProcessingChain();
mResourceManager.getThreadPoolManager() );
/* Get the optional alias list for the decode modules to use */ /* Get the optional alias list for the decode modules to use */
AliasList aliasList = mResourceManager.getPlaylistManager() AliasList aliasList = mResourceManager.getPlaylistManager()
@@ -901,6 +900,8 @@ public class Channel extends Configuration
{ {
if( !mEnabled && mProcessingChain != null ) if( !mEnabled && mProcessingChain != null )
{ {
fireChannelEvent( Event.CHANNEL_PROCESSING_STOPPED );
mProcessingChain.stop(); mProcessingChain.stop();
if( mChannelType == ChannelType.STANDARD ) if( mChannelType == ChannelType.STANDARD )
@@ -910,8 +911,6 @@ public class Channel extends Configuration
} }
mSelected = false; mSelected = false;
fireChannelEvent( Event.CHANNEL_PROCESSING_STOPPED );
} }
} }

View File

@@ -99,8 +99,7 @@ public class ProcessingChain implements IChannelEventListener
private Broadcaster<RealBuffer> mRealBufferBroadcaster = new Broadcaster<>(); private Broadcaster<RealBuffer> mRealBufferBroadcaster = new Broadcaster<>();
private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>(); private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
private ThreadPoolManager mThreadPoolManager; // private ThreadPoolManager mThreadPoolManager;
private ScheduledFuture<?> mBufferProcessingTask;
private AtomicBoolean mRunning = new AtomicBoolean(); private AtomicBoolean mRunning = new AtomicBoolean();
protected Source mSource; protected Source mSource;
@@ -109,12 +108,6 @@ public class ProcessingChain implements IChannelEventListener
public ProcessingChain() public ProcessingChain()
{ {
this( new ThreadPoolManager() );
}
public ProcessingChain( ThreadPoolManager threadManager )
{
mThreadPoolManager = threadManager;
} }
public void dispose() public void dispose()
@@ -128,14 +121,6 @@ public class ProcessingChain implements IChannelEventListener
mModules.clear(); mModules.clear();
// mComplexReceiver.dispose();
// mComplexReceiver = null;
//
// mRealReceiver.dispose();
// mRealReceiver = null;
mThreadPoolManager = null;
mBufferProcessingTask = null;
mFrequencyCorrectionController = null; mFrequencyCorrectionController = null;
mAudioPacketBroadcaster.dispose(); mAudioPacketBroadcaster.dispose();
@@ -496,12 +481,6 @@ public class ProcessingChain implements IChannelEventListener
mSource = null; mSource = null;
} }
if( mBufferProcessingTask != null )
{
mThreadPoolManager.cancel( mBufferProcessingTask );
mBufferProcessingTask = null;
}
/* Stop each of the modules */ /* Stop each of the modules */
for( Module module: mModules ) for( Module module: mModules )
{ {

View File

@@ -228,7 +228,8 @@ public class DecoderFactory
Sync sync = mptConfig.getSync(); Sync sync = mptConfig.getSync();
modules.add( new MPT1327Decoder( aliasList, sync ) ); modules.add( new MPT1327Decoder( aliasList, sync ) );
modules.add( new MPT1327DecoderState( aliasList, channelMap, channelType ) ); modules.add( new MPT1327DecoderState( aliasList, channelMap,
channelType, mptConfig.getCallTimeout() * 1000 ) );
if( channelType == ChannelType.STANDARD ) if( channelType == ChannelType.STANDARD )
{ {

View File

@@ -310,6 +310,11 @@ public abstract class CallEvent implements Comparable<CallEvent>
@Override @Override
public int compareTo( CallEvent other ) public int compareTo( CallEvent other )
{ {
if( other != null && other == this )
{
return 0;
}
return Long.compare( mEventStart, other.getEventStartTime() ); return Long.compare( mEventStart, other.getEventStartTime() );
} }
} }

View File

@@ -112,6 +112,7 @@ public class LTRNetDecoder extends Decoder implements IRealBufferListener, Instr
@Override @Override
public void reset() public void reset()
{ {
mLTRMessageFramer.reset();
} }
@Override @Override

View File

@@ -76,6 +76,7 @@ public class LTRStandardDecoder extends Decoder implements IRealBufferListener
@Override @Override
public void reset() public void reset()
{ {
mLTRMessageFramer.reset();
} }
@Override @Override

View File

@@ -301,8 +301,8 @@ public class MPT1327Decoder extends Decoder
@Override @Override
public void reset() public void reset()
{ {
// TODO Auto-generated method stub mControlMessageFramer.reset();
mTrafficMessageFramer.reset();
} }
@Override @Override

View File

@@ -26,9 +26,11 @@ import java.util.TreeSet;
import message.Message; import message.Message;
import module.decode.DecoderType; import module.decode.DecoderType;
import module.decode.config.DecodeConfiguration;
import module.decode.event.CallEvent; import module.decode.event.CallEvent;
import module.decode.event.CallEvent.CallEventType; import module.decode.event.CallEvent.CallEventType;
import module.decode.mpt1327.MPT1327Message.IdentType; import module.decode.mpt1327.MPT1327Message.IdentType;
import module.decode.state.ChangeChannelTimeoutEvent;
import module.decode.state.ChangedAttribute; import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderState; import module.decode.state.DecoderState;
import module.decode.state.DecoderStateEvent; import module.decode.state.DecoderStateEvent;
@@ -41,6 +43,8 @@ import org.slf4j.LoggerFactory;
import alias.Alias; import alias.Alias;
import alias.AliasList; import alias.AliasList;
import audio.metadata.Metadata;
import audio.metadata.MetadataType;
import controller.channel.Channel.ChannelType; import controller.channel.Channel.ChannelType;
import controller.channel.map.ChannelMap; import controller.channel.map.ChannelMap;
@@ -60,15 +64,18 @@ public class MPT1327DecoderState extends DecoderState
private ChannelMap mChannelMap; private ChannelMap mChannelMap;
private long mFrequency = 0; private long mFrequency = 0;
private long mCallTimeout;
public MPT1327DecoderState( AliasList aliasList, ChannelMap channelMap, public MPT1327DecoderState( AliasList aliasList,
ChannelType channelType ) ChannelMap channelMap,
ChannelType channelType,
long callTimeout )
{ {
super( aliasList ); super( aliasList );
mChannelMap = channelMap; mChannelMap = channelMap;
mChannelType = channelType; mChannelType = channelType;
mCallTimeout = callTimeout;
} }
@Override @Override
@@ -252,8 +259,42 @@ public class MPT1327DecoderState extends DecoderState
/* Traffic Channel Events */ /* Traffic Channel Events */
case CLEAR: case CLEAR:
mChannelNumber = mpt.getChannel();
broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
break;
case MAINT: case MAINT:
broadcast( new DecoderStateEvent( this, Event.END, State.END ) ); if( mChannelType == ChannelType.STANDARD )
{
/**
* When we receive a MAINT message and we're configured
* as a standard channel, we need to apply the call
* timeout specified by the user. Otherwise we'll
* be using the shorter default call timeout
*/
broadcast( new ChangeChannelTimeoutEvent( this,
mChannelType, mCallTimeout ) );
if( mCurrentCallEvent == null )
{
mCurrentCallEvent = new MPT1327CallEvent.Builder( CallEventType.CALL )
.aliasList( getAliasList() )
.channel( String.valueOf( mChannelNumber ) )
.details( "MONITORED TRAFFIC CHANNEL" )
.frequency( mFrequency )
.to( mpt.getToID() )
.build();
broadcast( mCurrentCallEvent );
}
broadcast(new Metadata(MetadataType.TO,
mpt.getToID(), mpt.getToIDAlias(), true));
broadcast( new DecoderStateEvent( this, Event.START, State.CALL ) );
setToTalkgroup( mpt.getToID() );
}
break; break;
default: default:
break; break;
@@ -276,6 +317,25 @@ public class MPT1327DecoderState extends DecoderState
mToTalkgroup = null; mToTalkgroup = null;
broadcast( ChangedAttribute.TO_TALKGROUP ); broadcast( ChangedAttribute.TO_TALKGROUP );
/**
* If this is a standard channel, reset the fade timeout to the default
* timeout. Once processing is underway, if we get a MAINT message,
* this indicates we're processing a traffic channel as a standard
* channel, so we'll issue a different call timeout then.
*/
if( mChannelType == ChannelType.STANDARD )
{
broadcast( new ChangeChannelTimeoutEvent( this, mChannelType,
DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000 ) );
if( mCurrentCallEvent != null )
{
mCurrentCallEvent.end();
broadcast( mCurrentCallEvent );
mCurrentCallEvent = null;
}
}
} }
public String getSite() public String getSite()

View File

@@ -436,7 +436,7 @@ public class P25DecoderState extends DecoderState
{ {
if( tdulc.getOpcode() == LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION ) if( tdulc.getOpcode() == LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION )
{ {
broadcast( new DecoderStateEvent( this, Event.END, State.END ) ); broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
if( mCurrentCallEvent != null ) if( mCurrentCallEvent != null )
{ {
@@ -1056,7 +1056,7 @@ public class P25DecoderState extends DecoderState
} }
break; break;
case CALL_TERMINATION_OR_CANCELLATION: case CALL_TERMINATION_OR_CANCELLATION:
broadcast( new DecoderStateEvent( this, Event.END, State.END ) ); broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
mCurrentCallEvent = null; mCurrentCallEvent = null;
if( !( ldu instanceof CallTermination ) ) if( !( ldu instanceof CallTermination ) )
@@ -1520,7 +1520,7 @@ public class P25DecoderState extends DecoderState
private void processTSBK( TSBKMessage tsbk ) private void processTSBK( TSBKMessage tsbk )
{ {
/* Trunking Signalling Block Messages - indicates Control Channel */ /* Trunking Signalling Block Messages - indicates Control Channel */
broadcast( new DecoderStateEvent( this, Event.START, State.CONTROL ) ); broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
if( tsbk.getVendor() == Vendor.STANDARD ) if( tsbk.getVendor() == Vendor.STANDARD )
{ {

View File

@@ -87,6 +87,8 @@ public class P25Message extends Message
sb.append( " " ); sb.append( " " );
sb.append( getDUID().getLabel() ); sb.append( getDUID().getLabel() );
sb.append( " DATA UNIT ID:" + mMessage.getInt( DUID ) );
return sb.toString(); return sb.toString();
} }

View File

@@ -103,6 +103,7 @@ public class PassportDecoder extends Decoder
@Override @Override
public void reset() public void reset()
{ {
mPassportMessageFramer.reset();
} }
@Override @Override

View File

@@ -0,0 +1,29 @@
package module.decode.state;
import controller.channel.Channel.ChannelType;
/**
* Allows decoders to change the call timeout setting in the channel state
*/
public class ChangeChannelTimeoutEvent extends DecoderStateEvent
{
private ChannelType mChannelType;
private long mCallTimeout;
public ChangeChannelTimeoutEvent( Object source, ChannelType channelType, long timeout )
{
super( source, Event.CHANGE_CALL_TIMEOUT, State.IDLE );
mChannelType = channelType;
mCallTimeout = timeout;
}
public ChannelType getChannelType()
{
return mChannelType;
}
public long getCallTimeout()
{
return mCallTimeout;
}
}

View File

@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer * Copyright (C) 2014-2016 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -22,16 +22,9 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import module.Module;
import module.decode.config.DecodeConfiguration;
import module.decode.event.CallEvent;
import module.decode.event.ICallEventProvider;
import module.decode.state.DecoderStateEvent.Event;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sample.Listener;
import audio.metadata.IMetadataProvider; import audio.metadata.IMetadataProvider;
import audio.metadata.Metadata; import audio.metadata.Metadata;
import audio.metadata.MetadataReset; import audio.metadata.MetadataReset;
@@ -41,38 +34,13 @@ import audio.squelch.SquelchState;
import controller.ThreadPoolManager; import controller.ThreadPoolManager;
import controller.ThreadPoolManager.ThreadType; import controller.ThreadPoolManager.ThreadType;
import controller.channel.Channel.ChannelType; import controller.channel.Channel.ChannelType;
import module.Module;
import module.decode.config.DecodeConfiguration;
import module.decode.event.CallEvent;
import module.decode.event.ICallEventProvider;
import module.decode.state.DecoderStateEvent.Event;
import sample.Listener;
/**
* Channel state tracks the overall state of all processing modules and decoders
* configured for the channel and provides squelch control events.
*
* Primary channel state provides a state machine for tracking decode events
* during a voice or data call through it's life cycle of CALL/DATA, FADE, END,
* and then back to an IDLE state. This class also provides squelch control
* to support any audio processing.
*
* State Descriptions:
* IDLE No voice or data call activity
* CALL/DATA A voice or data call has started, or is continuing
* CONTROL Control channel
* FADE The phase after a voice or data call when either an explicit
* call end has been received, or when no new signalling updates
* have been received, and the fade timer has expired. This phase
* allows for gui updates to signal to the user that the call is
* ended, while continuing to display the call details for the user
* END The phase after the voice or data call FADE, when the call is
* now over, and the gui should be reset to IDLE.
*
* When a call is started, invoke mTimerService.startFadeTimer() to start the
* call fade monitoring timer process that will automatically signal a call end
* once no more call signalling has been detected after the fade timeout has
* occurred, by invoking the fade() method.
*
* When a call ends, invoke the mTimerService.stopFadeTimer() and then invoke
* the mTimerService.startResetTimer(). Once the reset timeout occurs,
* the reset() method will be called, allowing the channel state to perform
* any reset actions, like clearing status and id labels on any view panels.
*/
public class ChannelState extends Module implements ICallEventProvider, public class ChannelState extends Module implements ICallEventProvider,
IChangedAttributeProvider, IDecoderStateEventListener, IChangedAttributeProvider, IDecoderStateEventListener,
IDecoderStateEventProvider, IMetadataProvider, ISquelchStateProvider IDecoderStateEventProvider, IMetadataProvider, ISquelchStateProvider
@@ -84,8 +52,6 @@ public class ChannelState extends Module implements ICallEventProvider,
public static final long RESET_TIMEOUT_DELAY = 2000; public static final long RESET_TIMEOUT_DELAY = 2000;
private State mState = State.IDLE; private State mState = State.IDLE;
private long mTrafficChannelTimeout =
DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS;
private Listener<CallEvent> mCallEventListener; private Listener<CallEvent> mCallEventListener;
private Listener<ChangedAttribute> mChangedAttributeListener; private Listener<ChangedAttribute> mChangedAttributeListener;
@@ -98,12 +64,15 @@ public class ChannelState extends Module implements ICallEventProvider,
private boolean mSquelchLocked = false; private boolean mSquelchLocked = false;
private boolean mSelected = false; private boolean mSelected = false;
private long mStandardChannelFadeTimeout = FADE_TIMEOUT_DELAY;
private long mTrafficChannelFadeTimeout =
DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000;
private long mFadeTimeout; private long mFadeTimeout;
private long mResetTimeout; private long mEndTimeout;
private StateMonitor mStateMonitor = new StateMonitor(); private StateMonitor mStateMonitor = new StateMonitor();
private AtomicBoolean mMonitoring = new AtomicBoolean(); private AtomicBoolean mMonitoring = new AtomicBoolean();
private ScheduledFuture<?> mRunningMonitor; private ScheduledFuture<?> mStateMonitorFuture;
private ThreadPoolManager mThreadPoolManager; private ThreadPoolManager mThreadPoolManager;
private ChannelType mChannelType; private ChannelType mChannelType;
@@ -111,15 +80,48 @@ public class ChannelState extends Module implements ICallEventProvider,
private TrafficChannelStatusListener mTrafficChannelStatusListener; private TrafficChannelStatusListener mTrafficChannelStatusListener;
private CallEvent mTrafficChannelCallEvent; private CallEvent mTrafficChannelCallEvent;
/**
* Channel state tracks the overall state of all processing modules and decoders
* configured for the channel and provides squelch control events.
*
* Uses a state enumeration that defines allowable channel state transitions in
* order to track a call or data decode event from start to finish. Uses a
* timer to monitor for inactivity and to provide a FADE period that indicates
* to the user that the activity has stopped while continuing to provide details
* about the call, before the state is reset to IDLE.
*
* Since this class is multi-threaded between decoder events and the internal
* timer that monitors for inactivity, this class uses the mState object for
* thread synchronization.
*
* State Descriptions:
* IDLE Normal state. No voice or data call activity
* CALL/DATA/ENCRYPTED/CONTROL
* Decoding states.
* FADE The phase after a voice or data call when either an explicit
* call end has been received, or when no new signalling updates
* have been received, and the fade timer has expired. This phase
* allows for gui updates to signal to the user that the call is
* ended, while continuing to display the call details for the user
* TEARDOWN Indicates a traffic channel that will be torn down for reuse.
*/
public ChannelState( ThreadPoolManager manager, ChannelType channelType ) public ChannelState( ThreadPoolManager manager, ChannelType channelType )
{ {
mThreadPoolManager = manager; mThreadPoolManager = manager;
mChannelType = channelType; mChannelType = channelType;
} }
/**
* Resets this channel state and prepares it for reuse.
*/
@Override @Override
public void reset() public void reset()
{ {
broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) );
broadcast( new MetadataReset() );
mState = State.IDLE;
broadcast( ChangedAttribute.CHANNEL_STATE );
} }
@Override @Override
@@ -131,6 +133,7 @@ public class ChannelState extends Module implements ICallEventProvider,
public void stop() public void stop()
{ {
mSquelchLocked = false; mSquelchLocked = false;
stopMonitor();
} }
public void dispose() public void dispose()
@@ -145,9 +148,34 @@ public class ChannelState extends Module implements ICallEventProvider,
mStateMonitor = null; mStateMonitor = null;
} }
private boolean isStandardChannel()
{
return mChannelType == ChannelType.STANDARD;
}
private boolean isTrafficChannel()
{
return mChannelType == ChannelType.TRAFFIC;
}
public void setStandardChannelTimeout( long milliseconds )
{
mStandardChannelFadeTimeout = milliseconds;
if( mChannelType == ChannelType.STANDARD )
{
mFadeTimeout = mStandardChannelFadeTimeout;
}
}
public void setTrafficChannelTimeout( long milliseconds ) public void setTrafficChannelTimeout( long milliseconds )
{ {
mTrafficChannelTimeout = milliseconds; mTrafficChannelFadeTimeout = milliseconds;
if( mChannelType == ChannelType.TRAFFIC )
{
mFadeTimeout = mTrafficChannelFadeTimeout;
}
} }
public void setSelected( boolean selected ) public void setSelected( boolean selected )
@@ -165,14 +193,6 @@ public class ChannelState extends Module implements ICallEventProvider,
return mState; return mState;
} }
private boolean isIdleOrEndingState()
{
return mState == State.FADE ||
mState == State.END ||
mState == State.IDLE;
}
/** /**
* Starts the state monitor running when we're not in IDLE state. Calls to * Starts the state monitor running when we're not in IDLE state. Calls to
* start an already started monitor are ignored. * start an already started monitor are ignored.
@@ -181,12 +201,12 @@ public class ChannelState extends Module implements ICallEventProvider,
{ {
if( mMonitoring.compareAndSet( false, true ) ) if( mMonitoring.compareAndSet( false, true ) )
{ {
if( mRunningMonitor == null && mStateMonitor != null ) if( mStateMonitorFuture == null && mStateMonitor != null )
{ {
try try
{ {
mRunningMonitor = mThreadPoolManager.scheduleFixedRate( mStateMonitorFuture = mThreadPoolManager.scheduleFixedRate(
ThreadType.DECODER, mStateMonitor, 20, TimeUnit.MILLISECONDS ); ThreadType.DECODER, mStateMonitor, 20, TimeUnit.MILLISECONDS );
} }
catch( RejectedExecutionException ree ) catch( RejectedExecutionException ree )
{ {
@@ -210,12 +230,17 @@ public class ChannelState extends Module implements ICallEventProvider,
{ {
if( mMonitoring.compareAndSet( true, false ) ) if( mMonitoring.compareAndSet( true, false ) )
{ {
if( mRunningMonitor != null ) if( mStateMonitorFuture != null )
{ {
mThreadPoolManager.cancel( mRunningMonitor ); boolean success = mThreadPoolManager.cancel( mStateMonitorFuture );
if( !success )
{
mLog.error("Couldn't stop monitoring scheduled future" );
}
} }
mRunningMonitor = null; mStateMonitorFuture = null;
} }
} }
@@ -224,20 +249,13 @@ public class ChannelState extends Module implements ICallEventProvider,
*/ */
private void updateFadeTimeout() private void updateFadeTimeout()
{ {
/* Standard channel uses the FADE_TIMEOUT_DELAY and traffic channels if( isTrafficChannel() )
* use the TRAFFIC_FADE_TIMEOUT_DELAY. Some traffic channels (e.g. MPT)
* don't have signaling on the traffic channel that can be used to
* update the fade timeout, so we set the timeout value larger to ensure
* the channel doesn't fade before the end of the call. */
long current = System.currentTimeMillis();
long timeout = current +
( mChannelType == ChannelType.STANDARD ? FADE_TIMEOUT_DELAY :
mTrafficChannelTimeout );
if( timeout > mFadeTimeout )
{ {
mFadeTimeout = timeout; mFadeTimeout = System.currentTimeMillis() + mTrafficChannelFadeTimeout;
}
else
{
mFadeTimeout = System.currentTimeMillis() + mStandardChannelFadeTimeout;
} }
} }
@@ -246,13 +264,13 @@ public class ChannelState extends Module implements ICallEventProvider,
*/ */
private void updateResetTimeout() private void updateResetTimeout()
{ {
if( mChannelType == ChannelType.TRAFFIC ) if( isTrafficChannel() )
{ {
mResetTimeout = System.currentTimeMillis(); mEndTimeout = System.currentTimeMillis();
} }
else else
{ {
mResetTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY; mEndTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY;
} }
} }
@@ -294,134 +312,129 @@ public class ChannelState extends Module implements ICallEventProvider,
*/ */
protected void setState( State state ) protected void setState( State state )
{ {
if( mState == state ) synchronized( mState )
{ {
switch( state ) if( state == mState )
{ {
case CALL: if( State.CALL_STATES.contains( state ) )
case CONTROL: {
case DATA:
case ENCRYPTED:
updateFadeTimeout(); updateFadeTimeout();
break; }
default:
break;
} }
} else if( state != mState && mState.canChangeTo( state ) )
else if( mState.canChangeTo( state ) )
{
switch( state )
{ {
case CONTROL: switch( state )
//Don't allow traffic channels to be control channels, {
//otherwise they won't get call tear down case CONTROL:
if( mChannelType != ChannelType.TRAFFIC ) //Don't allow traffic channels to be control channels,
{ //otherwise they can't transition to call tear down
if( isStandardChannel() )
{
broadcast( SquelchState.SQUELCH );
updateFadeTimeout();
startMonitor();
mState = state;
broadcast( ChangedAttribute.CHANNEL_STATE );
}
break;
case DATA:
case ENCRYPTED:
broadcast( SquelchState.SQUELCH ); broadcast( SquelchState.SQUELCH );
updateFadeTimeout(); updateFadeTimeout();
startMonitor(); startMonitor();
mState = state; mState = state;
broadcast( ChangedAttribute.CHANNEL_STATE ); broadcast( ChangedAttribute.CHANNEL_STATE );
} break;
break; case CALL:
case DATA: broadcast( SquelchState.UNSQUELCH );
case ENCRYPTED: updateFadeTimeout();
broadcast( SquelchState.SQUELCH ); startMonitor();
updateFadeTimeout(); mState = state;
startMonitor(); broadcast( ChangedAttribute.CHANNEL_STATE );
mState = state; break;
broadcast( ChangedAttribute.CHANNEL_STATE ); case FADE:
break; processFadeState();
case CALL: break;
broadcast( SquelchState.UNSQUELCH ); case IDLE:
updateFadeTimeout(); processIdleState();
startMonitor(); break;
mState = state; case TEARDOWN:
broadcast( ChangedAttribute.CHANNEL_STATE ); processTeardownState();
break; break;
case FADE: case RESET:
broadcast( SquelchState.SQUELCH ); mState = State.IDLE;
updateResetTimeout(); break;
startMonitor(); default:
mState = state; break;
broadcast( ChangedAttribute.CHANNEL_STATE ); }
break; }
case END: else if( mState == State.ENCRYPTED && state == State.CALL )
broadcast( SquelchState.SQUELCH ); {
broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) ); updateFadeTimeout();
broadcast( new MetadataReset() ); }
mState = state; else
broadcast( ChangedAttribute.CHANNEL_STATE ); {
break; mLog.debug( "Can't change from [" + mState + "] to [" + state + "]" );
case IDLE:
broadcast( SquelchState.SQUELCH );
stopMonitor();
if( mChannelType == ChannelType.TRAFFIC )
{
if( mTrafficChannelStatusListener != null )
{
mTrafficChannelStatusListener.callEnd();
mTrafficChannelStatusListener = null;
}
if( mTrafficChannelCallEvent != null )
{
mTrafficChannelCallEvent.end();
broadcast( mTrafficChannelCallEvent );
mTrafficChannelCallEvent = null;
}
}
mState = state;
broadcast( ChangedAttribute.CHANNEL_STATE );
break;
default:
break;
} }
}
else
{
mLog.debug( "Can't change from [" + mState + "] to [" + state + "]" );
} }
} }
public class StateMonitor implements Runnable /**
* Sets the state and processes related actions
*/
private void processFadeState()
{ {
@Override updateResetTimeout();
public void run() mState = State.FADE;
startMonitor();
broadcast( SquelchState.SQUELCH );
broadcast( ChangedAttribute.CHANNEL_STATE );
}
private void processIdleState()
{
broadcast( SquelchState.SQUELCH );
if( mState == State.FADE )
{ {
try broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) );
{ broadcast( new MetadataReset() );
if( getState() == State.FADE ) }
{
if( mResetTimeout <= System.currentTimeMillis() ) stopMonitor();
{ mState = State.IDLE;
setState( State.END );
} broadcast( ChangedAttribute.CHANNEL_STATE );
} }
else if( getState() == State.END )
{ private void processTeardownState()
setState( State.IDLE ); {
} broadcast( SquelchState.SQUELCH );
else
{ stopMonitor();
if( mFadeTimeout <= System.currentTimeMillis() )
{ mState = State.TEARDOWN;
setState( State.FADE );
} broadcast( ChangedAttribute.CHANNEL_STATE );
}
} if( mTrafficChannelCallEvent != null )
catch( Exception e ) {
{ mTrafficChannelCallEvent.end();
mLog.error( "Exception thrown while state monitor is running " +
"- state [" + getState() + broadcast( mTrafficChannelCallEvent );
"] current [" + System.currentTimeMillis() + mTrafficChannelCallEvent = null;
"] mResetTimeout [" + mResetTimeout + }
"] mFadeTimeout [" + mFadeTimeout +
"]", e ); if( mTrafficChannelStatusListener != null )
} {
mTrafficChannelStatusListener.callEnd();
mTrafficChannelStatusListener = null;
}
else
{
mLog.error( "Traffic channel status listener was null - couldn't "
+ "teardown the channel");
} }
} }
@@ -615,35 +628,95 @@ public class ChannelState extends Module implements ICallEventProvider,
broadcast( SquelchState.UNSQUELCH ); broadcast( SquelchState.UNSQUELCH );
mSquelchLocked = true; mSquelchLocked = true;
break; break;
case CHANGE_CALL_TIMEOUT:
if( event instanceof ChangeChannelTimeoutEvent )
{
ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent)event;
if( timeout.getChannelType() == ChannelType.STANDARD )
{
setStandardChannelTimeout( timeout.getCallTimeout() );
}
else
{
setTrafficChannelTimeout( timeout.getCallTimeout() );
}
}
case CONTINUATION: case CONTINUATION:
if( isIdleOrEndingState() )
{
setState( event.getState() );
}
else
{
updateFadeTimeout();
}
break;
case DECODE: case DECODE:
if( getState() == State.IDLE ) case START:
if( State.CALL_STATES.contains( event.getState() ) )
{ {
setState( event.getState() ); setState( event.getState() );
} }
break; break;
case END: case END:
setState( State.FADE ); if( isTrafficChannel() )
{
setState( State.TEARDOWN );
}
else
{
setState( State.FADE );
}
break; break;
case RESET: case RESET:
/* Channel State does not respond to reset events */ /* Channel State does not respond to reset events */
break; break;
case START:
setState( event.getState() );
break;
default: default:
break; break;
} }
} }
} }
} }
/**
* Monitors decoder state events to automatically transition the channel
* state to IDLE (standard channel) or to TEARDOWN (traffic channel) when
* decoding stops or the monitored channel returns to a no signal state.
*
* Provides a FADE transition state to allow for momentary decoding dropouts
* and to allow the user access to call details for a fade period upon
* call end.
*/
public class StateMonitor implements Runnable
{
@Override
public void run()
{
try
{
synchronized( mState )
{
if( State.CALL_STATES.contains( mState ) &&
mFadeTimeout <= System.currentTimeMillis() )
{
mState = State.FADE;
processFadeState();
}
else if( mState == State.FADE &&
mEndTimeout <= System.currentTimeMillis() )
{
if( isTrafficChannel() )
{
processTeardownState();
}
else
{
processIdleState();
}
}
}
}
catch( Exception e )
{
mLog.error( "Exception thrown while state monitor is running " +
"- state [" + getState() +
"] current [" + System.currentTimeMillis() +
"] mResetTimeout [" + mEndTimeout +
"] mFadeTimeout [" + mFadeTimeout +
"]", e );
}
}
}
} }

View File

@@ -14,12 +14,13 @@ import javax.swing.JLabel;
import javax.swing.JMenu; import javax.swing.JMenu;
import javax.swing.JPanel; import javax.swing.JPanel;
import module.decode.DecoderFactory;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import controller.channel.Channel;
import controller.channel.ChannelUtils;
import module.decode.DecoderFactory;
import net.miginfocom.swing.MigLayout;
import playlist.PlaylistManager; import playlist.PlaylistManager;
import record.RecorderType; import record.RecorderType;
import record.config.RecordConfiguration; import record.config.RecordConfiguration;
@@ -29,8 +30,6 @@ import settings.ColorSetting.ColorSettingName;
import settings.Setting; import settings.Setting;
import settings.SettingChangeListener; import settings.SettingChangeListener;
import settings.SettingsManager; import settings.SettingsManager;
import controller.channel.Channel;
import controller.channel.ChannelUtils;
public class ChannelStatePanel extends JPanel public class ChannelStatePanel extends JPanel
implements Listener<ChangedAttribute>, SettingChangeListener implements Listener<ChangedAttribute>, SettingChangeListener
@@ -237,25 +236,36 @@ public class ChannelStatePanel extends JPanel
Paint p = null; Paint p = null;
switch( mChannel.getChannelState().getState() ) final Channel channel = mChannel;
final ChannelState channelState = ( channel == null ? null : channel.getChannelState() );
if( channelState != null )
{ {
case CALL: switch( channelState.getState() )
p = getGradient( mColorTopCall, mColorMiddleCall ); {
break; case CALL:
case CONTROL: p = getGradient( mColorTopCall, mColorMiddleCall );
p = getGradient( mColorTopControl, mColorMiddleControl ); break;
break; case CONTROL:
case DATA: p = getGradient( mColorTopControl, mColorMiddleControl );
p = getGradient( mColorTopData, mColorMiddleData ); break;
break; case DATA:
case FADE: p = getGradient( mColorTopData, mColorMiddleData );
p = getGradient( mColorTopFade, mColorMiddleFade ); break;
break; case FADE:
case END: p = getGradient( mColorTopFade, mColorMiddleFade );
case IDLE: break;
default: case IDLE:
p = getGradient( mColorTopIdle, mColorMiddleIdle ); default:
break; p = getGradient( mColorTopIdle, mColorMiddleIdle );
break;
}
}
if( p == null )
{
p = getGradient( mColorTopIdle, mColorMiddleIdle );
} }
g2.setPaint( p ); g2.setPaint( p );

View File

@@ -56,6 +56,7 @@ public class DecoderStateEvent
public enum Event public enum Event
{ {
ALWAYS_UNSQUELCH, ALWAYS_UNSQUELCH,
CHANGE_CALL_TIMEOUT,
CONTINUATION, CONTINUATION,
DECODE, DECODE,
END, END,

View File

@@ -1,5 +1,10 @@
package module.decode.state; package module.decode.state;
import java.util.EnumSet;
/**
* Details the set of states for a channel and the allowable transition states
*/
public enum State public enum State
{ {
IDLE( "IDLE" ) IDLE( "IDLE" )
@@ -7,7 +12,8 @@ public enum State
@Override @Override
public boolean canChangeTo( State state ) public boolean canChangeTo( State state )
{ {
return true; return state != TEARDOWN &&
state != RESET;
} }
}, },
CALL( "CALL" ) CALL( "CALL" )
@@ -15,11 +21,11 @@ public enum State
@Override @Override
public boolean canChangeTo( State state ) public boolean canChangeTo( State state )
{ {
return state == CALL || return state == CONTROL ||
state == CONTROL ||
state == DATA || state == DATA ||
state == ENCRYPTED || state == ENCRYPTED ||
state == FADE; state == FADE ||
state == TEARDOWN;
} }
}, },
DATA( "DATA" ) DATA( "DATA" )
@@ -29,9 +35,9 @@ public enum State
{ {
return state == CALL || return state == CALL ||
state == CONTROL || state == CONTROL ||
state == DATA || state == ENCRYPTED ||
state == State.ENCRYPTED || state == FADE ||
state == FADE; state == TEARDOWN;
} }
}, },
ENCRYPTED( "ENCRYPTED" ) ENCRYPTED( "ENCRYPTED" )
@@ -39,7 +45,7 @@ public enum State
@Override @Override
public boolean canChangeTo( State state ) public boolean canChangeTo( State state )
{ {
return state == FADE; return state == FADE || state == TEARDOWN;
} }
}, },
CONTROL( "CONTROL" ) CONTROL( "CONTROL" )
@@ -47,7 +53,8 @@ public enum State
@Override @Override
public boolean canChangeTo( State state ) public boolean canChangeTo( State state )
{ {
return true; return state == IDLE ||
state == FADE;
} }
}, },
FADE( "FADE" ) FADE( "FADE" )
@@ -55,20 +62,36 @@ public enum State
@Override @Override
public boolean canChangeTo( State state ) public boolean canChangeTo( State state )
{ {
return state != FADE; //All states except fade allowed return state != FADE &&
} state != RESET;
}
}, },
END( "END" ) RESET( "RESET" )
{ {
@Override @Override
public boolean canChangeTo( State state ) public boolean canChangeTo(State state)
{ {
return state != FADE; //Fade is only disallowed state return state == IDLE;
} }
}; },
TEARDOWN( "TEARDOWN" )
{
@Override
public boolean canChangeTo(State state)
{
return state == RESET;
}
};
private String mDisplayValue; private String mDisplayValue;
public static final EnumSet<State> CALL_STATES =
EnumSet.of( CALL, CONTROL, DATA, ENCRYPTED );
public static final EnumSet<State> IDLE_STATES =
EnumSet.of( IDLE, FADE );
private State( String displayValue ) private State( String displayValue )
{ {
mDisplayValue = displayValue; mDisplayValue = displayValue;

View File

@@ -15,6 +15,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import record.config.RecordConfiguration; import record.config.RecordConfiguration;
import sample.Broadcaster;
import sample.Listener; import sample.Listener;
import source.config.SourceConfigTuner; import source.config.SourceConfigTuner;
import source.tuner.TunerChannel; import source.tuner.TunerChannel;
@@ -96,6 +97,17 @@ public class TrafficChannelManager extends Module
mPreviousDoNotMonitorCallEvent = null; mPreviousDoNotMonitorCallEvent = null;
} }
/**
* Provides a channel by either reusing an existing channel or constructing
* a new one, limited by the total number of constructed channels allowed.
*
* Note: you must enforce thread safety on the mTrafficChannelsInUse
* external to this method.
*
* @param channelNumber
* @param tunerChannel
* @return
*/
private Channel getChannel( String channelNumber, TunerChannel tunerChannel ) private Channel getChannel( String channelNumber, TunerChannel tunerChannel )
{ {
Channel channel = null; Channel channel = null;
@@ -151,6 +163,7 @@ public class TrafficChannelManager extends Module
channel.setSystem( mSystem, false ); channel.setSystem( mSystem, false );
channel.setSite( mSite, false ); channel.setSite( mSite, false );
channel.setName( "Traffic" ); channel.setName( "Traffic" );
mLog.debug("Channel not processing after setting enabled to true - why" );
} }
} }
} }
@@ -164,72 +177,79 @@ public class TrafficChannelManager extends Module
*/ */
private void process( TrafficChannelAllocationEvent event ) private void process( TrafficChannelAllocationEvent event )
{ {
if( mCallEventListener != null ) CallEvent callEvent = event.getCallEvent();
{
synchronized( mCallEventListener )
{
CallEvent callEvent = event.getCallEvent();
/* Check for duplicate events and suppress */ /* Check for duplicate events and suppress */
if( mTrafficChannelsInUse.containsKey( callEvent.getChannel() ) ) synchronized( mTrafficChannelsInUse )
{
if( mTrafficChannelsInUse.containsKey( callEvent.getChannel() ) )
{
return;
}
long frequency = callEvent.getFrequency();
/* Check the from/to aliases for do not monitor priority */
if( isDoNotMonitor( callEvent ) )
{
callEvent.setCallEventType( CallEventType.CALL_DO_NOT_MONITOR );
if( isSameCallEvent( mPreviousDoNotMonitorCallEvent, callEvent ) )
{ {
return; return;
} }
else
long frequency = callEvent.getFrequency();
/* Check the from/to aliases for do not monitor priority */
if( isDoNotMonitor( callEvent ) )
{ {
callEvent.setCallEventType( CallEventType.CALL_DO_NOT_MONITOR ); mPreviousDoNotMonitorCallEvent = callEvent;
if( isSameCallEvent( mPreviousDoNotMonitorCallEvent, callEvent ) )
{
return;
}
else
{
mPreviousDoNotMonitorCallEvent = callEvent;
}
} }
else if( frequency > 0 ) }
else if( frequency > 0 )
{
Channel channel = getChannel( callEvent.getChannel(),
new TunerChannel( Type.TRAFFIC, frequency,
mDecodeConfiguration.getDecoderType()
.getChannelBandwidth() ) );
if( channel != null )
{ {
Channel channel = getChannel( callEvent.getChannel(), if( channel.isProcessing() )
new TunerChannel( Type.TRAFFIC, frequency,
mDecodeConfiguration.getDecoderType()
.getChannelBandwidth() ) );
if( channel != null )
{ {
if( channel.isProcessing() ) callEvent.setDetails( "TRAFFIC CHANNEL ALLOCATED" );
{
ChannelState state = channel.getChannelState();
/* Register as a listener to be notified when the call ChannelState state = channel.getChannelState();
* is completed */
state.configureAsTrafficChannel(
new TrafficChannelStatusListener( this,
callEvent.getChannel() ), event );
/* Set state to call so that audio squelch state is correct */ /* Register as a listener to be notified when the call
state.setState( State.CALL ); * is completed */
} state.configureAsTrafficChannel(
else new TrafficChannelStatusListener( this,
{ callEvent.getChannel() ), event );
callEvent.setCallEventType( CallEventType.CALL_DETECT );
} /* Set state to call so that audio squelch state is correct */
state.setState( State.CALL );
} }
else else
{ {
callEvent.setCallEventType( CallEventType.CALL_DETECT ); callEvent.setCallEventType( CallEventType.CALL_DETECT );
callEvent.setDetails( "CHANNEL PROCESSING ERROR" );
} }
} }
else else
{ {
callEvent.setCallEventType( CallEventType.CALL_DETECT ); callEvent.setCallEventType( CallEventType.CALL_DETECT );
callEvent.setDetails( "NO TUNER AVAILABLE" );
} }
}
else
{
callEvent.setCallEventType( CallEventType.CALL_DETECT );
callEvent.setDetails( "UNKNOWN FREQUENCY" );
}
mCallEventListener.receive( callEvent ); final Listener<CallEvent> listener = mCallEventListener;
if( listener != null )
{
listener.receive( callEvent );
} }
} }
} }
@@ -370,14 +390,18 @@ public class TrafficChannelManager extends Module
*/ */
public void callEnd( String channelNumber ) public void callEnd( String channelNumber )
{ {
if( channelNumber != null && mTrafficChannelsInUse.containsKey( channelNumber ) ) synchronized( mTrafficChannelsInUse )
{ {
Channel channel = mTrafficChannelsInUse.get( channelNumber ); if( channelNumber != null &&
mTrafficChannelsInUse.containsKey( channelNumber ) )
{
Channel channel = mTrafficChannelsInUse.get( channelNumber );
mTrafficChannelsInUse.remove( channelNumber ); mTrafficChannelsInUse.remove( channelNumber );
/* Disable the channel and broadcast a notification */ /* Disable the channel and broadcast a notification */
channel.setEnabled( false, true ); channel.setEnabled( false, true );
}
} }
} }

View File

@@ -246,10 +246,6 @@ public abstract class TunerController implements Tunable
{ {
mTunedChannels.remove( tunerChannelSource.getTunerChannel() ); mTunedChannels.remove( tunerChannelSource.getTunerChannel() );
} }
else
{
mLog.error( "Request to release channel that is not in current tuned channels set" );
}
} }
/** /**