Initial commit of redesigned internal processing and audio management.

Note: Several of the decoders are currently non-functioning in this
commit.  If you want the latest functioning codebase, please checkout
the commit prior to this one.  All decoders will eventually be updated
with this new architecture.

Currently broken:
- Logging and recording
- Aux decoders (Fleetsync/MDC/LoJack/Tait)
- LTR/Passport decoders

Changes in this commit:
- Module architecture for decoders and components with central
provisioning by the DecoderFactory
- Transition from Complex sample stream processing to float buffer
processing for improved memory management
- Redesigned filter structures - to support buffers and streams
- Resolved several memory leak issues
- Universal sharing of the thread pool manager
- Added centralized audio management with call/user prioritization and
new gui with mute/volume controls
- Embedded audio metadata for eventual streaming support
- Redesigned ProcessingChain to support independent re-use
- Added new TrafficChannelManager to handle traffic channel allocations
- Traffic Channel pool size and Call timeout available in P25 and
MPT1327 configs
- Redesigned channel and decoder gui components
This commit is contained in:
denny
2015-09-01 07:10:26 -04:00
parent 538a299246
commit f4d8ea281d
570 changed files with 21578 additions and 17012 deletions

View File

@@ -0,0 +1,83 @@
/*******************************************************************************
* SDR Trunk
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.state;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import message.Message;
import module.ProcessingChain;
import module.decode.state.DecoderStateEvent.Event;
/**
* Basic decoder channel state - provides the minimum channel state functionality
* to support an always un-squelched audio decoder.
*/
public class AlwaysUnsquelchedDecoderState extends DecoderState
{
private final static Logger mLog =
LoggerFactory.getLogger( AlwaysUnsquelchedDecoderState.class );
private String mProtocol;
public AlwaysUnsquelchedDecoderState( String protocol )
{
super( null );
mProtocol = protocol;
}
@Override
public void init()
{
mLog.debug( "init() - broadcasting unsquelch event" );
broadcast( new DecoderStateEvent( this, Event.ALWAYS_UNSQUELCH, State.IDLE ) );
}
@Override
public void reset()
{
/* No reset actions needed */
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "Activity Summary\n" );
sb.append( "\tDecoder:\t" );
sb.append( mProtocol );
sb.append( "\n\n" );
return sb.toString();
}
@Override
public void receive( Message t )
{
/* Not implemented */
}
@Override
public void receiveDecoderStateEvent( DecoderStateEvent event )
{
/* Not implemented */
}
}

View File

@@ -0,0 +1,29 @@
package module.decode.state;
public enum ChangedAttribute
{
AUDIO_TYPE,
CHANNEL_NUMBER,
CHANNEL_NAME,
CHANNEL_SITE_NUMBER,
CHANNEL_SITE_NUMBER_ALIAS,
CHANNEL_STATE,
DESCRIPTION,
FROM_TALKGROUP,
FROM_TALKGROUP_ALIAS,
FROM_TALKGROUP_TYPE,
MESSAGE,
MESSAGE_TYPE,
NAC,
SITE,
SITE_ALIAS,
SITE_NAME,
SOURCE,
SYSTEM,
SYSTEM_NAME,
SQUELCH,
TO_TALKGROUP,
TO_TALKGROUP_ALIAS,
TO_TALKGROUP_TYPE,
WACN;
}

View File

@@ -0,0 +1,150 @@
package module.decode.state;
import java.awt.Component;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JMenu;
import javax.swing.JPanel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.miginfocom.swing.MigLayout;
import playlist.PlaylistManager;
import settings.SettingsManager;
import controller.channel.Channel;
/**
* Channel Collection assembles a primary channel panel and any related traffic
* channel panels, provides contextual access to the primary channel and
* supports channel selection for the primary or any traffic channels.
*/
public class ChannelCollectionPanel extends JPanel
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger( ChannelCollectionPanel.class );
private PlaylistManager mPlaylistManager;
private SettingsManager mSettingsManager;
private List<ChannelStatePanel> mChannelPanels = new ArrayList<ChannelStatePanel>();
public ChannelCollectionPanel( PlaylistManager playlistManager,
SettingsManager settingsManager,
Channel channel )
{
mPlaylistManager = playlistManager;
mSettingsManager = settingsManager;
init( channel );
}
protected void init( Channel channel )
{
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]0[]") );
setOpaque( false );
ChannelStatePanel csp = new ChannelStatePanel( mPlaylistManager,
mSettingsManager, channel );
mChannelPanels.add( csp );
add( csp, "span,grow" );
}
public Channel getChannel()
{
if( mChannelPanels.size() >= 1 )
{
return mChannelPanels.get( 0 ).getChannel();
}
return null;
}
public void dispose()
{
for( ChannelStatePanel panel: mChannelPanels )
{
panel.dispose();
}
mChannelPanels.clear();
mPlaylistManager = null;
mSettingsManager = null;
}
/**
* Toggles the selection state of the channel if it matches the channel
* argument. Otherwise, sets the selection state to false.
*/
public void setSelectedChannel( Channel channel )
{
for( ChannelStatePanel panel: mChannelPanels )
{
if( panel.getChannel() == channel )
{
/* Toggle the selection state */
panel.getChannel().setSelected( !panel.getChannel().isSelected() );
panel.repaint();
}
else
{
if( panel.getChannel().isSelected() )
{
panel.getChannel().setSelected( false );
panel.repaint();
}
}
}
}
/**
* Returns the channel represented by the channel panel at the gui point
* location, to selecting the channel
*
* @param point - current location of the mouse
*
* @return - channel or null
*/
public Channel getChannelAt( Point point )
{
Component c = this.getComponentAt( point );
if( c instanceof ChannelCollectionPanel )
{
return ((ChannelCollectionPanel)c).getChannel();
}
else
{
return getChannel();
}
}
protected SettingsManager getSettingsManager()
{
return mSettingsManager;
}
public JMenu getContextMenu()
{
if( !mChannelPanels.isEmpty() )
{
return mChannelPanels.get( 0 ).getContextMenu();
}
return null;
}
public void addTrafficChannel( Channel channel )
{
//TODO: add logic to handle traffic channels.
}
}

