Compare commits

...

12 Commits

Author SHA1 Message Date
denny
d6e161f3e9 Updating build to 0.2.0.beta4 2016-02-05 09:11:37 -05:00
denny
ba952af721 Added support for parsing additional Packet Data Unit unrecognized types
and removed logging of unrecognized PDUC messages.
2016-02-05 09:10:23 -05:00
denny
d9be346d7f Added new message for Packet Data Unit Confirmed messages with unknown
PDUType.
2016-02-04 21:14:16 -05:00
denny
133adec27a Adds support for PacketData messages that were being flagged as Packet
Data Unit - Confirmed unknown message types.
2016-02-04 20:51:13 -05:00
denny
981a8fec55 Removes logger from TrafficChannelManager that was logging when a
traffic channel is allocated but fails to get a source and is
subsequently torn down.  Logging was ambiguous and unneeded.
2016-02-04 20:50:17 -05:00
denny
9c94266adb Resolves issue with Audio Channel display showing a call in progress
with TO/FROM identifiers displayed, but no audio playing.  This occurred
when the decoder sent a few audio buffers which caused the display to
update from the metadata, however the decoder stopped sending buffers
and the audio start threshold is never met.  Clear display is invoked
upon audio stop, so the panel was never started or stopped or cleared
until the next call came in.

Changed to store the incoming metadata and only display it once the
audio playback starts and reset the display once the audio stops.
2016-02-04 20:49:11 -05:00
denny
4460f1ee30 Suppresses logging of attempts to change to invalid channel state. The
state enumeration is designed to block these attempts which can occur
when a non-standard call sequence of events occurs, normally due to
intermittent decoding.
2016-02-02 20:14:33 -05:00
denny
ddfefe4b85 Resolves issue with ChannelState FADE monitor - changes from
starting/stopping the monitor for each call to simply leaving the
monitor task running continuously.  Was causing issue with scheduled
thread executor not shutting down the task in a timely manner -- new
approach should avoid start/stop related issues altogether.

Updates P25DecoderState with reordering of timing of when we send
updates for the TO/FROM identifiers so that the audio manager receives
up-to-date metadata and hopefully resolves issue with 'Do Not Monitor'
talkgroups being monitored by the audio manager.

Updates the AudioManager with a try/catch block around the processing
task so that we can catch any audio processing exceptions that occur
during the timer task and might get suppressed by the scheduled thread
executor.
2016-02-02 19:44:18 -05:00
Denny Sheirer
debdc39a5f 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
2016-02-01 09:17:56 +00:00
Denny Sheirer
6f76f6fca4 Resolves issue with Tuner retaining released TunerChannels, preventing
the tuner from sourcing additional out-of-band channels.
2016-01-30 16:58:52 +00:00
denny
1290b8c4d3 Adds VSELP message and unknown Data Unit ID handling to P25 decoder. 2016-01-28 19:18:05 -05:00
denny
b95b3ff415 Updated readme 2016-01-23 21:19:54 -05:00
34 changed files with 960 additions and 600 deletions

1
.gitignore vendored
View File

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

View File

