mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 00:57:31 -05:00
#90 [WIP] on redesigning the channel processing gui components and backing model. Simplifies channel state by introducing a new Metadata class that tracks all metadata for all decoders.
This commit is contained in:
477
src/channel/traffic/TrafficChannelManager.java
Normal file
477
src/channel/traffic/TrafficChannelManager.java
Normal file
@@ -0,0 +1,477 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 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 channel.traffic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import channel.state.DecoderStateEvent;
|
||||
import channel.state.IDecoderStateEventListener;
|
||||
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 record.config.RecordConfiguration;
|
||||
import sample.Listener;
|
||||
import source.config.SourceConfigTuner;
|
||||
import source.tuner.TunerChannel;
|
||||
import source.tuner.TunerChannel.Type;
|
||||
import alias.Alias;
|
||||
import alias.id.priority.Priority;
|
||||
import controller.channel.Channel;
|
||||
import controller.channel.Channel.ChannelType;
|
||||
import controller.channel.ChannelEvent;
|
||||
import controller.channel.ChannelEvent.Event;
|
||||
import controller.channel.ChannelModel;
|
||||
import controller.channel.ChannelProcessingManager;
|
||||
import controller.channel.TrafficChannelEvent;
|
||||
|
||||
public class TrafficChannelManager extends Module
|
||||
implements ICallEventProvider, IDecoderStateEventListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( TrafficChannelManager.class );
|
||||
public static final String CHANNEL_START_REJECTED = "CHANNEL START REJECTED";
|
||||
public static final String NO_TUNER_AVAILABLE = "NO TUNER AVAILABLE";
|
||||
public static final String UNKNOWN_FREQUENCY = "UNKNOWN FREQUENCY";
|
||||
|
||||
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 ChannelModel mChannelModel;
|
||||
private ChannelProcessingManager mChannelProcessingManager;
|
||||
private DecodeConfiguration mDecodeConfiguration;
|
||||
private RecordConfiguration mRecordConfiguration;
|
||||
private String mSystem;
|
||||
private String mSite;
|
||||
private String mAliasListName;
|
||||
private CallEvent mPreviousDoNotMonitorCallEvent;
|
||||
|
||||
/**
|
||||
* 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 channelModel containing channels currently in use
|
||||
*
|
||||
* @param channelProcessingManager
|
||||
*
|
||||
* @param decodeConfiguration - decoder configuration to use for each
|
||||
* traffic channel allocation.
|
||||
*
|
||||
* @param recordConfiguration - recording options for each traffic channel
|
||||
*
|
||||
* @param system label to use to describe the system
|
||||
*
|
||||
* @param site label to use to describe the site
|
||||
*
|
||||
* @param aliasListName designated for the channel
|
||||
*
|
||||
* @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( ChannelModel channelModel,
|
||||
ChannelProcessingManager channelProcessingManager,
|
||||
DecodeConfiguration decodeConfiguration,
|
||||
RecordConfiguration recordConfiguration,
|
||||
String system,
|
||||
String site,
|
||||
String aliasListName,
|
||||
long trafficChannelTimeout,
|
||||
int trafficChannelPoolSize )
|
||||
{
|
||||
mChannelModel = channelModel;
|
||||
mChannelProcessingManager = channelProcessingManager;
|
||||
mDecodeConfiguration = decodeConfiguration;
|
||||
mRecordConfiguration = recordConfiguration;
|
||||
mSystem = system;
|
||||
mSite = site;
|
||||
mAliasListName = aliasListName;
|
||||
mTrafficChannelPoolMaximumSize = trafficChannelPoolSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
for( Channel trafficChannel: mTrafficChannelPool )
|
||||
{
|
||||
mChannelModel.broadcast( new ChannelEvent( trafficChannel, Event.REQUEST_DISABLE ) );
|
||||
}
|
||||
|
||||
mTrafficChannelPool.clear();
|
||||
|
||||
mTrafficChannelsInUse.clear();
|
||||
|
||||
mCallEventListener = null;
|
||||
mDecodeConfiguration = null;
|
||||
mPreviousDoNotMonitorCallEvent = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a channel by either reusing an existing channel or constructing
|
||||
* a new one, limited by the total number of constructed channels allowed.
|
||||
*
|
||||
* Note: you must enforce thread safety on the mTrafficChannelsInUse
|
||||
* external to this method.
|
||||
*
|
||||
* @param channelNumber
|
||||
* @param tunerChannel
|
||||
* @return
|
||||
*/
|
||||
private Channel getChannel( String channelNumber, TunerChannel tunerChannel )
|
||||
{
|
||||
Channel channel = null;
|
||||
|
||||
if( !mTrafficChannelsInUse.containsKey( channelNumber ) )
|
||||
{
|
||||
for( Channel configuredChannel: mTrafficChannelPool )
|
||||
{
|
||||
if( !configuredChannel.getEnabled() )
|
||||
{
|
||||
channel = configuredChannel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( channel == null && mTrafficChannelPool.size() < mTrafficChannelPoolMaximumSize )
|
||||
{
|
||||
channel = new Channel( "Traffic", ChannelType.TRAFFIC );
|
||||
|
||||
channel.setDecodeConfiguration( mDecodeConfiguration );
|
||||
|
||||
channel.setRecordConfiguration( mRecordConfiguration );
|
||||
|
||||
channel.setAliasListName( mAliasListName );
|
||||
|
||||
mChannelModel.addChannel( channel );
|
||||
|
||||
mTrafficChannelPool.add( channel );
|
||||
}
|
||||
|
||||
/* If we have a configured channel, update metadata */
|
||||
if( channel != null )
|
||||
{
|
||||
channel.setSourceConfiguration( new SourceConfigTuner( tunerChannel ) );
|
||||
channel.setSystem( mSystem );
|
||||
channel.setSite( mSite );
|
||||
channel.setName( channelNumber );
|
||||
|
||||
mChannelModel.broadcast( new ChannelEvent( channel, Event.NOTIFICATION_CONFIGURATION_CHANGE ) );
|
||||
}
|
||||
}
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the event and creates a traffic channel is resources are
|
||||
* available
|
||||
*/
|
||||
private void process( TrafficChannelAllocationEvent event )
|
||||
{
|
||||
CallEvent callEvent = event.getCallEvent();
|
||||
|
||||
/* 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;
|
||||
}
|
||||
else
|
||||
{
|
||||
mPreviousDoNotMonitorCallEvent = callEvent;
|
||||
}
|
||||
}
|
||||
else if( frequency > 0 )
|
||||
{
|
||||
Channel channel = getChannel( callEvent.getChannel(), new TunerChannel( Type.TRAFFIC,
|
||||
frequency, mDecodeConfiguration.getDecoderType().getChannelBandwidth() ) );
|
||||
|
||||
if( channel != null )
|
||||
{
|
||||
TrafficChannelEvent trafficChannelEvent =
|
||||
new TrafficChannelEvent( this, channel, Event.REQUEST_ENABLE, callEvent );
|
||||
|
||||
//Request to enable the channel
|
||||
mChannelModel.broadcast( trafficChannelEvent );
|
||||
|
||||
if( channel.getEnabled() )
|
||||
{
|
||||
mTrafficChannelsInUse.put( callEvent.getChannel(), channel );
|
||||
}
|
||||
else
|
||||
{
|
||||
callEvent.setCallEventType( CallEventType.CALL_DETECT );
|
||||
|
||||
String details = callEvent.getDetails();
|
||||
|
||||
if(details == null || details.isEmpty())
|
||||
{
|
||||
callEvent.setDetails(CHANNEL_START_REJECTED);
|
||||
}
|
||||
else if(!details.contains(CHANNEL_START_REJECTED))
|
||||
{
|
||||
callEvent.setDetails(new StringBuilder(CHANNEL_START_REJECTED)
|
||||
.append(" : ")
|
||||
.append(callEvent.getDetails()).toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
callEvent.setCallEventType( CallEventType.CALL_DETECT );
|
||||
|
||||
String details = callEvent.getDetails();
|
||||
|
||||
if(details == null || details.isEmpty())
|
||||
{
|
||||
callEvent.setDetails(NO_TUNER_AVAILABLE);
|
||||
}
|
||||
else if(!details.contains(NO_TUNER_AVAILABLE))
|
||||
{
|
||||
callEvent.setDetails(new StringBuilder(NO_TUNER_AVAILABLE)
|
||||
.append(" : ")
|
||||
.append(callEvent.getDetails()).toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
callEvent.setCallEventType( CallEventType.CALL_DETECT );
|
||||
|
||||
String details = callEvent.getDetails();
|
||||
|
||||
if(details == null || details.isEmpty())
|
||||
{
|
||||
callEvent.setDetails(UNKNOWN_FREQUENCY);
|
||||
}
|
||||
else if(!details.contains(UNKNOWN_FREQUENCY))
|
||||
{
|
||||
callEvent.setDetails(new StringBuilder(UNKNOWN_FREQUENCY)
|
||||
.append(" : ")
|
||||
.append(callEvent.getDetails()).toString());
|
||||
}
|
||||
}
|
||||
|
||||
final Listener<CallEvent> listener = mCallEventListener;
|
||||
|
||||
if( listener != null )
|
||||
{
|
||||
listener.receive( callEvent );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the call type, channel and to fields for equivalence and the
|
||||
* from field for either both null, or equivalence.
|
||||
*
|
||||
* @param e1
|
||||
* @param e2
|
||||
* @return
|
||||
*/
|
||||
public static boolean isSameCallEvent( CallEvent e1, CallEvent e2 )
|
||||
{
|
||||
if( e1 == null || e2 == null )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( e1.getCallEventType() != e2.getCallEventType() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( e1.getChannel() == null ||
|
||||
e2.getChannel() == null ||
|
||||
!e1.getChannel().contentEquals( e2.getChannel() ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( e1.getToID() == null ||
|
||||
e2.getToID() == null ||
|
||||
!e1.getToID().contentEquals( e2.getToID() ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( e1.getFromID() == null || e2.getFromID() == null )
|
||||
{
|
||||
if( e1.getFromID() == null && e2.getFromID() == null )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if( e1.getFromID().contentEquals( e2.getFromID() ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for aliases that contain a do not follow/process priority.
|
||||
*
|
||||
* @param event
|
||||
* @return - true if there is an alias that has a do not process alias id
|
||||
*/
|
||||
private boolean isDoNotMonitor( CallEvent event )
|
||||
{
|
||||
Alias to = event.getToIDAlias();
|
||||
|
||||
if( to != null &&
|
||||
to.hasCallPriority() &&
|
||||
to.getCallPriority() == Priority.DO_NOT_MONITOR )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Alias from = event.getFromIDAlias();
|
||||
|
||||
if( from != null &&
|
||||
from.hasCallPriority() &&
|
||||
from.getCallPriority() == Priority.DO_NOT_MONITOR )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<DecoderStateEvent> getDecoderStateListener()
|
||||
{
|
||||
return mEventListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCallEventListener( Listener<CallEvent> listener )
|
||||
{
|
||||
mCallEventListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCallEventListener( Listener<CallEvent> listener )
|
||||
{
|
||||
mCallEventListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start( ScheduledExecutorService executor )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
if( !mTrafficChannelsInUse.isEmpty() )
|
||||
{
|
||||
List<String> channels = new ArrayList<>();
|
||||
|
||||
/* Copy the keyset so we don't get concurrent modification of the map */
|
||||
channels.addAll( mTrafficChannelsInUse.keySet() );
|
||||
|
||||
for( String channel: channels )
|
||||
{
|
||||
callEnd( channel );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 )
|
||||
{
|
||||
synchronized( mTrafficChannelsInUse )
|
||||
{
|
||||
if( channelNumber != null && mTrafficChannelsInUse.containsKey( channelNumber ) )
|
||||
{
|
||||
Channel channel = mTrafficChannelsInUse.get( channelNumber );
|
||||
|
||||
mChannelModel.broadcast( new ChannelEvent( channel, Event.REQUEST_DISABLE ) );
|
||||
|
||||
mTrafficChannelsInUse.remove( channelNumber );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user