View File

@@ -0,0 +1,304 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.state;
import java.awt.Color;
import java.awt.Component;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.HashMap;
import javax.swing.JMenu;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.SwingUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.miginfocom.swing.MigLayout;
import playlist.PlaylistManager;
import settings.ColorSetting.ColorSettingName;
import settings.ColorSettingMenuItem;
import settings.ColorSettingResetMenuItem;
import settings.SettingsManager;
import controller.channel.Channel;
import controller.channel.ChannelEvent;
import controller.channel.ChannelEventListener;
/**
* Gui wrapper for the list of currently processing primary channels
*/
public class ChannelList extends JPanel implements ChannelEventListener
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger( ChannelList.class );
private HashMap<Channel,ChannelCollectionPanel> mDisplayedPanels =
new HashMap<Channel,ChannelCollectionPanel>();
private PlaylistManager mPlaylistManager;
private SettingsManager mSettingsManager;
public ChannelList( PlaylistManager playlistManager,
SettingsManager settingsManager )
{
mPlaylistManager = playlistManager;
mSettingsManager = settingsManager;
init();
}
private void init()
{
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]") );
setBackground( Color.BLACK );
addMouseListener( new ListSelectionListener() );
}
@Override
public void channelChanged( ChannelEvent event )
{
switch( event.getEvent() )
{
case CHANNEL_PROCESSING_STARTED:
addChannelPanel( event.getChannel() );
break;
case CHANNEL_PROCESSING_STOPPED:
removeChannelPanel( event.getChannel() );
break;
default:
break;
}
}
private void addChannelPanel( Channel channel )
{
if( !mDisplayedPanels.containsKey( channel ) )
{
ChannelCollectionPanel panel = new ChannelCollectionPanel(
mPlaylistManager, mSettingsManager, channel );
add( panel, "wrap" );
mDisplayedPanels.put( channel, panel );
revalidate();
repaint();
}
}
public void removeChannelPanel( Channel channel )
{
if( mDisplayedPanels.containsKey( channel ) )
{
ChannelCollectionPanel panel = mDisplayedPanels.remove( channel );
if( panel != null )
{
remove( panel );
panel.dispose();
}
revalidate();
repaint();
}
}
public void setSelectedChannel( Channel channel )
{
/* Send channel selection to each channel panel */
for( ChannelCollectionPanel panel: mDisplayedPanels.values() )
{
panel.setSelectedChannel( channel );
}
}
public class ListSelectionListener implements MouseListener
{
@Override
public void mouseClicked( MouseEvent e )
{
Component component = getComponentAt( e.getPoint() );
if( component instanceof ChannelCollectionPanel )
{
ChannelCollectionPanel panel = (ChannelCollectionPanel)component;
Point translatedPoint = SwingUtilities
.convertPoint( e.getComponent(), e.getPoint(), panel );
Channel channel = panel.getChannelAt( translatedPoint );
if( e.getButton() == MouseEvent.BUTTON1 )
{
setSelectedChannel( channel );
}
else if( e.getButton() == MouseEvent.BUTTON3 )
{
JPopupMenu popupMenu = new JPopupMenu();
JMenu menu = panel.getContextMenu();
if( menu != null )
{
popupMenu.add( menu );
}
JMenu colorMenu = new JMenu( "Color" );
popupMenu.add( colorMenu );
JMenu backgroundMenu = new JMenu( "Background" );
colorMenu.add( backgroundMenu );
backgroundMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_BACKGROUND ) );
backgroundMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_BACKGROUND ) );
JMenu channelsMenu = new JMenu( "Channel State" );
colorMenu.add( channelsMenu );
JMenu callMenu = new JMenu( "Call" );
channelsMenu.add( callMenu );
callMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CALL ) );
callMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CALL ) );
callMenu.add( new JSeparator() );
callMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CALL ) );
callMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CALL ) );
JMenu controlMenu = new JMenu( "Control" );
channelsMenu.add( controlMenu );
controlMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CONTROL ) );
controlMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CONTROL ) );
controlMenu.add( new JSeparator() );
controlMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CONTROL ) );
controlMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CONTROL ) );
JMenu dataMenu = new JMenu( "Data" );
channelsMenu.add( dataMenu );
dataMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_DATA ) );
dataMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_DATA ) );
dataMenu.add( new JSeparator() );
dataMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_DATA ) );
dataMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_DATA ) );
JMenu fadeMenu = new JMenu( "Fade" );
channelsMenu.add( fadeMenu );
fadeMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_FADE ) );
fadeMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_FADE ) );
fadeMenu.add( new JSeparator() );
fadeMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_FADE ) );
fadeMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_FADE ) );
JMenu idleMenu = new JMenu( "Idle" );
channelsMenu.add( idleMenu );
idleMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_IDLE ) );
idleMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_IDLE ) );
idleMenu.add( new JSeparator() );
idleMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_IDLE ) );
idleMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_IDLE ) );
JMenu noTunerMenu = new JMenu( "No Tuner" );
channelsMenu.add( noTunerMenu );
noTunerMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER ) );
noTunerMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER ) );
noTunerMenu.add( new JSeparator() );
noTunerMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER ) );
noTunerMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER ) );
JMenu labelMenu = new JMenu( "Labels" );
colorMenu.add( labelMenu );
labelMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER ) );
labelMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER ) );
labelMenu.add( new JSeparator() );
labelMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_LABEL_DECODER ) );
labelMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_LABEL_DECODER ) );
labelMenu.add( new JSeparator() );
labelMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_LABEL_DETAILS ) );
labelMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_LABEL_DETAILS ) );
JMenu selectedMenu = new JMenu( "Selected" );
colorMenu.add( selectedMenu );
selectedMenu.add( new ColorSettingMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_SELECTED_CHANNEL ) );
selectedMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
ColorSettingName.CHANNEL_STATE_SELECTED_CHANNEL ) );
popupMenu.show( ChannelList.this, e.getX(), e.getY() );
}
}
}
public void mousePressed( MouseEvent e ) {}
public void mouseReleased( MouseEvent e ) {}
public void mouseEntered( MouseEvent e ) {}
public void mouseExited( MouseEvent e ) {}
}
}