@@ -3,7 +3,6 @@ A cross-platform java application for decoding, monitoring, recording and stream
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted) * [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted)
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/UserManual) * [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/UserManual)
* [Download](https://drive.google.com/folderview?id=0B7BHsssXUq8eOHBTNndCczZTd0k&usp=sharing)
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk) * [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
![](https://github.com/DSheirer/sdrtrunk/wiki/images/SDRTrunk_application.png) ![](https://github.com/DSheirer/sdrtrunk/wiki/images/SDRTrunk_application.png)

View File

@@ -7,7 +7,7 @@
<property name="major" value="0"/> <property name="major" value="0"/>
<property name="minor" value="2"/> <property name="minor" value="2"/>
<property name="patch" value="0"/> <property name="patch" value="0"/>
<property name="release" value="beta.3"/> <property name="release" value="beta.4"/>
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}-${release}"/> <property name="filename" value="sdrtrunk_${major}.${minor}.${patch}-${release}"/>
<!-- Build properties --> <!-- Build properties -->

View File

@@ -49,7 +49,10 @@ public class AudioChannelPanel extends JPanel
private JLabel mFrom = new JLabel( "" ); private JLabel mFrom = new JLabel( "" );
private JLabel mFromAlias = new JLabel( "" ); private JLabel mFromAlias = new JLabel( "" );
private boolean mConfigured = false; private enum AudioStatus { STARTED,STOPPED };
private AudioStatus mAudioStatus = AudioStatus.STOPPED;
private AudioMetadata mAudioMetadata;
public AudioChannelPanel( SettingsManager settingsManager, AudioOutput audioOutput ) public AudioChannelPanel( SettingsManager settingsManager, AudioOutput audioOutput )
{ {
@@ -134,15 +137,19 @@ public class AudioChannelPanel extends JPanel
{ {
switch( audioEvent.getType() ) switch( audioEvent.getType() )
{ {
case AUDIO_STOPPED: case AUDIO_STARTED:
EventQueue.invokeLater( new Runnable() synchronized( mAudioStatus )
{ {
@Override mAudioStatus = AudioStatus.STARTED;
public void run() updateLabels();
{ }
resetLabels(); break;
} case AUDIO_STOPPED:
} ); synchronized( mAudioStatus )
{
mAudioStatus = AudioStatus.STOPPED;
resetLabels();
}
break; break;
case AUDIO_MUTED: case AUDIO_MUTED:
case AUDIO_UNMUTED: case AUDIO_UNMUTED:
@@ -166,13 +173,55 @@ public class AudioChannelPanel extends JPanel
*/ */
private void resetLabels() private void resetLabels()
{ {
mFrom.setText( "" ); EventQueue.invokeLater( new Runnable()
updateAlias( mFromAlias, null ); {
@Override
public void run()
{
mFrom.setText( "" );
updateAlias( mFromAlias, null );
mTo.setText( "" ); mTo.setText( "" );
updateAlias( mToAlias, null ); updateAlias( mToAlias, null );
}
} );
}
mConfigured = false; private void updateLabels()
{
if( mAudioMetadata != null )
{
final Metadata from = mAudioMetadata.getMetadata( MetadataType.FROM );
final Metadata to = mAudioMetadata.getMetadata( MetadataType.TO );
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
if( from != null )
{
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( "-----" );
}
}
} );
}
} }
/** /**
@@ -214,32 +263,17 @@ public class AudioChannelPanel extends JPanel
@Override @Override
public void receive( AudioMetadata audioMetadata ) public void receive( AudioMetadata audioMetadata )
{ {
if( !mConfigured || audioMetadata.isUpdated() ) mAudioMetadata = audioMetadata;
if( audioMetadata.isUpdated() )
{ {
final Metadata from = audioMetadata.getMetadata( MetadataType.FROM ); synchronized( mAudioStatus )
final Metadata to = audioMetadata.getMetadata( MetadataType.TO );
EventQueue.invokeLater( new Runnable()
{ {
@Override if( mAudioStatus == AudioStatus.STARTED )
public void run()
{ {
if( from != null ) updateLabels();
{
mFrom.setText( from.getValue() );
updateAlias( mFromAlias, from.getAlias() );
}
if( to != null )
{
mTo.setText( to.getValue() );
updateAlias( mToAlias, to.getAlias() );
}
} }
} ); }
mConfigured = true;
} }
} }
} }

View File

@@ -17,7 +17,6 @@ package audio;
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/ ******************************************************************************/
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@@ -41,7 +40,6 @@ import source.mixer.MixerChannel;
import source.mixer.MixerChannelConfiguration; import source.mixer.MixerChannelConfiguration;
import source.mixer.MixerManager; import source.mixer.MixerManager;
import audio.AudioEvent.Type; import audio.AudioEvent.Type;
import audio.AudioPanel.MixerSelectionItem;
import audio.output.AudioOutput; import audio.output.AudioOutput;
import audio.output.MonoAudioOutput; import audio.output.MonoAudioOutput;
import audio.output.StereoAudioOutput; import audio.output.StereoAudioOutput;
@@ -392,29 +390,36 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
@Override @Override
public void run() public void run()
{ {
disconnectInactiveChannelAssignments(); try
if( mAudioPacketQueue != null )
{ {
List<AudioPacket> packets = new ArrayList<AudioPacket>(); disconnectInactiveChannelAssignments();
mAudioPacketQueue.drainTo( packets ); if( mAudioPacketQueue != null )
for( AudioPacket packet: packets )
{ {
/* Don't process any packet's marked as do not monitor */ List<AudioPacket> packets = new ArrayList<AudioPacket>();
if( !packet.getAudioMetadata().isDoNotMonitor() &&
packet.getType() == AudioPacket.Type.AUDIO )
{
AudioOutputConnection connection = getConnection( packet );
if( connection != null ) mAudioPacketQueue.drainTo( packets );
for( AudioPacket packet: packets )
{
/* Don't process any packet's marked as do not monitor */
if( !packet.getAudioMetadata().isDoNotMonitor() &&
packet.getType() == AudioPacket.Type.AUDIO )
{ {
connection.receive( packet ); AudioOutputConnection connection = getConnection( packet );
if( connection != null )
{
connection.receive( packet );
}
} }
} }
} }
} }
catch( Exception e )
{
mLog.error( "Encountered error while processing audio packets", e );
}
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -42,6 +42,8 @@ import module.decode.p25.message.ldu.lc.TelephoneInterconnectVoiceChannelUser;
import module.decode.p25.message.ldu.lc.UnitToUnitVoiceChannelUser; import module.decode.p25.message.ldu.lc.UnitToUnitVoiceChannelUser;
import module.decode.p25.message.pdu.PDUMessage; import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.message.pdu.confirmed.PDUConfirmedMessage; import module.decode.p25.message.pdu.confirmed.PDUConfirmedMessage;
import module.decode.p25.message.pdu.confirmed.PDUTypeUnknown;
import module.decode.p25.message.pdu.confirmed.PacketData;
import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextAccept; import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextAccept;
import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextReject; import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextReject;
import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextRequest; import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextRequest;
@@ -405,12 +407,12 @@ public class P25DecoderState extends DecoderState
{ {
if( hdu.isValid() ) if( hdu.isValid() )
{ {
State state = hdu.isEncrypted() ? State.ENCRYPTED : State.CALL; broadcast( new DecoderStateEvent( this, Event.START, State.CALL ) );
broadcast( new DecoderStateEvent( this, Event.START, state ) );
String to = hdu.getToID(); String to = hdu.getToID();
updateTo( to );
if( mCurrentCallEvent == null ) if( mCurrentCallEvent == null )
{ {
mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.CALL ) mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.CALL )
@@ -424,7 +426,6 @@ public class P25DecoderState extends DecoderState
broadcast( mCurrentCallEvent ); broadcast( mCurrentCallEvent );
} }
updateTo( to );
} }
} }
@@ -436,7 +437,7 @@ public class P25DecoderState extends DecoderState
{ {
if( tdulc.getOpcode() == LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION ) if( tdulc.getOpcode() == LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION )
{ {
broadcast( new DecoderStateEvent( this, Event.END, State.END ) ); broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
if( mCurrentCallEvent != null ) if( mCurrentCallEvent != null )
{ {
@@ -614,12 +615,14 @@ public class P25DecoderState extends DecoderState
module.decode.p25.message.tdu.lc.GroupVoiceChannelUser gvcuser = module.decode.p25.message.tdu.lc.GroupVoiceChannelUser gvcuser =
(module.decode.p25.message.tdu.lc.GroupVoiceChannelUser)tdulc; (module.decode.p25.message.tdu.lc.GroupVoiceChannelUser)tdulc;
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CALL ) );
( gvcuser.isEncrypted() ? State.ENCRYPTED : State.CALL ) ) );
String from = gvcuser.getSourceAddress(); String from = gvcuser.getSourceAddress();
String to = gvcuser.getGroupAddress(); String to = gvcuser.getGroupAddress();
updateFrom( from );
updateTo( to );
if( mCurrentCallEvent == null ) if( mCurrentCallEvent == null )
{ {
mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.GROUP_CALL )
@@ -635,9 +638,6 @@ public class P25DecoderState extends DecoderState
broadcast( mCurrentCallEvent ); broadcast( mCurrentCallEvent );
} }
updateFrom( from );
updateTo( to );
} }
break; break;
case MESSAGE_UPDATE: case MESSAGE_UPDATE:
@@ -844,6 +844,8 @@ public class P25DecoderState extends DecoderState
String to = tivcu.getAddress(); String to = tivcu.getAddress();
updateTo( to );
if( mCurrentCallEvent == null ) if( mCurrentCallEvent == null )
{ {
mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.TELEPHONE_INTERCONNECT ) mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.TELEPHONE_INTERCONNECT )
@@ -857,8 +859,6 @@ public class P25DecoderState extends DecoderState
broadcast( mCurrentCallEvent ); broadcast( mCurrentCallEvent );
} }
updateTo( to );
} }
break; break;
case UNIT_AUTHENTICATION_COMMAND: case UNIT_AUTHENTICATION_COMMAND:
@@ -921,7 +921,10 @@ public class P25DecoderState extends DecoderState
(module.decode.p25.message.tdu.lc.UnitToUnitVoiceChannelUser)tdulc; (module.decode.p25.message.tdu.lc.UnitToUnitVoiceChannelUser)tdulc;
String from = uuvcu.getSourceAddress(); String from = uuvcu.getSourceAddress();
updateFrom( from );
String to = uuvcu.getTargetAddress(); String to = uuvcu.getTargetAddress();
updateTo( to );
if( mCurrentCallEvent != null ) if( mCurrentCallEvent != null )
{ {
@@ -935,9 +938,6 @@ public class P25DecoderState extends DecoderState
.to( to ) .to( to )
.build(); .build();
} }
updateFrom( from );
updateTo( to );
} }
break; break;
default: default:
@@ -949,35 +949,14 @@ public class P25DecoderState extends DecoderState
* Log optional vendor format messages that we don't yet support so that we * Log optional vendor format messages that we don't yet support so that we
* can understand those messages and eventually add support. * can understand those messages and eventually add support.
*/ */
private void logAlternateVendorMessage( P25Message p25 ) private void logAlternateVendorMessage( P25Message message )
{ {
// if( p25 instanceof TDULinkControlMessage ) // if( message.isValid() )
// { // {
// TDULinkControlMessage tdulc = (TDULinkControlMessage)p25; // mLog.info( "PLEASE NOTIFY DEVELOPER - UNRECOGNIZED P25 VENDOR FORMAT -"
// // + " DUID:" + message.getDUID()
// mLog.debug( "TDULC - VENDOR [" + tdulc.getVendor() + // + " MSG:" + message.getBinaryMessage()
// "] SPECIFIC FORMAT FOR " + tdulc.getVendorOpcode().getLabel() + // + " CLASS:" + message.getClass() );
// " " + tdulc.getBinaryMessage() );
// }
// else if( p25 instanceof LDU1Message )
// {
// LDU1Message ldu1 = (LDU1Message)p25;
//
// mLog.debug( "LDU1 - VENDOR [" + ldu1.getVendor() +
// "] SPECIFIC FORMAT FOR " + ldu1.getVendorOpcode().getLabel() +
// " " + ldu1.getBinaryMessage() );
// }
// else if( p25 instanceof PDUMessage )
// {
// PDUMessage pdu = (PDUMessage)p25;
//
// mLog.debug( "PDU - VENDOR [" + pdu.getVendor() +
// "] SPECIFIC FORMAT FOR " + pdu.getVendorOpcode().getLabel() +
// " " + pdu.getBinaryMessage() );
// }
// else
// {
// mLog.debug( "P25 - ALTERNATE VENDOR MESSAGE [" + p25.getClass() + "]" );
// } // }
} }
@@ -988,15 +967,11 @@ public class P25DecoderState extends DecoderState
*/ */
private void processLDU( LDUMessage ldu ) private void processLDU( LDUMessage ldu )
{ {
State state = ( ldu.isValid() && ldu.isEncrypted() ) ? broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CALL ) );
State.ENCRYPTED : State.CALL;
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, state ) );
if( mCurrentCallEvent == null ) if( mCurrentCallEvent == null )
{ {
mCurrentCallEvent = new P25CallEvent.Builder( mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.CALL )
( state == State.CALL ) ? CallEventType.CALL : CallEventType.ENCRYPTED_CALL )
.aliasList( getAliasList() ) .aliasList( getAliasList() )
.channel( mCurrentChannel ) .channel( mCurrentChannel )
.frequency( mCurrentChannelFrequency ) .frequency( mCurrentChannelFrequency )
@@ -1056,7 +1031,7 @@ public class P25DecoderState extends DecoderState
} }
break; break;
case CALL_TERMINATION_OR_CANCELLATION: case CALL_TERMINATION_OR_CANCELLATION:
broadcast( new DecoderStateEvent( this, Event.END, State.END ) ); broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
mCurrentCallEvent = null; mCurrentCallEvent = null;
if( !( ldu instanceof CallTermination ) ) if( !( ldu instanceof CallTermination ) )
@@ -1114,42 +1089,43 @@ public class P25DecoderState extends DecoderState
} }
break; break;
case GROUP_VOICE_CHANNEL_UPDATE: case GROUP_VOICE_CHANNEL_UPDATE:
if( mChannelType == ChannelType.STANDARD && if( ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate )
ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate )
{ {
module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate gvcu = module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate gvcu =
(module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate)ldu; (module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate)ldu;
String userA = gvcu.getGroupAddressA(); String userA = gvcu.getGroupAddressA();
if( !mCallDetectTalkgroups.contains( userA ) )
{
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
.aliasList( getAliasList() )
.channel( gvcu.getChannelA() )
.details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) )
.frequency( gvcu.getDownlinkFrequencyA() )
.to( userA )
.build() );
mCallDetectTalkgroups.add( userA );
}
String userB = gvcu.getGroupAddressB(); String userB = gvcu.getGroupAddressB();
if( userB != null && if( mChannelType == ChannelType.STANDARD )
!userB.contentEquals( "0000" ) &&
!mCallDetectTalkgroups.contains( userB ) )
{ {
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT ) if( !mCallDetectTalkgroups.contains( userA ) )
.aliasList( getAliasList() ) {
.channel( gvcu.getChannelB() ) broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
.details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) ) .aliasList( getAliasList() )
.frequency( gvcu.getDownlinkFrequencyB() ) .channel( gvcu.getChannelA() )
.to( userB ) .details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) )
.build() ); .frequency( gvcu.getDownlinkFrequencyA() )
.to( userA )
.build() );
mCallDetectTalkgroups.add( userB ); mCallDetectTalkgroups.add( userA );
}
if( userB != null &&
!userB.contentEquals( "0000" ) &&
!mCallDetectTalkgroups.contains( userB ) )
{
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
.aliasList( getAliasList() )
.channel( gvcu.getChannelB() )
.details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) )
.frequency( gvcu.getDownlinkFrequencyB() )
.to( userB )
.build() );
mCallDetectTalkgroups.add( userB );
}
} }
} }
else else
@@ -1158,25 +1134,27 @@ public class P25DecoderState extends DecoderState
} }
break; break;
case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT: case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT:
if( mChannelType == ChannelType.STANDARD && if( ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit )
ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit )
{ {
module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit gvcue = module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit gvcue =
(module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit)ldu; (module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit)ldu;
String group = gvcue.getGroupAddress(); String group = gvcue.getGroupAddress();
if( !mCallDetectTalkgroups.contains( group ) ) if( mChannelType == ChannelType.STANDARD )
{ {
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT ) if( !mCallDetectTalkgroups.contains( group ) )
.aliasList( getAliasList() ) {
.channel( gvcue.getTransmitChannel() ) broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
.details( ( gvcue.isEncrypted() ? "ENCRYPTED" : "" ) ) .aliasList( getAliasList() )
.frequency( gvcue.getDownlinkFrequency() ) .channel( gvcue.getTransmitChannel() )
.to( group ) .details( ( gvcue.isEncrypted() ? "ENCRYPTED" : "" ) )
.build() ); .frequency( gvcue.getDownlinkFrequency() )
.to( group )
.build() );
mCallDetectTalkgroups.add( group ); mCallDetectTalkgroups.add( group );
}
} }
} }
else else
@@ -1186,29 +1164,32 @@ public class P25DecoderState extends DecoderState
break; break;
case GROUP_VOICE_CHANNEL_USER: case GROUP_VOICE_CHANNEL_USER:
/* Indicates the current user of the current channel */ /* Indicates the current user of the current channel */
if( mChannelType == ChannelType.STANDARD && if( ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUser )
ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUser )
{ {
module.decode.p25.message.ldu.lc.GroupVoiceChannelUser gvcuser = module.decode.p25.message.ldu.lc.GroupVoiceChannelUser gvcuser =
(module.decode.p25.message.ldu.lc.GroupVoiceChannelUser)ldu; (module.decode.p25.message.ldu.lc.GroupVoiceChannelUser)ldu;
if( mCurrentCallEvent.getCallEventType() != CallEventType.GROUP_CALL )
{
mCurrentCallEvent.setCallEventType( CallEventType.GROUP_CALL );
broadcast( mCurrentCallEvent );
}
if( mCurrentCallEvent.getDetails() == null )
{
mCurrentCallEvent.setDetails(
( gvcuser.isEncrypted() ? "ENCRYPTED " : "" ) +
( gvcuser.isEmergency() ? "EMERGENCY " : "" ) );
broadcast( mCurrentCallEvent );
}
updateFrom( gvcuser.getSourceAddress() ); updateFrom( gvcuser.getSourceAddress() );
updateTo( gvcuser.getGroupAddress() ); updateTo( gvcuser.getGroupAddress() );
if( mChannelType == ChannelType.STANDARD )
{
if( mCurrentCallEvent.getCallEventType() != CallEventType.GROUP_CALL )
{
mCurrentCallEvent.setCallEventType( CallEventType.GROUP_CALL );
broadcast( mCurrentCallEvent );
}
if( mCurrentCallEvent.getDetails() == null )
{
mCurrentCallEvent.setDetails(
( gvcuser.isEncrypted() ? "ENCRYPTED " : "" ) +
( gvcuser.isEmergency() ? "EMERGENCY " : "" ) );
broadcast( mCurrentCallEvent );
}
}
} }
else else
{ {
@@ -1404,6 +1385,8 @@ public class P25DecoderState extends DecoderState
TelephoneInterconnectVoiceChannelUser tivcu = TelephoneInterconnectVoiceChannelUser tivcu =
(TelephoneInterconnectVoiceChannelUser)ldu; (TelephoneInterconnectVoiceChannelUser)ldu;
updateTo( tivcu.getAddress() );
if( mCurrentCallEvent.getCallEventType() != CallEventType.TELEPHONE_INTERCONNECT ) if( mCurrentCallEvent.getCallEventType() != CallEventType.TELEPHONE_INTERCONNECT )
{ {
mCurrentCallEvent.setCallEventType( CallEventType.TELEPHONE_INTERCONNECT ); mCurrentCallEvent.setCallEventType( CallEventType.TELEPHONE_INTERCONNECT );
@@ -1418,8 +1401,6 @@ public class P25DecoderState extends DecoderState
broadcast( mCurrentCallEvent ); broadcast( mCurrentCallEvent );
} }
updateTo( tivcu.getAddress() );
} }
else else
{ {
@@ -1485,6 +1466,10 @@ public class P25DecoderState extends DecoderState
UnitToUnitVoiceChannelUser uuvcu = UnitToUnitVoiceChannelUser uuvcu =
(UnitToUnitVoiceChannelUser)ldu; (UnitToUnitVoiceChannelUser)ldu;
updateFrom( uuvcu.getSourceAddress() );
updateTo( uuvcu.getTargetAddress() );
if( mCurrentCallEvent.getCallEventType() != CallEventType.UNIT_TO_UNIT_CALL ) if( mCurrentCallEvent.getCallEventType() != CallEventType.UNIT_TO_UNIT_CALL )
{ {
mCurrentCallEvent.setCallEventType( CallEventType.UNIT_TO_UNIT_CALL ); mCurrentCallEvent.setCallEventType( CallEventType.UNIT_TO_UNIT_CALL );
@@ -1499,9 +1484,6 @@ public class P25DecoderState extends DecoderState
broadcast( mCurrentCallEvent ); broadcast( mCurrentCallEvent );
} }
updateFrom( uuvcu.getSourceAddress() );
updateTo( uuvcu.getTargetAddress() );
} }
else else
{ {
@@ -1520,7 +1502,7 @@ public class P25DecoderState extends DecoderState
private void processTSBK( TSBKMessage tsbk ) private void processTSBK( TSBKMessage tsbk )
{ {
/* Trunking Signalling Block Messages - indicates Control Channel */ /* Trunking Signalling Block Messages - indicates Control Channel */
broadcast( new DecoderStateEvent( this, Event.START, State.CONTROL ) ); broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
if( tsbk.getVendor() == Vendor.STANDARD ) if( tsbk.getVendor() == Vendor.STANDARD )
{ {
@@ -1649,7 +1631,11 @@ public class P25DecoderState extends DecoderState
*/ */
private void processPDU( PDUMessage pdu ) private void processPDU( PDUMessage pdu )
{ {
if( pdu instanceof PDUConfirmedMessage ) if( pdu instanceof PacketData || pdu instanceof PDUTypeUnknown )
{
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.DATA ) );
}
else if( pdu instanceof PDUConfirmedMessage )
{ {
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.DATA ) ); broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.DATA ) );
@@ -1657,7 +1643,7 @@ public class P25DecoderState extends DecoderState
switch( pduc.getPDUType() ) switch( pduc.getPDUType() )
{ {
case SN_ACTIVATE_TDS_CONTEXT_ACCEPT: case SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT:
SNDCPActivateTDSContextAccept satca = SNDCPActivateTDSContextAccept satca =
(SNDCPActivateTDSContextAccept)pduc; (SNDCPActivateTDSContextAccept)pduc;
@@ -1669,7 +1655,7 @@ public class P25DecoderState extends DecoderState
.to( satca.getLogicalLinkID() ) .to( satca.getLogicalLinkID() )
.build() ); .build() );
break; break;
case SN_ACTIVATE_TDS_CONTEXT_REJECT: case SNDCP_ACTIVATE_TDS_CONTEXT_REJECT:
SNDCPActivateTDSContextReject satcr = SNDCPActivateTDSContextReject satcr =
(SNDCPActivateTDSContextReject)pduc; (SNDCPActivateTDSContextReject)pduc;
@@ -1694,7 +1680,7 @@ public class P25DecoderState extends DecoderState
.from( satcreq.getLogicalLinkID() ) .from( satcreq.getLogicalLinkID() )
.build() ); .build() );
break; break;
case SN_DEACTIVATE_TDS_CONTEXT_ACCEPT: case SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT:
SNDCPDeactivateTDSContext sdtca = SNDCPDeactivateTDSContext sdtca =
(SNDCPDeactivateTDSContext)pduc; (SNDCPDeactivateTDSContext)pduc;
@@ -1706,7 +1692,7 @@ public class P25DecoderState extends DecoderState
.from( sdtca.getLogicalLinkID() ) .from( sdtca.getLogicalLinkID() )
.build() ); .build() );
break; break;
case SN_DEACTIVATE_TDS_CONTEXT_REQUEST: case SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST:
SNDCPDeactivateTDSContext sdtcreq = SNDCPDeactivateTDSContext sdtcreq =
(SNDCPDeactivateTDSContext)pduc; (SNDCPDeactivateTDSContext)pduc;
@@ -1718,7 +1704,7 @@ public class P25DecoderState extends DecoderState
.from( sdtcreq.getLogicalLinkID() ) .from( sdtcreq.getLogicalLinkID() )
.build() ); .build() );
break; break;
case SN_RF_CONFIRMED_DATA: case SNDCP_RF_CONFIRMED_DATA:
SNDCPUserData sud = (SNDCPUserData)pduc; SNDCPUserData sud = (SNDCPUserData)pduc;
StringBuilder sbFrom = new StringBuilder(); StringBuilder sbFrom = new StringBuilder();
@@ -1747,10 +1733,10 @@ public class P25DecoderState extends DecoderState
.to( pduc.getLogicalLinkID() ) .to( pduc.getLogicalLinkID() )
.build() ); .build() );
break; break;
case SN_RF_UNCONFIRMED_DATA: case SNDCP_RF_UNCONFIRMED_DATA:
break; break;
default: default:
mLog.debug( "PDUC - Unrecognized Message: " + pduc.toString() ); // mLog.debug( "PDUC - Unrecognized Message: " + pduc.toString() );
break; break;
} }
} }
@@ -3101,15 +3087,8 @@ public class P25DecoderState extends DecoderState
if( mToTalkgroup == null || !mToTalkgroup.contentEquals( to ) ) if( mToTalkgroup == null || !mToTalkgroup.contentEquals( to ) )
{ {
mToTalkgroup = to; mToTalkgroup = to;
broadcast( ChangedAttribute.TO_TALKGROUP );
if( mCurrentCallEvent != null && broadcast( ChangedAttribute.TO_TALKGROUP );
( mCurrentCallEvent.getToID() == null ||
!mCurrentCallEvent.getToID().contentEquals( to ) ) )
{
mCurrentCallEvent.setToID( to );
broadcast( mCurrentCallEvent );
}
if( hasAliasList() ) if( hasAliasList() )
{ {
@@ -3121,10 +3100,18 @@ public class P25DecoderState extends DecoderState
} }
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS ); broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
/* Send audio metadata update */ /* Send audio metadata update */
broadcast( new Metadata( MetadataType.TO, mToTalkgroup, mToAlias, true ) ); broadcast( new Metadata( MetadataType.TO, mToTalkgroup, mToAlias, true ) );
if( mCurrentCallEvent != null &&
( mCurrentCallEvent.getToID() == null ||
!mCurrentCallEvent.getToID().contentEquals( to ) ) )
{
mCurrentCallEvent.setToID( to );
broadcast( mCurrentCallEvent );
}
}
} }
} }
@@ -3140,14 +3127,6 @@ public class P25DecoderState extends DecoderState
mFromTalkgroup = from; mFromTalkgroup = from;
broadcast( ChangedAttribute.FROM_TALKGROUP ); broadcast( ChangedAttribute.FROM_TALKGROUP );
if( mCurrentCallEvent != null &&
mCurrentCallEvent.getFromID() == null ||
!mCurrentCallEvent.getFromID().contentEquals( from ) )
{
mCurrentCallEvent.setFromID( from );
broadcast( mCurrentCallEvent );
}
if( hasAliasList() ) if( hasAliasList() )
{ {
mFromAlias = getAliasList().getTalkgroupAlias( mFromTalkgroup ); mFromAlias = getAliasList().getTalkgroupAlias( mFromTalkgroup );
@@ -3158,10 +3137,19 @@ public class P25DecoderState extends DecoderState
} }
broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS ); broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS );
/* Send audio metadata update */
broadcast( new Metadata( MetadataType.FROM, mFromTalkgroup, mFromAlias, true ) );
if( mCurrentCallEvent != null &&
mCurrentCallEvent.getFromID() == null ||
!mCurrentCallEvent.getFromID().contentEquals( from ) )
{
mCurrentCallEvent.setFromID( from );
broadcast( mCurrentCallEvent );
}
} }
/* Send audio metadata update */
broadcast( new Metadata( MetadataType.FROM, mFromTalkgroup, mFromAlias, true ) );
} }
} }

