From debdc39a5fe79110c1b69933453f1aa7f87206ab Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Mon, 1 Feb 2016 09:17:56 +0000 Subject: [PATCH] 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 --- .gitignore | 3 +- src/audio/AudioChannelPanel.java | 8 + src/bits/MessageFramer.java | 10 + src/controller/ThreadPoolManager.java | 6 +- src/controller/channel/Channel.java | 7 +- src/module/ProcessingChain.java | 23 +- src/module/decode/DecoderFactory.java | 3 +- src/module/decode/event/CallEvent.java | 5 + src/module/decode/ltrnet/LTRNetDecoder.java | 1 + .../ltrstandard/LTRStandardDecoder.java | 1 + src/module/decode/mpt1327/MPT1327Decoder.java | 4 +- .../decode/mpt1327/MPT1327DecoderState.java | 68 ++- src/module/decode/p25/P25DecoderState.java | 6 +- src/module/decode/p25/message/P25Message.java | 2 + .../decode/passport/PassportDecoder.java | 1 + .../state/ChangeChannelTimeoutEvent.java | 29 ++ src/module/decode/state/ChannelState.java | 477 ++++++++++-------- .../decode/state/ChannelStatePanel.java | 56 +- .../decode/state/DecoderStateEvent.java | 1 + src/module/decode/state/State.java | 59 ++- .../decode/state/TrafficChannelManager.java | 136 +++-- src/source/tuner/TunerController.java | 4 - 22 files changed, 568 insertions(+), 342 deletions(-) create mode 100644 src/module/decode/state/ChangeChannelTimeoutEvent.java diff --git a/.gitignore b/.gitignore index 7599d847..7be9efe9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /*.class /classes* -/product* \ No newline at end of file +/product* +/bin/ diff --git a/src/audio/AudioChannelPanel.java b/src/audio/AudioChannelPanel.java index 298e6b18..207cc403 100644 --- a/src/audio/AudioChannelPanel.java +++ b/src/audio/AudioChannelPanel.java @@ -230,12 +230,20 @@ public class AudioChannelPanel extends JPanel mFrom.setText( from.getValue() ); updateAlias( mFromAlias, from.getAlias() ); } + else + { + mFrom.setText( "-----" ); + } if( to != null ) { mTo.setText( to.getValue() ); updateAlias( mToAlias, to.getAlias() ); } + else + { + mTo.setText( "-----" ); + } } } ); diff --git a/src/bits/MessageFramer.java b/src/bits/MessageFramer.java index 99d07888..cd41737c 100644 --- a/src/bits/MessageFramer.java +++ b/src/bits/MessageFramer.java @@ -58,6 +58,16 @@ public class MessageFramer implements Listener, mMessageLength = messageLength; } + public void reset() + { + for( MessageAssembler assembler: mMessageAssemblers ) + { + assembler.dispose(); + } + + mMessageAssemblers.clear(); + } + public void dispose() { mBroadcaster.dispose(); diff --git a/src/controller/ThreadPoolManager.java b/src/controller/ThreadPoolManager.java index 1f727e80..0ca9a00b 100644 --- a/src/controller/ThreadPoolManager.java +++ b/src/controller/ThreadPoolManager.java @@ -64,11 +64,13 @@ public class ThreadPoolManager 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 ); + + return success; } public int getTaskCount( ThreadType type ) diff --git a/src/controller/channel/Channel.java b/src/controller/channel/Channel.java index 3503a0e7..e23e5d30 100644 --- a/src/controller/channel/Channel.java +++ b/src/controller/channel/Channel.java @@ -809,8 +809,7 @@ public class Channel extends Configuration { if( mProcessingChain == null ) { - mProcessingChain = new ProcessingChain( - mResourceManager.getThreadPoolManager() ); + mProcessingChain = new ProcessingChain(); /* Get the optional alias list for the decode modules to use */ AliasList aliasList = mResourceManager.getPlaylistManager() @@ -901,6 +900,8 @@ public class Channel extends Configuration { if( !mEnabled && mProcessingChain != null ) { + fireChannelEvent( Event.CHANNEL_PROCESSING_STOPPED ); + mProcessingChain.stop(); if( mChannelType == ChannelType.STANDARD ) @@ -910,8 +911,6 @@ public class Channel extends Configuration } mSelected = false; - - fireChannelEvent( Event.CHANNEL_PROCESSING_STOPPED ); } } diff --git a/src/module/ProcessingChain.java b/src/module/ProcessingChain.java index 5f2ff6cf..c8e3da4f 100644 --- a/src/module/ProcessingChain.java +++ b/src/module/ProcessingChain.java @@ -99,8 +99,7 @@ public class ProcessingChain implements IChannelEventListener private Broadcaster mRealBufferBroadcaster = new Broadcaster<>(); private Broadcaster mSquelchStateBroadcaster = new Broadcaster<>(); - private ThreadPoolManager mThreadPoolManager; - private ScheduledFuture mBufferProcessingTask; +// private ThreadPoolManager mThreadPoolManager; private AtomicBoolean mRunning = new AtomicBoolean(); protected Source mSource; @@ -109,14 +108,8 @@ public class ProcessingChain implements IChannelEventListener public ProcessingChain() { - this( new ThreadPoolManager() ); } - public ProcessingChain( ThreadPoolManager threadManager ) - { - mThreadPoolManager = threadManager; - } - public void dispose() { stop(); @@ -128,14 +121,6 @@ public class ProcessingChain implements IChannelEventListener mModules.clear(); -// mComplexReceiver.dispose(); -// mComplexReceiver = null; -// -// mRealReceiver.dispose(); -// mRealReceiver = null; - - mThreadPoolManager = null; - mBufferProcessingTask = null; mFrequencyCorrectionController = null; mAudioPacketBroadcaster.dispose(); @@ -496,12 +481,6 @@ public class ProcessingChain implements IChannelEventListener mSource = null; } - if( mBufferProcessingTask != null ) - { - mThreadPoolManager.cancel( mBufferProcessingTask ); - mBufferProcessingTask = null; - } - /* Stop each of the modules */ for( Module module: mModules ) { diff --git a/src/module/decode/DecoderFactory.java b/src/module/decode/DecoderFactory.java index 59d2f3d3..2f40e741 100644 --- a/src/module/decode/DecoderFactory.java +++ b/src/module/decode/DecoderFactory.java @@ -228,7 +228,8 @@ public class DecoderFactory Sync sync = mptConfig.getSync(); 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 ) { diff --git a/src/module/decode/event/CallEvent.java b/src/module/decode/event/CallEvent.java index 000cb27c..28990ad9 100644 --- a/src/module/decode/event/CallEvent.java +++ b/src/module/decode/event/CallEvent.java @@ -310,6 +310,11 @@ public abstract class CallEvent implements Comparable @Override public int compareTo( CallEvent other ) { + if( other != null && other == this ) + { + return 0; + } + return Long.compare( mEventStart, other.getEventStartTime() ); } } diff --git a/src/module/decode/ltrnet/LTRNetDecoder.java b/src/module/decode/ltrnet/LTRNetDecoder.java index 54e4f0e2..095dffe4 100644 --- a/src/module/decode/ltrnet/LTRNetDecoder.java +++ b/src/module/decode/ltrnet/LTRNetDecoder.java @@ -112,6 +112,7 @@ public class LTRNetDecoder extends Decoder implements IRealBufferListener, Instr @Override public void reset() { + mLTRMessageFramer.reset(); } @Override diff --git a/src/module/decode/ltrstandard/LTRStandardDecoder.java b/src/module/decode/ltrstandard/LTRStandardDecoder.java index 4242c11f..a39d659c 100644 --- a/src/module/decode/ltrstandard/LTRStandardDecoder.java +++ b/src/module/decode/ltrstandard/LTRStandardDecoder.java @@ -76,6 +76,7 @@ public class LTRStandardDecoder extends Decoder implements IRealBufferListener @Override public void reset() { + mLTRMessageFramer.reset(); } @Override diff --git a/src/module/decode/mpt1327/MPT1327Decoder.java b/src/module/decode/mpt1327/MPT1327Decoder.java index f08a98c2..679da857 100644 --- a/src/module/decode/mpt1327/MPT1327Decoder.java +++ b/src/module/decode/mpt1327/MPT1327Decoder.java @@ -301,8 +301,8 @@ public class MPT1327Decoder extends Decoder @Override public void reset() { - // TODO Auto-generated method stub - + mControlMessageFramer.reset(); + mTrafficMessageFramer.reset(); } @Override diff --git a/src/module/decode/mpt1327/MPT1327DecoderState.java b/src/module/decode/mpt1327/MPT1327DecoderState.java index ad40aa52..cc22c847 100644 --- a/src/module/decode/mpt1327/MPT1327DecoderState.java +++ b/src/module/decode/mpt1327/MPT1327DecoderState.java @@ -26,9 +26,11 @@ import java.util.TreeSet; import message.Message; import module.decode.DecoderType; +import module.decode.config.DecodeConfiguration; import module.decode.event.CallEvent; import module.decode.event.CallEvent.CallEventType; import module.decode.mpt1327.MPT1327Message.IdentType; +import module.decode.state.ChangeChannelTimeoutEvent; import module.decode.state.ChangedAttribute; import module.decode.state.DecoderState; import module.decode.state.DecoderStateEvent; @@ -41,6 +43,8 @@ import org.slf4j.LoggerFactory; import alias.Alias; import alias.AliasList; +import audio.metadata.Metadata; +import audio.metadata.MetadataType; import controller.channel.Channel.ChannelType; import controller.channel.map.ChannelMap; @@ -60,15 +64,18 @@ public class MPT1327DecoderState extends DecoderState private ChannelMap mChannelMap; private long mFrequency = 0; + private long mCallTimeout; - public MPT1327DecoderState( AliasList aliasList, ChannelMap channelMap, - ChannelType channelType ) + public MPT1327DecoderState( AliasList aliasList, + ChannelMap channelMap, + ChannelType channelType, + long callTimeout ) { super( aliasList ); mChannelMap = channelMap; - mChannelType = channelType; + mCallTimeout = callTimeout; } @Override @@ -252,8 +259,42 @@ public class MPT1327DecoderState extends DecoderState /* Traffic Channel Events */ case CLEAR: + mChannelNumber = mpt.getChannel(); + + broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) ); + break; 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; default: break; @@ -276,6 +317,25 @@ public class MPT1327DecoderState extends DecoderState mToTalkgroup = null; 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() diff --git a/src/module/decode/p25/P25DecoderState.java b/src/module/decode/p25/P25DecoderState.java index e1981197..6c096666 100644 --- a/src/module/decode/p25/P25DecoderState.java +++ b/src/module/decode/p25/P25DecoderState.java @@ -436,7 +436,7 @@ public class P25DecoderState extends DecoderState { 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 ) { @@ -1056,7 +1056,7 @@ public class P25DecoderState extends DecoderState } break; case CALL_TERMINATION_OR_CANCELLATION: - broadcast( new DecoderStateEvent( this, Event.END, State.END ) ); + broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) ); mCurrentCallEvent = null; if( !( ldu instanceof CallTermination ) ) @@ -1520,7 +1520,7 @@ public class P25DecoderState extends DecoderState private void processTSBK( TSBKMessage tsbk ) { /* 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 ) { diff --git a/src/module/decode/p25/message/P25Message.java b/src/module/decode/p25/message/P25Message.java index e8421211..dc33108f 100644 --- a/src/module/decode/p25/message/P25Message.java +++ b/src/module/decode/p25/message/P25Message.java @@ -87,6 +87,8 @@ public class P25Message extends Message sb.append( " " ); sb.append( getDUID().getLabel() ); + sb.append( " DATA UNIT ID:" + mMessage.getInt( DUID ) ); + return sb.toString(); } diff --git a/src/module/decode/passport/PassportDecoder.java b/src/module/decode/passport/PassportDecoder.java index 1e0f4e27..b6d46a2b 100644 --- a/src/module/decode/passport/PassportDecoder.java +++ b/src/module/decode/passport/PassportDecoder.java @@ -103,6 +103,7 @@ public class PassportDecoder extends Decoder @Override public void reset() { + mPassportMessageFramer.reset(); } @Override diff --git a/src/module/decode/state/ChangeChannelTimeoutEvent.java b/src/module/decode/state/ChangeChannelTimeoutEvent.java new file mode 100644 index 00000000..a2950171 --- /dev/null +++ b/src/module/decode/state/ChangeChannelTimeoutEvent.java @@ -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; + } +} diff --git a/src/module/decode/state/ChannelState.java b/src/module/decode/state/ChannelState.java index 8a76047f..71f999c9 100644 --- a/src/module/decode/state/ChannelState.java +++ b/src/module/decode/state/ChannelState.java @@ -1,6 +1,6 @@ /******************************************************************************* * 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 * 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.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.LoggerFactory; -import sample.Listener; import audio.metadata.IMetadataProvider; import audio.metadata.Metadata; import audio.metadata.MetadataReset; @@ -41,38 +34,13 @@ import audio.squelch.SquelchState; import controller.ThreadPoolManager; import controller.ThreadPoolManager.ThreadType; 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, IChangedAttributeProvider, IDecoderStateEventListener, IDecoderStateEventProvider, IMetadataProvider, ISquelchStateProvider @@ -84,8 +52,6 @@ public class ChannelState extends Module implements ICallEventProvider, public static final long RESET_TIMEOUT_DELAY = 2000; private State mState = State.IDLE; - private long mTrafficChannelTimeout = - DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS; private Listener mCallEventListener; private Listener mChangedAttributeListener; @@ -98,12 +64,15 @@ public class ChannelState extends Module implements ICallEventProvider, private boolean mSquelchLocked = 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 mResetTimeout; + private long mEndTimeout; private StateMonitor mStateMonitor = new StateMonitor(); private AtomicBoolean mMonitoring = new AtomicBoolean(); - private ScheduledFuture mRunningMonitor; + private ScheduledFuture mStateMonitorFuture; private ThreadPoolManager mThreadPoolManager; private ChannelType mChannelType; @@ -111,15 +80,48 @@ public class ChannelState extends Module implements ICallEventProvider, private TrafficChannelStatusListener mTrafficChannelStatusListener; 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 ) { mThreadPoolManager = manager; mChannelType = channelType; } + /** + * Resets this channel state and prepares it for reuse. + */ @Override public void reset() { + broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) ); + broadcast( new MetadataReset() ); + + mState = State.IDLE; + broadcast( ChangedAttribute.CHANNEL_STATE ); } @Override @@ -131,6 +133,7 @@ public class ChannelState extends Module implements ICallEventProvider, public void stop() { mSquelchLocked = false; + stopMonitor(); } public void dispose() @@ -145,9 +148,34 @@ public class ChannelState extends Module implements ICallEventProvider, 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 ) { - mTrafficChannelTimeout = milliseconds; + mTrafficChannelFadeTimeout = milliseconds; + + if( mChannelType == ChannelType.TRAFFIC ) + { + mFadeTimeout = mTrafficChannelFadeTimeout; + } } public void setSelected( boolean selected ) @@ -165,14 +193,6 @@ public class ChannelState extends Module implements ICallEventProvider, 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 * start an already started monitor are ignored. @@ -181,12 +201,12 @@ public class ChannelState extends Module implements ICallEventProvider, { if( mMonitoring.compareAndSet( false, true ) ) { - if( mRunningMonitor == null && mStateMonitor != null ) + if( mStateMonitorFuture == null && mStateMonitor != null ) { try { - mRunningMonitor = mThreadPoolManager.scheduleFixedRate( - ThreadType.DECODER, mStateMonitor, 20, TimeUnit.MILLISECONDS ); + mStateMonitorFuture = mThreadPoolManager.scheduleFixedRate( + ThreadType.DECODER, mStateMonitor, 20, TimeUnit.MILLISECONDS ); } catch( RejectedExecutionException ree ) { @@ -210,12 +230,17 @@ public class ChannelState extends Module implements ICallEventProvider, { 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() { - /* Standard channel uses the FADE_TIMEOUT_DELAY and traffic channels - * 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 ) + if( isTrafficChannel() ) { - 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() { - if( mChannelType == ChannelType.TRAFFIC ) + if( isTrafficChannel() ) { - mResetTimeout = System.currentTimeMillis(); + mEndTimeout = System.currentTimeMillis(); } 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 ) { - if( mState == state ) + synchronized( mState ) { - switch( state ) + if( state == mState ) { - case CALL: - case CONTROL: - case DATA: - case ENCRYPTED: + if( State.CALL_STATES.contains( state ) ) + { updateFadeTimeout(); - break; - default: - break; + } } - } - else if( mState.canChangeTo( state ) ) - { - switch( state ) + else if( state != mState && mState.canChangeTo( state ) ) { - case CONTROL: - //Don't allow traffic channels to be control channels, - //otherwise they won't get call tear down - if( mChannelType != ChannelType.TRAFFIC ) - { + switch( state ) + { + case CONTROL: + //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 ); updateFadeTimeout(); startMonitor(); mState = state; broadcast( ChangedAttribute.CHANNEL_STATE ); - } - break; - case DATA: - case ENCRYPTED: - broadcast( SquelchState.SQUELCH ); - updateFadeTimeout(); - startMonitor(); - mState = state; - broadcast( ChangedAttribute.CHANNEL_STATE ); - break; - case CALL: - broadcast( SquelchState.UNSQUELCH ); - updateFadeTimeout(); - startMonitor(); - mState = state; - broadcast( ChangedAttribute.CHANNEL_STATE ); - break; - case FADE: - broadcast( SquelchState.SQUELCH ); - updateResetTimeout(); - startMonitor(); - mState = state; - broadcast( ChangedAttribute.CHANNEL_STATE ); - break; - case END: - broadcast( SquelchState.SQUELCH ); - broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) ); - broadcast( new MetadataReset() ); - mState = state; - broadcast( ChangedAttribute.CHANNEL_STATE ); - break; - 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; + break; + case CALL: + broadcast( SquelchState.UNSQUELCH ); + updateFadeTimeout(); + startMonitor(); + mState = state; + broadcast( ChangedAttribute.CHANNEL_STATE ); + break; + case FADE: + processFadeState(); + break; + case IDLE: + processIdleState(); + break; + case TEARDOWN: + processTeardownState(); + break; + case RESET: + mState = State.IDLE; + break; + default: + break; + } } + else if( mState == State.ENCRYPTED && state == State.CALL ) + { + updateFadeTimeout(); + } + else + { + mLog.debug( "Can't change from [" + mState + "] to [" + state + "]" ); + } + } + } + + /** + * Sets the state and processes related actions + */ + private void processFadeState() + { + updateResetTimeout(); + mState = State.FADE; + startMonitor(); + + broadcast( SquelchState.SQUELCH ); + broadcast( ChangedAttribute.CHANNEL_STATE ); + } + + private void processIdleState() + { + broadcast( SquelchState.SQUELCH ); + + if( mState == State.FADE ) + { + broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) ); + broadcast( new MetadataReset() ); + } + + stopMonitor(); + mState = State.IDLE; + + broadcast( ChangedAttribute.CHANNEL_STATE ); + } + + private void processTeardownState() + { + broadcast( SquelchState.SQUELCH ); + + stopMonitor(); + + mState = State.TEARDOWN; + + broadcast( ChangedAttribute.CHANNEL_STATE ); + + if( mTrafficChannelCallEvent != null ) + { + mTrafficChannelCallEvent.end(); + + broadcast( mTrafficChannelCallEvent ); + mTrafficChannelCallEvent = null; + } + + if( mTrafficChannelStatusListener != null ) + { + mTrafficChannelStatusListener.callEnd(); + mTrafficChannelStatusListener = null; } else { - mLog.debug( "Can't change from [" + mState + "] to [" + state + "]" ); - } - } - - public class StateMonitor implements Runnable - { - @Override - public void run() - { - try - { - if( getState() == State.FADE ) - { - if( mResetTimeout <= System.currentTimeMillis() ) - { - setState( State.END ); - } - } - else if( getState() == State.END ) - { - setState( State.IDLE ); - } - else - { - if( mFadeTimeout <= System.currentTimeMillis() ) - { - setState( State.FADE ); - } - } - } - catch( Exception e ) - { - mLog.error( "Exception thrown while state monitor is running " + - "- state [" + getState() + - "] current [" + System.currentTimeMillis() + - "] mResetTimeout [" + mResetTimeout + - "] mFadeTimeout [" + mFadeTimeout + - "]", e ); - } + 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 ); mSquelchLocked = true; 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: - if( isIdleOrEndingState() ) - { - setState( event.getState() ); - } - else - { - updateFadeTimeout(); - } - break; case DECODE: - if( getState() == State.IDLE ) + case START: + if( State.CALL_STATES.contains( event.getState() ) ) { setState( event.getState() ); } break; case END: - setState( State.FADE ); + if( isTrafficChannel() ) + { + setState( State.TEARDOWN ); + } + else + { + setState( State.FADE ); + } break; case RESET: /* Channel State does not respond to reset events */ break; - case START: - setState( event.getState() ); - break; default: 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 ); + } + } + } } diff --git a/src/module/decode/state/ChannelStatePanel.java b/src/module/decode/state/ChannelStatePanel.java index b789fe35..3ce05a01 100644 --- a/src/module/decode/state/ChannelStatePanel.java +++ b/src/module/decode/state/ChannelStatePanel.java @@ -14,12 +14,13 @@ import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JPanel; -import module.decode.DecoderFactory; -import net.miginfocom.swing.MigLayout; - import org.slf4j.Logger; 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 record.RecorderType; import record.config.RecordConfiguration; @@ -29,8 +30,6 @@ import settings.ColorSetting.ColorSettingName; import settings.Setting; import settings.SettingChangeListener; import settings.SettingsManager; -import controller.channel.Channel; -import controller.channel.ChannelUtils; public class ChannelStatePanel extends JPanel implements Listener, SettingChangeListener @@ -236,26 +235,37 @@ public class ChannelStatePanel extends JPanel Graphics2D g2 = (Graphics2D)g.create(); Paint p = null; + + final Channel channel = mChannel; - switch( mChannel.getChannelState().getState() ) + final ChannelState channelState = ( channel == null ? null : channel.getChannelState() ); + + if( channelState != null ) { - case CALL: - p = getGradient( mColorTopCall, mColorMiddleCall ); - break; - case CONTROL: - p = getGradient( mColorTopControl, mColorMiddleControl ); - break; - case DATA: - p = getGradient( mColorTopData, mColorMiddleData ); - break; - case FADE: - p = getGradient( mColorTopFade, mColorMiddleFade ); - break; - case END: - case IDLE: - default: - p = getGradient( mColorTopIdle, mColorMiddleIdle ); - break; + switch( channelState.getState() ) + { + case CALL: + p = getGradient( mColorTopCall, mColorMiddleCall ); + break; + case CONTROL: + p = getGradient( mColorTopControl, mColorMiddleControl ); + break; + case DATA: + p = getGradient( mColorTopData, mColorMiddleData ); + break; + case FADE: + p = getGradient( mColorTopFade, mColorMiddleFade ); + break; + case IDLE: + default: + p = getGradient( mColorTopIdle, mColorMiddleIdle ); + break; + } + } + + if( p == null ) + { + p = getGradient( mColorTopIdle, mColorMiddleIdle ); } g2.setPaint( p ); diff --git a/src/module/decode/state/DecoderStateEvent.java b/src/module/decode/state/DecoderStateEvent.java index 19623bbc..fa60b7d8 100644 --- a/src/module/decode/state/DecoderStateEvent.java +++ b/src/module/decode/state/DecoderStateEvent.java @@ -56,6 +56,7 @@ public class DecoderStateEvent public enum Event { ALWAYS_UNSQUELCH, + CHANGE_CALL_TIMEOUT, CONTINUATION, DECODE, END, diff --git a/src/module/decode/state/State.java b/src/module/decode/state/State.java index 2820429d..141c5ea6 100644 --- a/src/module/decode/state/State.java +++ b/src/module/decode/state/State.java @@ -1,5 +1,10 @@ package module.decode.state; +import java.util.EnumSet; + +/** + * Details the set of states for a channel and the allowable transition states + */ public enum State { IDLE( "IDLE" ) @@ -7,7 +12,8 @@ public enum State @Override public boolean canChangeTo( State state ) { - return true; + return state != TEARDOWN && + state != RESET; } }, CALL( "CALL" ) @@ -15,11 +21,11 @@ public enum State @Override public boolean canChangeTo( State state ) { - return state == CALL || - state == CONTROL || + return state == CONTROL || state == DATA || state == ENCRYPTED || - state == FADE; + state == FADE || + state == TEARDOWN; } }, DATA( "DATA" ) @@ -29,9 +35,9 @@ public enum State { return state == CALL || state == CONTROL || - state == DATA || - state == State.ENCRYPTED || - state == FADE; + state == ENCRYPTED || + state == FADE || + state == TEARDOWN; } }, ENCRYPTED( "ENCRYPTED" ) @@ -39,7 +45,7 @@ public enum State @Override public boolean canChangeTo( State state ) { - return state == FADE; + return state == FADE || state == TEARDOWN; } }, CONTROL( "CONTROL" ) @@ -47,7 +53,8 @@ public enum State @Override public boolean canChangeTo( State state ) { - return true; + return state == IDLE || + state == FADE; } }, FADE( "FADE" ) @@ -55,20 +62,36 @@ public enum State @Override public boolean canChangeTo( State state ) { - return state != FADE; //All states except fade allowed - } + return state != FADE && + state != RESET; + } }, - END( "END" ) + RESET( "RESET" ) { - @Override - public boolean canChangeTo( State state ) - { - return state != FADE; //Fade is only disallowed state - } - }; + @Override + public boolean canChangeTo(State state) + { + return state == IDLE; + } + }, + + TEARDOWN( "TEARDOWN" ) + { + @Override + public boolean canChangeTo(State state) + { + return state == RESET; + } + }; private String mDisplayValue; + public static final EnumSet CALL_STATES = + EnumSet.of( CALL, CONTROL, DATA, ENCRYPTED ); + + public static final EnumSet IDLE_STATES = + EnumSet.of( IDLE, FADE ); + private State( String displayValue ) { mDisplayValue = displayValue; diff --git a/src/module/decode/state/TrafficChannelManager.java b/src/module/decode/state/TrafficChannelManager.java index 33a2ab1d..10e2bcc1 100644 --- a/src/module/decode/state/TrafficChannelManager.java +++ b/src/module/decode/state/TrafficChannelManager.java @@ -15,6 +15,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import record.config.RecordConfiguration; +import sample.Broadcaster; import sample.Listener; import source.config.SourceConfigTuner; import source.tuner.TunerChannel; @@ -96,10 +97,21 @@ public class TrafficChannelManager extends Module 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 ) { Channel channel = null; - + if( !mTrafficChannelsInUse.containsKey( channelNumber ) ) { for( Channel configuredChannel: mTrafficChannelPool ) @@ -151,10 +163,11 @@ public class TrafficChannelManager extends Module channel.setSystem( mSystem, false ); channel.setSite( mSite, false ); channel.setName( "Traffic" ); + mLog.debug("Channel not processing after setting enabled to true - why" ); } } } - + return channel; } @@ -164,72 +177,79 @@ public class TrafficChannelManager extends Module */ private void process( TrafficChannelAllocationEvent event ) { - if( mCallEventListener != null ) - { - synchronized( mCallEventListener ) - { - CallEvent callEvent = event.getCallEvent(); + CallEvent callEvent = event.getCallEvent(); - /* Check for duplicate events and suppress */ - if( mTrafficChannelsInUse.containsKey( callEvent.getChannel() ) ) + /* Check for duplicate events and suppress */ + 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; } - - long frequency = callEvent.getFrequency(); - - /* Check the from/to aliases for do not monitor priority */ - if( isDoNotMonitor( callEvent ) ) + else { - callEvent.setCallEventType( CallEventType.CALL_DO_NOT_MONITOR ); - - if( isSameCallEvent( mPreviousDoNotMonitorCallEvent, callEvent ) ) - { - return; - } - else - { - mPreviousDoNotMonitorCallEvent = callEvent; - } + mPreviousDoNotMonitorCallEvent = callEvent; } - else if( frequency > 0 ) - { - Channel channel = getChannel( callEvent.getChannel(), - new TunerChannel( Type.TRAFFIC, frequency, - mDecodeConfiguration.getDecoderType() - .getChannelBandwidth() ) ); + } + else if( frequency > 0 ) + { + Channel channel = getChannel( callEvent.getChannel(), + new TunerChannel( Type.TRAFFIC, frequency, + mDecodeConfiguration.getDecoderType() + .getChannelBandwidth() ) ); - if( channel != null ) + if( channel != null ) + { + if( channel.isProcessing() ) { - if( channel.isProcessing() ) - { - ChannelState state = channel.getChannelState(); - - /* Register as a listener to be notified when the call - * is completed */ - state.configureAsTrafficChannel( - new TrafficChannelStatusListener( this, - callEvent.getChannel() ), event ); - - /* Set state to call so that audio squelch state is correct */ - state.setState( State.CALL ); - } - else - { - callEvent.setCallEventType( CallEventType.CALL_DETECT ); - } + callEvent.setDetails( "TRAFFIC CHANNEL ALLOCATED" ); + + ChannelState state = channel.getChannelState(); + + /* Register as a listener to be notified when the call + * is completed */ + state.configureAsTrafficChannel( + new TrafficChannelStatusListener( this, + callEvent.getChannel() ), event ); + + /* Set state to call so that audio squelch state is correct */ + state.setState( State.CALL ); } else { callEvent.setCallEventType( CallEventType.CALL_DETECT ); + callEvent.setDetails( "CHANNEL PROCESSING ERROR" ); } } else { callEvent.setCallEventType( CallEventType.CALL_DETECT ); + callEvent.setDetails( "NO TUNER AVAILABLE" ); } - - mCallEventListener.receive( callEvent ); + } + else + { + callEvent.setCallEventType( CallEventType.CALL_DETECT ); + callEvent.setDetails( "UNKNOWN FREQUENCY" ); + } + + final Listener listener = mCallEventListener; + + if( listener != null ) + { + listener.receive( callEvent ); } } } @@ -370,14 +390,18 @@ public class TrafficChannelManager extends Module */ public void callEnd( String channelNumber ) { - if( channelNumber != null && mTrafficChannelsInUse.containsKey( channelNumber ) ) + synchronized( mTrafficChannelsInUse ) { - Channel channel = mTrafficChannelsInUse.get( channelNumber ); - - mTrafficChannelsInUse.remove( channelNumber ); + if( channelNumber != null && + mTrafficChannelsInUse.containsKey( channelNumber ) ) + { + Channel channel = mTrafficChannelsInUse.get( channelNumber ); + + mTrafficChannelsInUse.remove( channelNumber ); - /* Disable the channel and broadcast a notification */ - channel.setEnabled( false, true ); + /* Disable the channel and broadcast a notification */ + channel.setEnabled( false, true ); + } } } diff --git a/src/source/tuner/TunerController.java b/src/source/tuner/TunerController.java index 8d701fde..aacb5f6b 100644 --- a/src/source/tuner/TunerController.java +++ b/src/source/tuner/TunerController.java @@ -246,10 +246,6 @@ public abstract class TunerController implements Tunable { mTunedChannels.remove( tunerChannelSource.getTunerChannel() ); } - else - { - mLog.error( "Request to release channel that is not in current tuned channels set" ); - } } /**