View File

@@ -0,0 +1,587 @@
/*******************************************************************************
* SDR Trunk
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.state;
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 alias.Metadata;
import alias.MetadataReset;
import alias.MetadataType;
import audio.metadata.IMetadataProvider;
import audio.squelch.ISquelchStateProvider;
import audio.squelch.SquelchState;
import controller.ThreadPoolManager;
import controller.ThreadPoolManager.ThreadType;
import controller.channel.Channel.ChannelType;
/**
* 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
{
private final static Logger mLog =
LoggerFactory.getLogger( ChannelState.class );
public static final long FADE_TIMEOUT_DELAY = 1000;
public static final long RESET_TIMEOUT_DELAY = 2000;
private State mState = State.IDLE;
private long mTrafficChannelTimeout =
DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS;
private Listener<CallEvent> mCallEventListener;
private Listener<ChangedAttribute> mChangedAttributeListener;
private Listener<DecoderStateEvent> mDecoderStateListener;
private Listener<Metadata> mMetadataListener;
private Listener<SquelchState> mSquelchStateListener;
private DecoderStateEventReceiver mDecoderStateEventReceiver =
new DecoderStateEventReceiver();
private boolean mSquelchLocked = false;
private boolean mSelected = false;
private long mFadeTimeout;
private long mResetTimeout;
private StateMonitor mStateMonitor = new StateMonitor();
private AtomicBoolean mMonitoring = new AtomicBoolean();
private ScheduledFuture<?> mRunningMonitor;
private ThreadPoolManager mThreadPoolManager;
private ChannelType mChannelType;
private TrafficChannelStatusListener mTrafficChannelStatusListener;
private CallEvent mTrafficChannelCallEvent;
public ChannelState( ThreadPoolManager manager, ChannelType channelType )
{
mThreadPoolManager = manager;
mChannelType = channelType;
}
@Override
public void init()
{
}
public void dispose()
{
stopMonitor();
mCallEventListener = null;
mChangedAttributeListener = null;
mDecoderStateListener = null;
mSquelchStateListener = null;
mThreadPoolManager = null;
mStateMonitor = null;
}
public void setTrafficChannelTimeout( long milliseconds )
{
mTrafficChannelTimeout = milliseconds;
}
public void setSelected( boolean selected )
{
mSelected = selected;
}
public boolean isSelected()
{
return mSelected;
}
public State getState()
{
return mState;
}
private boolean isEndingState()
{
return mState == State.IDLE ||
mState == State.FADE ||
mState == State.END;
}
/**
* Starts the state monitor running when we're not in IDLE state. Calls to
* start an already started monitor are ignored.
*/
private void startMonitor()
{
if( mMonitoring.compareAndSet( false, true ) )
{
if( mRunningMonitor == null )
{
mRunningMonitor = mThreadPoolManager.scheduleFixedRate(
ThreadType.DECODER, mStateMonitor, 20, TimeUnit.MILLISECONDS );
}
else
{
throw new RuntimeException( "Channel state monitor's scheduled "
+ "future pointer was not null" );
}
}
}
/**
* Stops the state monitor when we're in an IDLE state. Calls to stop an
* already stopped monitor are ignored.
*/
private void stopMonitor()
{
if( mMonitoring.compareAndSet( true, false ) )
{
if( mRunningMonitor != null )
{
mThreadPoolManager.cancel( mRunningMonitor );
}
else
{
throw new RuntimeException( "Channel state monitor's scheduled "
+ "future pointer was null when trying to stop the monitor" );
}
mRunningMonitor = null;
}
}
/**
* Updates the fade timeout threshold to the current time plus delay
*/
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 timeout = System.currentTimeMillis() +
( mChannelType == ChannelType.STANDARD ? FADE_TIMEOUT_DELAY :
mTrafficChannelTimeout );
if( timeout > mFadeTimeout )
{
mFadeTimeout = timeout;
}
}
/**
* Updates the reset timeout threshold to the current time plus delay
*/
private void updateResetTimeout()
{
mResetTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY;
}
/**
* Broadcasts the squelch state to the registered listener
*/
protected void broadcast( SquelchState state )
{
if( mSquelchStateListener != null && !mSquelchLocked )
{
mSquelchStateListener.receive( state );
}
}
/**
* Sets the squelch state listener
*/
@Override
public void setSquelchStateListener( Listener<SquelchState> listener )
{
mSquelchStateListener = listener;
}
/**
* Removes the squelch state listener
*/
@Override
public void removeSquelchStateListener()
{
mSquelchStateListener = null;
}
/**
* Sets the channel state to the specified state, or updates the timeout
* values so that the state monitor will not change state. Broadcasts a
* squelch event when the state changes and the audio squelch state should
* change. Also broadcasts changed attribute and decoder state events so
* that external processes can maintain sync with this channel state.
*/
protected void setState( State state )
{
if( mState == state )
{
switch( state )
{
case CALL:
case CONTROL:
case DATA:
case ENCRYPTED:
updateFadeTimeout();
break;
default:
break;
}
}
else if( mState.canChangeTo( state ) )
{
switch( state )
{
case CONTROL:
case DATA:
case ENCRYPTED:
broadcast( SquelchState.SQUELCH );
updateFadeTimeout();
startMonitor();
break;
case CALL:
broadcast( SquelchState.UNSQUELCH );
updateFadeTimeout();
startMonitor();
break;
case FADE:
broadcast( SquelchState.SQUELCH );
updateResetTimeout();
startMonitor();
break;
case END:
broadcast( SquelchState.SQUELCH );
broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) );
broadcast( new MetadataReset() );
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;
}
}
break;
default:
break;
}
mState = state;
broadcast( ChangedAttribute.CHANNEL_STATE );
}
}
public class StateMonitor implements Runnable
{
@Override
public void run()
{
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 );
}
}
}
}
/**
* Broadcasts the call event to the registered listener
*/
protected void broadcast( CallEvent event )
{
if( mCallEventListener != null )
{
mCallEventListener.receive( event );
}
}
@Override
public void setCallEventListener( Listener<CallEvent> listener )
{
mCallEventListener = listener;
}
@Override
public void removeCallEventListener()
{
mCallEventListener = null;
}
/**
* Broadcasts the channel state attribute change event to all registered
* listeners
*/
protected void broadcast( ChangedAttribute attribute )
{
if( mChangedAttributeListener != null )
{
mChangedAttributeListener.receive( attribute );
}
}
/**
* Adds the listener to receive channel state attribute change events
*/
@Override
public void setChangedAttributeListener( Listener<ChangedAttribute> listener )
{
mChangedAttributeListener = listener;
}
/**
* Removes the listener to receive channel state attribute change events
*/
@Override
public void removeChangedAttributeListener()
{
mChangedAttributeListener = null;
}
/**
* Broadcasts a channel state event to any registered listeners
*/
protected void broadcast( DecoderStateEvent event )
{
if( mDecoderStateListener != null )
{
mDecoderStateListener.receive( event );
}
}
/**
* Adds a decoder state event listener
*/
@Override
public void setDecoderStateListener( Listener<DecoderStateEvent> listener )
{
mDecoderStateListener = listener;
}
/**
* Removes the decoder state event listener
*/
@Override
public void removeDecoderStateListener()
{
mDecoderStateListener = null;
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{
return mDecoderStateEventReceiver;
}
/**
* Broadcasts metadata to the registered listener
*/
protected void broadcast( Metadata metadata )
{
if( mMetadataListener != null )
{
mMetadataListener.receive( metadata );
}
}
/**
* Adds a metadata listener
*/
@Override
public void setMetadataListener( Listener<Metadata> listener )
{
mMetadataListener = listener;
}
/**
* Removes the decoder state event listener
*/
@Override
public void removeMetadataListener()
{
mMetadataListener = null;
}
/**
* Registers a listener to be notified when a traffic channel call event is
* completed, so that the listener can perform call tear-down
*/
public void configureAsTrafficChannel( TrafficChannelStatusListener listener,
TrafficChannelAllocationEvent allocationEvent )
{
mTrafficChannelStatusListener = listener;
mTrafficChannelCallEvent = allocationEvent.getCallEvent();
/* Broadcast the call event details as metadata for the audio manager */
String channel = mTrafficChannelCallEvent.getChannel();
if( channel != null )
{
broadcast( new Metadata( MetadataType.CHANNEL, channel, true ) );
}
broadcast( new Metadata( MetadataType.PROTOCOL,
mTrafficChannelCallEvent.getDecoderType().getDisplayString(), true ) );
String details = mTrafficChannelCallEvent.getDetails();
if( details != null )
{
broadcast( new Metadata( MetadataType.DETAILS, details, true ) );
}
String from = mTrafficChannelCallEvent.getFromID();
if( from != null )
{
broadcast( new Metadata( mTrafficChannelCallEvent.getFromIDType(),
from, mTrafficChannelCallEvent.getFromIDAlias(), true ) );
}
String to = mTrafficChannelCallEvent.getToID();
if( to != null )
{
broadcast( new Metadata( mTrafficChannelCallEvent.getToIDType(), to,
mTrafficChannelCallEvent.getToIDAlias(), true ) );
}
long frequency = mTrafficChannelCallEvent.getFrequency();
if( frequency > 0 )
{
broadcast( new Metadata( MetadataType.FREQUENCY,
String.valueOf( frequency ), true ) );
}
/* Rebroadcast the allocation event so that the internal decoder states
* can self-configure with the call event details */
broadcast( allocationEvent );
}
/**
* DecoderStateEvent receiver wrapper
*/
public class DecoderStateEventReceiver implements Listener<DecoderStateEvent>
{
@Override
public void receive( DecoderStateEvent event )
{
if( event.getSource() != this )
{
switch( event.getEvent() )
{
case ALWAYS_UNSQUELCH:
broadcast( SquelchState.UNSQUELCH );
mSquelchLocked = true;
break;
case CONTINUATION:
case DECODE:
if( isEndingState() )
{
setState( event.getState() );
}
else
{
updateFadeTimeout();
}
break;
case END:
setState( State.FADE );
break;
case RESET:
/* Channel State does not respond to reset events */
break;
case START:
setState( event.getState() );
break;
default:
break;
}
}
}
}
}