View File

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

View File

@@ -1,5 +1,6 @@
package module.decode.p25.message.pdu; package module.decode.p25.message.pdu;
import module.decode.p25.message.pdu.confirmed.PacketData;
import module.decode.p25.message.pdu.osp.control.AdjacentStatusBroadcastExtended; import module.decode.p25.message.pdu.osp.control.AdjacentStatusBroadcastExtended;
import module.decode.p25.message.pdu.osp.control.CallAlertExtended; import module.decode.p25.message.pdu.osp.control.CallAlertExtended;
import module.decode.p25.message.pdu.osp.control.GroupAffiliationQueryExtended; import module.decode.p25.message.pdu.osp.control.GroupAffiliationQueryExtended;
@@ -121,7 +122,8 @@ public class PDUMessageFactory
} }
return new PDUMessage( message, duid, aliasList ); return new PDUMessage( message, duid, aliasList );
case PACKET_DATA:
return new PacketData( message, aliasList );
case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL: case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL:
return new PDUMessage( message, duid, aliasList ); return new PDUMessage( message, duid, aliasList );
default: default:

View File

@@ -48,19 +48,27 @@ public class PDUConfirmedMessageFactory
case UNENCRYPTED_USER_DATA: case UNENCRYPTED_USER_DATA:
switch( message.getPDUType() ) switch( message.getPDUType() )
{ {
case SN_ACTIVATE_TDS_CONTEXT_ACCEPT: case SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT:
return new SNDCPActivateTDSContextAccept( message ); return new SNDCPActivateTDSContextAccept( message );
case SN_ACTIVATE_TDS_CONTEXT_REJECT: case SNDCP_ACTIVATE_TDS_CONTEXT_REJECT:
return new SNDCPActivateTDSContextReject( message ); return new SNDCPActivateTDSContextReject( message );
case SN_ACTIVATE_TDS_CONTEXT_REQUEST: case SN_ACTIVATE_TDS_CONTEXT_REQUEST:
return new SNDCPActivateTDSContextRequest( message ); return new SNDCPActivateTDSContextRequest( message );
case SN_DEACTIVATE_TDS_CONTEXT_ACCEPT: case SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT:
case SN_DEACTIVATE_TDS_CONTEXT_REQUEST: case SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST:
return new SNDCPDeactivateTDSContext( message ); return new SNDCPDeactivateTDSContext( message );
case SN_RF_CONFIRMED_DATA: case SNDCP_RF_CONFIRMED_DATA:
return new SNDCPUserData( message ); return new SNDCPUserData( message );
case SN_RF_UNCONFIRMED_DATA: case SNDCP_RF_UNCONFIRMED_DATA:
break; break;
case PDU_TYPE_6:
case PDU_TYPE_7:
case PDU_TYPE_8:
case PDU_TYPE_9:
case PDU_TYPE_10:
case PDU_TYPE_11:
return new PDUTypeUnknown( message );
case UNKNOWN:
default: default:
break; break;
} }

