From 1d7f2557840a8537c39eb5b90af537808eb6a143 Mon Sep 17 00:00:00 2001 From: denny Date: Sat, 13 Feb 2016 14:14:54 -0500 Subject: [PATCH] Revert "Merges master onto development branch" This reverts commit 2420bc901c63ad9d41e7c99ac95a426f826182ff. --- .gitignore | 3 +- README.md | 31 +- build/build.xml | 114 ++-- scripts/run_sdrtrunk_windows.bat | 2 +- src/audio/AudioChannelPanel.java | 108 ++-- src/audio/AudioManager.java | 608 +++++++----------- src/audio/output/AudioOutput.java | 36 +- src/audio/output/MonoAudioOutput.java | 155 ++--- src/audio/output/StereoAudioOutput.java | 139 ++-- src/bits/MessageFramer.java | 10 - src/controller/ThreadPoolManager.java | 24 +- src/gui/SDRTrunk.java | 4 +- src/message/Message.java | 96 +-- src/module/decode/event/CallEvent.java | 5 - .../decode/event/MessageActivityModel.java | 3 +- .../decode/event/MessageDetailsProvider.java | 80 +++ src/module/decode/ltrnet/LTRCallEvent.java | 17 +- src/module/decode/ltrnet/LTRNetDecoder.java | 1 - .../ltrstandard/LTRStandardDecoder.java | 1 - .../ltrstandard/LTRStandardDecoderState.java | 476 ++++++-------- .../ltrstandard/LTRStandardISWMessage.java | 238 +++++++ .../ltrstandard/LTRStandardMessage.java | 254 ++++++++ .../ltrstandard/LTRStandardMessageFilter.java | 1 - .../LTRStandardMessageProcessor.java | 78 +-- .../ltrstandard/LTRStandardOSWMessage.java | 248 +++++++ .../ltrstandard/message/CallEndMessage.java | 67 -- .../ltrstandard/message/CallMessage.java | 68 -- .../ltrstandard/message/IdleMessage.java | 58 -- .../message/LTRStandardMessage.java | 213 ------ .../ltrstandard/message/UnknownMessage.java | 58 -- src/module/decode/mpt1327/MPT1327Decoder.java | 4 +- .../decode/mpt1327/MPT1327DecoderState.java | 68 +- src/module/decode/p25/P25DecoderState.java | 275 ++++---- src/module/decode/p25/message/P25Message.java | 2 - .../p25/message/pdu/PDUMessageFactory.java | 4 +- .../confirmed/PDUConfirmedMessageFactory.java | 20 +- .../message/pdu/confirmed/PDUTypeUnknown.java | 55 -- .../p25/message/pdu/confirmed/PacketData.java | 66 -- .../confirmed/SNDCPDeactivateTDSContext.java | 2 +- .../p25/message/tsbk/TSBKMessageFactory.java | 7 +- .../osp/control/IdentifierUpdateTDMA.java | 159 ----- .../tsbk/osp/control/SyncBroadcast.java | 220 ++----- src/module/decode/p25/reference/Opcode.java | 2 +- src/module/decode/p25/reference/PDUType.java | 20 +- .../p25/reference/ServiceAccessPoint.java | 2 +- .../decode/passport/PassportDecoder.java | 1 - .../state/ChangeChannelTimeoutEvent.java | 29 - src/module/decode/state/ChannelList.java | 23 +- src/module/decode/state/ChannelState.java | 507 +++++++-------- .../decode/state/DecoderStateEvent.java | 1 - src/module/decode/state/State.java | 59 +- src/org/jdesktop/swingx/JXMapViewer.java | 9 +- src/properties/SystemProperties.java | 23 +- src/settings/ColorSetting.java | 2 +- src/settings/SettingsManager.java | 27 +- .../mixer/MixerChannelConfiguration.java | 8 - src/source/tuner/TunerChannel.java | 8 +- src/spectrum/DFTProcessor.java | 18 +- src/spectrum/SpectralDisplayPanel.java | 37 -- src/spectrum/WaterfallPanel.java | 2 - 60 files changed, 2106 insertions(+), 2750 deletions(-) create mode 100644 src/module/decode/event/MessageDetailsProvider.java create mode 100644 src/module/decode/ltrstandard/LTRStandardISWMessage.java create mode 100644 src/module/decode/ltrstandard/LTRStandardMessage.java create mode 100644 src/module/decode/ltrstandard/LTRStandardOSWMessage.java delete mode 100644 src/module/decode/ltrstandard/message/CallEndMessage.java delete mode 100644 src/module/decode/ltrstandard/message/CallMessage.java delete mode 100644 src/module/decode/ltrstandard/message/IdleMessage.java delete mode 100644 src/module/decode/ltrstandard/message/LTRStandardMessage.java delete mode 100644 src/module/decode/ltrstandard/message/UnknownMessage.java delete mode 100644 src/module/decode/p25/message/pdu/confirmed/PDUTypeUnknown.java delete mode 100644 src/module/decode/p25/message/pdu/confirmed/PacketData.java delete mode 100644 src/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java delete mode 100644 src/module/decode/state/ChangeChannelTimeoutEvent.java diff --git a/.gitignore b/.gitignore index 7be9efe9..7599d847 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /*.class /classes* -/product* -/bin/ +/product* \ No newline at end of file diff --git a/README.md b/README.md index 22bcc018..8c1e9dc4 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,19 @@ -# sdrtrunk # -A cross-platform java application for decoding, monitoring and recording trunked -mobile and related radio protocols using Software Defined Radios (SDR). +# sdrtrunk +A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR). -# Documentation # * [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted) -* [User's Manual (Version 0.2.0)](https://github.com/DSheirer/sdrtrunk/wiki/UserManual) -* [Wiki](https://github.com/DSheirer/sdrtrunk/wiki) - -# Support Group and Error Reporting # -You can get help with setting up the sdrtrunk application or report any errors -that you encounter while running the application in the sdrtrunk Google Group at: - +* [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) -# Updates # -Subscribe to this group to receive updates about new releases and the ongoing -application development effort. - -* [Subscribe for Updates](https://groups.google.com/forum/#!forum/sdrtrunk) - ![](https://github.com/DSheirer/sdrtrunk/wiki/images/SDRTrunk_application.png) + diff --git a/build/build.xml b/build/build.xml index 4cdb3eaa..3667c7ff 100644 --- a/build/build.xml +++ b/build/build.xml @@ -7,115 +7,113 @@ - - - - - - - - - - - + - - - - - - + + + + + + + + + - + - + - + - - + + + - + - + - - - + - + - - - + + + - + - - + + - - - + + + - - - - + + - - + + + + + + + + - - - - - - - - - - - - + - + + + + + + + + + + - - + + \ No newline at end of file diff --git a/scripts/run_sdrtrunk_windows.bat b/scripts/run_sdrtrunk_windows.bat index 27c0b763..1206e039 100644 --- a/scripts/run_sdrtrunk_windows.bat +++ b/scripts/run_sdrtrunk_windows.bat @@ -1 +1 @@ -java -XX:+UseG1GC -cp "sdrtrunk.jar;*;libs/*;config/*;images/*" gui.SDRTrunk \ No newline at end of file +java -XX:+UseG1GC -cp "SDRTrunk.jar;*;libs/*;config/*;images/*" gui.SDRTrunk \ No newline at end of file diff --git a/src/audio/AudioChannelPanel.java b/src/audio/AudioChannelPanel.java index d23c2024..298e6b18 100644 --- a/src/audio/AudioChannelPanel.java +++ b/src/audio/AudioChannelPanel.java @@ -48,11 +48,8 @@ public class AudioChannelPanel extends JPanel private JLabel mFromLabel = new JLabel( "FROM:" ); private JLabel mFrom = new JLabel( "" ); private JLabel mFromAlias = new JLabel( "" ); - - private enum AudioStatus { STARTED,STOPPED }; - private AudioStatus mAudioStatus = AudioStatus.STOPPED; - private AudioMetadata mAudioMetadata; + private boolean mConfigured = false; public AudioChannelPanel( SettingsManager settingsManager, AudioOutput audioOutput ) { @@ -137,19 +134,15 @@ public class AudioChannelPanel extends JPanel { switch( audioEvent.getType() ) { - case AUDIO_STARTED: - synchronized( mAudioStatus ) - { - mAudioStatus = AudioStatus.STARTED; - updateLabels(); - } - break; case AUDIO_STOPPED: - synchronized( mAudioStatus ) + EventQueue.invokeLater( new Runnable() { - mAudioStatus = AudioStatus.STOPPED; - resetLabels(); - } + @Override + public void run() + { + resetLabels(); + } + } ); break; case AUDIO_MUTED: case AUDIO_UNMUTED: @@ -173,55 +166,13 @@ public class AudioChannelPanel extends JPanel */ private void resetLabels() { - EventQueue.invokeLater( new Runnable() - { - @Override - public void run() - { - mFrom.setText( "" ); - updateAlias( mFromAlias, null ); - - mTo.setText( "" ); - updateAlias( mToAlias, null ); - } - } ); - } - - 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( "-----" ); - } - } - } ); - } + mFrom.setText( "" ); + updateAlias( mFromAlias, null ); + + mTo.setText( "" ); + updateAlias( mToAlias, null ); + + mConfigured = false; } /** @@ -263,17 +214,32 @@ public class AudioChannelPanel extends JPanel @Override public void receive( AudioMetadata audioMetadata ) { - mAudioMetadata = audioMetadata; - - if( audioMetadata.isUpdated() ) + if( !mConfigured || audioMetadata.isUpdated() ) { - synchronized( mAudioStatus ) + final Metadata from = audioMetadata.getMetadata( MetadataType.FROM ); + + final Metadata to = audioMetadata.getMetadata( MetadataType.TO ); + + EventQueue.invokeLater( new Runnable() { - if( mAudioStatus == AudioStatus.STARTED ) + @Override + public void run() { - updateLabels(); + if( from != null ) + { + mFrom.setText( from.getValue() ); + updateAlias( mFromAlias, from.getAlias() ); + } + + if( to != null ) + { + mTo.setText( to.getValue() ); + updateAlias( mToAlias, to.getAlias() ); + } } - } + } ); + + mConfigured = true; } } } diff --git a/src/audio/AudioManager.java b/src/audio/AudioManager.java index b8e1e183..6fa1a9f7 100644 --- a/src/audio/AudioManager.java +++ b/src/audio/AudioManager.java @@ -2,7 +2,7 @@ package audio; /******************************************************************************* * SDR Trunk - * Copyright (C) 2014-2016 Dennis Sheirer + * Copyright (C) 2014,2015 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,12 +17,16 @@ package audio; * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ + import java.util.ArrayList; + import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -33,12 +37,10 @@ import javax.sound.sampled.Mixer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import properties.SystemProperties; import sample.Broadcaster; import sample.Listener; import source.mixer.MixerChannel; import source.mixer.MixerChannelConfiguration; -import source.mixer.MixerManager; import audio.AudioEvent.Type; import audio.output.AudioOutput; import audio.output.MonoAudioOutput; @@ -51,9 +53,7 @@ public class AudioManager implements Listener, IAudioController private static final Logger mLog = LoggerFactory.getLogger( AudioManager.class ); public static final int AUDIO_TIMEOUT = 2000; //2 seconds - - public static final String AUDIO_CHANNELS_PROPERTY = "audio.manager.channels"; - public static final String AUDIO_MIXER_PROPERTY = "audio.manager.mixer"; + public static final int NO_SOURCE = 0; public static final AudioEvent CONFIGURATION_CHANGE_STARTED = new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null ); @@ -62,90 +62,49 @@ public class AudioManager implements Listener, IAudioController new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_COMPLETE, null ); private LinkedTransferQueue mAudioPacketQueue = new LinkedTransferQueue<>(); - private Map mChannelConnectionMap = new HashMap<>(); - private List mAudioOutputConnections = new ArrayList<>(); - private AudioOutputConnection mLowestPriorityConnection; - private int mAvailableConnectionCount; + private Map mChannelAssignments = new HashMap<>(); + private int mLowestPrioritySource; + private List mAvailableAudioOutputs = new ArrayList<>(); private Map mAudioOutputMap = new HashMap<>(); + private List mChannelNames = new ArrayList<>(); private Broadcaster mControllerBroadcaster = new Broadcaster<>(); private ThreadPoolManager mThreadPoolManager; private ScheduledFuture mProcessingTask; - private MixerManager mMixerManager; - private MixerChannelConfiguration mMixerChannelConfiguration; + private MixerChannelConfiguration mMixerChannel; /** * Processes all audio produced by the decoding channels and routes audio * packets to any combination of outputs based on any alias audio routing * options specified by the user. */ - public AudioManager( ThreadPoolManager manager, MixerManager mixerManager ) + public AudioManager( ThreadPoolManager manager ) { mThreadPoolManager = manager; - mMixerManager = mixerManager; - loadSettings(); - } - - private void loadSettings() - { - MixerChannelConfiguration configuration = null; + //TODO: save audio system preferences to the settings manager - SystemProperties properties = SystemProperties.getInstance(); + /* Use the system default mixer and mono channel as default startup */ + Mixer mixer = AudioSystem.getMixer( null ); - Mixer defaultMixer = AudioSystem.getMixer( null ); - - String mixer = properties.get( AUDIO_MIXER_PROPERTY, defaultMixer.getMixerInfo().getName() ); - - String channels = properties.get( AUDIO_CHANNELS_PROPERTY, MixerChannel.MONO.name() ); - - MixerChannelConfiguration[] mixerConfigurations = mMixerManager.getOutputMixers(); - - for( MixerChannelConfiguration mixerConfig: mixerConfigurations ) + if( mixer != null ) { - if( mixerConfig.matches( mixer, channels ) ) - { - configuration = mixerConfig; - } - } - - if( configuration == null ) - { - configuration = getDefaultConfiguration(); - } - - try - { - setMixerChannelConfiguration( configuration ); - } - catch( Exception e ) - { - mLog.error( "Couldn't set stored audio mixer/channel configuration" - + " - using default", e ); + mMixerChannel = new MixerChannelConfiguration( mixer, MixerChannel.MONO ); try { - setMixerChannelConfiguration( getDefaultConfiguration() ); - } - catch( Exception e2 ) + setMixerChannelConfiguration( mMixerChannel ); + } + catch ( AudioException e ) { - mLog.error( "Couldn't set default audio mixer/channel " - + "configuration - no audio will be available", e2 ); + mLog.error( "Couldn't create an audio output device", e ); } } } - private MixerChannelConfiguration getDefaultConfiguration() - { - /* Use the system default mixer and mono channel as default startup */ - Mixer defaultMixer = AudioSystem.getMixer( null ); - - return new MixerChannelConfiguration( defaultMixer, MixerChannel.MONO ); - } - public void dispose() { if( mThreadPoolManager != null && mProcessingTask != null ) @@ -157,15 +116,6 @@ public class AudioManager implements Listener, IAudioController mThreadPoolManager = null; mProcessingTask = null; - - mChannelConnectionMap.clear(); - - for( AudioOutputConnection connection: mAudioOutputConnections ) - { - connection.dispose(); - } - - mAudioOutputConnections.clear(); } @Override @@ -174,44 +124,207 @@ public class AudioManager implements Listener, IAudioController mAudioPacketQueue.add( packet ); } - /** - * Checks each audio channel assignment and disconnects any inactive connections - */ - private void disconnectInactiveChannelAssignments() + public class AudioPacketProcessor implements Runnable { - boolean changed = false; - - for( AudioOutputConnection connection: mAudioOutputConnections ) + @Override + public void run() { - if( connection.isInactive() && - mChannelConnectionMap.containsKey( connection.getSource() ) ) + removeCompletedChannelAssignments(); + + if( mAudioPacketQueue != null ) { - mChannelConnectionMap.remove( connection.getSource() ); - connection.disconnect(); - changed = true; + List packets = new ArrayList(); + + mAudioPacketQueue.drainTo( packets ); + + for( AudioPacket packet: packets ) + { + /* Don't process any packet's marked as do not monitor */ + if( !packet.getAudioMetadata().isDoNotMonitor() ) + { + int source = packet.getAudioMetadata().getSource(); + + if( packet.getType() != AudioPacket.Type.END ) + { + /* Existing channel assignment */ + if( mChannelAssignments.containsKey( source ) ) + { + mChannelAssignments.get( source ).getAudioOutput() + .receive( packet ); + } + /* Start a new channel assignment */ + else if( !mAvailableAudioOutputs.isEmpty() ) + { + AudioOutput channel = mAvailableAudioOutputs.remove( 0 ); + + mChannelAssignments.put( packet.getAudioMetadata().getSource(), + new AudioChannelAssignment( channel, packet.getAudioMetadata().getPriority() ) ); + + updateLowestPriorityAssignment(); + + channel.receive( packet ); + } + /* Override the lowest priority assignment */ + else + { + if( mLowestPrioritySource > -1 ) + { + int priority = packet.getAudioMetadata().getPriority(); + + if( packet.getAudioMetadata().isSelected() || + priority < mChannelAssignments + .get( mLowestPrioritySource ).getPriority() ) + { + AudioChannelAssignment assignment = + mChannelAssignments.remove( mLowestPrioritySource ); + + if( assignment != null ) + { + assignment.setPriority( priority ); + + int overrideSource = packet.getAudioMetadata().getSource(); + + mChannelAssignments.put( overrideSource, assignment ); + + mLowestPrioritySource = overrideSource; + + assignment.getAudioOutput().receive( packet ); + } + else + { + mLowestPrioritySource = -1; + } + } + } + } + } + } + } } } - - if( changed ) + } + + /** + * Checks each audio channel assignment and removes any completed channels. + */ + private void removeCompletedChannelAssignments() + { + if( !mChannelAssignments.isEmpty() ) { - updateLowestPriorityAssignment(); + Iterator> it = + mChannelAssignments.entrySet().iterator(); + + Entry entry; + + boolean removed = false; + + while( it.hasNext() ) + { + entry = it.next(); + + if( entry.getValue().isComplete() ) + { + mAvailableAudioOutputs.add( entry.getValue().getAudioOutput() ); + entry.getValue().dispose(); + it.remove(); + removed = true; + } + } + + if( removed ) + { + updateLowestPriorityAssignment(); + } } } /** - * Identifies the lowest priority channel connection + * Identifies the lowest priority channel assignment ... the one that can + * be overridden with a higher priority call */ private void updateLowestPriorityAssignment() { - mLowestPriorityConnection = null; + int lowestPriority = -1; - for( AudioOutputConnection connection: mAudioOutputConnections ) + mLowestPrioritySource = -1; + + for( int source: mChannelAssignments.keySet() ) { - if( connection.isConnected() && - ( mLowestPriorityConnection == null || - mLowestPriorityConnection.getPriority() > connection.getPriority() ) ) + AudioChannelAssignment assignment = mChannelAssignments.get( source ); + + if( assignment.getPriority() > lowestPriority ) { - mLowestPriorityConnection = connection; + lowestPriority = assignment.getPriority(); + + mLowestPrioritySource = source; + } + } + } + + public void endAllAudioAssignments() + { + for( AudioOutput output: mAudioOutputMap.values() ) + { + output.receive( new AudioPacket( AudioPacket.Type.END, null ) ); + } + } + + public class AudioChannelAssignment implements Listener + { + private static final long CALL_END_DELAY = 2000; //2 seconds + + private AudioOutput mAudioOutput; + private int mPriority; + private long mAudioStoppedTime = System.currentTimeMillis(); + + public AudioChannelAssignment( AudioOutput audioOutput, int priority ) + { + mAudioOutput = audioOutput; + mPriority = priority; + + mAudioOutput.addAudioEventListener( this ); + } + + public boolean isComplete() + { + return mAudioStoppedTime != Long.MAX_VALUE && + mAudioStoppedTime + CALL_END_DELAY < System.currentTimeMillis(); + } + + public void dispose() + { + mAudioOutput.removeAudioEventListener( this ); + mAudioOutput.reset(); + mAudioOutput = null; + } + + public AudioOutput getAudioOutput() + { + return mAudioOutput; + } + + public int getPriority() + { + return mPriority; + } + + public void setPriority( int priority ) + { + mPriority = priority; + } + + @Override + public void receive( AudioEvent event ) + { + switch( event.getType() ) + { + case AUDIO_STARTED: + case AUDIO_STOPPED: + case AUDIO_CONTINUATION: + mAudioStoppedTime = System.currentTimeMillis(); + break; + default: + break; } } } @@ -229,29 +342,41 @@ public class AudioManager implements Listener, IAudioController { mThreadPoolManager.cancel( mProcessingTask ); } + + endAllAudioAssignments(); - disposeCurrentConfiguration(); + for( Entry e: mChannelAssignments.entrySet() ) + { + e.getValue().getAudioOutput().dispose(); + } + mChannelAssignments.clear(); + for( AudioOutput output: mAvailableAudioOutputs ) + { + output.dispose(); + } + mAvailableAudioOutputs.clear(); + + mChannelNames.clear(); + mAudioOutputMap.clear(); + switch( entry.getMixerChannel() ) { case MONO: - AudioOutput mono = new MonoAudioOutput( mThreadPoolManager, - entry.getMixer() ); - mAudioOutputConnections.add( new AudioOutputConnection( mono ) ); - mAvailableConnectionCount++; + AudioOutput mono = new MonoAudioOutput( entry.getMixer() ); + mAvailableAudioOutputs.add( mono ); + mChannelNames.add( mono.getChannelName() ); mAudioOutputMap.put( mono.getChannelName(), mono ); break; case STEREO: - AudioOutput left = new StereoAudioOutput( mThreadPoolManager, - entry.getMixer(), MixerChannel.LEFT ); - mAudioOutputConnections.add( new AudioOutputConnection( left ) ); - mAvailableConnectionCount++; + AudioOutput left = new StereoAudioOutput( entry.getMixer(), MixerChannel.LEFT ); + mAvailableAudioOutputs.add( left ); + mChannelNames.add( left.getChannelName() ); mAudioOutputMap.put( left.getChannelName(), left ); - AudioOutput right = new StereoAudioOutput( mThreadPoolManager, - entry.getMixer(), MixerChannel.RIGHT ); - mAudioOutputConnections.add( new AudioOutputConnection( right ) ); - mAvailableConnectionCount++; + AudioOutput right = new StereoAudioOutput( entry.getMixer(), MixerChannel.RIGHT ); + mAvailableAudioOutputs.add( right ); + mChannelNames.add( right.getChannelName() ); mAudioOutputMap.put( right.getChannelName(), right ); break; default: @@ -264,12 +389,6 @@ public class AudioManager implements Listener, IAudioController TimeUnit.MILLISECONDS ); mControllerBroadcaster.broadcast( CONFIGURATION_CHANGE_COMPLETE ); - - SystemProperties properties = SystemProperties.getInstance(); - - properties.set( AUDIO_MIXER_PROPERTY, entry.getMixer().getMixerInfo().getName() ); - properties.set( AUDIO_CHANNELS_PROPERTY, entry.getMixerChannel().name() ); - properties.save(); } } @@ -296,33 +415,10 @@ public class AudioManager implements Listener, IAudioController mLowestPriorityConnection = null; } - /** - * Clears all channel assignments and terminates all audio outputs in - * preparation for complete shutdown or change to another mixer/channel - * configuration - */ - private void disposeCurrentConfiguration() - { - mChannelConnectionMap.clear(); - - for( AudioOutputConnection connection : mAudioOutputConnections ) - { - connection.dispose(); - } - - mAvailableConnectionCount = 0; - - mAudioOutputConnections.clear(); - - mAudioOutputMap.clear(); - - mLowestPriorityConnection = null; - } - @Override public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException { - return mMixerChannelConfiguration; + return mMixerChannel; } @Override @@ -353,244 +449,4 @@ public class AudioManager implements Listener, IAudioController { mControllerBroadcaster.removeListener( listener ); } - - /** - * Returns an audio output connection for the packet if one is - * available, or overrides an existing lower priority connection. - * Returns null if no connection is available for the audio packet. - * - * @param packet from a source - * @return an audio output connection or null - */ - private AudioOutputConnection getConnection( AudioPacket packet ) - { - int source = packet.getAudioMetadata().getSource(); - - //Use an existing connection - if( mChannelConnectionMap.containsKey( source ) ) - { - return mChannelConnectionMap.get( source ); - } - - //Connect to and use an available connection - if( mAvailableConnectionCount > 0 ) - { - for( AudioOutputConnection connection: mAudioOutputConnections ) - { - if( connection.isDisconnected() ) - { - connection.connect( source ); - mChannelConnectionMap.put( source, connection ); - return connection; - } - } - } - //Preempt an existing connection and connect it to this higher priority source - else //Check for a channel priority override - { - int priority = packet.getAudioMetadata().getPriority(); - - if( mLowestPriorityConnection != null && - priority < mLowestPriorityConnection.getPriority() ) - { - AudioOutputConnection connection = mLowestPriorityConnection; - - mChannelConnectionMap.remove( connection.getSource() ); - - connection.connect( source ); - - mChannelConnectionMap.put( source, connection ); - - updateLowestPriorityAssignment(); - - return connection; - } - } - - return null; - } - - public class AudioPacketProcessor implements Runnable - { - @Override - public void run() - { - try - { - disconnectInactiveChannelAssignments(); - - if( mAudioPacketQueue != null ) - { - List packets = new ArrayList(); - - 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 ) - { - AudioOutputConnection connection = getConnection( packet ); - - if( connection != null ) - { - connection.receive( packet ); - } - } - } - } - } - catch( Exception e ) - { - mLog.error( "Encountered error while processing audio packets", e ); - } - } - } - - /** - * Audio output connection manages a connection between a source and an audio - * output and maintains current state information about the audio activity - * received form the source. - */ - public class AudioOutputConnection implements Listener - { - private static final long CALL_END_DELAY = 2000; //2 seconds - private static final int DISCONNECTED = -1; - - private AudioOutput mAudioOutput; - private int mPriority = 0; - private long mLastActivity = System.currentTimeMillis(); - private int mSource = DISCONNECTED; - - public AudioOutputConnection( AudioOutput audioOutput ) - { - mAudioOutput = audioOutput; - mAudioOutput.addAudioEventListener( this ); - } - - public void receive( AudioPacket packet ) - { - if( packet.hasAudioMetadata() && - packet.getAudioMetadata().getSource() == mSource ) - { - int priority = packet.getAudioMetadata().getPriority(); - - if( mPriority != priority ) - { - mPriority = priority; - updateLowestPriorityAssignment(); - } - - updateTimestamp(); - - if( mAudioOutput != null ) - { - mAudioOutput.receive( packet ); - } - } - else - { - if( packet.hasAudioMetadata() ) - { - mLog.error( "Received audio packet from channel source [" + - packet.getAudioMetadata().getSource() + - "] however this assignment is currently connected " - + "to channel source [" + mSource + "]" ); - } - else - { - mLog.error( "Received audio packet with no metadata - " - + "cannot route audio packet" ); - } - } - } - - private void updateTimestamp() - { - mLastActivity = System.currentTimeMillis(); - } - - /** - * Terminates the audio output and prepares this connection for disposal - */ - public void dispose() - { - mAudioOutput.dispose(); - mAudioOutput = null; - } - - /** - * Indicates if this assignment is currently disconnected from a channel source - */ - public boolean isDisconnected() - { - return mSource == DISCONNECTED; - } - - /** - * Indicates if this assignment is currently connected to a channel source - */ - public boolean isConnected() - { - return !isDisconnected(); - } - - /** - * Connects this assignment to the indicated source so that audio - * packets from this source can be sent to the audio output - */ - public void connect( int source ) - { - mSource = source; - updateTimestamp(); - } - - /** - * Currently connected source or -1 if disconnected - */ - public int getSource() - { - return mSource; - } - - /** - * Disconnects this assignment from the source and prevents any audio - * from being routed to the audio output until another source is assigned - */ - public void disconnect() - { - mSource = DISCONNECTED; - } - - /** - * Indicates if audio output is current inactive, meaning that the - * audio output hasn't recently processed any audio packets. - */ - public boolean isInactive() - { - return mLastActivity != Long.MAX_VALUE && - mLastActivity + CALL_END_DELAY < System.currentTimeMillis(); - } - - public int getPriority() - { - return mPriority; - } - - @Override - public void receive( AudioEvent event ) - { - switch( event.getType() ) - { - case AUDIO_STARTED: - case AUDIO_STOPPED: - case AUDIO_CONTINUATION: - updateTimestamp(); - break; - default: - break; - } - } - } } diff --git a/src/audio/output/AudioOutput.java b/src/audio/output/AudioOutput.java index 74b2e822..d9506472 100644 --- a/src/audio/output/AudioOutput.java +++ b/src/audio/output/AudioOutput.java @@ -1,9 +1,10 @@ package audio.output; import java.util.concurrent.LinkedTransferQueue; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; import javax.sound.sampled.FloatControl; -import javax.sound.sampled.LineListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,26 +12,23 @@ import org.slf4j.LoggerFactory; import sample.Broadcaster; import sample.Listener; import audio.AudioEvent; +import audio.AudioEvent.Type; import audio.AudioPacket; import audio.metadata.AudioMetadata; -import controller.ThreadPoolManager; -public abstract class AudioOutput implements Listener, LineListener +public abstract class AudioOutput implements Listener { private final static Logger mLog = LoggerFactory.getLogger( AudioOutput.class ); + private AtomicBoolean mSquelched = new AtomicBoolean( false ); private Listener mAudioMetadataListener; + protected Broadcaster mAudioEventBroadcaster = new Broadcaster<>(); protected LinkedTransferQueue mBuffer = new LinkedTransferQueue<>(); - protected ThreadPoolManager mThreadPoolManager; + protected ScheduledExecutorService mExecutorService; protected boolean mCanProcessAudio; protected long mLastActivity; - public AudioOutput( ThreadPoolManager threadPoolManager ) - { - mThreadPoolManager = threadPoolManager; - } - public void reset() { broadcast( new AudioEvent( AudioEvent.Type.AUDIO_STOPPED, @@ -43,11 +41,14 @@ public abstract class AudioOutput implements Listener, LineListener mBuffer.clear(); + if( mExecutorService != null ) + { + mExecutorService.shutdown(); + } + mAudioEventBroadcaster.dispose(); mAudioEventBroadcaster = null; mAudioMetadataListener = null; - - mThreadPoolManager = null; } public abstract String getChannelName(); @@ -71,6 +72,19 @@ public abstract class AudioOutput implements Listener, LineListener mAudioEventBroadcaster.broadcast( audioEvent ); } + public void setSquelched( boolean squelched ) + { + mSquelched.set( squelched ); + + broadcast( new AudioEvent( squelched ? Type.AUDIO_SQUELCHED : + Type.AUDIO_UNSQUELCHED, getChannelName() ) ); + } + + public boolean isSquelched() + { + return mSquelched.get(); + } + /** * Registers a single listener to receive audio start and audio stop events */ diff --git a/src/audio/output/MonoAudioOutput.java b/src/audio/output/MonoAudioOutput.java index ec1f7816..76bf2324 100644 --- a/src/audio/output/MonoAudioOutput.java +++ b/src/audio/output/MonoAudioOutput.java @@ -5,14 +5,13 @@ import java.nio.ByteOrder; import java.nio.ShortBuffer; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import javax.sound.sampled.BooleanControl; import javax.sound.sampled.Control; import javax.sound.sampled.FloatControl; -import javax.sound.sampled.LineEvent; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.Mixer; import javax.sound.sampled.SourceDataLine; @@ -25,8 +24,8 @@ import audio.AudioEvent; import audio.AudioFormats; import audio.AudioPacket; import audio.AudioPacket.Type; -import controller.ThreadPoolManager; -import controller.ThreadPoolManager.ThreadType; +import audio.output.StereoAudioOutput.QueueProcessor; +import controller.NamingThreadFactory; /** * Mono Audio output with automatic flow control based on the availability of @@ -51,14 +50,14 @@ public class MonoAudioOutput extends AudioOutput private SourceDataLine mOutput; private FloatControl mGainControl; private BooleanControl mMuteControl; + private AudioEvent mAudioStartEvent; private AudioEvent mAudioStopEvent; private AudioEvent mAudioContinuationEvent; - private ScheduledFuture mProcessorTask; - public MonoAudioOutput( ThreadPoolManager threadPoolManager, Mixer mixer ) + public MonoAudioOutput( Mixer mixer ) { - super( threadPoolManager ); + super(); mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED, getChannelName() ); @@ -75,8 +74,15 @@ public class MonoAudioOutput extends AudioOutput if( mOutput != null ) { mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO, BUFFER_SIZE ); - mOutput.addLineListener( this ); + mCanProcessAudio = true; + + mExecutorService = Executors.newSingleThreadScheduledExecutor( + new NamingThreadFactory( "audio (mono) output" ) ); + + /* Run the queue processor task every 40 milliseconds */ + mExecutorService.scheduleAtFixedRate( new QueueProcessor(), + 0, 40, TimeUnit.MILLISECONDS ); } } catch ( LineUnavailableException e ) @@ -109,10 +115,12 @@ public class MonoAudioOutput extends AudioOutput mixer.getMixerInfo().getName() + "]" ); } + mExecutorService = Executors.newSingleThreadScheduledExecutor( + new NamingThreadFactory( "audio (mono) output" ) ); + /* Run the queue processor task every 40 milliseconds */ - mProcessorTask = mThreadPoolManager.scheduleFixedRate( - ThreadType.AUDIO_PROCESSING, new QueueProcessor(), - 40, TimeUnit.MILLISECONDS ); + mExecutorService.scheduleAtFixedRate( new QueueProcessor(), + 0, 40, TimeUnit.MILLISECONDS ); } } @@ -124,108 +132,102 @@ public class MonoAudioOutput extends AudioOutput public void dispose() { - if( mProcessorTask != null && mThreadPoolManager != null ) - { - mThreadPoolManager.cancel( mProcessorTask ); - } - - mProcessorTask = null; - super.dispose(); if( mOutput != null ) { mOutput.close(); } - - mOutput = null; - mGainControl = null; - mMuteControl = null; } public class QueueProcessor implements Runnable { private AtomicBoolean mProcessing = new AtomicBoolean(); + public QueueProcessor() + { + } + @Override public void run() { - try + /* The mProcessing flag ensures that only one instance of the + * processor can run at any given time */ + if( mProcessing.compareAndSet( false, true ) ) { - /* The mProcessing flag ensures that only one instance of the - * processor can run at any given time */ - if( mProcessing.compareAndSet( false, true ) ) + List packets = new ArrayList(); + + mBuffer.drainTo( packets ); + + if( packets.size() > 0 ) { - List packets = new ArrayList(); + broadcast( mAudioContinuationEvent ); - mBuffer.drainTo( packets ); - - if( packets.size() > 0 ) + for( AudioPacket packet: packets ) { - for( AudioPacket packet: packets ) + if( mCanProcessAudio ) { - broadcast( mAudioContinuationEvent ); - - if( mCanProcessAudio ) + if( isSquelched() ) { - if( packet.getType() == Type.AUDIO ) + checkStop(); + } + else if( packet.getType() == Type.AUDIO ) + { + float[] samples = packet.getAudioBuffer().getSamples(); + + /* Little-endian byte buffer */ + ByteBuffer buffer = + ByteBuffer.allocate( samples.length * 2 ) + .order( ByteOrder.LITTLE_ENDIAN ); + + ShortBuffer shortBuffer = buffer.asShortBuffer(); + + for( float sample: samples ) { - float[] samples = packet.getAudioBuffer().getSamples(); - - /* Little-endian byte buffer */ - ByteBuffer buffer = - ByteBuffer.allocate( samples.length * 2 ) - .order( ByteOrder.LITTLE_ENDIAN ); - - ShortBuffer shortBuffer = buffer.asShortBuffer(); - - for( float sample: samples ) - { - shortBuffer.put( (short)( sample * Short.MAX_VALUE ) ); - } - - //This is a blocking write - mOutput.write( buffer.array(), 0, buffer.array().length ); - - checkStart(); - - broadcast( packet.getAudioMetadata() ); - - mLastActivity = System.currentTimeMillis(); + shortBuffer.put( (short)( sample * Short.MAX_VALUE ) ); } + + mOutput.write( buffer.array(), 0, + buffer.array().length ); + + checkStart(); + + broadcast( packet.getAudioMetadata() ); + + mLastActivity = System.currentTimeMillis(); } } } - else - { - checkStop(); - } - - mProcessing.set( false ); } - } - catch( Exception e ) - { - mLog.error( "Error while processing audio buffers", e ); + else + { + checkStop(); + } + + mProcessing.set( false ); } } private void checkStart() { - if( mCanProcessAudio && + if( mCanProcessAudio && !isSquelched() && !mOutput.isRunning() && mOutput.available() <= START_THRESHOLD ) { mOutput.start(); + + mAudioEventBroadcaster.broadcast( mAudioStartEvent ); } } private void checkStop() { if( mCanProcessAudio && mOutput.isRunning() && - mOutput.available() >= STOP_THRESHOLD ) + ( mOutput.available() >= STOP_THRESHOLD || isSquelched() ) ) { mOutput.drain(); mOutput.stop(); + + mAudioEventBroadcaster.broadcast( mAudioStopEvent ); } } } @@ -264,19 +266,4 @@ public class MonoAudioOutput extends AudioOutput { return mGainControl != null; } - - @Override - public void update( LineEvent event ) - { - LineEvent.Type type = event.getType(); - - if( type == LineEvent.Type.START ) - { - mAudioEventBroadcaster.broadcast( mAudioStartEvent ); - } - else if( type == LineEvent.Type.STOP ) - { - mAudioEventBroadcaster.broadcast( mAudioStopEvent ); - } - } } diff --git a/src/audio/output/StereoAudioOutput.java b/src/audio/output/StereoAudioOutput.java index 1c3d1683..81892ca5 100644 --- a/src/audio/output/StereoAudioOutput.java +++ b/src/audio/output/StereoAudioOutput.java @@ -5,14 +5,13 @@ import java.nio.ByteOrder; import java.nio.ShortBuffer; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import javax.sound.sampled.BooleanControl; import javax.sound.sampled.Control; import javax.sound.sampled.FloatControl; -import javax.sound.sampled.LineEvent; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.Mixer; import javax.sound.sampled.SourceDataLine; @@ -25,8 +24,7 @@ import audio.AudioEvent; import audio.AudioFormats; import audio.AudioPacket; import audio.AudioPacket.Type; -import controller.ThreadPoolManager; -import controller.ThreadPoolManager.ThreadType; +import controller.NamingThreadFactory; /** * Mono Audio output in stereo format with automatic flow control based on the @@ -41,9 +39,9 @@ public class StereoAudioOutput extends AudioOutput private final static int BUFFER_SIZE = 96000; /* The queue processor will run every 20 milliseconds checking for inbound - * audio and automatically start or stop audio playback. We set the + * audio and automatically starting or stopping audio playback. We set the * data line available space threshold to start when we're 20 ms away from - * being full and the stop threshold when 20 ms away from being empty */ + * being full, and the stop threshold when 20 ms away from being empty */ private final static int SAMPLE_SIZE_40_MS = BUFFER_SIZE / 25; private final static int START_THRESHOLD = SAMPLE_SIZE_40_MS; private final static int STOP_THRESHOLD = BUFFER_SIZE - SAMPLE_SIZE_40_MS; @@ -51,16 +49,16 @@ public class StereoAudioOutput extends AudioOutput private SourceDataLine mOutput; private FloatControl mGainControl; private BooleanControl mMuteControl; + private AudioEvent mAudioStartEvent; private AudioEvent mAudioStopEvent; private AudioEvent mAudioContinuationEvent; - private MixerChannel mMixerChannel; - private ScheduledFuture mProcessorTask; - public StereoAudioOutput( ThreadPoolManager threadPoolManager, - Mixer mixer, MixerChannel channel ) + private MixerChannel mMixerChannel; + + public StereoAudioOutput( Mixer mixer, MixerChannel channel ) { - super( threadPoolManager ); + super(); assert( channel == MixerChannel.LEFT || channel == MixerChannel.RIGHT ); @@ -81,7 +79,6 @@ public class StereoAudioOutput extends AudioOutput if( mOutput != null ) { mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO, BUFFER_SIZE ); - mOutput.addLineListener( this ); mCanProcessAudio = true; } } @@ -116,10 +113,12 @@ public class StereoAudioOutput extends AudioOutput mixer.getMixerInfo().getName() + " | " + channel.name() + "]" ); } + mExecutorService = Executors.newSingleThreadScheduledExecutor( + new NamingThreadFactory( "audio (stereo) output" ) ); + /* Run the queue processor task every 40 milliseconds */ - mProcessorTask = mThreadPoolManager.scheduleFixedRate( - ThreadType.AUDIO_PROCESSING, new QueueProcessor(), - 40, TimeUnit.MILLISECONDS ); + mExecutorService.scheduleAtFixedRate( new QueueProcessor(), + 0, 40, TimeUnit.MILLISECONDS ); } } @@ -131,30 +130,20 @@ public class StereoAudioOutput extends AudioOutput public void dispose() { - if( mProcessorTask != null && mThreadPoolManager != null ) - { - mThreadPoolManager.cancel( mProcessorTask ); - } - - mProcessorTask = null; - super.dispose(); if( mOutput != null ) { mOutput.close(); } - - mOutput = null; - mGainControl = null; - mMuteControl = null; } private ByteBuffer convert( float[] samples ) { /* Little-endian byte buffer */ - ByteBuffer buffer = ByteBuffer.allocate( samples.length * 4 ) - .order( ByteOrder.LITTLE_ENDIAN ); + ByteBuffer buffer = + ByteBuffer.allocate( samples.length * 4 ) + .order( ByteOrder.LITTLE_ENDIAN ); ShortBuffer shortBuffer = buffer.asShortBuffer(); @@ -189,72 +178,72 @@ public class StereoAudioOutput extends AudioOutput @Override public void run() { - try + /* The mProcessing flag ensures that only one instance of the + * processor can run at any given time */ + if( mProcessing.compareAndSet( false, true ) ) { - /* The mProcessing flag ensures that only one instance of the - * processor can run at any given time */ - if( mProcessing.compareAndSet( false, true ) ) + List packets = new ArrayList(); + + mBuffer.drainTo( packets ); + + if( packets.size() > 0 ) { - List packets = new ArrayList(); + broadcast( mAudioContinuationEvent ); - mBuffer.drainTo( packets ); - - if( packets.size() > 0 ) + for( AudioPacket packet: packets ) { - - for( AudioPacket packet: packets ) + if( mCanProcessAudio ) { - broadcast( mAudioContinuationEvent ); - - if( mCanProcessAudio ) + if( isSquelched() ) { - if( packet.getType() == Type.AUDIO ) - { - ByteBuffer buffer = convert( - packet.getAudioBuffer().getSamples() ); - - mOutput.write( buffer.array(), 0, - buffer.array().length ); - - checkStart(); - - broadcast( packet.getAudioMetadata() ); - - mLastActivity = System.currentTimeMillis(); - } + checkStop(); + } + else if( packet.getType() == Type.AUDIO ) + { + ByteBuffer buffer = convert( + packet.getAudioBuffer().getSamples() ); + + mOutput.write( buffer.array(), 0, + buffer.array().length ); + + checkStart(); + + broadcast( packet.getAudioMetadata() ); + + mLastActivity = System.currentTimeMillis(); } } } - else - { - checkStop(); - } - - mProcessing.set( false ); } - } - catch( Exception e ) - { - mLog.error( "Error while processing audio buffers", e ); + else + { + checkStop(); + } + + mProcessing.set( false ); } } private void checkStart() { - if( mCanProcessAudio && + if( mCanProcessAudio && !isSquelched() && !mOutput.isRunning() && mOutput.available() <= START_THRESHOLD ) { mOutput.start(); + + mAudioEventBroadcaster.broadcast( mAudioStartEvent ); } } private void checkStop() { if( mCanProcessAudio && mOutput.isRunning() && - mOutput.available() >= STOP_THRESHOLD ) + ( mOutput.available() >= STOP_THRESHOLD || isSquelched() ) ) { mOutput.drain(); mOutput.stop(); + + mAudioEventBroadcaster.broadcast( mAudioStopEvent ); } } } @@ -293,20 +282,4 @@ public class StereoAudioOutput extends AudioOutput { return mGainControl != null; } - - - @Override - public void update( LineEvent event ) - { - LineEvent.Type type = event.getType(); - - if( type == LineEvent.Type.START ) - { - mAudioEventBroadcaster.broadcast( mAudioStartEvent ); - } - else if( type == LineEvent.Type.STOP ) - { - mAudioEventBroadcaster.broadcast( mAudioStopEvent ); - } - } } diff --git a/src/bits/MessageFramer.java b/src/bits/MessageFramer.java index cd41737c..99d07888 100644 --- a/src/bits/MessageFramer.java +++ b/src/bits/MessageFramer.java @@ -58,16 +58,6 @@ public class MessageFramer implements Listener, mMessageLength = messageLength; } - public void reset() - { - for( MessageAssembler assembler: mMessageAssemblers ) - { - assembler.dispose(); - } - - mMessageAssemblers.clear(); - } - public void dispose() { mBroadcaster.dispose(); diff --git a/src/controller/ThreadPoolManager.java b/src/controller/ThreadPoolManager.java index 0ca9a00b..c5527970 100644 --- a/src/controller/ThreadPoolManager.java +++ b/src/controller/ThreadPoolManager.java @@ -36,20 +36,24 @@ public class ThreadPoolManager private HashMap,ThreadType> mTasks = new HashMap,ThreadType>(); - private ScheduledExecutorService mExecutor = - Executors.newScheduledThreadPool( THREAD_POOL_SIZE, - new NamingThreadFactory( "sdrtrunk" ) ); + private ScheduledExecutorService mExecutor; public ThreadPoolManager() { } - + public ScheduledFuture scheduleFixedRate( ThreadType type, Runnable command, long period, TimeUnit unit ) throws RejectedExecutionException { + if( mExecutor == null ) + { + mExecutor = Executors.newScheduledThreadPool( THREAD_POOL_SIZE, + new NamingThreadFactory( "sdrtrunk" ) ); + } + ScheduledFuture task = mExecutor.scheduleAtFixedRate( command, 0, period, unit ); @@ -61,16 +65,20 @@ public class ThreadPoolManager public void scheduleOnce( Runnable command, long delay, TimeUnit unit ) throws RejectedExecutionException { + if( mExecutor == null ) + { + mExecutor = Executors.newScheduledThreadPool( THREAD_POOL_SIZE, + new NamingThreadFactory( "sdrtrunk" ) ); + } + mExecutor.schedule( command, delay, unit ); } - public boolean cancel( ScheduledFuture task ) + public void cancel( ScheduledFuture task ) { - boolean success = task.cancel( true ); + task.cancel( true ); mTasks.remove( task ); - - return success; } public int getTaskCount( ThreadType type ) diff --git a/src/gui/SDRTrunk.java b/src/gui/SDRTrunk.java index a81ebe6e..db22429c 100644 --- a/src/gui/SDRTrunk.java +++ b/src/gui/SDRTrunk.java @@ -140,7 +140,7 @@ public class SDRTrunk AliasActionManager aliasActionManager = new AliasActionManager( threadPoolManager ); channelProcessingManager.addMessageListener( aliasActionManager ); - AudioManager audioManager = new AudioManager( threadPoolManager, sourceManager.getMixerManager() ); + AudioManager audioManager = new AudioManager( threadPoolManager ); channelProcessingManager.addAudioPacketListener( audioManager ); MapService mapService = new MapService( mSettingsManager ); @@ -471,7 +471,7 @@ public class SDRTrunk if( version != null ) { - sb.append( " v" ); + sb.append( " V" ); sb.append( version ); } } diff --git a/src/message/Message.java b/src/message/Message.java index 9b6b7a17..ac1ab775 100644 --- a/src/message/Message.java +++ b/src/message/Message.java @@ -1,6 +1,6 @@ /******************************************************************************* * SDR Trunk - * Copyright (C) 2014-2016 Dennis Sheirer + * Copyright (C) 2014 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,94 +17,52 @@ ******************************************************************************/ package message; + import java.util.Collections; +import java.util.Date; import java.util.List; import map.Plottable; +import module.decode.event.MessageDetailsProvider; import alias.Alias; -public abstract class Message +public abstract class Message implements MessageDetailsProvider { protected long mTimeReceived; + protected MessageType mType; public Message() { - mTimeReceived = System.currentTimeMillis(); + this( MessageType.UN_KNWN ); } + public Message( MessageType type ) + { + mTimeReceived = System.currentTimeMillis(); + mType = type; + } + + public abstract Plottable getPlottable(); + + public abstract String toString(); + + public abstract boolean isValid(); + public long getTimeReceived() { return mTimeReceived; } - - /** - * Map plottable - */ - public Plottable getPlottable() + + public Date getDateReceived() { -//TODO: move this to an IPlottable interface that only gets implemented as needed - return null; + return new Date( mTimeReceived ); + } + + public MessageType getType() + { + return mType; } - /** - * Decoded textual representation of the message - */ - public abstract String toString(); - - /** - * Indicates if the message is valid and has passed crc/integrity checks - */ - public abstract boolean isValid(); - - /** - * Status of the CRC check of the message - */ - public abstract String getErrorStatus(); - - /** - * Parsed Message - * @return - */ - public abstract String getMessage(); - - /** - * Raw ( 0 & 1 ) message bits - */ - public abstract String getBinaryMessage(); - - - /** - * Decoded protocol - */ - public abstract String getProtocol(); - - /** - * Event - call, data, idle, etc. - */ - public abstract String getEventType(); - - /** - * Formatted from identifier - */ - public abstract String getFromID(); - - /** - * From identifier alias (from AliasManager) - */ - public abstract Alias getFromIDAlias(); - - /** - * Formatted to identifier - */ - public abstract String getToID(); - - /** - * To identifier alias (from AliasManager) - * @return - */ - public abstract Alias getToIDAlias(); - - /** * Provides a listing of aliases contained in the message. */ diff --git a/src/module/decode/event/CallEvent.java b/src/module/decode/event/CallEvent.java index 28990ad9..000cb27c 100644 --- a/src/module/decode/event/CallEvent.java +++ b/src/module/decode/event/CallEvent.java @@ -310,11 +310,6 @@ public abstract class CallEvent implements Comparable @Override public int compareTo( CallEvent other ) { - if( other != null && other == this ) - { - return 0; - } - return Long.compare( mEventStart, other.getEventStartTime() ); } } diff --git a/src/module/decode/event/MessageActivityModel.java b/src/module/decode/event/MessageActivityModel.java index 390c4c59..65850196 100644 --- a/src/module/decode/event/MessageActivityModel.java +++ b/src/module/decode/event/MessageActivityModel.java @@ -19,7 +19,6 @@ package module.decode.event; import java.awt.EventQueue; import java.text.SimpleDateFormat; -import java.util.Date; import java.util.LinkedList; import javax.swing.table.AbstractTableModel; @@ -175,7 +174,7 @@ public class MessageActivityModel extends AbstractTableModel switch( columnIndex ) { case sTIME: - return mSDFTime.format( new Date( message.getTimeReceived() ) ); + return mSDFTime.format( message.getDateReceived() ); case sPROTOCOL: return message.getProtocol(); case sERROR_STATUS: diff --git a/src/module/decode/event/MessageDetailsProvider.java b/src/module/decode/event/MessageDetailsProvider.java new file mode 100644 index 00000000..bfce825e --- /dev/null +++ b/src/module/decode/event/MessageDetailsProvider.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + ******************************************************************************/ +package module.decode.event; + +import alias.Alias; + +/** + * Getter methods to support the Message Activity Table/Model + */ +public interface MessageDetailsProvider +{ + /** + * Indicates if the message is valid and has passed crc/integrity checks + */ + public abstract boolean isValid(); + + /** + * Status of the CRC check of the message + */ + public abstract String getErrorStatus(); + + /** + * Parsed Message + * @return + */ + public abstract String getMessage(); + + /** + * Raw ( 0 & 1 ) message bits + */ + public abstract String getBinaryMessage(); + + + /** + * Decoded protocol + */ + public abstract String getProtocol(); + + /** + * Event - call, data, idle, etc. + */ + public abstract String getEventType(); + + /** + * Formatted from identifier + */ + public abstract String getFromID(); + + /** + * From identifier alias (from AliasManager) + */ + public abstract Alias getFromIDAlias(); + + /** + * Formatted to identifier + */ + public abstract String getToID(); + + /** + * To identifier alias (from AliasManager) + * @return + */ + public abstract Alias getToIDAlias(); + +} diff --git a/src/module/decode/ltrnet/LTRCallEvent.java b/src/module/decode/ltrnet/LTRCallEvent.java index 3bb553ca..21a46afb 100644 --- a/src/module/decode/ltrnet/LTRCallEvent.java +++ b/src/module/decode/ltrnet/LTRCallEvent.java @@ -51,16 +51,17 @@ public class LTRCallEvent extends CallEvent */ public boolean isMatchingTalkgroup( LTRNetMessage message ) { - return isMatchingTalkgroup( message.getTalkgroupID() ); + String talkgroup = message.getTalkgroupID(); + + if( talkgroup != null && getToID() != null && + talkgroup.contentEquals( getToID() ) ) + { + return true; + } + + return false; } - public boolean isMatchingTalkgroup( String talkgroup ) - { - return talkgroup != null && - getToID() != null && - talkgroup.contentEquals( getToID() ); - } - private Alias getAlias( String talkgroup ) { if( hasAliasList() ) diff --git a/src/module/decode/ltrnet/LTRNetDecoder.java b/src/module/decode/ltrnet/LTRNetDecoder.java index f5c1d3ca..ff12edca 100644 --- a/src/module/decode/ltrnet/LTRNetDecoder.java +++ b/src/module/decode/ltrnet/LTRNetDecoder.java @@ -117,7 +117,6 @@ public class LTRNetDecoder extends Decoder @Override public void reset() { - mLTRMessageFramer.reset(); } @Override diff --git a/src/module/decode/ltrstandard/LTRStandardDecoder.java b/src/module/decode/ltrstandard/LTRStandardDecoder.java index 228d68f5..365bd74c 100644 --- a/src/module/decode/ltrstandard/LTRStandardDecoder.java +++ b/src/module/decode/ltrstandard/LTRStandardDecoder.java @@ -80,7 +80,6 @@ public class LTRStandardDecoder extends Decoder implements IUnFilteredRealBuffer @Override public void reset() { - mLTRMessageFramer.reset(); } @Override diff --git a/src/module/decode/ltrstandard/LTRStandardDecoderState.java b/src/module/decode/ltrstandard/LTRStandardDecoderState.java index 6dacaf17..94cd32b9 100644 --- a/src/module/decode/ltrstandard/LTRStandardDecoderState.java +++ b/src/module/decode/ltrstandard/LTRStandardDecoderState.java @@ -1,6 +1,6 @@ /******************************************************************************* * SDR Trunk - * Copyright (C) 2014-2016 Dennis Sheirer + * Copyright (C) 2014,2015 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,31 +17,21 @@ ******************************************************************************/ package module.decode.ltrstandard; -import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.Iterator; import java.util.TreeSet; import message.Message; import module.decode.DecoderType; import module.decode.event.CallEvent.CallEventType; import module.decode.ltrnet.LTRCallEvent; -import module.decode.ltrstandard.message.CallEndMessage; -import module.decode.ltrstandard.message.CallMessage; -import module.decode.ltrstandard.message.IdleMessage; -import module.decode.ltrstandard.message.LTRStandardMessage; import module.decode.state.ChangedAttribute; import module.decode.state.DecoderState; import module.decode.state.DecoderStateEvent; import module.decode.state.DecoderStateEvent.Event; import module.decode.state.State; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import alias.Alias; import alias.AliasList; import audio.metadata.Metadata; @@ -49,17 +39,15 @@ import audio.metadata.MetadataType; public class LTRStandardDecoderState extends DecoderState { - private final static Logger mLog = - LoggerFactory.getLogger( LTRStandardDecoderState.class ); - - private Map mActiveCalls = new HashMap<>(); - private Set mTalkgroupsFirstHeard = new HashSet<>(); - private Set mTalkgroups = new TreeSet<>(); + private HashMap mActiveCalls = new HashMap(); + private HashSet mTalkgroupsFirstHeard = new HashSet(); + private TreeSet mTalkgroups = new TreeSet(); + private TreeSet mActiveLCNs = new TreeSet(); private String mTalkgroup; private Alias mTalkgroupAlias; + private int mChannelNumber; private long mFrequency; - private LCNTracker mLCNTracker = new LCNTracker(); public LTRStandardDecoderState( AliasList aliasList ) { @@ -82,128 +70,171 @@ public class LTRStandardDecoderState extends DecoderState { } + private LTRCallEvent getCurrentCallEvent() + { + return (LTRCallEvent)mCurrentCallEvent; + } + @Override public void receive( Message message ) { - if( message.isValid() && message instanceof LTRStandardMessage ) + if( message.isValid() ) { - switch( ((LTRStandardMessage)message).getMessageType() ) + if( message instanceof LTRStandardOSWMessage ) { - case CA_STRT: - CallMessage start = (CallMessage)message; - - int channel = start.getChannel(); - - mLCNTracker.processCallChannel( channel ); - - mLCNTracker.processFreeChannel( start.getFree() ); + LTRStandardOSWMessage ltr = ((LTRStandardOSWMessage)message); + + switch( ltr.getMessageType() ) + { + case CA_STRT: + if( 0 < ltr.getChannel() && ltr.getChannel() <=20 ) + { + /* If we haven't captured the LCN yet ... */ + if( mChannelNumber == 0 ) + { + mChannelNumber = ltr.getChannel(); + broadcast( ChangedAttribute.CHANNEL_NUMBER ); + } - //Only process calls on this LCN, or call detects for - //talkgroups that are homed on this LCN - if( mLCNTracker.isValidChannel( channel ) && - ( mLCNTracker.isCurrentChannel( channel ) || - mLCNTracker.isCurrentChannel( start.getHomeRepeater() ) ) ) - { - LTRCallEvent event = mActiveCalls.get( channel ); - - if( event == null || - !event.isMatchingTalkgroup( start.getToID() ) ) - { - //Check for different talkgroup - if( event != null ) - { - event.end(); - mActiveCalls.remove( channel ); - } + /* If this call event is for this channel, then update + * talkgroup and alias */ + if( ltr.getChannel() == mChannelNumber ) + { + mTalkgroup = ltr.getTalkgroupID(); + broadcast( ChangedAttribute.TO_TALKGROUP ); + + mTalkgroupAlias = ltr.getTalkgroupIDAlias(); + broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS ); + + LTRCallEvent current = getCurrentCallEvent(); - boolean current = mLCNTracker.isCurrentChannel( channel ); - - event = new LTRCallEvent.Builder( - DecoderType.LTR_STANDARD, - current ? CallEventType.CALL : CallEventType.CALL_DETECT ) - .to( start.getToID() ) - .aliasList( getAliasList() ) - .channel( start.getChannelFormatted() ) - .frequency( current ? mFrequency : 0 ) - .build(); - - mActiveCalls.put( channel, event ); - - broadcast( event ); - - if( mLCNTracker.isCurrentChannel( channel ) ) - { - mTalkgroup = start.getToID(); - broadcast( ChangedAttribute.TO_TALKGROUP ); - - processTalkgroup( mTalkgroup ); - - mTalkgroupAlias = start.getToIDAlias(); - broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS ); + /* Detect when we haven't reset from the previous + * call and now have a new call coming in with + * a different talkgroup */ + if( current != null && isDifferentTalkgroup( mTalkgroup ) ) + { + mCurrentCallEvent.end(); + broadcast( mCurrentCallEvent ); + mCurrentCallEvent = null; + } + + if( current == null ) + { + mCurrentCallEvent = + new LTRCallEvent.Builder( + DecoderType.LTR_STANDARD, CallEventType.CALL ) + .to( ltr.getTalkgroupID() ) + .aliasList( getAliasList() ) + .channel( String.valueOf( ltr.getChannel() ) ) + .frequency( mFrequency ) + .build(); - broadcast( new Metadata( MetadataType.TO, - mTalkgroup, mTalkgroupAlias, true ) ); - } - } - - broadcast( new DecoderStateEvent( this, - Event.CONTINUATION, State.CALL ) ); - } - break; - case CA_ENDD: - CallEndMessage end = (CallEndMessage)message; + broadcast( mCurrentCallEvent ); + + broadcast( new Metadata( MetadataType.TO, + mTalkgroup, mTalkgroupAlias, true ) ); - //Home channel is 31 for call end -- use the free channel - //as the call end channel - int repeater = end.getFree(); - - mLCNTracker.processCallChannel( repeater ); - - if( mLCNTracker.isCurrentChannel( repeater ) ) - { - mTalkgroup = end.getToID(); - broadcast( ChangedAttribute.TO_TALKGROUP ); - - processTalkgroup( mTalkgroup ); - - mTalkgroupAlias = end.getToIDAlias(); - broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS ); + broadcast( new DecoderStateEvent( this, + Event.START, State.CALL ) ); + } + else + { + broadcast( new DecoderStateEvent( this, + Event.CONTINUATION, State.CALL ) ); + } + } + } + break; + case CA_ENDD: + mTalkgroup = ltr.getTalkgroupID(); + broadcast( ChangedAttribute.TO_TALKGROUP ); - LTRCallEvent event = mActiveCalls.remove( repeater ); - - if( event != null ) - { - event.end(); - broadcast( event ); - broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) ); - } - } - break; - case SY_IDLE: - IdleMessage idle = (IdleMessage)message; + mTalkgroupAlias = ltr.getTalkgroupIDAlias(); + broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS ); - int lcn = idle.getChannel(); + if( mCurrentCallEvent != null ) + { + mCurrentCallEvent.end(); + broadcast( mCurrentCallEvent ); + broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) ); + mCurrentCallEvent = null; + } + + int home = ltr.getHomeRepeater(); + + if( 0 < home && home <= 20 ) + { + String talkgroup = ltr.getTalkgroupID( false ); + + if( mTalkgroupsFirstHeard.contains( talkgroup ) ) + { + mTalkgroups.add( talkgroup ); + } + else + { + mTalkgroupsFirstHeard.add( talkgroup ); + } + } - mLCNTracker.processCallChannel( lcn ); - - break; - case UN_KNWN: - default: - break; + int channel = ltr.getChannel(); + + if( 0 < channel && channel <= 20 ) + { + if( channel == home ) + { + if( mChannelNumber != channel ) + { + mChannelNumber = channel; + broadcast( ChangedAttribute.CHANNEL_NUMBER ); + } + + mActiveLCNs.add( channel ); + } + + int free = ltr.getFree(); + + if( 0 < free && free <= 20 ) + { + mActiveLCNs.add( free ); + } + } + + break; + case SY_IDLE: + int idleChannel = ltr.getChannel(); + + if( idleChannel == ltr.getHomeRepeater() && + idleChannel == ltr.getFree() ) + { + if( mChannelNumber != idleChannel ) + { + mChannelNumber = idleChannel; + broadcast( ChangedAttribute.CHANNEL_NUMBER ); + } + + mActiveLCNs.add( idleChannel ); + } + broadcast( new DecoderStateEvent( this, + Event.CONTINUATION, State.IDLE ) ); + break; + case UN_KNWN: + default: + break; + } } } } - - private void processTalkgroup( String talkgroup ) + + /** + * Indicates if the talkgroup is different than the talkgroup specified in + * the current call event + */ + private boolean isDifferentTalkgroup( String talkgroup ) { - if( mTalkgroupsFirstHeard.contains( talkgroup ) ) - { - mTalkgroups.add( talkgroup ); - } - else - { - mTalkgroupsFirstHeard.add( talkgroup ); - } + return talkgroup != null && + mCurrentCallEvent != null && + mCurrentCallEvent.getToID() != null && + !mCurrentCallEvent.getToID().contentEquals( talkgroup ); } /** @@ -214,7 +245,8 @@ public class LTRStandardDecoderState extends DecoderState mActiveCalls.clear(); mTalkgroupsFirstHeard.clear(); mTalkgroups.clear(); - mLCNTracker.reset(); + mActiveLCNs.clear(); + mChannelNumber = 0; resetState(); } @@ -224,20 +256,13 @@ public class LTRStandardDecoderState extends DecoderState */ private void resetState() { - for( Integer key: mActiveCalls.keySet() ) + if( mCurrentCallEvent != null ) { - LTRCallEvent event = mActiveCalls.get( key ); - - if( event != null ) - { - event.end(); - - broadcast( event ); - } + mCurrentCallEvent.setEnd( System.currentTimeMillis() ); + broadcast( mCurrentCallEvent ); + mCurrentCallEvent = null; } - - mActiveCalls.clear(); - + mTalkgroup = null; broadcast( ChangedAttribute.TO_TALKGROUP ); @@ -257,12 +282,12 @@ public class LTRStandardDecoderState extends DecoderState public boolean hasChannelNumber() { - return mLCNTracker.getCurrentChannel() != 0; + return mChannelNumber != 0; } public int getChannelNumber() { - return mLCNTracker.getCurrentChannel(); + return mChannelNumber; } @Override @@ -297,28 +322,28 @@ public class LTRStandardDecoderState extends DecoderState sb.append( "Monitored LCN: " ); - if( hasChannelNumber() ) + if( mChannelNumber > 0 ) { - sb.append( getChannelNumber() ); + sb.append( mChannelNumber ); } else { - sb.append( "*Insufficient Data*" ); + sb.append( "unknown" ); } sb.append( "\n" ); - sb.append( "Active LCNs:\t" ); - - List lcns = mLCNTracker.getActiveLCNs(); + Integer[] lcns = mActiveLCNs.toArray( new Integer[0] ); - if( lcns.size() > 0 ) + sb.append( "Active LCNs:\t" ); + + if( lcns.length > 0 ) { - sb.append( mLCNTracker.getActiveLCNs() ); + sb.append( Arrays.toString( lcns ) ); } else { - sb.append( "*Insufficient Data*" ); + sb.append( "none" ); } sb.append( "\n\n" ); @@ -330,15 +355,19 @@ public class LTRStandardDecoderState extends DecoderState } else { - for( String talkgroup: mTalkgroups ) + Iterator it = mTalkgroups.iterator(); + + while( it.hasNext() ) { + String tgid = it.next(); + sb.append( " " ); - sb.append( talkgroup ); + sb.append( formatTalkgroup( tgid ) ); sb.append( " " ); if( hasAliasList() ) { - Alias alias = getAliasList().getTalkgroupAlias( talkgroup ); + Alias alias = getAliasList().getTalkgroupAlias( tgid ); if( alias != null ) { @@ -347,147 +376,30 @@ public class LTRStandardDecoderState extends DecoderState } sb.append( "\n" ); - } } return sb.toString(); } - /** - * Tracks the set of call and free channels for a system in order to - * dynamically determine the current LCN and minimize false triggers from - * bogus decoded LTR messages. Tracks the number of occurances of each - * LCN and uses a dynamic threshold to determine LCN validity. - */ - public class LCNTracker + public static String formatTalkgroup( String talkgroup ) { - private static final int DEFAULT_COUNT = 10; - private int[] mCallLCNCounts; - private int[] mFreeLCNCounts; - private int mCallHighestCount; - private int mFreeHighestCount; - private int mCurrentLCN; + StringBuilder sb = new StringBuilder(); - public LCNTracker() + if( talkgroup.length() == 6 ) { - reset(); - } - - public void logStatistics() - { - for( int x = 1; x <= 20; x++ ) - { - mLog.debug( "Call " + x + ": " + mCallLCNCounts[ x ] ); - } - mLog.debug( "Call Highest Count: " + mCallHighestCount ); - for( int x = 1; x <= 20; x++ ) - { - mLog.debug( "Free " + x + ": " + mFreeLCNCounts[ x ] ); - } - mLog.debug( "Free Highest Count: " + mFreeHighestCount ); + sb.append( talkgroup.substring( 0, 1 ) ); + sb.append( "-" ); + sb.append( talkgroup.substring( 1, 3 ) ); + sb.append( "-" ); + sb.append( talkgroup.substring( 3, 6 ) ); - mLog.debug( "Current LCN: " + mCurrentLCN ); + return sb.toString(); } - - public void reset() + else { - //Dim channel arrays to 21 -- ignore the 0 index - mCallLCNCounts = new int[ 21 ]; - mFreeLCNCounts = new int[ 21 ]; - mCallHighestCount = 3; - mFreeHighestCount = DEFAULT_COUNT; - mCurrentLCN = 0; - } - - public boolean isCurrentChannel( int channel ) - { - if( mCurrentLCN == 0 ) - { - return true; - } - else - { - return channel == mCurrentLCN; - } - } - - public int getCurrentChannel() - { - return mCurrentLCN; - } - - /** - * Indicates if the channel is valid based on the observed current and - * free channels reported by the system. Tracks the count of each - * reported free channel the highest free channel count is used as a - * threshold where a channel is valid when its count exceeds 20% of the - * highest free channel count. A channel is also valid when it is - * the currently monitored channel. - */ - public boolean isValidChannel( int channel ) - { - if( isCurrentChannel( channel ) ) - { - return true; - } - - int count = mFreeLCNCounts[ channel ]; - - int threshold = (int)( (double)mFreeHighestCount * 0.2 ); - - return count >= threshold; - } - - public void processCallChannel( int channel ) - { - if( 1 <= channel && channel <= 20 ) - { - mCallLCNCounts[ channel ]++; - - if( mCallLCNCounts[ channel ] > mCallHighestCount ) - { - mCallHighestCount = mCallLCNCounts[ channel ]; - mCurrentLCN = channel; - - broadcast( ChangedAttribute.CHANNEL_NUMBER ); - } - } - } - - public void processFreeChannel( int channel ) - { - if( 1 <= channel && channel <= 20 ) - { - mFreeLCNCounts[ channel ]++; - - if( mFreeLCNCounts[ channel ] > mFreeHighestCount ) - { - mFreeHighestCount = mFreeLCNCounts[ channel ]; - } - } - } - - public List getActiveLCNs() - { - List active = new ArrayList<>(); - - if( mFreeHighestCount > DEFAULT_COUNT ) - { - for( int x = 1; x <= 20; x++ ) - { - if( mCurrentLCN != 0 && isCurrentChannel( x ) ) - { - active.add( x ); - } - else if( isValidChannel( x ) ) - { - active.add( x ); - } - } - } - - return active; + return talkgroup; } } + } diff --git a/src/module/decode/ltrstandard/LTRStandardISWMessage.java b/src/module/decode/ltrstandard/LTRStandardISWMessage.java new file mode 100644 index 00000000..8f16ac1f --- /dev/null +++ b/src/module/decode/ltrstandard/LTRStandardISWMessage.java @@ -0,0 +1,238 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + ******************************************************************************/ +package module.decode.ltrstandard; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import map.Plottable; +import message.MessageDirection; +import message.MessageType; +import alias.Alias; +import alias.AliasList; +import bits.BinaryMessage; + +public class LTRStandardISWMessage extends LTRStandardMessage +{ + private static final int sCALL_END = 31; + private static final int sIDLE = 255; + + public LTRStandardISWMessage( BinaryMessage message, AliasList list ) + { + super( message, MessageDirection.OSW, list ); + mMessageType = getType(); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append( mDatestampFormatter.format( + new Date( System.currentTimeMillis() ) ) ); + sb.append( " LTR OSW [" ); + sb.append( mCRC.getAbbreviation() ); + sb.append( "] " ); + sb.append( getMessage() ); + pad( sb, 100 ); + sb.append( mMessage.toString() ); + + return sb.toString(); + } + + @Override + public String getMessage() + { + StringBuilder sb = new StringBuilder(); + + switch( mMessageType ) + { + case SY_IDLE: + sb.append( "IDLE AREA:" ); + sb.append( getArea() ); + sb.append( " LCN:" ); + sb.append( format( getChannel(), 2 ) ); + sb.append( " HOME:" ); + sb.append( format( getHomeRepeater(), 2 ) ); + sb.append( " GRP:" ); + sb.append( format( getGroup(), 3 ) ); + sb.append( " FREE:" ); + sb.append( format( getFree(), 2 ) ); + break; + case CA_STRT: + sb.append( "CALL LCN:" ); + sb.append( format( getChannel(), 2 ) ); + sb.append( " TG [ " ); + sb.append( getTalkgroupID() ); + sb.append( "/" ); + sb.append( getTalkgroupIDAlias() ); + sb.append( " ] FREE:" ); + sb.append( format( getFree(), 2 ) ); + break; + case CA_ENDD: + sb.append( "END* AREA:" ); + sb.append( getArea() ); + sb.append( " LCN:" ); + sb.append( format( getChannel(), 2 ) ); + sb.append( " TG [" ); + sb.append( getTalkgroupID() ); + sb.append( "/" ); + sb.append( getTalkgroupIDAlias() ); + sb.append( "] FREE:" ); + sb.append( format( getFree(), 2 ) ); + break; + default: + sb.append( "UNKNOWN A:" ); + sb.append( getArea() ); + sb.append( " C:" ); + sb.append( format( getChannel(), 2 ) ); + sb.append( " H:" ); + sb.append( format( getHomeRepeater(), 2 ) ); + sb.append( " G:" ); + sb.append( format( getGroup(), 3 ) ); + sb.append( " F:" ); + sb.append( format( getFree(), 2 ) ); + } + + return sb.toString(); + } + + public String getTalkgroupID() + { + return getTalkgroupID( true ); + } + + public String getTalkgroupID( boolean format ) + { + StringBuilder sb = new StringBuilder(); + + sb.append( getArea() ); + + if( format ) + { + sb.append( "-" ); + } + + sb.append( format( getHomeRepeater(), 2 ) ); + + if( format ) + { + sb.append( "-" ); + } + + sb.append( format( getGroup(), 3 ) ); + + return sb.toString(); + } + + public Alias getTalkgroupIDAlias() + { + StringBuilder sb = new StringBuilder(); + sb.append( getArea() ); + sb.append( format( getHomeRepeater(), 2 ) ); + sb.append( format( getGroup(), 3 ) ); + + return mAliasList.getTalkgroupAlias( sb.toString() ); + } + + public MessageType getType() + { + MessageType retVal = MessageType.UN_KNWN; + + int group = getGroup(); + + if( group == sIDLE ) + { + retVal = MessageType.SY_IDLE; + } + else + { + int channel = getChannel(); + + if( channel == sCALL_END ) + { + retVal = MessageType.CA_ENDD; + } + else + { + retVal = MessageType.CA_STRT; + } + } + + return retVal; + } + + @Override + public String getFromID() + { + if( mMessageType == MessageType.CA_STRT || + mMessageType == MessageType.CA_ENDD ) + { + return getTalkgroupID(); + } + else + { + return null; + } + } + + @Override + public Alias getFromIDAlias() + { + if( mMessageType == MessageType.CA_STRT || + mMessageType == MessageType.CA_ENDD ) + { + return getTalkgroupIDAlias(); + } + else + { + return null; + } + } + + @Override + public Plottable getPlottable() + { + // TODO Auto-generated method stub + return null; + } + + /** + * Provides a listing of aliases contained in the message. + */ + public List getAliases() + { + List aliases = new ArrayList(); + + Alias from = getFromIDAlias(); + + if( from != null ) + { + aliases.add( from ); + } + + Alias to = getToIDAlias(); + + if( to != null ) + { + aliases.add( to ); + } + + return aliases; + } +} diff --git a/src/module/decode/ltrstandard/LTRStandardMessage.java b/src/module/decode/ltrstandard/LTRStandardMessage.java new file mode 100644 index 00000000..408c7bd9 --- /dev/null +++ b/src/module/decode/ltrstandard/LTRStandardMessage.java @@ -0,0 +1,254 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + ******************************************************************************/ +package module.decode.ltrstandard; + +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; + +import message.Message; +import message.MessageDirection; +import message.MessageType; +import module.decode.DecoderType; +import alias.Alias; +import alias.AliasList; +import bits.BinaryMessage; +import edac.CRC; +import edac.CRCLTR; + +public abstract class LTRStandardMessage extends Message +{ + protected static final String sUNKNOWN = "**UNKNOWN**"; + protected static final int sINT_NULL_VALUE = -1; + protected static final double sDOUBLE_NULL_VALUE = -1.0D; + protected SimpleDateFormat mDatestampFormatter = + new SimpleDateFormat( "yyyyMMdd HHmmss" ); + protected DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000"); + + protected static final int[] sSYNC = { 8,7,6,5,4,3,2,1,0 }; + protected static final int[] sAREA = { 9 }; + protected static final int[] sCHANNEL = { 14,13,12,11,10 }; + protected static final int[] sHOME_REPEATER = { 19,18,17,16,15 }; + protected static final int[] sGROUP = { 27,26,25,24,23,22,21,20 }; + protected static final int[] sFREE = { 32,31,30,29,28 }; + protected static final int[] sCRC = { 39,38,37,36,35,34,33 }; + + protected BinaryMessage mMessage; + protected MessageType mMessageType; + protected CRC mCRC; + protected AliasList mAliasList; + + public LTRStandardMessage( BinaryMessage message, + MessageDirection direction, + AliasList list ) + { + mMessage = message; + mCRC = CRCLTR.check( message, direction ); + mAliasList = list; + } + + public MessageType getMessageType() + { + return mMessageType; + } + + public boolean isValid() + { + return mCRC != CRC.FAILED_CRC && mCRC != CRC.FAILED_PARITY; + } + + /** + * Appends spaces to the end of the stringbuilder to make it length long + */ + protected void pad( StringBuilder sb, int length ) + { + while( sb.length() < length ) + { + sb.append( " " ); + } + } + + @Override + public String getBinaryMessage() + { + return mMessage.toString(); + } + + /** + * Returns a value formatted in hex notation. + * + * @param value - integer value + * @return - hex digits returned + */ + protected static String getHex( int value, int digits ) + { + return String.format( "%0" + digits + "X", value ); + } + + protected int getInt( int[] bits ) + { + int retVal = 0; + + for( int x = 0; x < bits.length; x++ ) + { + if( mMessage.get( bits[ x ] ) ) + { + retVal += 1< { @@ -36,12 +29,6 @@ public class LTRStandardMessageProcessor implements Listener private AliasList mAliasList; private Listener mMessageListener; - /** - * Processes raw binary messages and converts them to the correct message class - * - * @param direction - inbound (ISW) or outbound (OSW) - * @param list - alias list - */ public LTRStandardMessageProcessor( MessageDirection direction, AliasList list ) { mDirection = direction; @@ -49,66 +36,27 @@ public class LTRStandardMessageProcessor implements Listener } @Override - public void receive( BinaryMessage binaryMessage ) + public void receive( BinaryMessage buffer ) { if( mMessageListener != null ) { - //Inbound Status Word (ISW) is a bit-flipped version of the Outbound - //(OSW), so flip the bits and process it as an OSW - if( mDirection == MessageDirection.ISW ) + LTRStandardMessage message; + + if( mDirection == MessageDirection.OSW ) { - binaryMessage.flip( 0, 40 ); + message = new LTRStandardOSWMessage( buffer, mAliasList ); + } + else + { + buffer.flip( 0, 40 ); + + message = new LTRStandardISWMessage( buffer, mAliasList ); } - CRC crc = CRCLTR.check( binaryMessage, mDirection ); - - if( crc != CRC.FAILED_CRC && - crc != CRC.FAILED_PARITY ) - { - LTRStandardMessage message; - - int channel = binaryMessage.getInt( LTRStandardMessage.CHANNEL ); - int home = binaryMessage.getInt( LTRStandardMessage.HOME_REPEATER ); - int free = binaryMessage.getInt( LTRStandardMessage.FREE ); - int group = binaryMessage.getInt( LTRStandardMessage.GROUP ); - - if( isValidChannel( channel ) && - isValidChannel( home ) && - isValidChannel( free ) ) - { - if( channel == free && group == 255 ) - { - message = new IdleMessage( binaryMessage, mDirection, mAliasList, crc ); - } - else - { - message = new CallMessage( binaryMessage, mDirection, mAliasList, crc ); - } - } - else if( channel == 31 && - isValidChannel( home ) && - isValidChannel( free ) ) - { - message = new CallEndMessage( binaryMessage, mDirection, mAliasList, crc ); - } - else - { - message = new UnknownMessage( binaryMessage, mDirection, mAliasList, crc ); - } - - mMessageListener.receive( message ); - } + mMessageListener.receive( message ); } } - /** - * Checks the channel (LCN) number to ensure it is in the range 1 -20 - */ - private boolean isValidChannel( int channel ) - { - return ( 1 <= channel && channel <= 20 ); - } - public void setMessageListener( Listener listener ) { mMessageListener = listener; diff --git a/src/module/decode/ltrstandard/LTRStandardOSWMessage.java b/src/module/decode/ltrstandard/LTRStandardOSWMessage.java new file mode 100644 index 00000000..df3bde82 --- /dev/null +++ b/src/module/decode/ltrstandard/LTRStandardOSWMessage.java @@ -0,0 +1,248 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + ******************************************************************************/ +package module.decode.ltrstandard; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import map.Plottable; +import message.MessageDirection; +import message.MessageType; +import alias.Alias; +import alias.AliasList; +import bits.BinaryMessage; + +public class LTRStandardOSWMessage extends LTRStandardMessage +{ + private static final int sCALL_END = 31; + private static final int sIDLE = 255; + + public LTRStandardOSWMessage( BinaryMessage message, AliasList list ) + { + super( message, MessageDirection.OSW, list ); + mMessageType = getType(); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append( mDatestampFormatter.format( + new Date( System.currentTimeMillis() ) ) ); + sb.append( " LTR OSW [" ); + sb.append( mCRC.getAbbreviation() ); + sb.append( "] " ); + sb.append( getMessage() ); + pad( sb, 100 ); + sb.append( mMessage.toString() ); + + return sb.toString(); + } + + @Override + public String getMessage() + { + StringBuilder sb = new StringBuilder(); + + switch( mMessageType ) + { + case SY_IDLE: + sb.append( "IDLE AREA:" ); + sb.append( getArea() ); + sb.append( " LCN:" ); + sb.append( format( getChannel(), 2 ) ); + sb.append( " HOME:" ); + sb.append( format( getHomeRepeater(), 2 ) ); + sb.append( " GRP:" ); + sb.append( format( getGroup(), 3 ) ); + sb.append( " FREE:" ); + sb.append( format( getFree(), 2 ) ); + break; + case CA_STRT: + sb.append( "CALL LCN:" ); + sb.append( format( getChannel(), 2 ) ); + sb.append( " TG [ " ); + sb.append( getTalkgroupID() ); + sb.append( "/" ); + Alias alias = getTalkgroupIDAlias(); + if( alias != null ) + { + sb.append( getTalkgroupIDAlias().getName() ); + } + else + { + sb.append( "UNKNOWN" ); + } + sb.append( " ] FREE:" ); + sb.append( format( getFree(), 2 ) ); + break; + case CA_ENDD: + sb.append( "END* AREA:" ); + sb.append( getArea() ); + sb.append( " LCN:" ); + sb.append( format( getChannel(), 2 ) ); + sb.append( " TG [" ); + sb.append( getTalkgroupID() ); + sb.append( "/" ); + Alias endAlias = getTalkgroupIDAlias(); + if( endAlias != null ) + { + sb.append( getTalkgroupIDAlias().getName() ); + } + else + { + sb.append( "UNKNOWN" ); + } + sb.append( "] FREE:" ); + sb.append( format( getFree(), 2 ) ); + break; + default: + sb.append( "UNKNOWN A:" ); + sb.append( getArea() ); + sb.append( " C:" ); + sb.append( format( getChannel(), 2 ) ); + sb.append( " H:" ); + sb.append( format( getHomeRepeater(), 2 ) ); + sb.append( " G:" ); + sb.append( format( getGroup(), 3 ) ); + sb.append( " F:" ); + sb.append( format( getFree(), 2 ) ); + } + + return sb.toString(); + } + + public String getTalkgroupID() + { + return getTalkgroupID( true ); + } + + public String getTalkgroupID( boolean format ) + { + StringBuilder sb = new StringBuilder(); + + sb.append( getArea() ); + + if( format ) + { + sb.append( "-" ); + } + + sb.append( format( getHomeRepeater(), 2 ) ); + + if( format ) + { + sb.append( "-" ); + } + + sb.append( format( getGroup(), 3 ) ); + + return sb.toString(); + } + + public Alias getTalkgroupIDAlias() + { + return mAliasList.getTalkgroupAlias( getTalkgroupID() ); + } + + public MessageType getType() + { + MessageType retVal = MessageType.UN_KNWN; + + /* Group 255 when the channel equals the free is the IDLE message */ + if( getGroup() == sIDLE && getChannel() == getFree() ) + { + retVal = MessageType.SY_IDLE; + } + else + { + int channel = getChannel(); + + if( channel == sCALL_END ) + { + retVal = MessageType.CA_ENDD; + } + else + { + retVal = MessageType.CA_STRT; + } + } + + return retVal; + } + + @Override + public String getFromID() + { + if( mMessageType == MessageType.CA_STRT || + mMessageType == MessageType.CA_ENDD ) + { + return getTalkgroupID(); + } + else + { + return null; + } + } + + @Override + public Alias getFromIDAlias() + { + if( mMessageType == MessageType.CA_STRT || + mMessageType == MessageType.CA_ENDD ) + { + return getTalkgroupIDAlias(); + } + else + { + return null; + } + } + + @Override + public Plottable getPlottable() + { + // TODO Auto-generated method stub + return null; + } + + /** + * Provides a listing of aliases contained in the message. + */ + public List getAliases() + { + List aliases = new ArrayList(); + + Alias from = getFromIDAlias(); + + if( from != null ) + { + aliases.add( from ); + } + + Alias to = getToIDAlias(); + + if( to != null ) + { + aliases.add( to ); + } + + return aliases; + } +} diff --git a/src/module/decode/ltrstandard/message/CallEndMessage.java b/src/module/decode/ltrstandard/message/CallEndMessage.java deleted file mode 100644 index e5023139..00000000 --- a/src/module/decode/ltrstandard/message/CallEndMessage.java +++ /dev/null @@ -1,67 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * 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 - ******************************************************************************/ -package module.decode.ltrstandard.message; - -import message.MessageDirection; -import message.MessageType; -import alias.Alias; -import alias.AliasList; -import bits.BinaryMessage; -import edac.CRC; - -public class CallEndMessage extends LTRStandardMessage -{ - public CallEndMessage( BinaryMessage message, MessageDirection direction, - AliasList list, CRC crc ) - { - super( message, direction, list, crc ); - } - - @Override - public MessageType getMessageType() - { - return MessageType.CA_ENDD; - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "END* AREA:" ); - sb.append( getArea() ); - sb.append( " LCN:" ); - sb.append( getChannelFormatted() ); - sb.append( " TG [" ); - sb.append( getToID() ); - sb.append( "/" ); - Alias endAlias = getToIDAlias(); - if( endAlias != null ) - { - sb.append( getToIDAlias().getName() ); - } - else - { - sb.append( "UNKNOWN" ); - } - sb.append( "] FREE:" ); - sb.append( getFreeFormatted() ); - - return sb.toString(); - } -} diff --git a/src/module/decode/ltrstandard/message/CallMessage.java b/src/module/decode/ltrstandard/message/CallMessage.java deleted file mode 100644 index f7d44ca6..00000000 --- a/src/module/decode/ltrstandard/message/CallMessage.java +++ /dev/null @@ -1,68 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * 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 - ******************************************************************************/ -package module.decode.ltrstandard.message; - -import message.MessageDirection; -import message.MessageType; -import alias.Alias; -import alias.AliasList; -import bits.BinaryMessage; -import edac.CRC; - -public class CallMessage extends LTRStandardMessage -{ - public CallMessage( BinaryMessage message, MessageDirection direction, - AliasList list, CRC crc ) - { - super( message, direction, list, crc ); - } - - @Override - public MessageType getMessageType() - { - return MessageType.CA_STRT; - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "CALL LCN:" ); - sb.append( getChannelFormatted() ); - sb.append( " TG [ " ); - sb.append( getToID() ); - sb.append( "/" ); - - Alias alias = getToIDAlias(); - - if( alias != null ) - { - sb.append( getToIDAlias().getName() ); - } - else - { - sb.append( "UNKNOWN" ); - } - - sb.append( " ] FREE:" ); - sb.append( getFreeFormatted() ); - - return sb.toString(); - } -} diff --git a/src/module/decode/ltrstandard/message/IdleMessage.java b/src/module/decode/ltrstandard/message/IdleMessage.java deleted file mode 100644 index ba8e4d97..00000000 --- a/src/module/decode/ltrstandard/message/IdleMessage.java +++ /dev/null @@ -1,58 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * 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 - ******************************************************************************/ -package module.decode.ltrstandard.message; - -import message.MessageDirection; -import message.MessageType; -import alias.AliasList; -import bits.BinaryMessage; -import edac.CRC; - -public class IdleMessage extends LTRStandardMessage -{ - public IdleMessage( BinaryMessage message, MessageDirection direction, - AliasList list, CRC crc ) - { - super( message, direction, list, crc ); - } - - @Override - public MessageType getMessageType() - { - return MessageType.SY_IDLE; - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "IDLE AREA:" ); - sb.append( getArea() ); - sb.append( " LCN:" ); - sb.append( getChannelFormatted() ); - sb.append( " HOME:" ); - sb.append( getHomeRepeaterFormatted() ); - sb.append( " GRP:" ); - sb.append( getGroupFormatted() ); - sb.append( " FREE:" ); - sb.append( getFreeFormatted() ); - - return sb.toString(); - } -} diff --git a/src/module/decode/ltrstandard/message/LTRStandardMessage.java b/src/module/decode/ltrstandard/message/LTRStandardMessage.java deleted file mode 100644 index 87f381b2..00000000 --- a/src/module/decode/ltrstandard/message/LTRStandardMessage.java +++ /dev/null @@ -1,213 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * 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 - ******************************************************************************/ -package module.decode.ltrstandard.message; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import message.Message; -import message.MessageDirection; -import message.MessageType; -import module.decode.DecoderType; - -import org.apache.commons.lang3.StringUtils; - -import alias.Alias; -import alias.AliasList; -import bits.BinaryMessage; -import edac.CRC; - -public abstract class LTRStandardMessage extends Message -{ - protected SimpleDateFormat mDatestampFormatter = - new SimpleDateFormat( "yyyyMMdd HHmmss" ); - - public static final int[] SYNC = { 0,1,2,3,4,5,6,7,8 }; - public static final int[] AREA = { 9 }; - public static final int[] CHANNEL = { 10,11,12,13,14 }; - public static final int[] HOME_REPEATER = { 15,16,17,18,19 }; - public static final int[] GROUP = { 20,21,22,23,24,25,26,27 }; - public static final int[] FREE = { 28,29,30,31,32 }; - public static final int[] CHECKSUM = { 33,34,35,36,37,38,39 }; - - protected BinaryMessage mMessage; - protected MessageDirection mMessageDirection; - protected AliasList mAliasList; - protected CRC mCRC; - - public LTRStandardMessage( BinaryMessage message, - MessageDirection direction, - AliasList list, - CRC crc ) - { - mMessage = message; - mMessageDirection = direction; - mAliasList = list; - mCRC = crc; - } - - public abstract MessageType getMessageType(); - - public boolean isValid() - { - return mCRC != CRC.FAILED_CRC && mCRC != CRC.FAILED_PARITY; - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append( mDatestampFormatter.format( - new Date( System.currentTimeMillis() ) ) ); - sb.append( " LTR " ); - sb.append( mMessageDirection.name() ); - sb.append( " [" ); - sb.append( mCRC.getAbbreviation() ); - sb.append( "] " ); - sb.append( getMessage() ); - - return sb.toString(); - } - - @Override - public String getBinaryMessage() - { - return mMessage.toString(); - } - - public CRC getCRC() - { - return mCRC; - } - - public int getArea() - { - return mMessage.getInt( AREA ); - } - - public int getChannel() - { - return mMessage.getInt( CHANNEL ); - } - - public String getChannelFormatted() - { - return StringUtils.leftPad( String.valueOf( getChannel() ), 2, "0" ); - } - - public int getHomeRepeater() - { - return mMessage.getInt( HOME_REPEATER ); - } - - public String getHomeRepeaterFormatted() - { - return StringUtils.leftPad( String.valueOf( getHomeRepeater() ), 2, "0" ); - } - - public int getGroup() - { - return mMessage.getInt( GROUP ); - } - - public String getGroupFormatted() - { - return StringUtils.leftPad( String.valueOf( getGroup() ), 3, "0" ); - } - - public int getFree() - { - return mMessage.getInt( FREE ); - } - - public String getFreeFormatted() - { - return StringUtils.leftPad( String.valueOf( getFree() ), 2, "0" ); - } - - @Override - public String getFromID() - { - return null; - } - - @Override - public Alias getFromIDAlias() - { - return null; - } - - @Override - public String getToID() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getArea() ); - sb.append( "-" ); - sb.append( getHomeRepeaterFormatted() ); - sb.append( "-" ); - sb.append( getGroupFormatted() ); - - return sb.toString(); - } - - @Override - public Alias getToIDAlias() - { - return mAliasList.getTalkgroupAlias( getToID() ); - } - - public List getAliases() - { - List aliases = new ArrayList<>(); - - Alias talkgroupAlias = getToIDAlias(); - - if( talkgroupAlias != null ) - { - aliases.add( talkgroupAlias ); - } - - return aliases; - } - - public int getCRCChecksum() - { - return mMessage.getInt( CHECKSUM ); - } - - @Override - public String getProtocol() - { - return DecoderType.LTR_STANDARD.getDisplayString(); - } - - @Override - public String getEventType() - { - return getMessageType().getDisplayText(); - } - - @Override - public String getErrorStatus() - { - return mCRC.getDisplayText(); - } -} diff --git a/src/module/decode/ltrstandard/message/UnknownMessage.java b/src/module/decode/ltrstandard/message/UnknownMessage.java deleted file mode 100644 index b975c667..00000000 --- a/src/module/decode/ltrstandard/message/UnknownMessage.java +++ /dev/null @@ -1,58 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * 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 - ******************************************************************************/ -package module.decode.ltrstandard.message; - -import message.MessageDirection; -import message.MessageType; -import alias.AliasList; -import bits.BinaryMessage; -import edac.CRC; - -public class UnknownMessage extends LTRStandardMessage -{ - public UnknownMessage( BinaryMessage message, MessageDirection direction, - AliasList list, CRC crc ) - { - super( message, direction, list, crc ); - } - - @Override - public MessageType getMessageType() - { - return MessageType.UN_KNWN; - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "UNKNOWN AREA:" ); - sb.append( getArea() ); - sb.append( " LCN:" ); - sb.append( getChannelFormatted() ); - sb.append( " HOME:" ); - sb.append( getHomeRepeaterFormatted() ); - sb.append( " GRP:" ); - sb.append( getGroupFormatted() ); - sb.append( " FREE:" ); - sb.append( getFreeFormatted() ); - - return sb.toString(); - } -} diff --git a/src/module/decode/mpt1327/MPT1327Decoder.java b/src/module/decode/mpt1327/MPT1327Decoder.java index 799fd5a0..45bc3372 100644 --- a/src/module/decode/mpt1327/MPT1327Decoder.java +++ b/src/module/decode/mpt1327/MPT1327Decoder.java @@ -301,8 +301,8 @@ public class MPT1327Decoder extends Decoder @Override public void reset() { - mControlMessageFramer.reset(); - mTrafficMessageFramer.reset(); + // TODO Auto-generated method stub + } @Override diff --git a/src/module/decode/mpt1327/MPT1327DecoderState.java b/src/module/decode/mpt1327/MPT1327DecoderState.java index cc22c847..ad40aa52 100644 --- a/src/module/decode/mpt1327/MPT1327DecoderState.java +++ b/src/module/decode/mpt1327/MPT1327DecoderState.java @@ -26,11 +26,9 @@ import java.util.TreeSet; import message.Message; import module.decode.DecoderType; -import module.decode.config.DecodeConfiguration; import module.decode.event.CallEvent; import module.decode.event.CallEvent.CallEventType; import module.decode.mpt1327.MPT1327Message.IdentType; -import module.decode.state.ChangeChannelTimeoutEvent; import module.decode.state.ChangedAttribute; import module.decode.state.DecoderState; import module.decode.state.DecoderStateEvent; @@ -43,8 +41,6 @@ import org.slf4j.LoggerFactory; import alias.Alias; import alias.AliasList; -import audio.metadata.Metadata; -import audio.metadata.MetadataType; import controller.channel.Channel.ChannelType; import controller.channel.map.ChannelMap; @@ -64,18 +60,15 @@ public class MPT1327DecoderState extends DecoderState private ChannelMap mChannelMap; private long mFrequency = 0; - private long mCallTimeout; - public MPT1327DecoderState( AliasList aliasList, - ChannelMap channelMap, - ChannelType channelType, - long callTimeout ) + public MPT1327DecoderState( AliasList aliasList, ChannelMap channelMap, + ChannelType channelType ) { super( aliasList ); mChannelMap = channelMap; + mChannelType = channelType; - mCallTimeout = callTimeout; } @Override @@ -259,42 +252,8 @@ public class MPT1327DecoderState extends DecoderState /* Traffic Channel Events */ case CLEAR: - mChannelNumber = mpt.getChannel(); - - broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) ); - break; case MAINT: - 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() ); - } + broadcast( new DecoderStateEvent( this, Event.END, State.END ) ); break; default: break; @@ -317,25 +276,6 @@ public class MPT1327DecoderState extends DecoderState mToTalkgroup = null; broadcast( ChangedAttribute.TO_TALKGROUP ); - - /** - * If this is a standard channel, reset the fade timeout to the default - * timeout. Once processing is underway, if we get a MAINT message, - * this indicates we're processing a traffic channel as a standard - * channel, so we'll issue a different call timeout then. - */ - if( mChannelType == ChannelType.STANDARD ) - { - broadcast( new ChangeChannelTimeoutEvent( this, mChannelType, - DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000 ) ); - - if( mCurrentCallEvent != null ) - { - mCurrentCallEvent.end(); - broadcast( mCurrentCallEvent ); - mCurrentCallEvent = null; - } - } } public String getSite() diff --git a/src/module/decode/p25/P25DecoderState.java b/src/module/decode/p25/P25DecoderState.java index 91ab112c..0ffe62ce 100644 --- a/src/module/decode/p25/P25DecoderState.java +++ b/src/module/decode/p25/P25DecoderState.java @@ -42,8 +42,6 @@ import module.decode.p25.message.ldu.lc.TelephoneInterconnectVoiceChannelUser; import module.decode.p25.message.ldu.lc.UnitToUnitVoiceChannelUser; import module.decode.p25.message.pdu.PDUMessage; 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.SNDCPActivateTDSContextReject; import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextRequest; @@ -120,6 +118,7 @@ import module.decode.p25.message.tsbk.osp.voice.UnitToUnitVoiceChannelGrantUpdat import module.decode.p25.reference.IPProtocol; import module.decode.p25.reference.LinkControlOpcode; import module.decode.p25.reference.Response; +import module.decode.p25.reference.Service; import module.decode.p25.reference.Vendor; import module.decode.state.ChangedAttribute; import module.decode.state.DecoderState; @@ -407,12 +406,12 @@ public class P25DecoderState extends DecoderState { if( hdu.isValid() ) { - broadcast( new DecoderStateEvent( this, Event.START, State.CALL ) ); + State state = hdu.isEncrypted() ? State.ENCRYPTED : State.CALL; + + broadcast( new DecoderStateEvent( this, Event.START, state ) ); String to = hdu.getToID(); - - updateTo( to ); - + if( mCurrentCallEvent == null ) { mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.CALL ) @@ -426,6 +425,7 @@ public class P25DecoderState extends DecoderState broadcast( mCurrentCallEvent ); } + updateTo( to ); } } @@ -437,7 +437,7 @@ public class P25DecoderState extends DecoderState { if( tdulc.getOpcode() == LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION ) { - broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) ); + broadcast( new DecoderStateEvent( this, Event.END, State.END ) ); if( mCurrentCallEvent != null ) { @@ -615,14 +615,12 @@ public class P25DecoderState extends DecoderState module.decode.p25.message.tdu.lc.GroupVoiceChannelUser gvcuser = (module.decode.p25.message.tdu.lc.GroupVoiceChannelUser)tdulc; - broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CALL ) ); + broadcast( new DecoderStateEvent( this, Event.CONTINUATION, + ( gvcuser.isEncrypted() ? State.ENCRYPTED : State.CALL ) ) ); String from = gvcuser.getSourceAddress(); String to = gvcuser.getGroupAddress(); - updateFrom( from ); - updateTo( to ); - if( mCurrentCallEvent == null ) { mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) @@ -638,6 +636,9 @@ public class P25DecoderState extends DecoderState broadcast( mCurrentCallEvent ); } + + updateFrom( from ); + updateTo( to ); } break; case MESSAGE_UPDATE: @@ -844,8 +845,6 @@ public class P25DecoderState extends DecoderState String to = tivcu.getAddress(); - updateTo( to ); - if( mCurrentCallEvent == null ) { mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.TELEPHONE_INTERCONNECT ) @@ -859,6 +858,8 @@ public class P25DecoderState extends DecoderState broadcast( mCurrentCallEvent ); } + + updateTo( to ); } break; case UNIT_AUTHENTICATION_COMMAND: @@ -921,10 +922,7 @@ public class P25DecoderState extends DecoderState (module.decode.p25.message.tdu.lc.UnitToUnitVoiceChannelUser)tdulc; String from = uuvcu.getSourceAddress(); - updateFrom( from ); - String to = uuvcu.getTargetAddress(); - updateTo( to ); if( mCurrentCallEvent != null ) { @@ -938,6 +936,9 @@ public class P25DecoderState extends DecoderState .to( to ) .build(); } + + updateFrom( from ); + updateTo( to ); } break; default: @@ -949,14 +950,35 @@ public class P25DecoderState extends DecoderState * Log optional vendor format messages that we don't yet support so that we * can understand those messages and eventually add support. */ - private void logAlternateVendorMessage( P25Message message ) + private void logAlternateVendorMessage( P25Message p25 ) { -// if( message.isValid() ) +// if( p25 instanceof TDULinkControlMessage ) // { -// mLog.info( "PLEASE NOTIFY DEVELOPER - UNRECOGNIZED P25 VENDOR FORMAT -" -// + " DUID:" + message.getDUID() -// + " MSG:" + message.getBinaryMessage() -// + " CLASS:" + message.getClass() ); +// TDULinkControlMessage tdulc = (TDULinkControlMessage)p25; +// +// mLog.debug( "TDULC - VENDOR [" + tdulc.getVendor() + +// "] SPECIFIC FORMAT FOR " + tdulc.getVendorOpcode().getLabel() + +// " " + 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() + "]" ); // } } @@ -967,11 +989,15 @@ public class P25DecoderState extends DecoderState */ private void processLDU( LDUMessage ldu ) { - broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CALL ) ); + State state = ( ldu.isValid() && ldu.isEncrypted() ) ? + State.ENCRYPTED : State.CALL; + + broadcast( new DecoderStateEvent( this, Event.CONTINUATION, state ) ); if( mCurrentCallEvent == null ) { - mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.CALL ) + mCurrentCallEvent = new P25CallEvent.Builder( + ( state == State.CALL ) ? CallEventType.CALL : CallEventType.ENCRYPTED_CALL ) .aliasList( getAliasList() ) .channel( mCurrentChannel ) .frequency( mCurrentChannelFrequency ) @@ -1031,7 +1057,7 @@ public class P25DecoderState extends DecoderState } break; case CALL_TERMINATION_OR_CANCELLATION: - broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) ); + broadcast( new DecoderStateEvent( this, Event.END, State.END ) ); mCurrentCallEvent = null; if( !( ldu instanceof CallTermination ) ) @@ -1089,43 +1115,42 @@ public class P25DecoderState extends DecoderState } break; case GROUP_VOICE_CHANNEL_UPDATE: - if( ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate ) + if( mChannelType == ChannelType.STANDARD && + ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate ) { 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(); + + 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(); - if( mChannelType == ChannelType.STANDARD ) + if( userB != null && + !userB.contentEquals( "0000" ) && + !mCallDetectTalkgroups.contains( userB ) ) { - 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 ); - } + broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT ) + .aliasList( getAliasList() ) + .channel( gvcu.getChannelB() ) + .details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) ) + .frequency( gvcu.getDownlinkFrequencyB() ) + .to( userB ) + .build() ); - 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 ); - } + mCallDetectTalkgroups.add( userB ); } } else @@ -1134,27 +1159,25 @@ public class P25DecoderState extends DecoderState } break; case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT: - if( ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit ) + if( mChannelType == ChannelType.STANDARD && + ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit ) { module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit gvcue = (module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit)ldu; String group = gvcue.getGroupAddress(); - if( mChannelType == ChannelType.STANDARD ) + if( !mCallDetectTalkgroups.contains( group ) ) { - if( !mCallDetectTalkgroups.contains( group ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT ) - .aliasList( getAliasList() ) - .channel( gvcue.getTransmitChannel() ) - .details( ( gvcue.isEncrypted() ? "ENCRYPTED" : "" ) ) - .frequency( gvcue.getDownlinkFrequency() ) - .to( group ) - .build() ); - - mCallDetectTalkgroups.add( group ); - } + broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT ) + .aliasList( getAliasList() ) + .channel( gvcue.getTransmitChannel() ) + .details( ( gvcue.isEncrypted() ? "ENCRYPTED" : "" ) ) + .frequency( gvcue.getDownlinkFrequency() ) + .to( group ) + .build() ); + + mCallDetectTalkgroups.add( group ); } } else @@ -1164,32 +1187,29 @@ public class P25DecoderState extends DecoderState break; case GROUP_VOICE_CHANNEL_USER: /* Indicates the current user of the current channel */ - if( ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUser ) + if( mChannelType == ChannelType.STANDARD && + ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUser ) { module.decode.p25.message.ldu.lc.GroupVoiceChannelUser gvcuser = (module.decode.p25.message.ldu.lc.GroupVoiceChannelUser)ldu; - updateFrom( gvcuser.getSourceAddress() ); - - updateTo( gvcuser.getGroupAddress() ); - - if( mChannelType == ChannelType.STANDARD ) + if( mCurrentCallEvent.getCallEventType() != CallEventType.GROUP_CALL ) { - 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 ); - } + mCurrentCallEvent.setCallEventType( CallEventType.GROUP_CALL ); + broadcast( mCurrentCallEvent ); } + + if( mCurrentCallEvent.getDetails() == null ) + { + mCurrentCallEvent.setDetails( + ( gvcuser.isEncrypted() ? "ENCRYPTED " : "" ) + + ( gvcuser.isEmergency() ? "EMERGENCY " : "" ) ); + + broadcast( mCurrentCallEvent ); + } + + updateFrom( gvcuser.getSourceAddress() ); + updateTo( gvcuser.getGroupAddress() ); } else { @@ -1385,8 +1405,6 @@ public class P25DecoderState extends DecoderState TelephoneInterconnectVoiceChannelUser tivcu = (TelephoneInterconnectVoiceChannelUser)ldu; - updateTo( tivcu.getAddress() ); - if( mCurrentCallEvent.getCallEventType() != CallEventType.TELEPHONE_INTERCONNECT ) { mCurrentCallEvent.setCallEventType( CallEventType.TELEPHONE_INTERCONNECT ); @@ -1401,6 +1419,8 @@ public class P25DecoderState extends DecoderState broadcast( mCurrentCallEvent ); } + + updateTo( tivcu.getAddress() ); } else { @@ -1466,10 +1486,6 @@ public class P25DecoderState extends DecoderState UnitToUnitVoiceChannelUser uuvcu = (UnitToUnitVoiceChannelUser)ldu; - updateFrom( uuvcu.getSourceAddress() ); - - updateTo( uuvcu.getTargetAddress() ); - if( mCurrentCallEvent.getCallEventType() != CallEventType.UNIT_TO_UNIT_CALL ) { mCurrentCallEvent.setCallEventType( CallEventType.UNIT_TO_UNIT_CALL ); @@ -1484,6 +1500,9 @@ public class P25DecoderState extends DecoderState broadcast( mCurrentCallEvent ); } + + updateFrom( uuvcu.getSourceAddress() ); + updateTo( uuvcu.getTargetAddress() ); } else { @@ -1502,7 +1521,7 @@ public class P25DecoderState extends DecoderState private void processTSBK( TSBKMessage tsbk ) { /* Trunking Signalling Block Messages - indicates Control Channel */ - broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) ); + broadcast( new DecoderStateEvent( this, Event.START, State.CONTROL ) ); if( tsbk.getVendor() == Vendor.STANDARD ) { @@ -1631,11 +1650,7 @@ public class P25DecoderState extends DecoderState */ private void processPDU( PDUMessage pdu ) { - if( pdu instanceof PacketData || pdu instanceof PDUTypeUnknown ) - { - broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.DATA ) ); - } - else if( pdu instanceof PDUConfirmedMessage ) + if( pdu instanceof PDUConfirmedMessage ) { broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.DATA ) ); @@ -1643,7 +1658,7 @@ public class P25DecoderState extends DecoderState switch( pduc.getPDUType() ) { - case SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT: + case SN_ACTIVATE_TDS_CONTEXT_ACCEPT: SNDCPActivateTDSContextAccept satca = (SNDCPActivateTDSContextAccept)pduc; @@ -1655,7 +1670,7 @@ public class P25DecoderState extends DecoderState .to( satca.getLogicalLinkID() ) .build() ); break; - case SNDCP_ACTIVATE_TDS_CONTEXT_REJECT: + case SN_ACTIVATE_TDS_CONTEXT_REJECT: SNDCPActivateTDSContextReject satcr = (SNDCPActivateTDSContextReject)pduc; @@ -1680,7 +1695,7 @@ public class P25DecoderState extends DecoderState .from( satcreq.getLogicalLinkID() ) .build() ); break; - case SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT: + case SN_DEACTIVATE_TDS_CONTEXT_ACCEPT: SNDCPDeactivateTDSContext sdtca = (SNDCPDeactivateTDSContext)pduc; @@ -1692,7 +1707,7 @@ public class P25DecoderState extends DecoderState .from( sdtca.getLogicalLinkID() ) .build() ); break; - case SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST: + case SN_DEACTIVATE_TDS_CONTEXT_REQUEST: SNDCPDeactivateTDSContext sdtcreq = (SNDCPDeactivateTDSContext)pduc; @@ -1704,7 +1719,7 @@ public class P25DecoderState extends DecoderState .from( sdtcreq.getLogicalLinkID() ) .build() ); break; - case SNDCP_RF_CONFIRMED_DATA: + case SN_RF_CONFIRMED_DATA: SNDCPUserData sud = (SNDCPUserData)pduc; StringBuilder sbFrom = new StringBuilder(); @@ -1733,10 +1748,8 @@ public class P25DecoderState extends DecoderState .to( pduc.getLogicalLinkID() ) .build() ); break; - case SNDCP_RF_UNCONFIRMED_DATA: - break; default: -// mLog.debug( "PDUC - Unrecognized Message: " + pduc.toString() ); + mLog.debug( "PDUC - Unrecognized Message: " + pduc.toString() ); break; } } @@ -3087,9 +3100,16 @@ public class P25DecoderState extends DecoderState if( mToTalkgroup == null || !mToTalkgroup.contentEquals( to ) ) { mToTalkgroup = to; - broadcast( ChangedAttribute.TO_TALKGROUP ); + if( mCurrentCallEvent != null && + ( mCurrentCallEvent.getToID() == null || + !mCurrentCallEvent.getToID().contentEquals( to ) ) ) + { + mCurrentCallEvent.setToID( to ); + broadcast( mCurrentCallEvent ); + } + if( hasAliasList() ) { mToAlias = getAliasList().getTalkgroupAlias( mToTalkgroup ); @@ -3098,20 +3118,12 @@ public class P25DecoderState extends DecoderState { mToAlias = null; } - + broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS ); - - /* Send audio metadata update */ - broadcast( new Metadata( MetadataType.TO, mToTalkgroup, mToAlias, true ) ); - - if( mCurrentCallEvent != null && - ( mCurrentCallEvent.getToID() == null || - !mCurrentCallEvent.getToID().contentEquals( to ) ) ) - { - mCurrentCallEvent.setToID( to ); - broadcast( mCurrentCallEvent ); - } } + + /* Send audio metadata update */ + broadcast( new Metadata( MetadataType.TO, mToTalkgroup, mToAlias, true ) ); } } @@ -3127,6 +3139,14 @@ public class P25DecoderState extends DecoderState mFromTalkgroup = from; broadcast( ChangedAttribute.FROM_TALKGROUP ); + if( mCurrentCallEvent != null && + mCurrentCallEvent.getFromID() == null || + !mCurrentCallEvent.getFromID().contentEquals( from ) ) + { + mCurrentCallEvent.setFromID( from ); + broadcast( mCurrentCallEvent ); + } + if( hasAliasList() ) { mFromAlias = getAliasList().getTalkgroupAlias( mFromTalkgroup ); @@ -3137,19 +3157,10 @@ public class P25DecoderState extends DecoderState } 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 ) ); } } diff --git a/src/module/decode/p25/message/P25Message.java b/src/module/decode/p25/message/P25Message.java index dc33108f..e8421211 100644 --- a/src/module/decode/p25/message/P25Message.java +++ b/src/module/decode/p25/message/P25Message.java @@ -87,8 +87,6 @@ public class P25Message extends Message sb.append( " " ); sb.append( getDUID().getLabel() ); - sb.append( " DATA UNIT ID:" + mMessage.getInt( DUID ) ); - return sb.toString(); } diff --git a/src/module/decode/p25/message/pdu/PDUMessageFactory.java b/src/module/decode/p25/message/pdu/PDUMessageFactory.java index 1e11796d..26b25b1c 100644 --- a/src/module/decode/p25/message/pdu/PDUMessageFactory.java +++ b/src/module/decode/p25/message/pdu/PDUMessageFactory.java @@ -1,6 +1,5 @@ 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.CallAlertExtended; import module.decode.p25.message.pdu.osp.control.GroupAffiliationQueryExtended; @@ -122,8 +121,7 @@ public class PDUMessageFactory } return new PDUMessage( message, duid, aliasList ); - case PACKET_DATA: - return new PacketData( message, aliasList ); + case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL: return new PDUMessage( message, duid, aliasList ); default: diff --git a/src/module/decode/p25/message/pdu/confirmed/PDUConfirmedMessageFactory.java b/src/module/decode/p25/message/pdu/confirmed/PDUConfirmedMessageFactory.java index e8c08bcb..b695dd79 100644 --- a/src/module/decode/p25/message/pdu/confirmed/PDUConfirmedMessageFactory.java +++ b/src/module/decode/p25/message/pdu/confirmed/PDUConfirmedMessageFactory.java @@ -48,27 +48,19 @@ public class PDUConfirmedMessageFactory case UNENCRYPTED_USER_DATA: switch( message.getPDUType() ) { - case SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT: + case SN_ACTIVATE_TDS_CONTEXT_ACCEPT: return new SNDCPActivateTDSContextAccept( message ); - case SNDCP_ACTIVATE_TDS_CONTEXT_REJECT: + case SN_ACTIVATE_TDS_CONTEXT_REJECT: return new SNDCPActivateTDSContextReject( message ); case SN_ACTIVATE_TDS_CONTEXT_REQUEST: return new SNDCPActivateTDSContextRequest( message ); - case SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT: - case SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST: + case SN_DEACTIVATE_TDS_CONTEXT_ACCEPT: + case SN_DEACTIVATE_TDS_CONTEXT_REQUEST: return new SNDCPDeactivateTDSContext( message ); - case SNDCP_RF_CONFIRMED_DATA: + case SN_RF_CONFIRMED_DATA: return new SNDCPUserData( message ); - case SNDCP_RF_UNCONFIRMED_DATA: + case SN_RF_UNCONFIRMED_DATA: 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: break; } diff --git a/src/module/decode/p25/message/pdu/confirmed/PDUTypeUnknown.java b/src/module/decode/p25/message/pdu/confirmed/PDUTypeUnknown.java deleted file mode 100644 index 0f08ca73..00000000 --- a/src/module/decode/p25/message/pdu/confirmed/PDUTypeUnknown.java +++ /dev/null @@ -1,55 +0,0 @@ -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(); - } -} diff --git a/src/module/decode/p25/message/pdu/confirmed/PacketData.java b/src/module/decode/p25/message/pdu/confirmed/PacketData.java deleted file mode 100644 index e1b0af6d..00000000 --- a/src/module/decode/p25/message/pdu/confirmed/PacketData.java +++ /dev/null @@ -1,66 +0,0 @@ -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(); - } -} diff --git a/src/module/decode/p25/message/pdu/confirmed/SNDCPDeactivateTDSContext.java b/src/module/decode/p25/message/pdu/confirmed/SNDCPDeactivateTDSContext.java index ec8ec0b5..859303be 100644 --- a/src/module/decode/p25/message/pdu/confirmed/SNDCPDeactivateTDSContext.java +++ b/src/module/decode/p25/message/pdu/confirmed/SNDCPDeactivateTDSContext.java @@ -49,7 +49,7 @@ public class SNDCPDeactivateTDSContext extends PDUConfirmedMessage public String getDeactivationType() { - if( getPDUType() == PDUType.SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST ) + if( getPDUType() == PDUType.SN_DEACTIVATE_TDS_CONTEXT_REQUEST ) { return mMessage.getInt( DEACTIVATION_TYPE ) == 0 ? "ALL NSAPIS" : "THIS NSAPI"; diff --git a/src/module/decode/p25/message/tsbk/TSBKMessageFactory.java b/src/module/decode/p25/message/tsbk/TSBKMessageFactory.java index 3e8ab315..e06ac784 100644 --- a/src/module/decode/p25/message/tsbk/TSBKMessageFactory.java +++ b/src/module/decode/p25/message/tsbk/TSBKMessageFactory.java @@ -1,7 +1,5 @@ package module.decode.p25.message.tsbk; -import alias.AliasList; -import bits.BinaryMessage; import module.decode.p25.message.tsbk.motorola.ControlChannelBaseStationIdentification; import module.decode.p25.message.tsbk.motorola.MotorolaOpcode; import module.decode.p25.message.tsbk.motorola.MotorolaTSBKMessage; @@ -21,7 +19,6 @@ import module.decode.p25.message.tsbk.osp.control.ExtendedFunctionCommand; import module.decode.p25.message.tsbk.osp.control.GroupAffiliationQuery; import module.decode.p25.message.tsbk.osp.control.GroupAffiliationResponse; import module.decode.p25.message.tsbk.osp.control.IdentifierUpdateNonVUHF; -import module.decode.p25.message.tsbk.osp.control.IdentifierUpdateTDMA; import module.decode.p25.message.tsbk.osp.control.IdentifierUpdateVUHF; import module.decode.p25.message.tsbk.osp.control.LocationRegistrationResponse; import module.decode.p25.message.tsbk.osp.control.MessageUpdate; @@ -59,6 +56,8 @@ import module.decode.p25.message.tsbk.osp.voice.UnitToUnitVoiceChannelGrantUpdat import module.decode.p25.reference.DataUnitID; import module.decode.p25.reference.Opcode; import module.decode.p25.reference.Vendor; +import alias.AliasList; +import bits.BinaryMessage; public class TSBKMessageFactory { @@ -120,8 +119,6 @@ public class TSBKMessageFactory case IDENTIFIER_UPDATE_NON_VUHF: return new IdentifierUpdateNonVUHF( message, duid, aliasList ); - case IDENTIFIER_UPDATE_TDMA: - return new IdentifierUpdateTDMA( message, duid, aliasList ); case IDENTIFIER_UPDATE_VHF_UHF_BANDS: return new IdentifierUpdateVUHF( message, duid, aliasList ); diff --git a/src/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java b/src/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java deleted file mode 100644 index ca31b7c6..00000000 --- a/src/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java +++ /dev/null @@ -1,159 +0,0 @@ -package module.decode.p25.message.tsbk.osp.control; - -import module.decode.p25.reference.DataUnitID; -import module.decode.p25.reference.Opcode; -import alias.AliasList; -import bits.BinaryMessage; - -public class IdentifierUpdateTDMA extends IdentifierUpdate -{ - public static final int[] CHANNEL_TYPE = { 84,85,86,87 }; - public static final int TRANSMIT_OFFSET_SIGN = 88; - public static final int[] TRANSMIT_OFFSET = { 89,90,91,92,93,94,95,96,97,98, - 99,100,101 }; - - public IdentifierUpdateTDMA( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.IDENTIFIER_UPDATE_TDMA.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " IDEN:" + getIdentifier() ); - sb.append( " BASE:" + getBaseFrequency() ); - sb.append( " BW:" + getBandwidth() ); - sb.append( " SPACING:" + getChannelSpacing() ); - sb.append( " OFFSET:" + getTransmitOffset() ); - sb.append( " TYPE:" + getChannelType().toString() ); - - return sb.toString(); - } - - public ChannelType getChannelType() - { - return ChannelType.fromValue( mMessage.getInt( CHANNEL_TYPE) ); - } - - /** - * Channel bandwidth in hertz - */ - public int getBandwidth() - { - return getChannelType().getBandwidth(); - } - - /** - * Transmit offset in hertz - */ - @Override - public long getTransmitOffset() - { - long offset = mMessage.getLong( TRANSMIT_OFFSET ) * - getChannelSpacing(); - - if( mMessage.get( TRANSMIT_OFFSET_SIGN ) ) - { - return offset; - } - else - { - return -offset; - } - } - - public enum ChannelType - { - TYPE_0( AccessType.FDMA, 12500, 1, Vocoder.HALF_RATE ), - TYPE_1( AccessType.FDMA, 12500, 1, Vocoder.FULL_RATE ), - TYPE_2( AccessType.FDMA, 6250, 1, Vocoder.HALF_RATE ), - TYPE_3( AccessType.TDMA, 12500, 2, Vocoder.HALF_RATE ), - TYPE_4( AccessType.TDMA, 25000, 4, Vocoder.HALF_RATE ), - TYPE_5( AccessType.TDMA, 12500, 2, Vocoder.HALF_RATE ), //HD8PSK simulcast - UNKNOWN( AccessType.UNKNOWN, 0, 0, Vocoder.HALF_RATE ); - - private AccessType mAccessType; - private int mBandwidth; - private int mSlotsPerCarrier; - private Vocoder mVocoder; - - private ChannelType( AccessType accessType, - int bandwidth, - int slots, - Vocoder vocoder ) - { - mAccessType = accessType; - mBandwidth = bandwidth; - mSlotsPerCarrier = slots; - mVocoder = vocoder; - } - - public String toString() - { - StringBuilder sb = new StringBuilder(); - - if( this.equals( TYPE_5 ) ) - { - sb.append("H-D8PSK SIMULCAST " ); - } - - sb.append( mAccessType.name() ); - sb.append( " BW:" + mBandwidth ); - sb.append( " SLOT/CARRIER:" + mSlotsPerCarrier ); - sb.append( " VOCODER:" + mVocoder.name() ); - - return sb.toString(); - } - - public static ChannelType fromValue( int value ) - { - if( 0 <= value && value <= 5 ) - { - return ChannelType.values()[ value ]; - } - - return ChannelType.UNKNOWN; - } - - public AccessType getAccessType() - { - return mAccessType; - } - - public int getBandwidth() - { - return mBandwidth; - } - - public int getSlotsPerCarrier() - { - return mSlotsPerCarrier; - } - - public Vocoder getVocoder() - { - return mVocoder; - } - } - - public enum AccessType - { - FDMA,TDMA,UNKNOWN; - } - - public enum Vocoder - { - HALF_RATE, FULL_RATE; - } -} diff --git a/src/module/decode/p25/message/tsbk/osp/control/SyncBroadcast.java b/src/module/decode/p25/message/tsbk/osp/control/SyncBroadcast.java index c59d8f12..50c09e73 100644 --- a/src/module/decode/p25/message/tsbk/osp/control/SyncBroadcast.java +++ b/src/module/decode/p25/message/tsbk/osp/control/SyncBroadcast.java @@ -1,10 +1,8 @@ package module.decode.p25.message.tsbk.osp.control; -import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; -import java.util.SimpleTimeZone; +import java.util.GregorianCalendar; import java.util.TimeZone; import module.decode.p25.message.tsbk.TSBKMessage; @@ -14,21 +12,20 @@ import alias.AliasList; import bits.BinaryMessage; /** - * Sync Broadcast - used to broadcast FDMA-TDMA timing synchronization - * information so that a subscriber unit that is time synchronized on an FDMA - * control channel is also time synchronized with all TDMA traffic channels at - * the site. + * Sync Broadcast - used to broadcast FDMA-TDMA synchronization timestamp so + * that a subscriber unit that is time synchronized on an FDMA control channel + * is also synchronized with a TDMA traffic channel. */ public class SyncBroadcast extends TSBKMessage { - public static final int[] RESERVED = { 80,81,82,83,84,85,86,87,88,89,90,91,92 }; - public static final int SYSTEM_TIME_NOT_LOCKED_TO_EXTERNAL_REFERENCE = 93; - public static final int MICRO_SLOTS_TO_MINUTE_ROLLOVER_UNLOCKED = 94; - public static final int[] LEAP_SECOND_CORRECTION = { 95,96 }; - public static final int LOCAL_TIME_OFFSET_VALID = 97; + public static final int US = 92; + public static final int IST = 93; + public static final int MMU = 94; + public static final int[] MC = { 95,96 }; + public static final int VL = 97; public static final int LOCAL_TIME_OFFSET_SIGN = 98; - public static final int[] LOCAL_TIME_OFFSET_HOURS = { 99,100,101,102 }; - public static final int LOCAL_TIME_OFFSET_HALF_HOUR = 103; + public static final int[] LOCAL_TIME_OFFSET = { 99,100,101,102 }; + public static final int LOCAL_TIME_OFFSET_HALF = 103; public static final int[] YEAR = { 104,105,106,107,108,109,110 }; public static final int[] MONTH = { 111,112,113,114 }; @@ -39,10 +36,7 @@ public class SyncBroadcast extends TSBKMessage 139,140,141,142,143 }; public static final int[] TSBK_CRC = { 144,145,146,147,148,149,150,151,152, 153,154,155,156,157,158,159 }; - - private static final DateFormat TIME_FORMATTER = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss.SSS Z" ); - private static final TimeZone NO_TIME_ZONE = new SimpleTimeZone( 0, "NONE" ); - + public SyncBroadcast( BinaryMessage message, DataUnitID duid, AliasList aliasList ) @@ -62,41 +56,24 @@ public class SyncBroadcast extends TSBKMessage sb.append( getMessageStub() ); - sb.append( " SYSTEM TIME" ); - - if( isSystemTimeNotLockedToExternalReference() ) - { - sb.append( " UNLOCKED" ); - } - - sb.append( ":" ); - - TIME_FORMATTER.setTimeZone( getTimeZone() ); - sb.append( " " + TIME_FORMATTER.format( new Date( getSystemTime() ) ) ); - - sb.append( " LEAP-SECOND CORRECTION:" + getLeapSecondCorrection() + "mS" ); - - if( isMicroslotsLockedToMinuteRollover() ) - { - sb.append( " MICROSLOT-MINUTE ROLLOVER:LOCKED" ); - } - else - { - sb.append( " MICROSLOT-MINUTE ROLLOVER:UNLOCKED" ); - } + sb.append( " " + new Date( getTime() ).toString() ); + sb.append( " TIME ZONE OFFSET:" + getTimeZone().toUpperCase() ); + sb.append( " US:" + isUS() ); + sb.append( " IST:" + isIST() ); + sb.append( " MMU:" + isMMU() ); + sb.append( " VL:" + isVL() ); return sb.toString(); } /** - * System Time (UTC) in milliseconds since java epoch + * Returns the time sync in java time (ie milliseconds since epoch ) */ - public long getSystemTime() + public long getTime() { - Calendar cal = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); - + Calendar cal = new GregorianCalendar(); cal.clear(); - + cal.setTimeZone( TimeZone.getTimeZone( "GMT" ) ); cal.set( Calendar.YEAR, getYear() ); cal.set( Calendar.MONTH, getMonth() - 1 ); cal.set( Calendar.DAY_OF_MONTH, getDay() ); @@ -107,165 +84,102 @@ public class SyncBroadcast extends TSBKMessage return cal.getTimeInMillis(); } - /** - * System Time not locked to external time reference indicator - */ - public boolean isSystemTimeNotLockedToExternalReference() + public boolean isUS() { - return mMessage.get( SYSTEM_TIME_NOT_LOCKED_TO_EXTERNAL_REFERENCE ); + return mMessage.get( US ); } /** - * Micro-slot-to-Minute rollover boundary lock indicator. - * - * Note: we invert the message value so that true indicates that the micro - * slots and minute rollovers are locked, whereas the ICD uses false to - * indicate a lock. - * - * @return true if the microslots rollover at each minute interval is locked - * to the system time minute rollover. - * - * @return false if the microslots and minutes rollover are not locked. This - * indicates that the microslots minute rollover is free rolling. - * + * Daylight Savings Time */ - public boolean isMicroslotsLockedToMinuteRollover() + public boolean isIST() { - return !mMessage.get( MICRO_SLOTS_TO_MINUTE_ROLLOVER_UNLOCKED ); + return mMessage.get( IST ); } /** - * Leap second correction value that should be applied to the system time. - * - * The leap-second correction field indicates the number of 2.5 millisecond - * units that must be added to the system time to account for the insertion - * of leap seconds when the system time is represented as UTC universal time. - * - * @return leap-second correction value in milliseconds 0.0-10.0 + * No idea ... */ - public double getLeapSecondCorrection() + public boolean isMMU() { - return (double)mMessage.getInt( LEAP_SECOND_CORRECTION ) * 2.5d; - } - - - - /** - * Indicates the local time offset fields contain valid information. - */ - public boolean isValidLocalTimeOffset() - { - return !mMessage.get( LOCAL_TIME_OFFSET_VALID ); + return mMessage.get( MMU ); } /** - * Local time zone offset from UTC universal time (zulu) when the VALID - * LOCAL TIME OFFSET flag indicates a valid offset. Otherwise, this method - * returns a static +00:00 indicating no local time offset. + * Millenium ...? */ - public TimeZone getTimeZone() + public int getMC() { - if( isValidLocalTimeOffset() ) - { - int offset = 0; - - offset += mMessage.getInt( LOCAL_TIME_OFFSET_HOURS ) * 3600000; - offset += mMessage.get( LOCAL_TIME_OFFSET_HALF_HOUR ) ? 1800000 : 0; - offset = mMessage.get( LOCAL_TIME_OFFSET_SIGN ) ? -offset : offset; - - return new SimpleTimeZone( offset, "LOCAL" ); - } - else - { - return NO_TIME_ZONE; - } + return mMessage.getInt( MC ); } /** - * System time - year. - * - * @return year in range 2000-2127 + * No idea .... */ + public boolean isVL() + { + return mMessage.get( VL ); + } + + /** + * Calculates the time zone from the local time offset field. + */ + public String getTimeZone() + { + StringBuilder sb = new StringBuilder(); + + sb.append( mMessage.get( LOCAL_TIME_OFFSET_SIGN ) ? "-" : "+" ); + + int hours = mMessage.getInt( LOCAL_TIME_OFFSET ); + sb.append( ( hours < 10 ? "0" : "" ) + String.valueOf( hours ) ); + + sb.append( ":" ); + + sb.append( mMessage.get( LOCAL_TIME_OFFSET_HALF ) ? "30" : "00" ); + + if( mMessage.get( IST ) ) + { + sb.append( " DST" ); + } + + return sb.toString(); + } + public int getYear() { return 2000 + mMessage.getInt( YEAR ); } - /** - * System time - month - * - * @return month 1-12 where 1=January and 12=December - */ public int getMonth() { return mMessage.getInt( MONTH ); } - /** - * System time - day of month - * - * @return day of month in range 1-31 - */ public int getDay() { return mMessage.getInt( DAY ); } - /** - * System time - hours - * - * @return hours in range 0 - 23 - */ public int getHours() { return mMessage.getInt( HOURS ); } - /** - * System time - minutes - * - * @return minutes in range 0 - 59 - */ public int getMinutes() { return mMessage.getInt( MINUTES ); } - /** - * System time - milli-seconds - * - * Note: sub-millisecond values are rounded to nearest millisecond unit to - * conform to java Date internal milli-second precision level. - * - * @return milli-seconds in range 0 - 59999; - */ public int getMilliSeconds() { return (int)( (double)getMicroSlots() * 7.5 ); } /** - * TDMA Micro Slots. - * - * Number of 7.5 millisecond micro slots since the last minute rollover, or - * since the last micro-slot counter rollover when the the micro-slot to - * minute rollover is unlocked. - * - * @see isMicroslotsLockedToMinuteRollover() method. - * - * This value supports mobile subscriber timing alignment with a TDMA traffic - * channel when a channel is granted from the control channels, so that when - * the mobile changes from the control channel to the traffic channel, it is - * already aligned with the traffic channel TDMA timing. - * - * Super frames occur every 360 ms (48 micro-slots) on FDMA and Ultra frames - * occur every 4 super frames on TDMA. - * - * Valid micro-slot values range 0-7999 and represent 60,000 - * milliseconds ( 8000 x 7.5 ms). - * - * @return number of 7.5 mS micro-slots since last minute or micro-slot - * rollover + * Micro Slots - each uslot = 7.5 milliseconds. The value ranges 0 - 7999 + * and represents 60,000 milliseconds ( 8000 x 7.5 ms). Superframes occur + * every 360 ms (48 micro-slots) on FDMA and Ultraframes occur every 4 + * super frames on TDMA. */ public int getMicroSlots() { diff --git a/src/module/decode/p25/reference/Opcode.java b/src/module/decode/p25/reference/Opcode.java index d94afec8..4dbf8870 100644 --- a/src/module/decode/p25/reference/Opcode.java +++ b/src/module/decode/p25/reference/Opcode.java @@ -53,7 +53,7 @@ public enum Opcode TDMA_SYNC_BROADCAST( "TDMA_SYNC_BCST ", "FDMA-TDMA Sync Broadcast", 48 ), RESERVED_31( "RESERVED_31 ", "Reserved", 49 ), RESERVED_32( "RESERVED_32 ", "Reserved", 50 ), - IDENTIFIER_UPDATE_TDMA( "IDEN_UPDATE_TDMA", "Identifier Update for TDMA", 51 ), + RESERVED_33( "RESERVED_33 ", "Reserved", 51 ), IDENTIFIER_UPDATE_VHF_UHF_BANDS( "IDEN_UPDATE_VUHF", "Identifier Update for VHF/UHF Bands", 52 ), TIME_DATE_ANNOUNCEMENT( "TIME_DATE_ANNOUN", "Time and Date Announcement", 53 ), ROAMING_ADDRESS_COMMAND( "ROAM_ADDR_CMD ", "Roaming Address Command", 54 ), diff --git a/src/module/decode/p25/reference/PDUType.java b/src/module/decode/p25/reference/PDUType.java index 20acb4ab..23f5da1e 100644 --- a/src/module/decode/p25/reference/PDUType.java +++ b/src/module/decode/p25/reference/PDUType.java @@ -3,18 +3,12 @@ package module.decode.p25.reference; public enum PDUType { /* Outbound */ - SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT( "SNDCP-ACTIVATE TDS CONTEXT ACCEPT", 0 ), - SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT( "SNDCP-DEACTIVATE TDS CONTEXT ACCEPT", 1 ), - SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST( "SNDCP-DEACTIVATE TDS CONTEXT REQUEST", 2 ), - SNDCP_ACTIVATE_TDS_CONTEXT_REJECT( "SNDCP-ACTIVATE TDS CONTEXT REJECT", 3 ), - SNDCP_RF_UNCONFIRMED_DATA( "SNDCP-RF UNCONFIRMED DATA", 4 ), - 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 ), + SN_ACTIVATE_TDS_CONTEXT_ACCEPT( "SN-ACTIVATE TDS CONTEXT ACCEPT", 0 ), + SN_DEACTIVATE_TDS_CONTEXT_ACCEPT( "SN-DEACTIVATE TDS CONTEXT ACCEPT", 1 ), + SN_DEACTIVATE_TDS_CONTEXT_REQUEST( "SN-DEACTIVATE TDS CONTEXT REQUEST", 2 ), + SN_ACTIVATE_TDS_CONTEXT_REJECT( "SN-ACTIVATE TDS CONTEXT REJECT", 3 ), + SN_RF_UNCONFIRMED_DATA( "SN-RF UNCONFIRMED DATA", 4 ), + SN_RF_CONFIRMED_DATA( "SN-RF CONFIRMED DATA", 5 ), /* Inbound */ SN_ACTIVATE_TDS_CONTEXT_REQUEST( "SN-ACTIVATE TDS CONTEXT REQUEST", 0 ), @@ -42,7 +36,7 @@ public enum PDUType public static PDUType fromValue( int value, boolean outbound ) { - if( outbound && 0 <= value && value <= 11 ) + if( outbound && 0 <= value && value <= 5 ) { return values()[ value ]; } diff --git a/src/module/decode/p25/reference/ServiceAccessPoint.java b/src/module/decode/p25/reference/ServiceAccessPoint.java index 1040aa7c..ea1c735d 100644 --- a/src/module/decode/p25/reference/ServiceAccessPoint.java +++ b/src/module/decode/p25/reference/ServiceAccessPoint.java @@ -2,7 +2,7 @@ package module.decode.p25.reference; public enum ServiceAccessPoint { - UNENCRYPTED_USER_DATA( "UNENCRYPTED USER DATA", 0 ), + UNENCRYPTED_USER_DATA( "USER DATA", 0 ), ENCRYPTED_USER_DATA( "ENCRYPTED USER DATA", 1 ), CIRCUIT_DATA( "CIRCUIT DATA", 2 ), CIRCUIT_DATA_CONTROL( "CIRCUIT DATA CONTROL", 3 ), diff --git a/src/module/decode/passport/PassportDecoder.java b/src/module/decode/passport/PassportDecoder.java index e125541d..46381d8d 100644 --- a/src/module/decode/passport/PassportDecoder.java +++ b/src/module/decode/passport/PassportDecoder.java @@ -106,7 +106,6 @@ public class PassportDecoder extends Decoder @Override public void reset() { - mPassportMessageFramer.reset(); } @Override diff --git a/src/module/decode/state/ChangeChannelTimeoutEvent.java b/src/module/decode/state/ChangeChannelTimeoutEvent.java deleted file mode 100644 index a2950171..00000000 --- a/src/module/decode/state/ChangeChannelTimeoutEvent.java +++ /dev/null @@ -1,29 +0,0 @@ -package module.decode.state; - -import controller.channel.Channel.ChannelType; - -/** - * Allows decoders to change the call timeout setting in the channel state - */ -public class ChangeChannelTimeoutEvent extends DecoderStateEvent -{ - private ChannelType mChannelType; - private long mCallTimeout; - - public ChangeChannelTimeoutEvent( Object source, ChannelType channelType, long timeout ) - { - super( source, Event.CHANGE_CALL_TIMEOUT, State.IDLE ); - mChannelType = channelType; - mCallTimeout = timeout; - } - - public ChannelType getChannelType() - { - return mChannelType; - } - - public long getCallTimeout() - { - return mCallTimeout; - } -} diff --git a/src/module/decode/state/ChannelList.java b/src/module/decode/state/ChannelList.java index 5cd8f9b8..be9f9a89 100644 --- a/src/module/decode/state/ChannelList.java +++ b/src/module/decode/state/ChannelList.java @@ -136,13 +136,10 @@ public class ChannelList extends JPanel implements ChannelEventListener public void setSelectedChannel( Channel channel ) { - synchronized( mDisplayedPanels ) + /* Send channel selection to each channel panel */ + for( ChannelCollectionPanel panel: mDisplayedPanels.values() ) { - /* Send channel selection to each channel panel */ - for( ChannelCollectionPanel panel: mDisplayedPanels.values() ) - { - panel.setSelectedChannel( channel ); - } + panel.setSelectedChannel( channel ); } } @@ -261,6 +258,20 @@ public class ChannelList extends JPanel implements ChannelEventListener idleMenu.add( new ColorSettingResetMenuItem( mSettingsManager, ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_IDLE ) ); + JMenu noTunerMenu = new JMenu( "No Tuner" ); + channelsMenu.add( noTunerMenu ); + + noTunerMenu.add( new ColorSettingMenuItem( mSettingsManager, + ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER ) ); + noTunerMenu.add( new ColorSettingResetMenuItem( mSettingsManager, + ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER ) ); + noTunerMenu.add( new JSeparator() ); + + noTunerMenu.add( new ColorSettingMenuItem( mSettingsManager, + ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER ) ); + noTunerMenu.add( new ColorSettingResetMenuItem( mSettingsManager, + ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER ) ); + JMenu labelMenu = new JMenu( "Labels" ); colorMenu.add( labelMenu ); diff --git a/src/module/decode/state/ChannelState.java b/src/module/decode/state/ChannelState.java index d75c904e..3b538349 100644 --- a/src/module/decode/state/ChannelState.java +++ b/src/module/decode/state/ChannelState.java @@ -1,6 +1,6 @@ /******************************************************************************* * SDR Trunk - * Copyright (C) 2014-2016 Dennis Sheirer + * Copyright (C) 2014,2015 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,14 +17,20 @@ ******************************************************************************/ package module.decode.state; -import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import module.Module; +import module.decode.config.DecodeConfiguration; +import module.decode.event.CallEvent; +import module.decode.event.ICallEventProvider; +import module.decode.state.DecoderStateEvent.Event; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sample.Listener; import audio.metadata.IMetadataProvider; import audio.metadata.Metadata; import audio.metadata.MetadataReset; @@ -34,13 +40,38 @@ import audio.squelch.SquelchState; import controller.ThreadPoolManager; import controller.ThreadPoolManager.ThreadType; import controller.channel.Channel.ChannelType; -import module.Module; -import module.decode.config.DecodeConfiguration; -import module.decode.event.CallEvent; -import module.decode.event.ICallEventProvider; -import module.decode.state.DecoderStateEvent.Event; -import sample.Listener; +/** + * Channel state tracks the overall state of all processing modules and decoders + * configured for the channel and provides squelch control events. + * + * Primary channel state provides a state machine for tracking decode events + * during a voice or data call through it's life cycle of CALL/DATA, FADE, END, + * and then back to an IDLE state. This class also provides squelch control + * to support any audio processing. + * + * State Descriptions: + * IDLE No voice or data call activity + * CALL/DATA A voice or data call has started, or is continuing + * CONTROL Control channel + * FADE The phase after a voice or data call when either an explicit + * call end has been received, or when no new signalling updates + * have been received, and the fade timer has expired. This phase + * allows for gui updates to signal to the user that the call is + * ended, while continuing to display the call details for the user + * END The phase after the voice or data call FADE, when the call is + * now over, and the gui should be reset to IDLE. + * + * When a call is started, invoke mTimerService.startFadeTimer() to start the + * call fade monitoring timer process that will automatically signal a call end + * once no more call signalling has been detected after the fade timeout has + * occurred, by invoking the fade() method. + * + * When a call ends, invoke the mTimerService.stopFadeTimer() and then invoke + * the mTimerService.startResetTimer(). Once the reset timeout occurs, + * the reset() method will be called, allowing the channel state to perform + * any reset actions, like clearing status and id labels on any view panels. + */ public class ChannelState extends Module implements ICallEventProvider, IChangedAttributeProvider, IDecoderStateEventListener, IDecoderStateEventProvider, IMetadataProvider, ISquelchStateProvider @@ -52,6 +83,8 @@ public class ChannelState extends Module implements ICallEventProvider, public static final long RESET_TIMEOUT_DELAY = 2000; private State mState = State.IDLE; + private long mTrafficChannelTimeout = + DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS; private Listener mCallEventListener; private Listener mChangedAttributeListener; @@ -64,15 +97,12 @@ public class ChannelState extends Module implements ICallEventProvider, private boolean mSquelchLocked = false; private boolean mSelected = false; - private long mStandardChannelFadeTimeout = FADE_TIMEOUT_DELAY; - private long mTrafficChannelFadeTimeout = - DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000; private long mFadeTimeout; - private long mEndTimeout; + private long mResetTimeout; private StateMonitor mStateMonitor = new StateMonitor(); private AtomicBoolean mMonitoring = new AtomicBoolean(); - private ScheduledFuture mStateMonitorFuture; + private ScheduledFuture mRunningMonitor; private ThreadPoolManager mThreadPoolManager; private ChannelType mChannelType; @@ -80,97 +110,32 @@ public class ChannelState extends Module implements ICallEventProvider, private TrafficChannelStatusListener mTrafficChannelStatusListener; private CallEvent mTrafficChannelCallEvent; - /** - * Channel state tracks the overall state of all processing modules and decoders - * configured for the channel and provides squelch control 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 ) { mThreadPoolManager = manager; mChannelType = channelType; } - /** - * Resets this channel state and prepares it for reuse. - */ @Override public void reset() { - broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) ); - broadcast( new MetadataReset() ); - - mState = State.IDLE; - broadcast( ChangedAttribute.CHANNEL_STATE ); } @Override 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 ); - } - } - } } @Override public void stop() { 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() { + stopMonitor(); + mCallEventListener = null; mChangedAttributeListener = null; mDecoderStateListener = null; @@ -179,34 +144,9 @@ public class ChannelState extends Module implements ICallEventProvider, mStateMonitor = null; } - private boolean isStandardChannel() - { - return mChannelType == ChannelType.STANDARD; - } - - private boolean isTrafficChannel() - { - return mChannelType == ChannelType.TRAFFIC; - } - - public void setStandardChannelTimeout( long milliseconds ) - { - mStandardChannelFadeTimeout = milliseconds; - - if( mChannelType == ChannelType.STANDARD ) - { - mFadeTimeout = mStandardChannelFadeTimeout; - } - } - public void setTrafficChannelTimeout( long milliseconds ) { - mTrafficChannelFadeTimeout = milliseconds; - - if( mChannelType == ChannelType.TRAFFIC ) - { - mFadeTimeout = mTrafficChannelFadeTimeout; - } + mTrafficChannelTimeout = milliseconds; } public void setSelected( boolean selected ) @@ -224,18 +164,71 @@ public class ChannelState extends Module implements ICallEventProvider, return mState; } + private boolean isIdleOrEndingState() + { + return mState == State.FADE || + mState == State.END || + mState == State.IDLE; + } + + + /** + * Starts the state monitor running when we're not in IDLE state. Calls to + * start an already started monitor are ignored. + */ + private void startMonitor() + { + if( mMonitoring.compareAndSet( false, true ) ) + { + if( mRunningMonitor == null && mStateMonitor != null ) + { + mRunningMonitor = mThreadPoolManager.scheduleFixedRate( + ThreadType.DECODER, mStateMonitor, 20, TimeUnit.MILLISECONDS ); + } + else + { + throw new RuntimeException( "Channel state monitor's scheduled " + + "future pointer was not null" ); + } + } + } + + /** + * Stops the state monitor when we're in an IDLE state. Calls to stop an + * already stopped monitor are ignored. + */ + private void stopMonitor() + { + if( mMonitoring.compareAndSet( true, false ) ) + { + if( mRunningMonitor != null ) + { + mThreadPoolManager.cancel( mRunningMonitor ); + } + + mRunningMonitor = null; + } + } + /** * Updates the fade timeout threshold to the current time plus delay */ private void updateFadeTimeout() { - if( isTrafficChannel() ) + /* Standard channel uses the FADE_TIMEOUT_DELAY and traffic channels + * use the TRAFFIC_FADE_TIMEOUT_DELAY. Some traffic channels (e.g. MPT) + * don't have signaling on the traffic channel that can be used to + * update the fade timeout, so we set the timeout value larger to ensure + * the channel doesn't fade before the end of the call. */ + long current = System.currentTimeMillis(); + + long timeout = current + + ( mChannelType == ChannelType.STANDARD ? FADE_TIMEOUT_DELAY : + mTrafficChannelTimeout ); + + if( timeout > mFadeTimeout ) { - mFadeTimeout = System.currentTimeMillis() + mTrafficChannelFadeTimeout; - } - else - { - mFadeTimeout = System.currentTimeMillis() + mStandardChannelFadeTimeout; + mFadeTimeout = timeout; } } @@ -244,13 +237,13 @@ public class ChannelState extends Module implements ICallEventProvider, */ private void updateResetTimeout() { - if( isTrafficChannel() ) + if( mChannelType == ChannelType.TRAFFIC ) { - mEndTimeout = System.currentTimeMillis(); + mResetTimeout = System.currentTimeMillis(); } else { - mEndTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY; + mResetTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY; } } @@ -292,120 +285,120 @@ public class ChannelState extends Module implements ICallEventProvider, */ protected void setState( State state ) { - synchronized( mState ) + if( mState == state ) { - if( state == mState ) + switch( state ) { - if( State.CALL_STATES.contains( state ) ) - { + case CALL: + case CONTROL: + case DATA: + case ENCRYPTED: updateFadeTimeout(); - } + break; + default: + break; } - else if( state != mState && mState.canChangeTo( state ) ) + } + else if( mState.canChangeTo( state ) ) + { + switch( state ) { - switch( state ) - { - case CONTROL: - //Don't allow traffic channels to be control channels, - //otherwise they can't transition to call tear down - if( isStandardChannel() ) - { - broadcast( SquelchState.SQUELCH ); - updateFadeTimeout(); - mState = state; - broadcast( ChangedAttribute.CHANNEL_STATE ); - } - break; - case DATA: - case ENCRYPTED: + case CONTROL: + //Don't allow traffic channels to be control channels, + //otherwise they won't get call tear down + if( mChannelType != ChannelType.TRAFFIC ) + { broadcast( SquelchState.SQUELCH ); updateFadeTimeout(); + startMonitor(); mState = state; broadcast( ChangedAttribute.CHANNEL_STATE ); - break; - case CALL: - broadcast( SquelchState.UNSQUELCH ); - updateFadeTimeout(); - mState = state; - broadcast( ChangedAttribute.CHANNEL_STATE ); - break; - case FADE: - processFadeState(); - break; - case IDLE: - processIdleState(); - break; - case TEARDOWN: - processTeardownState(); - break; - case RESET: - mState = State.IDLE; - break; - default: - break; + } + break; + case DATA: + case ENCRYPTED: + broadcast( SquelchState.SQUELCH ); + updateFadeTimeout(); + startMonitor(); + mState = state; + broadcast( ChangedAttribute.CHANNEL_STATE ); + break; + case CALL: + broadcast( SquelchState.UNSQUELCH ); + updateFadeTimeout(); + startMonitor(); + mState = state; + broadcast( ChangedAttribute.CHANNEL_STATE ); + break; + case FADE: + broadcast( SquelchState.SQUELCH ); + updateResetTimeout(); + startMonitor(); + mState = state; + broadcast( ChangedAttribute.CHANNEL_STATE ); + break; + case END: + broadcast( SquelchState.SQUELCH ); + broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) ); + broadcast( new MetadataReset() ); + mState = state; + broadcast( ChangedAttribute.CHANNEL_STATE ); + break; + case IDLE: + broadcast( SquelchState.SQUELCH ); + stopMonitor(); + + if( mChannelType == ChannelType.TRAFFIC ) + { + if( mTrafficChannelStatusListener != null ) + { + mTrafficChannelStatusListener.callEnd(); + mTrafficChannelStatusListener = null; + } + + if( mTrafficChannelCallEvent != null ) + { + mTrafficChannelCallEvent.end(); + + broadcast( mTrafficChannelCallEvent ); + mTrafficChannelCallEvent = null; + } + } + + mState = state; + broadcast( ChangedAttribute.CHANNEL_STATE ); + break; + default: + break; + } + } + } + + public class StateMonitor implements Runnable + { + @Override + public void run() + { + if( getState() == State.FADE ) + { + if( mResetTimeout <= System.currentTimeMillis() ) + { + setState( State.END ); } } + else if( getState() == State.END ) + { + setState( State.IDLE ); + } else { -// mLog.debug( "Can't change from [" + mState + "] to [" + state + "]" ); + if( mFadeTimeout <= System.currentTimeMillis() ) + { + setState( State.FADE ); + } } } } - - /** - * Sets the state and processes related actions - */ - private void processFadeState() - { - updateResetTimeout(); - mState = State.FADE; - - broadcast( SquelchState.SQUELCH ); - broadcast( ChangedAttribute.CHANNEL_STATE ); - } - - private void processIdleState() - { - broadcast( SquelchState.SQUELCH ); - - if( mState == State.FADE ) - { - broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) ); - broadcast( new MetadataReset() ); - } - - mState = State.IDLE; - - broadcast( ChangedAttribute.CHANNEL_STATE ); - } - - private void processTeardownState() - { - broadcast( SquelchState.SQUELCH ); - - mState = State.TEARDOWN; - - broadcast( ChangedAttribute.CHANNEL_STATE ); - - if( mTrafficChannelCallEvent != null ) - { - mTrafficChannelCallEvent.end(); - - broadcast( mTrafficChannelCallEvent ); - mTrafficChannelCallEvent = null; - } - - if( mTrafficChannelStatusListener != null ) - { - mTrafficChannelStatusListener.callEnd(); - mTrafficChannelStatusListener = null; - } - else - { - mLog.error( "Traffic channel status listener was null - couldn't " - + "teardown the channel"); - } - } /** * Broadcasts the call event to the registered listener @@ -597,95 +590,35 @@ public class ChannelState extends Module implements ICallEventProvider, broadcast( SquelchState.UNSQUELCH ); mSquelchLocked = true; break; - case CHANGE_CALL_TIMEOUT: - if( event instanceof ChangeChannelTimeoutEvent ) - { - ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent)event; - - if( timeout.getChannelType() == ChannelType.STANDARD ) - { - setStandardChannelTimeout( timeout.getCallTimeout() ); - } - else - { - setTrafficChannelTimeout( timeout.getCallTimeout() ); - } - } case CONTINUATION: + if( isIdleOrEndingState() ) + { + setState( event.getState() ); + } + else + { + updateFadeTimeout(); + } + break; case DECODE: - case START: - if( State.CALL_STATES.contains( event.getState() ) ) + if( getState() == State.IDLE ) { setState( event.getState() ); } break; case END: - if( isTrafficChannel() ) - { - setState( State.TEARDOWN ); - } - else - { - setState( State.FADE ); - } + setState( State.FADE ); break; case RESET: /* Channel State does not respond to reset events */ break; + case START: + setState( event.getState() ); + break; default: break; } } } } - - /** - * Monitors decoder state events to automatically transition the channel - * state to IDLE (standard channel) or to TEARDOWN (traffic channel) when - * decoding stops or the monitored channel returns to a no signal state. - * - * Provides a FADE transition state to allow for momentary decoding dropouts - * and to allow the user access to call details for a fade period upon - * call end. - */ - public class StateMonitor implements Runnable - { - @Override - public void run() - { - try - { - synchronized( mState ) - { - if( State.CALL_STATES.contains( mState ) && - mFadeTimeout <= System.currentTimeMillis() ) - { - mState = State.FADE; - processFadeState(); - } - else if( mState == State.FADE && - mEndTimeout <= System.currentTimeMillis() ) - { - if( isTrafficChannel() ) - { - processTeardownState(); - } - else - { - processIdleState(); - } - } - } - } - catch( Exception e ) - { - mLog.error( "Exception thrown while state monitor is running " + - "- state [" + getState() + - "] current [" + System.currentTimeMillis() + - "] mResetTimeout [" + mEndTimeout + - "] mFadeTimeout [" + mFadeTimeout + - "]", e ); - } - } - } } diff --git a/src/module/decode/state/DecoderStateEvent.java b/src/module/decode/state/DecoderStateEvent.java index fa60b7d8..19623bbc 100644 --- a/src/module/decode/state/DecoderStateEvent.java +++ b/src/module/decode/state/DecoderStateEvent.java @@ -56,7 +56,6 @@ public class DecoderStateEvent public enum Event { ALWAYS_UNSQUELCH, - CHANGE_CALL_TIMEOUT, CONTINUATION, DECODE, END, diff --git a/src/module/decode/state/State.java b/src/module/decode/state/State.java index 141c5ea6..2820429d 100644 --- a/src/module/decode/state/State.java +++ b/src/module/decode/state/State.java @@ -1,10 +1,5 @@ package module.decode.state; -import java.util.EnumSet; - -/** - * Details the set of states for a channel and the allowable transition states - */ public enum State { IDLE( "IDLE" ) @@ -12,8 +7,7 @@ public enum State @Override public boolean canChangeTo( State state ) { - return state != TEARDOWN && - state != RESET; + return true; } }, CALL( "CALL" ) @@ -21,11 +15,11 @@ public enum State @Override public boolean canChangeTo( State state ) { - return state == CONTROL || + return state == CALL || + state == CONTROL || state == DATA || state == ENCRYPTED || - state == FADE || - state == TEARDOWN; + state == FADE; } }, DATA( "DATA" ) @@ -35,9 +29,9 @@ public enum State { return state == CALL || state == CONTROL || - state == ENCRYPTED || - state == FADE || - state == TEARDOWN; + state == DATA || + state == State.ENCRYPTED || + state == FADE; } }, ENCRYPTED( "ENCRYPTED" ) @@ -45,7 +39,7 @@ public enum State @Override public boolean canChangeTo( State state ) { - return state == FADE || state == TEARDOWN; + return state == FADE; } }, CONTROL( "CONTROL" ) @@ -53,8 +47,7 @@ public enum State @Override public boolean canChangeTo( State state ) { - return state == IDLE || - state == FADE; + return true; } }, FADE( "FADE" ) @@ -62,36 +55,20 @@ public enum State @Override public boolean canChangeTo( State state ) { - return state != FADE && - state != RESET; - } + return state != FADE; //All states except fade allowed + } }, - RESET( "RESET" ) + END( "END" ) { - @Override - public boolean canChangeTo(State state) - { - return state == IDLE; - } - }, - - TEARDOWN( "TEARDOWN" ) - { - @Override - public boolean canChangeTo(State state) - { - return state == RESET; - } - }; + @Override + public boolean canChangeTo( State state ) + { + return state != FADE; //Fade is only disallowed state + } + }; private String mDisplayValue; - public static final EnumSet CALL_STATES = - EnumSet.of( CALL, CONTROL, DATA, ENCRYPTED ); - - public static final EnumSet IDLE_STATES = - EnumSet.of( IDLE, FADE ); - private State( String displayValue ) { mDisplayValue = displayValue; diff --git a/src/org/jdesktop/swingx/JXMapViewer.java b/src/org/jdesktop/swingx/JXMapViewer.java index e0bd81f9..81ffc083 100644 --- a/src/org/jdesktop/swingx/JXMapViewer.java +++ b/src/org/jdesktop/swingx/JXMapViewer.java @@ -22,9 +22,10 @@ import java.awt.image.BufferedImage; import java.beans.DesignMode; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.net.URL; import java.util.Set; -import javax.swing.ImageIcon; +import javax.imageio.ImageIO; import javax.swing.JPanel; import org.jdesktop.swingx.mapviewer.GeoPosition; @@ -125,13 +126,13 @@ public class JXMapViewer extends JPanel implements DesignMode // make a dummy loading image try { - ImageIcon imageIcon = new ImageIcon( "images/loading.png" ); - this.setLoadingImage( imageIcon.getImage() ); + URL url = JXMapViewer.class.getResource("mapviewer/resources/loading.png"); + this.setLoadingImage(ImageIO.read(url)); } catch (Throwable ex) { - mLog.error( "could not load 'loading.png'" ); + mLog.error( "JXMapViewer could not load 'loading.png'" ); BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = img.createGraphics(); diff --git a/src/properties/SystemProperties.java b/src/properties/SystemProperties.java index a36a171b..bf4b33f4 100644 --- a/src/properties/SystemProperties.java +++ b/src/properties/SystemProperties.java @@ -82,13 +82,18 @@ public class SystemProperties { out = new FileOutputStream( propsPath.toString() ); - String comments = "SDRTrunk - SDR Trunking Decoder Application Settings"; + String comments = + "SDRTrunk - SDR Trunking Decoder Application Settings"; mProperties.store( out, comments ); + + mLog.info( "SystemProperties - saved current properties [" + + propsPath.toString() + "]" ); } catch( Exception e ) { - mLog.error( "exception while saving application properties", e ); + mLog.error( "SystemProperties - exception while saving " + + "application properties", e ); } finally { @@ -141,7 +146,8 @@ public class SystemProperties } catch ( IOException e ) { - mLog.error( "exception while creating app folder [" + folder + "]", e ); + mLog.error( "SystemProperties - exception while creating " + + "app folder [" + folder + "]", e ); } } @@ -152,11 +158,11 @@ public class SystemProperties { if( mPropertiesPath == null ) { - mLog.info( "no properties file loaded - using defaults" ); + mLog.info( "SystemProperties - no properties file loaded - using defaults" ); } else { - mLog.info( "application properties loaded [" + mPropertiesPath.toString() + "]" ); + mLog.info( "SystemProperties - application properties loaded [" + mPropertiesPath.toString() + "]" ); } } @@ -178,7 +184,7 @@ public class SystemProperties } catch ( FileNotFoundException e ) { - mLog.error( "exception while opening inputstream on " + + mLog.error( "SDRTrunk - exception while opening inputstream on " + "application properties file", e ); } @@ -190,7 +196,7 @@ public class SystemProperties } catch ( IOException e ) { - mLog.error( "exception while loading properties " + + mLog.error( "SDRTrunk - exception while loading properties " + "inputstream into SystemProperties", e ); } finally @@ -206,7 +212,8 @@ public class SystemProperties } } - mLog.info( "loaded [" + propertiesPath.toString() + "]" ); + mLog.info( "SystemProperties - loaded [" + + propertiesPath.toString() + "]" ); } /** diff --git a/src/settings/ColorSetting.java b/src/settings/ColorSetting.java index d0cb3e1f..6040fce4 100644 --- a/src/settings/ColorSetting.java +++ b/src/settings/ColorSetting.java @@ -123,7 +123,7 @@ public class ColorSetting extends Setting "Control Gradient Middle", "Channel Control State Gradient Middle" ), CHANNEL_STATE_GRADIENT_TOP_DATA( Color.BLACK, "Data Gradient Top", "Channel Data State Gradient Top" ), - CHANNEL_STATE_GRADIENT_MIDDLE_DATA( new Color( 0xCC00CC ), + CHANNEL_STATE_GRADIENT_MIDDLE_DATA( Color.BLUE, "Data Gradient Middle", "Channel Data State Gradient Middle" ), CHANNEL_STATE_GRADIENT_TOP_FADE( Color.BLACK, "Fade Gradient Top", "Channel Fade State Gradient Top" ), diff --git a/src/settings/SettingsManager.java b/src/settings/SettingsManager.java index df430aa6..ee25bd78 100644 --- a/src/settings/SettingsManager.java +++ b/src/settings/SettingsManager.java @@ -437,7 +437,8 @@ public class SettingsManager } /* Something happened ... the above should always return an icon */ - mLog.error( "couldn't return an icon named [" + name + "] of heigh [" + height + "]" ); + mLog.error( "SettingsManager - couldn't return an icon named [" + + name + "] of heigh [" + height + "]" ); } return null; @@ -635,7 +636,8 @@ public class SettingsManager return r820TConfig; default: - throw new IllegalArgumentException( "tuner type is unrecognized [" + + throw new IllegalArgumentException( "TunerConfiguration" + + "Directory - tuner type is unrecognized [" + type.toString() + "]" ); } } @@ -709,7 +711,8 @@ public class SettingsManager } catch( Exception e ) { - mLog.error( "couldn't create file to save settings [" + filePath.toString() + "]", e ); + mLog.error( "SettingsManager - couldn't create file to save " + + "settings [" + filePath.toString() + "]", e ); } OutputStream out = null; @@ -730,12 +733,14 @@ public class SettingsManager } catch ( JAXBException e ) { - mLog.error( "jaxb exception while saving settings", e ); + mLog.error( "SettingsManager - jaxb exception while saving " + + "settings", e ); } } catch ( Exception e ) { - mLog.error( "couldn't open outputstream to save settings [" + filePath.toString() + "]" ); + mLog.error( "SettingsManager - coulcn't open outputstream to " + + "save settings [" + filePath.toString() + "]" ); } finally { @@ -761,7 +766,8 @@ public class SettingsManager { if( Files.exists( settingsPath ) ) { - mLog.info( "loading settings file [" + settingsPath.toString() + "]" ); + mLog.info( "SettingsManager - loading settings file [" + + settingsPath.toString() + "]" ); JAXBContext context = null; @@ -781,13 +787,13 @@ public class SettingsManager } catch ( JAXBException e ) { - mLog.error( "jaxb exception while loading " + + mLog.error( "SettingsManager - jaxb exception while loading " + "settings", e ); } } catch ( Exception e ) { - mLog.error( "coulcn't open inputstream to " + + mLog.error( "SettingsManager - coulcn't open inputstream to " + "load settings [" + settingsPath.toString() + "]", e ); } finally @@ -800,7 +806,7 @@ public class SettingsManager } catch ( IOException e ) { - mLog.error( "exception while closing " + + mLog.error( "SettingsManager - exception while closing " + "the settings file inputstream reader", e ); } } @@ -808,7 +814,8 @@ public class SettingsManager } else { - mLog.info( "settings does not exist [" + settingsPath.toString() + "]" ); + mLog.info( "SettingsManager - settings does not exist [" + + settingsPath.toString() + "]" ); } if( mSettings == null ) diff --git a/src/source/mixer/MixerChannelConfiguration.java b/src/source/mixer/MixerChannelConfiguration.java index b8520bcc..0be3b358 100644 --- a/src/source/mixer/MixerChannelConfiguration.java +++ b/src/source/mixer/MixerChannelConfiguration.java @@ -23,14 +23,6 @@ public class MixerChannelConfiguration return mMixerChannel; } - public boolean matches( String mixer, String channels ) - { - return mixer != null && - channels != null && - mMixer.getMixerInfo().getName().contentEquals( mixer ) && - mMixerChannel.name().contentEquals( channels ); - } - public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/src/source/tuner/TunerChannel.java b/src/source/tuner/TunerChannel.java index 625d7a98..7ff6e32d 100644 --- a/src/source/tuner/TunerChannel.java +++ b/src/source/tuner/TunerChannel.java @@ -97,13 +97,9 @@ public class TunerChannel implements Comparable } @Override - public int compareTo( TunerChannel otherTunerChannel ) + public int compareTo( TunerChannel otherLock ) { - if( this.equals( otherTunerChannel ) ) - { - return 0; - } - else if( mFrequency < otherTunerChannel.getFrequency() ) + if( mFrequency < otherLock.getFrequency() ) { return -1; } diff --git a/src/spectrum/DFTProcessor.java b/src/spectrum/DFTProcessor.java index 207d9eb9..7e844301 100644 --- a/src/spectrum/DFTProcessor.java +++ b/src/spectrum/DFTProcessor.java @@ -30,7 +30,6 @@ import org.jtransforms.fft.FloatFFT_1D; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import properties.SystemProperties; import sample.Buffer; import sample.Listener; import sample.SampleType; @@ -62,8 +61,6 @@ public class DFTProcessor implements Listener, private ScheduledExecutorService mScheduler = Executors .newScheduledThreadPool( 1, new NamingThreadFactory( "spectrum dft" ) ); - public static final String FRAME_RATE_PROPERTY = "spectral.display.frame.rate"; - private DFTSize mDFTSize = DFTSize.FFT04096; private DFTSize mNewDFTSize = DFTSize.FFT04096; @@ -92,16 +89,7 @@ public class DFTProcessor implements Listener, public DFTProcessor( SampleType sampleType ) { setSampleType( sampleType ); - - loadSettings(); - } - - private void loadSettings() - { - int frameRate = SystemProperties.getInstance() - .get( DFTProcessor.FRAME_RATE_PROPERTY, 20 ); - - setFrameRate( frameRate ); + setFrameRate( 20 ); } public void dispose() @@ -183,10 +171,6 @@ public class DFTProcessor implements Listener, mFrameRate = framesPerSecond; - SystemProperties properties = SystemProperties.getInstance(); - properties.set( FRAME_RATE_PROPERTY, String.valueOf( mFrameRate ) ); - properties.save(); - calculateConsumptionRate(); restart(); diff --git a/src/spectrum/SpectralDisplayPanel.java b/src/spectrum/SpectralDisplayPanel.java index f8d1acd9..9d76b912 100644 --- a/src/spectrum/SpectralDisplayPanel.java +++ b/src/spectrum/SpectralDisplayPanel.java @@ -54,7 +54,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import playlist.PlaylistManager; -import properties.SystemProperties; import sample.Listener; import sample.SampleType; import sample.complex.ComplexBuffer; @@ -100,10 +99,8 @@ public class SpectralDisplayPanel extends JPanel private static DecimalFormat sCURSOR_FORMAT = new DecimalFormat( "000.00000" ); - public static final String FFT_SIZE_PROPERTY = "spectral.display.dft.size"; public static final int NO_ZOOM = 0; public static final int MAX_ZOOM = 6; - private DFTSize mDFTSize = DFTSize.FFT04096; private int mZoom = 0; @@ -158,36 +155,6 @@ public class SpectralDisplayPanel extends JPanel } init(); - - loadSettings(); - } - - private void loadSettings() - { - SystemProperties properties = SystemProperties.getInstance(); - - String rawSize = properties.get( FFT_SIZE_PROPERTY, DFTSize.FFT04096.name() ); - - DFTSize size = null; - - if( rawSize != null ) - { - try - { - size = DFTSize.valueOf( rawSize ); - } - catch( Exception e ) - { - //Do nothing, we couldn't parse the stored value - } - } - - if( size == null ) - { - size = DFTSize.FFT04096; - } - - setDFTSize( size ); } public void dispose() @@ -232,10 +199,6 @@ public class SpectralDisplayPanel extends JPanel mDFTProcessor.setDFTSize( size ); mOverlayPanel.setDFTSize( size ); mDFTSize = size; - - SystemProperties properties = SystemProperties.getInstance(); - properties.set( FFT_SIZE_PROPERTY, size.name() ); - properties.save(); setZoom( 0, 0, 0 ); } diff --git a/src/spectrum/WaterfallPanel.java b/src/spectrum/WaterfallPanel.java index 336e8c6c..2aa7e37a 100644 --- a/src/spectrum/WaterfallPanel.java +++ b/src/spectrum/WaterfallPanel.java @@ -121,8 +121,6 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener, mMemoryImageSource.setAnimated( true ); mWaterfallImage = createImage( mMemoryImageSource ); - - repaint(); } /**