View File

@@ -0,0 +1,457 @@
package module.decode.state;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import module.decode.Decoder;
import module.decode.DecoderFactory;
import module.decode.event.ActivitySummaryFrame;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import playlist.PlaylistManager;
import sample.Listener;
import settings.ColorSetting;
import settings.ColorSetting.ColorSettingName;
import settings.Setting;
import settings.SettingChangeListener;
import settings.SettingsManager;
import controller.channel.Channel;
public class ChannelStatePanel extends JPanel
implements Listener<ChangedAttribute>, SettingChangeListener
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger( ChannelStatePanel.class );
private static final boolean ENABLED = true;
private static final boolean DISABLED = false;
private static final boolean BROADCAST_CHANGE = true;
private Font mFontDetails = new Font( Font.MONOSPACED, Font.PLAIN, 10 );
private Font mFontDecoder = new Font( Font.MONOSPACED, Font.PLAIN, 10 );
protected Color mColorChannelBackground;
protected Color mColorChannelSelected;
protected Color mColorTopCall;
protected Color mColorMiddleCall;
protected Color mColorTopControl;
protected Color mColorMiddleControl;
protected Color mColorTopData;
protected Color mColorMiddleData;
protected Color mColorTopFade;
protected Color mColorMiddleFade;
protected Color mColorTopIdle;
protected Color mColorMiddleIdle;
protected Color mColorTopNoTuner;
protected Color mColorMiddleNoTuner;
protected Color mColorLabelDetails;
protected Color mColorLabelDecoder;
protected Color mColorLabelAuxDecoder;
private JLabel mStateLabel;
private JLabel mSourceLabel;
private JLabel mChannelLabel;
private PlaylistManager mPlaylistManager;
protected SettingsManager mSettingsManager;
private Channel mChannel;
private List<DecoderPanel> mDecoderPanels = new ArrayList<>();
/**
* Gui component to a channel state. Provides System, Site and channel
* name, source and channel state visualization.
*
* Updates color background according to current channel state.
*/
public ChannelStatePanel( PlaylistManager playlistManager,
SettingsManager settingsManager,
Channel channel )
{
mPlaylistManager = playlistManager;
mSettingsManager = settingsManager;
mSettingsManager.addListener( this );
mChannel = channel;
mChannel.getChannelState().setChangedAttributeListener( this );
init();
for( Decoder decoder: getChannel().getProcessingChain().getDecoders() )
{
DecoderPanel panel = DecoderFactory.getDecoderPanel( mSettingsManager, decoder );
mDecoderPanels.add( panel );
add( panel, "grow,span" );
}
}
private void init()
{
getColors();
setLayout( new MigLayout( "insets 3 2 2 2", "[grow,fill]", "[]0[]0[]") );
mStateLabel = new JLabel( mChannel.getChannelState().getState()
.getDisplayValue() );
mStateLabel.setFont( mFontDecoder );
mStateLabel.setForeground( mColorLabelDecoder );
add( mStateLabel );
mSourceLabel = new JLabel( mChannel.getSourceConfiguration().getDescription() );
mSourceLabel.setFont( mFontDetails );
mSourceLabel.setForeground( mColorLabelDetails );
add( mSourceLabel );
mChannelLabel = new JLabel( mChannel.getChannelDisplayName() );
mChannelLabel.setFont( mFontDetails );
mChannelLabel.setForeground( mColorLabelDetails );
add( mChannelLabel, "wrap" );
}
public Channel getChannel()
{
return mChannel;
}
public void dispose()
{
mPlaylistManager = null;
mSettingsManager.removeListener( this );
mSettingsManager = null;
mChannel.getChannelState().removeChangedAttributeListener();
mChannel = null;
for( DecoderPanel panel: mDecoderPanels )
{
panel.dispose();
remove( panel );
}
mDecoderPanels.clear();
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
final ChannelState state = mChannel.getChannelState();
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
switch( changedAttribute )
{
case CHANNEL_STATE:
mStateLabel.setText( state.getState().getDisplayValue() );
break;
case SOURCE:
mSourceLabel.setText( mChannel.getSourceConfiguration()
.getDescription() );
break;
case CHANNEL_NAME:
case SITE_NAME:
case SYSTEM_NAME:
mChannelLabel.setText( mChannel.getChannelDisplayName() );
break;
default:
break;
}
repaint();
}
});
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent( g );
setBackground( mColorChannelBackground );
Graphics2D g2 = (Graphics2D)g.create();
Paint p = null;
switch( mChannel.getChannelState().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 END:
case IDLE:
default:
p = getGradient( mColorTopIdle, mColorMiddleIdle );
break;
}
g2.setPaint( p );
g2.fillRect( 0, 0, getWidth(), getHeight() );
/* Draw bottom separator line */
g2.setColor( Color.LIGHT_GRAY );
g2.drawLine( 0, getHeight() - 1, getWidth(), getHeight() - 1 );
/* Draw channel selected box */
if( getChannel().isSelected() )
{
g2.setColor( mColorChannelSelected );
g2.drawRect( 1, 1, getWidth() - 2, getHeight() - 2 );
}
g2.dispose();
}
private void getColors()
{
mColorChannelBackground = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_BACKGROUND ).getColor();
mColorChannelSelected = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_SELECTED_CHANNEL ).getColor();
mColorTopCall = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CALL ).getColor();
mColorMiddleCall = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CALL ).getColor();
mColorTopControl = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CONTROL ).getColor();
mColorMiddleControl = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CONTROL ).getColor();
mColorTopData = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_DATA ).getColor();
mColorMiddleData = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_DATA ).getColor();
mColorTopFade = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_FADE ).getColor();
mColorMiddleFade = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_FADE ).getColor();
mColorTopIdle = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_IDLE ).getColor();
mColorMiddleIdle = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_IDLE ).getColor();
mColorTopNoTuner = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER ).getColor();
mColorMiddleNoTuner = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER ).getColor();
mColorLabelDetails = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_DETAILS ).getColor();
mColorLabelDecoder = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_DECODER ).getColor();
mColorLabelAuxDecoder = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER ).getColor();
}
private GradientPaint getGradient( Color top, Color middle )
{
return new GradientPaint( 0f, -50.0f, top,
0f, (float)getHeight() / 2.2f, middle, true );
}
@Override
public void settingChanged( Setting setting )
{
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case CHANNEL_STATE_BACKGROUND:
mColorChannelBackground = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_MIDDLE_CALL:
mColorMiddleCall = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_TOP_CALL:
mColorTopCall = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_MIDDLE_CONTROL:
mColorMiddleControl = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_TOP_CONTROL:
mColorTopControl = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_MIDDLE_DATA:
mColorMiddleData = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_TOP_DATA:
mColorTopData = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_MIDDLE_FADE:
mColorMiddleFade = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_TOP_FADE:
mColorTopFade = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_MIDDLE_IDLE:
mColorMiddleIdle = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_TOP_IDLE:
mColorTopIdle = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER:
mColorMiddleNoTuner = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_GRADIENT_TOP_NO_TUNER:
mColorTopNoTuner = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_LABEL_AUX_DECODER:
mColorLabelAuxDecoder = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_LABEL_DECODER:
mColorLabelDecoder = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_LABEL_DETAILS:
mColorLabelDetails = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_SELECTED_CHANNEL:
mColorChannelSelected = colorSetting.getColor();
repaint();
break;
default:
break;
}
}
}
@Override
public void settingDeleted( Setting setting ) {}
public JMenu getContextMenu()
{
JMenu menu = new JMenu( "Channel: " + mChannel.getName() );
if( mChannel.getEnabled() )
{
JMenuItem disable = new JMenuItem( "Disable" );
disable.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
mChannel.setEnabled( DISABLED, BROADCAST_CHANGE );
if( mPlaylistManager != null )
{
mPlaylistManager.save();
}
}
} );
menu.add( disable );
menu.add( new JSeparator() );
JMenuItem actySummaryItem =
new JMenuItem( "Activity Summary" );
actySummaryItem.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
StringBuilder sb = new StringBuilder();
for( Decoder decoder: mChannel.getProcessingChain().getDecoders() )
{
sb.append( decoder.getDecoderState().getActivitySummary() );
}
new ActivitySummaryFrame( sb.toString(), ChannelStatePanel.this );
}
} );
menu.add( actySummaryItem );
}
else
{
JMenuItem enable = new JMenuItem( "Enable" );
enable.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
mChannel.setEnabled( ENABLED, BROADCAST_CHANGE );
if( mPlaylistManager != null )
{
mPlaylistManager.save();
}
}
} );
menu.add( enable );
}
menu.add( new JSeparator() );
JMenuItem deleteItem = new JMenuItem( "Delete" );
deleteItem.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
dispose();
if( mPlaylistManager != null )
{
mPlaylistManager.save();
}
}
} );
menu.add( deleteItem );
return menu;
}
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.state;
public interface ChannelStateSelectionListener
{
public void selected( ChannelState channelState );
}