View File

@@ -0,0 +1,55 @@
package module.decode.p25.message.pdu.confirmed;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PDUTypeUnknown extends PDUConfirmedMessage
{
public final static Logger mLog =
LoggerFactory.getLogger( PDUTypeUnknown.class );
public PDUTypeUnknown( PDUConfirmedMessage message )
{
super( message );
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " LLID:" );
sb.append( getLogicalLinkID() );
sb.append( " PACKET DATA UNIT CONFIRMED - " );
sb.append( getPDUType().getLabel() );
sb.append( " CRC[" );
sb.append( getErrorStatus() );
sb.append( "]" );
sb.append( " PACKET #" );
sb.append( getPacketSequenceNumber() );
if( isFinalFragment() && getFragmentSequenceNumber() == 0 )
{
sb.append( ".C" );
}
else
{
sb.append( "." );
sb.append( getFragmentSequenceNumber() );
if( isFinalFragment() )
{
sb.append( "C" );
}
}
sb.append( " " );
sb.append( mMessage.toString() );
return sb.toString();
}
}

View File

@@ -0,0 +1,66 @@
package module.decode.p25.message.pdu.confirmed;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.AliasList;
import bits.BinaryMessage;
public class PacketData extends PDUConfirmedMessage
{
public final static Logger mLog =
LoggerFactory.getLogger( PacketData.class );
public static final int DATA_BLOCK_START = 176;
public PacketData( BinaryMessage message, AliasList aliasList )
{
super( message, aliasList );
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " PDUC LLID:" );
sb.append( getLogicalLinkID() );
sb.append( " PACKET DATA" );
sb.append( " CRC[" );
sb.append( getErrorStatus() );
sb.append( "]" );
if( !mMessage.get( FINAL_FRAGMENT_FLAG ) )
{
sb.append( " RESENDING" );
}
sb.append( " PACKET #" );
sb.append( getPacketSequenceNumber() );
if( isFinalFragment() && getFragmentSequenceNumber() == 0 )
{
sb.append( ".C" );
}
else
{
sb.append( "." );
sb.append( getFragmentSequenceNumber() );
if( isFinalFragment() )
{
sb.append( "C" );
}
}
sb.append( " " );
sb.append( mMessage.toString() );
return sb.toString();
}
}