View File

@@ -0,0 +1,120 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.state;
import java.awt.Color;
import java.awt.Font;
import javax.swing.JPanel;
import module.decode.Decoder;
import sample.Listener;
import settings.ColorSetting;
import settings.ColorSetting.ColorSettingName;
import settings.Setting;
import settings.SettingChangeListener;
import settings.SettingsManager;
public abstract class DecoderPanel extends JPanel
implements Listener<ChangedAttribute>, SettingChangeListener
{
private static final long serialVersionUID = 1L;
protected Font mFontDetails = new Font( Font.MONOSPACED, Font.PLAIN, 10 );
protected Font mFontDecoder = new Font( Font.MONOSPACED, Font.PLAIN, 10 );
protected Font mFontAuxDecoder = new Font( Font.MONOSPACED, Font.PLAIN, 10 );
protected Color mColorLabelDetails;
protected Color mColorLabelDecoder;
protected Color mColorLabelAuxDecoder;
protected SettingsManager mSettingsManager;
protected Decoder mDecoder;
public DecoderPanel( SettingsManager settingsManager, Decoder decoder )
{
mSettingsManager = settingsManager;
mSettingsManager.addListener( this );
mDecoder = decoder;
mDecoder.getDecoderState().setChangedAttributeListener( this );
}
public void dispose()
{
mSettingsManager.removeListener( this );
mSettingsManager = null;
mDecoder.getDecoderState().removeChangedAttributeListener();
mDecoder = null;
}
public Decoder getDecoder()
{
return mDecoder;
}
protected void init()
{
getColors();
setOpaque( false ); //Use the parent panel's background color
}
private void getColors()
{
mColorLabelDetails = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_DETAILS ).getColor();
mColorLabelDecoder = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_DECODER ).getColor();
mColorLabelAuxDecoder = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER ).getColor();
}
@Override
public void settingChanged( Setting setting )
{
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case CHANNEL_STATE_LABEL_AUX_DECODER:
mColorLabelAuxDecoder = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_LABEL_DECODER:
mColorLabelDecoder = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_LABEL_DETAILS:
mColorLabelDetails = colorSetting.getColor();
repaint();
break;
default:
break;
}
}
}
@Override
public void settingDeleted( Setting setting ) {}
}