View File

@@ -49,7 +49,7 @@ public class SNDCPDeactivateTDSContext extends PDUConfirmedMessage
public String getDeactivationType() public String getDeactivationType()
{ {
if( getPDUType() == PDUType.SN_DEACTIVATE_TDS_CONTEXT_REQUEST ) if( getPDUType() == PDUType.SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST )
{ {
return mMessage.getInt( DEACTIVATION_TYPE ) == 0 ? return mMessage.getInt( DEACTIVATION_TYPE ) == 0 ?
"ALL NSAPIS" : "THIS NSAPI"; "ALL NSAPIS" : "THIS NSAPI";

View File

@@ -10,9 +10,11 @@ public enum DataUnitID
HDU ( 0, 712, false, "HDU ", "Header Data Unit" ), HDU ( 0, 712, false, "HDU ", "Header Data Unit" ),
TDU ( 3, 64, false, "TDU ", "Simple Terminator Data Unit" ), TDU ( 3, 64, false, "TDU ", "Simple Terminator Data Unit" ),
LDU1 ( 5, 1632, true, "LDU1 ", "Logical Link Data Unit 1" ), LDU1 ( 5, 1632, true, "LDU1 ", "Logical Link Data Unit 1" ),
VSELP1( 6, 1680, false, "VSEL1", "Motorola VSELP Unit 1" ),
TSBK1( 7, 260, false, "TSBK1", "Trunking Signaling Block" ), TSBK1( 7, 260, false, "TSBK1", "Trunking Signaling Block" ),
TSBK2( 7, 260, false, "TSBK2", "Trunking Signaling Block" ), TSBK2( 7, 260, false, "TSBK2", "Trunking Signaling Block" ),
TSBK3( 7, 260, false, "TSBK3", "Trunking Signaling Block" ), TSBK3( 7, 260, false, "TSBK3", "Trunking Signaling Block" ),
VSELP2( 9, 1680, false, "VSEL1", "Motorola VSELP Unit 1" ),
LDU2 ( 10, 1632, true, "LDU2 ", "Logical Link Data Unit 2" ), LDU2 ( 10, 1632, true, "LDU2 ", "Logical Link Data Unit 2" ),
PDU0 ( 12, 260, false, "PDU0 ", "Packet Header Data Unit" ), PDU0 ( 12, 260, false, "PDU0 ", "Packet Header Data Unit" ),
PDU1 ( 12, 356, false, "PDU1 ", "Packet Data Unit-Unconfirmed" ), PDU1 ( 12, 356, false, "PDU1 ", "Packet Data Unit-Unconfirmed" ),

View File

@@ -3,12 +3,18 @@ package module.decode.p25.reference;
public enum PDUType public enum PDUType
{ {
/* Outbound */ /* Outbound */
SN_ACTIVATE_TDS_CONTEXT_ACCEPT( "SN-ACTIVATE TDS CONTEXT ACCEPT", 0 ), SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT( "SNDCP-ACTIVATE TDS CONTEXT ACCEPT", 0 ),
SN_DEACTIVATE_TDS_CONTEXT_ACCEPT( "SN-DEACTIVATE TDS CONTEXT ACCEPT", 1 ), SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT( "SNDCP-DEACTIVATE TDS CONTEXT ACCEPT", 1 ),
SN_DEACTIVATE_TDS_CONTEXT_REQUEST( "SN-DEACTIVATE TDS CONTEXT REQUEST", 2 ), SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST( "SNDCP-DEACTIVATE TDS CONTEXT REQUEST", 2 ),
SN_ACTIVATE_TDS_CONTEXT_REJECT( "SN-ACTIVATE TDS CONTEXT REJECT", 3 ), SNDCP_ACTIVATE_TDS_CONTEXT_REJECT( "SNDCP-ACTIVATE TDS CONTEXT REJECT", 3 ),
SN_RF_UNCONFIRMED_DATA( "SN-RF UNCONFIRMED DATA", 4 ), SNDCP_RF_UNCONFIRMED_DATA( "SNDCP-RF UNCONFIRMED DATA", 4 ),
SN_RF_CONFIRMED_DATA( "SN-RF CONFIRMED DATA", 5 ), SNDCP_RF_CONFIRMED_DATA( "SNDCP-RF CONFIRMED DATA", 5 ),
PDU_TYPE_6( "PDU TYPE 6 UNKNOWN", 6 ),
PDU_TYPE_7( "PDU TYPE 7 UNKNOWN", 7 ),
PDU_TYPE_8( "PDU TYPE 8 UNKNOWN", 8 ),
PDU_TYPE_9( "PDU TYPE 9 UNKNOWN", 9 ),
PDU_TYPE_10( "PDU TYPE 10 UNKNOWN", 10 ),
PDU_TYPE_11( "PDU TYPE 11 UNKNOWN", 11 ),
/* Inbound */ /* Inbound */
SN_ACTIVATE_TDS_CONTEXT_REQUEST( "SN-ACTIVATE TDS CONTEXT REQUEST", 0 ), SN_ACTIVATE_TDS_CONTEXT_REQUEST( "SN-ACTIVATE TDS CONTEXT REQUEST", 0 ),
@@ -36,7 +42,7 @@ public enum PDUType
public static PDUType fromValue( int value, boolean outbound ) public static PDUType fromValue( int value, boolean outbound )
{ {
if( outbound && 0 <= value && value <= 5 ) if( outbound && 0 <= value && value <= 11 )
{ {
return values()[ value ]; return values()[ value ];
} }

View File

@@ -2,7 +2,7 @@ package module.decode.p25.reference;
public enum ServiceAccessPoint public enum ServiceAccessPoint
{ {
UNENCRYPTED_USER_DATA( "USER DATA", 0 ), UNENCRYPTED_USER_DATA( "UNENCRYPTED USER DATA", 0 ),
ENCRYPTED_USER_DATA( "ENCRYPTED USER DATA", 1 ), ENCRYPTED_USER_DATA( "ENCRYPTED USER DATA", 1 ),
CIRCUIT_DATA( "CIRCUIT DATA", 2 ), CIRCUIT_DATA( "CIRCUIT DATA", 2 ),
CIRCUIT_DATA_CONTROL( "CIRCUIT DATA CONTROL", 3 ), CIRCUIT_DATA_CONTROL( "CIRCUIT DATA CONTROL", 3 ),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -97,9 +97,13 @@ public class TunerChannel implements Comparable<TunerChannel>
} }
@Override @Override
public int compareTo( TunerChannel otherLock ) public int compareTo( TunerChannel otherTunerChannel )
{ {
if( mFrequency < otherLock.getFrequency() ) if( this.equals( otherTunerChannel ) )
{
return 0;
}
else if( mFrequency < otherTunerChannel.getFrequency() )
{ {
return -1; return -1;
} }

View File

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