View File

@@ -0,0 +1,186 @@
package module.decode.state;
import message.Message;
import module.decode.event.ActivitySummaryProvider;
import module.decode.event.CallEvent;
import module.decode.event.ICallEventProvider;
import sample.Listener;
import alias.AliasList;
/**
* Channel state monitors the stream of decoded messages produced by the
* decoder and broadcasts call events as they occur within the decoded message activity.
*
* Provides access to a textual activity summary of events observed.
*/
public abstract class DecoderState implements ActivitySummaryProvider,
Listener<Message>,
ICallEventProvider,
IChangedAttributeProvider,
IDecoderStateEventListener,
IDecoderStateEventProvider
{
private Listener<CallEvent> mCallEventListener;
private Listener<ChangedAttribute> mChangedAttributeListener;
private Listener<DecoderStateEvent> mDecoderStateListener;
private DecoderStateEventListener mDecoderStateEventListener =
new DecoderStateEventListener();
protected CallEvent mCurrentCallEvent;
private AliasList mAliasList;
public DecoderState( AliasList aliasList )
{
mAliasList = aliasList;
}
public AliasList getAliasList()
{
return mAliasList;
}
public boolean hasAliasList()
{
return mAliasList != null;
}
/**
* Reset the decoder state to prepare for processing a different sample
* source
*/
public abstract void reset();
/**
* Allow the decoder to perform any setup actions
*/
public abstract void init();
/**
* Implements the IDecoderStateEventListener interface to receive state
* reset events.
*/
public abstract void receiveDecoderStateEvent( DecoderStateEvent event );
/**
* Disposes any resources or pointers held by this instance to prepare for
* garbage collection
*/
public void dispose()
{
mCallEventListener = null;
mChangedAttributeListener = null;
mDecoderStateListener = null;
mAliasList = null;
}
/**
* Activity Summary - textual summary of activity observed by the channel state.
*/
public abstract String getActivitySummary();
/**
* Broadcasts a call event to any registered listeners
*/
protected void broadcast( CallEvent event )
{
if( mCallEventListener != null )
{
mCallEventListener.receive( event );
}
}
/**
* Adds a call event listener
*/
@Override
public void setCallEventListener( Listener<CallEvent> listener )
{
mCallEventListener = listener;
}
/**
* Removes the call event listener
*/
@Override
public void removeCallEventListener()
{
mCallEventListener = null;
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{
return mDecoderStateEventListener;
}
private class DecoderStateEventListener implements Listener<DecoderStateEvent>
{
@Override
public void receive( DecoderStateEvent event )
{
receiveDecoderStateEvent( event );
}
}
/**
* Broadcasts the channel state attribute change event to all registered
* listeners
*/
protected void broadcast( ChangedAttribute attribute )
{
if( mChangedAttributeListener != null )
{
mChangedAttributeListener.receive( attribute );
}
}
/**
* Adds the listener to receive channel state attribute change events
*/
@Override
public void setChangedAttributeListener( Listener<ChangedAttribute> listener )
{
mChangedAttributeListener = listener;
}
/**
* Removes the listener to receive channel state attribute change events
*/
@Override
public void removeChangedAttributeListener()
{
mChangedAttributeListener = null;
}
/**
* Broadcasts a channel state event to any registered listeners
*/
protected void broadcast( DecoderStateEvent event )
{
if( mDecoderStateListener != null )
{
mDecoderStateListener.receive( event );
}
}
/**
* Adds a decoder state event listener
*/
@Override
public void setDecoderStateListener( Listener<DecoderStateEvent> listener )
{
mDecoderStateListener = listener;
}
/**
* Removes the decoder state event listener
*/
@Override
public void removeDecoderStateListener()
{
mDecoderStateListener = null;
}
}

View File

@@ -0,0 +1,55 @@
package module.decode.state;
public class DecoderStateEvent
{
private Object mSource;
private Event mEvent;
private State mState;
private long mFrequency;
public DecoderStateEvent( Object source, Event event, State state )
{
mSource = source;
mEvent = event;
mState = state;
}
public DecoderStateEvent( Object source, Event event, State state, long frequency )
{
this( source, event, state );
mFrequency = frequency;
}
public Object getSource()
{
return mSource;
}
public Event getEvent()
{
return mEvent;
}
public State getState()
{
return mState;
}
public long getFrequency()
{
return mFrequency;
}
public enum Event
{
ALWAYS_UNSQUELCH,
CONTINUATION,
DECODE,
END,
RESET,
SOURCE_FREQUENCY,
START,
TRAFFIC_CHANNEL_ALLOCATION;
}
}

View File

@@ -0,0 +1,8 @@
package module.decode.state;
import sample.Listener;
public interface IChangedAttributeListener
{
public Listener<ChangedAttribute> getChangedAttributeListener();
}

View File

@@ -0,0 +1,9 @@
package module.decode.state;
import sample.Listener;
public interface IChangedAttributeProvider
{
public void setChangedAttributeListener( Listener<ChangedAttribute> listener );
public void removeChangedAttributeListener();
}

View File

@@ -0,0 +1,8 @@
package module.decode.state;
import sample.Listener;
public interface IDecoderStateEventListener
{
public Listener<DecoderStateEvent> getDecoderStateListener();
}

View File

@@ -0,0 +1,9 @@
package module.decode.state;
import sample.Listener;
public interface IDecoderStateEventProvider
{
public void setDecoderStateListener( Listener<DecoderStateEvent> listener );
public void removeDecoderStateListener();
}

View File

@@ -0,0 +1,83 @@
package module.decode.state;
public enum State
{
IDLE( "IDLE" )
{
@Override
public boolean canChangeTo( State state )
{
return true;
}
},
CALL( "CALL" )
{
@Override
public boolean canChangeTo( State state )
{
return state == CALL ||
state == CONTROL ||
state == DATA ||
state == ENCRYPTED ||
state == FADE;
}
},
DATA( "DATA" )
{
@Override
public boolean canChangeTo( State state )
{
return state == CALL ||
state == CONTROL ||
state == DATA ||
state == State.ENCRYPTED ||
state == FADE;
}
},
ENCRYPTED( "ENCRYPTED" )
{
@Override
public boolean canChangeTo( State state )
{
return state == FADE;
}
},
CONTROL( "CONTROL" )
{
@Override
public boolean canChangeTo( State state )
{
return true;
}
},
FADE( "FADE" )
{
@Override
public boolean canChangeTo( State state )
{
return state != FADE; //All states except fade allowed
}
},
END( "END" )
{
@Override
public boolean canChangeTo( State state )
{
return state != FADE; //Fade is only disallowed state
}
};
private String mDisplayValue;
private State( String displayValue )
{
mDisplayValue = displayValue;
}
public abstract boolean canChangeTo( State state );
public String getDisplayValue()
{
return mDisplayValue;
}
}

View File

@@ -0,0 +1,25 @@
package module.decode.state;
import module.decode.event.CallEvent;
/**
* Traffic channel allocation event wraps a call event indicating the channel
* and frequency for a traffic channel allocation event, so that the traffic
* channel manager can respond and allocate a decoder channel and source if
* available, or change the wrapped call event to a call detect event and log it.
*/
public class TrafficChannelAllocationEvent extends DecoderStateEvent
{
private CallEvent mCallEvent;
public TrafficChannelAllocationEvent( Object source, CallEvent callEvent )
{
super( source, Event.TRAFFIC_CHANNEL_ALLOCATION, State.CALL );
mCallEvent = callEvent;
}
public CallEvent getCallEvent()
{
return mCallEvent;
}
}

View File

@@ -0,0 +1,255 @@
package module.decode.state;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import module.Module;
import module.decode.config.DecodeConfiguration;
import module.decode.event.CallEvent;
import module.decode.event.CallEvent.CallEventType;
import module.decode.event.ICallEventProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import source.config.SourceConfigTuner;
import source.tuner.TunerChannel;
import source.tuner.TunerChannel.Type;
import controller.ResourceManager;
import controller.channel.Channel;
import controller.channel.Channel.ChannelType;
import controller.site.Site;
import controller.system.System;
public class TrafficChannelManager extends Module
implements ICallEventProvider, IDecoderStateEventListener
{
private final static Logger mLog = LoggerFactory.getLogger( TrafficChannelManager.class );
private int mTrafficChannelPoolMaximumSize =
DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT;
private List<Channel> mTrafficChannelPool = new ArrayList<Channel>();
private Map<String,Channel> mTrafficChannelsInUse = new ConcurrentHashMap<String,Channel>();
private DecoderStateEventListener mEventListener = new DecoderStateEventListener();
private Listener<CallEvent> mCallEventListener;
private DecodeConfiguration mDecodeConfiguration;
private ResourceManager mResourceManager;
private long mTrafficChannelTimeout;
/**
* Monitors call events and allocates traffic decoder channels in response
* to traffic channel allocation call events. Manages a pool of reusable
* traffic channel allocations.
*
* @param resourceManager - resource manager
*
* @param decodeConfiguration - decoder configuration to use for each
* traffic channel allocation.
*
* @param trafficChannelTimeout - millisecond call timer limit used when an
* end of call signalling event is not decoded.
*
* @param trafficChannelPoolSize - maximum number of allocated traffic channels
* in the pool
*/
public TrafficChannelManager( ResourceManager resourceManager,
DecodeConfiguration decodeConfiguration,
long trafficChannelTimeout,
int trafficChannelPoolSize )
{
mResourceManager = resourceManager;
mDecodeConfiguration = decodeConfiguration;
mTrafficChannelTimeout = trafficChannelTimeout;
mTrafficChannelPoolMaximumSize = trafficChannelPoolSize;
}
@Override
public void dispose()
{
mCallEventListener = null;
mResourceManager = null;
mDecodeConfiguration = null;
}
private Channel getChannel( String channelNumber, TunerChannel tunerChannel )
{
Channel channel = null;
if( !mTrafficChannelsInUse.containsKey( channelNumber ) )
{
for( Channel configuredChannel: mTrafficChannelPool )
{
if( !configuredChannel.isProcessing() )
{
channel = configuredChannel;
break;
}
}
if( channel == null && mTrafficChannelPool.size() < mTrafficChannelPoolMaximumSize )
{
channel = new Channel( "Traffic", ChannelType.TRAFFIC );
channel.setDecodeConfiguration( mDecodeConfiguration );
channel.setResourceManager( mResourceManager );
channel.setTrafficChannelTimeout( mTrafficChannelTimeout );
mTrafficChannelPool.add( channel );
}
/* If we have a configured channel, start it and track it */
if( channel != null )
{
channel.setSourceConfiguration( new SourceConfigTuner( tunerChannel ) );
channel.setSystem( new System( "Traffic" ), false );
channel.setSite( new Site( "Channel" ), false );
channel.setName( channelNumber );
channel.setEnabled( true );
/* Check the channel obtained a source and is currently processing */
if( channel.isProcessing() )
{
mTrafficChannelsInUse.put( channelNumber, channel );
}
else
{
channel.setEnabled( false );
channel.setSystem( null, false );
channel.setSite( null, false );
channel.setName( "Traffic" );
}
}
}
return channel;
}
private void process( TrafficChannelAllocationEvent event )
{
if( mCallEventListener != null )
{
CallEvent callEvent = event.getCallEvent();
/* Check for duplicate events and suppress */
if( mTrafficChannelsInUse.containsKey( callEvent.getChannel() ) )
{
return;
}
long frequency = callEvent.getFrequency();
if( frequency > 0 )
{
Channel channel = getChannel( callEvent.getChannel(),
new TunerChannel( Type.TRAFFIC, frequency,
mDecodeConfiguration.getDecoderType()
.getChannelBandwidth() ) );
if( channel != null )
{
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 );
}
}
else
{
callEvent.setCallEventType( CallEventType.CALL_DETECT );
}
}
else
{
callEvent.setCallEventType( CallEventType.CALL_DETECT );
}
mCallEventListener.receive( callEvent );
}
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{
return mEventListener;
}
@Override
public void setCallEventListener( Listener<CallEvent> listener )
{
mCallEventListener = listener;
}
@Override
public void removeCallEventListener()
{
mCallEventListener = null;
}
@Override
public void init()
{
}
/**
* Callback used by the TrafficChannelStatusListener class to signal the
* end of a an allocated traffic channel call event
*
* @param channelNumber - channel number from the call event that signaled
* the start of a traffic channel allocation
*/
public void callEnd( String 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 );
}
}
/**
* Wrapper class for the decoder state event listener interface to catch
* traffic channel allocation requests
*/
public class DecoderStateEventListener implements Listener<DecoderStateEvent>
{
@Override
public void receive( DecoderStateEvent event )
{
switch( event.getEvent() )
{
case TRAFFIC_CHANNEL_ALLOCATION:
process( (TrafficChannelAllocationEvent)event );
break;
default:
break;
}
}
}
}

View File

@@ -0,0 +1,21 @@
package module.decode.state;
public class TrafficChannelStatusListener
{
private TrafficChannelManager mTrafficChannelManager;
private String mChannelNumber;
public TrafficChannelStatusListener( TrafficChannelManager manager, String channelNumber )
{
mTrafficChannelManager = manager;
mChannelNumber = channelNumber;
}
public void callEnd()
{
if( mTrafficChannelManager != null )
{
mTrafficChannelManager.callEnd( mChannelNumber );
}
}
}