Compare commits

...

18 Commits

Author SHA1 Message Date
denny
3e8db5b216 Updates version 0.2.0 build to beta.3 2016-01-23 14:41:01 -05:00
denny
f0c11f2590 Cleanup. 2016-01-23 14:39:52 -05:00
denny
99a5fc9bcf Closes issue #46. Removes the recordings tree node from the
configuration controller until such time that it can be fully
implemented.  Existing skeleton framework was a placeholder that did not
materialize.
2016-01-23 14:38:02 -05:00
denny
f5fadc5004 Stores selected audio output mixer name and channels (mono/stereo) to
system properties so settings are loaded on startup.
2016-01-23 14:28:21 -05:00
denny
c922029233 Adds capability to persist spectral display fft size and frame rate
between sessions.
2016-01-23 14:07:01 -05:00
denny
94d85f636d Resolves Audio issues (underwater/overlapping audio buffers).
Redesigned audio manager handling of audio outputs to ensure that only
one channel's audio can be routed to an audio output at a time.

Changed audio outputs to use the shared thread pool manager for
scheduled tasks, instead of creating a thread pool for each audio
output.
2016-01-23 10:19:47 -05:00
denny
1db6779dec Audio outputs - added exception handler to catch any errors that occur
while the buffer processor thread is running.

Added check for 0 space available in data line buffers to avoid trying
to write a zero-length audio chunk to the data line.

Added thread sleep for 100 milliseconds when we detect that the data
line buffer is full, to allow time for the data line to drain before we
check it again.  Hopefully this should minimize the chattering during
playback when the code was repeatedly hitting the available() method to
check for space availability.
2016-01-22 16:35:18 -05:00
denny
676d26f4d8 Updated default color for data channel state 2016-01-22 16:31:59 -05:00
denny
abca4ebbd7 Updated to use concurrent containers to avoid concurrent modification
exceptions.
2016-01-22 16:30:58 -05:00
denny
cffc6b6eed Updates TDMA synchronization broadcast message to ICD 2016-01-22 10:39:09 -05:00
Denny Sheirer
39ebec78a8 Updates channel type description for TDMA channels. 2016-01-19 04:06:20 +00:00
Denny Sheirer
4066a4b3c3 Adds TDMA channel IdentifierUpdate message parsing. 2016-01-19 04:00:18 +00:00
denny
c12f535f3c Issue #47 - work in progress 2016-01-18 04:53:49 -05:00
denny
810bb7384e Closes #42 - ltr standard call state was not accurately tracking call
state. 

Note: a related issue described in ticket #42 involves issues with the
audio output module.  A new ticket will be opened with that issue.
2016-01-17 19:42:13 -05:00
denny
0c5692cb1e Closes issue #44. Creates /sdrtrunk folder within the .zip and .tar.gz
output products so that upon extracting the contents they are contained
within the /sdrtrunk folder.
2016-01-15 10:37:45 -05:00
denny
663378ad20 Logging cleanup 2016-01-15 09:50:10 -05:00
denny
5807bb191e Resolved issue with loading 'loading.png' image file for the map 2016-01-15 09:49:43 -05:00
denny
ab9ef2d8cc Updated build script internal variable naming. 2016-01-01 10:20:10 -05:00
42 changed files with 1934 additions and 1644 deletions

View File

@@ -6,114 +6,116 @@
<!-- version properties -->
<property name="major" value="0"/>
<property name="minor" value="2"/>
<property name="build" value="0"/>
<property name="release" value="beta.2"/>
<property name="patch" value="0"/>
<property name="release" value="beta.3"/>
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}-${release}"/>
<!-- Build properties -->
<property name="classes" location="../classes"/>
<property name="configs" location="../config"/>
<property name="images" location="../images"/>
<property name="libs" location="../imports"/>
<property name="license" location="../license"/>
<property name="product" location="../product"/>
<property name="scripts" location="../scripts"/>
<property name="src" location="../src"/>
<property name="repo" location=".."/>
<property name="source.code" location="${repo}/src"/>
<property name="source.configs" location="${repo}/config"/>
<property name="source.images" location="${repo}/images"/>
<property name="source.libs" location="${repo}/imports"/>
<property name="source.license" location="${repo}/license"/>
<property name="source.scripts" location="${repo}/scripts"/>
<property name="output" location="${repo}/product"/>
<property name="output.classes" location="${repo}/classes"/>
<property name="output.folder" location="${output}/sdrtrunk"/>
<property name="output.config" location="${output.folder}/config"/>
<property name="output.images" location="${output.folder}/images"/>
<property name="output.libs" location="${output.folder}/libs"/>
<path id="classpath">
<fileset dir="${libs}" includes="*.jar" />
<fileset dir="${source.libs}" includes="*.jar" />
</path>
<target name="init">
<!-- Create the classes folder to contain the compiled java classes -->
<mkdir dir="${classes}"/>
<mkdir dir="${output.classes}"/>
<!-- Create version file -->
<echo message="${major}.${minor}.${build}-${release}" file="${classes}/sdrtrunk-version"/>
<echo message="${major}.${minor}.${patch}-${release}" file="${output.classes}/sdrtrunk-version"/>
</target>
<target name="compile" depends="clean,init"
description="Compile java classes" >
<!-- Compile the java code from ${src} into ${classes} -->
<javac srcdir="${src}"
destdir="${classes}"
classpathref="classpath"
includeantruntime="false" />
<target name="compile" depends="clean,init" description="Compile java classes" >
<javac srcdir="${source.code}" destdir="${output.classes}" classpathref="classpath" includeantruntime="false" />
</target>
<target name="SDRTrunk" depends="compile" description="Create SDRTrunk application" >
<!-- Create the distribution directory -->
<mkdir dir="${product}"/>
<mkdir dir="${output.folder}"/>
<!-- Create jar file -->
<jar jarfile="${product}/SDRTrunk.jar" basedir="${classes}">
<jar jarfile="${output.folder}/sdrtrunk.jar" basedir="${output.classes}">
<manifest>
<attribute name="Title" value="sdrtrunk"/>
<attribute name="Main-Class" value="gui.SDRTrunk"/>
<attribute name="Version" value="${major}.${minor}.${patch}-${release}"/>
</manifest>
<fileset dir="${configs}">
<fileset dir="${source.configs}">
<exclude name="*.rules"/>
</fileset>
<fileset dir="${license}">
<fileset dir="${source.license}">
<include name="license.txt"/>
</fileset>
</jar>
<!-- Copy third-party libraries -->
<mkdir dir="${product}/libs"/>
<copy todir="${product}/libs">
<fileset dir="${libs}" includes="*.jar" />
<mkdir dir="${output.libs}"/>
<copy todir="${output.libs}">
<fileset dir="${source.libs}" includes="*.jar" />
</copy>
<!-- Copy config files -->
<mkdir dir="${product}/config"/>
<mkdir dir="${output.config}"/>
<copy todir="${product}/config">
<fileset dir="${configs}">
<copy todir="${output.config}">
<fileset dir="${source.configs}">
<include name="*.rules"/>
</fileset>
</copy>
<!-- Copy image files -->
<mkdir dir="${product}/images"/>
<copy todir="${product}/images">
<fileset dir="${images}" />
<mkdir dir="${output.images}"/>
<copy todir="${output.images}">
<fileset dir="${source.images}" />
</copy>
<!-- Copy license file -->
<copy todir="${product}">
<fileset dir="${license}" />
<copy todir="${output.folder}">
<fileset dir="${source.license}">
<include name="license.txt"/>
</fileset>
</copy>
<!-- Copy program scripts -->
<copy todir="${product}">
<fileset dir="${scripts}" />
<copy todir="${output.folder}">
<fileset dir="${source.scripts}" />
</copy>
<!-- Set linux permissions on start script -->
<chmod file="${product}/run_sdrtrunk_linux.sh" perm="777" />
<tstamp prefix="build" />
<property name="filename" value="sdrtrunk_${build.DSTAMP}_${build.TSTAMP}_${major}.${minor}.${build}-${release}"/>
<!-- Bundle the files into .zip -->
<zip destfile="${product}/${filename}.zip" basedir="${product}" />
<zip destfile="${output}/${filename}.zip" basedir="${output}" />
<!-- Remove linux start script before tar task -->
<delete file="${output.folder}/run_sdrtrunk_linux.sh"/>
<!-- Bundle files into .tar.gz and copy/set permissions on linux start script -->
<tar destfile="${output}/${filename}.tar.gz" compression="gzip">
<tarfileset dir="${output.folder}" prefix="sdrtrunk" />
<tarfileset dir="${source.scripts}" prefix="sdrtrunk" filemode="777">
<include name="run_sdrtrunk_linux.sh"/>
</tarfileset>
</tar>
<!-- Bundle the files into .tar.gz to preserve linux script permissions -->
<tar destfile="${product}/${filename}.tar.gz" compression="gzip">
<tarfileset dir="${product}" filemode="777">
<include name="*.sh"/>
</tarfileset>
<tarfileset dir="${product}">
<exclude name="*.sh"/>
<exclude name="${filename}.zip"/>
</tarfileset>
</tar>
<delete dir="${output.folder}"/>
</target>
<target name="clean" description="Clean previous build artifacts" >
<delete dir="${classes}"/>
<delete dir="${product}"/>
<delete dir="${output.classes}"/>
<delete dir="${output}"/>
</target>
</project>

View File

@@ -1 +1 @@
java -XX:+UseG1GC -cp "SDRTrunk.jar;*;libs/*;config/*;images/*" gui.SDRTrunk
java -XX:+UseG1GC -cp "sdrtrunk.jar;*;libs/*;config/*;images/*" gui.SDRTrunk

View File

@@ -2,7 +2,7 @@ package audio;
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,14 +19,11 @@ package audio;
******************************************************************************/
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;
@@ -37,11 +34,14 @@ 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.AudioPanel.MixerSelectionItem;
import audio.output.AudioOutput;
import audio.output.MonoAudioOutput;
import audio.output.StereoAudioOutput;
@@ -53,7 +53,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
private static final Logger mLog = LoggerFactory.getLogger( AudioManager.class );
public static final int AUDIO_TIMEOUT = 2000; //2 seconds
public static final int NO_SOURCE = 0;
public static final String AUDIO_CHANNELS_PROPERTY = "audio.manager.channels";
public static final String AUDIO_MIXER_PROPERTY = "audio.manager.mixer";
public static final AudioEvent CONFIGURATION_CHANGE_STARTED =
new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null );
@@ -62,19 +64,19 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_COMPLETE, null );
private LinkedTransferQueue<AudioPacket> mAudioPacketQueue = new LinkedTransferQueue<>();
private Map<Integer,AudioChannelAssignment> mChannelAssignments = new HashMap<>();
private int mLowestPrioritySource;
private Map<Integer,AudioOutputConnection> mChannelConnectionMap = new HashMap<>();
private List<AudioOutputConnection> mAudioOutputConnections = new ArrayList<>();
private AudioOutputConnection mLowestPriorityConnection;
private int mAvailableConnectionCount;
private List<AudioOutput> mAvailableAudioOutputs = new ArrayList<>();
private Map<String,AudioOutput> mAudioOutputMap = new HashMap<>();
private List<String> mChannelNames = new ArrayList<>();
private Broadcaster<AudioEvent> mControllerBroadcaster = new Broadcaster<>();
private ThreadPoolManager mThreadPoolManager;
private ScheduledFuture<?> mProcessingTask;
private MixerChannelConfiguration mMixerChannel;
private MixerChannelConfiguration mMixerChannelConfiguration;
/**
* Processes all audio produced by the decoding channels and routes audio
@@ -85,26 +87,66 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
{
mThreadPoolManager = manager;
//TODO: save audio system preferences to the settings manager
loadSettings();
}
private void loadSettings()
{
MixerChannelConfiguration configuration = null;
/* Use the system default mixer and mono channel as default startup */
Mixer mixer = AudioSystem.getMixer( null );
SystemProperties properties = SystemProperties.getInstance();
if( mixer != 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 =
MixerManager.getInstance().getOutputMixers();
for( MixerChannelConfiguration mixerConfig: mixerConfigurations )
{
mMixerChannel = new MixerChannelConfiguration( mixer, MixerChannel.MONO );
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 );
try
{
setMixerChannelConfiguration( mMixerChannel );
}
catch ( AudioException e )
setMixerChannelConfiguration( getDefaultConfiguration() );
}
catch( Exception e2 )
{
mLog.error( "Couldn't create an audio output device", e );
mLog.error( "Couldn't set default audio mixer/channel "
+ "configuration - no audio will be available", e2 );
}
}
}
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 )
@@ -116,6 +158,15 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
mThreadPoolManager = null;
mProcessingTask = null;
mChannelConnectionMap.clear();
for( AudioOutputConnection connection: mAudioOutputConnections )
{
connection.dispose();
}
mAudioOutputConnections.clear();
}
@Override
@@ -124,207 +175,43 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
mAudioPacketQueue.add( packet );
}
public class AudioPacketProcessor implements Runnable
{
@Override
public void run()
{
removeCompletedChannelAssignments();
if( mAudioPacketQueue != null )
{
List<AudioPacket> packets = new ArrayList<AudioPacket>();
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;
}
}
}
}
}
}
}
}
}
}
/**
* Checks each audio channel assignment and removes any completed channels.
* Checks each audio channel assignment and disconnects any inactive connections
*/
private void removeCompletedChannelAssignments()
private void disconnectInactiveChannelAssignments()
{
if( !mChannelAssignments.isEmpty() )
boolean changed = false;
for( AudioOutputConnection connection: mAudioOutputConnections )
{
Iterator<Entry<Integer,AudioChannelAssignment>> it =
mChannelAssignments.entrySet().iterator();
Entry<Integer,AudioChannelAssignment> entry;
boolean removed = false;
while( it.hasNext() )
if( connection.isConnected() && connection.isInactive() )
{
entry = it.next();
if( entry.getValue().isComplete() )
{
mAvailableAudioOutputs.add( entry.getValue().getAudioOutput() );
entry.getValue().dispose();
it.remove();
removed = true;
}
}
if( removed )
{
updateLowestPriorityAssignment();
mChannelConnectionMap.remove( connection.getSource() );
connection.disconnect();
changed = true;
}
}
if( changed )
{
updateLowestPriorityAssignment();
}
}
/**
* Identifies the lowest priority channel assignment ... the one that can
* be overridden with a higher priority call
* Identifies the lowest priority channel connection
*/
private void updateLowestPriorityAssignment()
{
int lowestPriority = -1;
mLowestPriorityConnection = null;
mLowestPrioritySource = -1;
for( int source: mChannelAssignments.keySet() )
for( AudioOutputConnection connection: mAudioOutputConnections )
{
AudioChannelAssignment assignment = mChannelAssignments.get( source );
if( assignment.getPriority() > lowestPriority )
if( connection.isConnected() &&
( mLowestPriorityConnection == null ||
mLowestPriorityConnection.getPriority() > connection.getPriority() ) )
{
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<AudioEvent>
{
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;
mLowestPriorityConnection = connection;
}
}
}
@@ -342,41 +229,29 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
{
mThreadPoolManager.cancel( mProcessingTask );
}
endAllAudioAssignments();
for( Entry<Integer,AudioChannelAssignment> e: mChannelAssignments.entrySet() )
{
e.getValue().getAudioOutput().dispose();
}
mChannelAssignments.clear();
disposeCurrentConfiguration();
for( AudioOutput output: mAvailableAudioOutputs )
{
output.dispose();
}
mAvailableAudioOutputs.clear();
mChannelNames.clear();
mAudioOutputMap.clear();
switch( entry.getMixerChannel() )
{
case MONO:
AudioOutput mono = new MonoAudioOutput( entry.getMixer() );
mAvailableAudioOutputs.add( mono );
mChannelNames.add( mono.getChannelName() );
AudioOutput mono = new MonoAudioOutput( mThreadPoolManager,
entry.getMixer() );
mAudioOutputConnections.add( new AudioOutputConnection( mono ) );
mAvailableConnectionCount++;
mAudioOutputMap.put( mono.getChannelName(), mono );
break;
case STEREO:
AudioOutput left = new StereoAudioOutput( entry.getMixer(), MixerChannel.LEFT );
mAvailableAudioOutputs.add( left );
mChannelNames.add( left.getChannelName() );
AudioOutput left = new StereoAudioOutput( mThreadPoolManager,
entry.getMixer(), MixerChannel.LEFT );
mAudioOutputConnections.add( new AudioOutputConnection( left ) );
mAvailableConnectionCount++;
mAudioOutputMap.put( left.getChannelName(), left );
AudioOutput right = new StereoAudioOutput( entry.getMixer(), MixerChannel.RIGHT );
mAvailableAudioOutputs.add( right );
mChannelNames.add( right.getChannelName() );
AudioOutput right = new StereoAudioOutput( mThreadPoolManager,
entry.getMixer(), MixerChannel.RIGHT );
mAudioOutputConnections.add( new AudioOutputConnection( right ) );
mAvailableConnectionCount++;
mAudioOutputMap.put( right.getChannelName(), right );
break;
default:
@@ -389,13 +264,42 @@ public class AudioManager implements Listener<AudioPacket>, 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();
}
}
/**
* 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 mMixerChannel;
return mMixerChannelConfiguration;
}
@Override
@@ -426,4 +330,237 @@ public class AudioManager implements Listener<AudioPacket>, 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()
{
disconnectInactiveChannelAssignments();
if( mAudioPacketQueue != null )
{
List<AudioPacket> packets = new ArrayList<AudioPacket>();
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 );
}
}
}
}
}
}
/**
* Audio output connection manages a connection between a soure and an audio
* output and maintains current state information about the audio activity
* received form the source.
*/
public class AudioOutputConnection implements Listener<AudioEvent>
{
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;
}
}
}
}

View File

@@ -1,7 +1,6 @@
package audio.output;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.sound.sampled.FloatControl;
@@ -15,6 +14,7 @@ import audio.AudioEvent;
import audio.AudioEvent.Type;
import audio.AudioPacket;
import audio.metadata.AudioMetadata;
import controller.ThreadPoolManager;
public abstract class AudioOutput implements Listener<AudioPacket>
{
@@ -25,10 +25,15 @@ public abstract class AudioOutput implements Listener<AudioPacket>
protected Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
protected LinkedTransferQueue<AudioPacket> mBuffer = new LinkedTransferQueue<>();
protected ScheduledExecutorService mExecutorService;
protected ThreadPoolManager mThreadPoolManager;
protected boolean mCanProcessAudio;
protected long mLastActivity;
public AudioOutput( ThreadPoolManager threadPoolManager )
{
mThreadPoolManager = threadPoolManager;
}
public void reset()
{
broadcast( new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
@@ -41,14 +46,11 @@ public abstract class AudioOutput implements Listener<AudioPacket>
mBuffer.clear();
if( mExecutorService != null )
{
mExecutorService.shutdown();
}
mAudioEventBroadcaster.dispose();
mAudioEventBroadcaster = null;
mAudioMetadataListener = null;
mThreadPoolManager = null;
}
public abstract String getChannelName();

View File

@@ -5,7 +5,7 @@ import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -24,8 +24,8 @@ import audio.AudioEvent;
import audio.AudioFormats;
import audio.AudioPacket;
import audio.AudioPacket.Type;
import audio.output.StereoAudioOutput.QueueProcessor;
import controller.NamingThreadFactory;
import controller.ThreadPoolManager;
import controller.ThreadPoolManager.ThreadType;
/**
* Mono Audio output with automatic flow control based on the availability of
@@ -55,9 +55,11 @@ public class MonoAudioOutput extends AudioOutput
private AudioEvent mAudioStopEvent;
private AudioEvent mAudioContinuationEvent;
public MonoAudioOutput( Mixer mixer )
private ScheduledFuture<?> mProcessorTask;
public MonoAudioOutput( ThreadPoolManager threadPoolManager, Mixer mixer )
{
super();
super( threadPoolManager );
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
getChannelName() );
@@ -74,15 +76,7 @@ public class MonoAudioOutput extends AudioOutput
if( mOutput != null )
{
mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO, BUFFER_SIZE );
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 )
@@ -115,12 +109,10 @@ public class MonoAudioOutput extends AudioOutput
mixer.getMixerInfo().getName() + "]" );
}
mExecutorService = Executors.newSingleThreadScheduledExecutor(
new NamingThreadFactory( "audio (mono) output" ) );
/* Run the queue processor task every 40 milliseconds */
mExecutorService.scheduleAtFixedRate( new QueueProcessor(),
0, 40, TimeUnit.MILLISECONDS );
mProcessorTask = mThreadPoolManager.scheduleFixedRate(
ThreadType.AUDIO_PROCESSING, new QueueProcessor(),
40, TimeUnit.MILLISECONDS );
}
}
@@ -132,79 +124,93 @@ 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()
{
/* The mProcessing flag ensures that only one instance of the
* processor can run at any given time */
if( mProcessing.compareAndSet( false, true ) )
try
{
List<AudioPacket> packets = new ArrayList<AudioPacket>();
mBuffer.drainTo( packets );
if( packets.size() > 0 )
/* The mProcessing flag ensures that only one instance of the
* processor can run at any given time */
if( mProcessing.compareAndSet( false, true ) )
{
broadcast( mAudioContinuationEvent );
List<AudioPacket> packets = new ArrayList<AudioPacket>();
for( AudioPacket packet: packets )
mBuffer.drainTo( packets );
if( packets.size() > 0 )
{
if( mCanProcessAudio )
broadcast( mAudioContinuationEvent );
for( AudioPacket packet: packets )
{
if( isSquelched() )
if( mCanProcessAudio )
{
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 )
if( isSquelched() )
{
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
checkStop();
}
mOutput.write( buffer.array(), 0,
buffer.array().length );
checkStart();
broadcast( packet.getAudioMetadata() );
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();
mLastActivity = System.currentTimeMillis();
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();
}
}
}
}
else
{
checkStop();
}
mProcessing.set( false );
}
else
{
checkStop();
}
mProcessing.set( false );
}
catch( Exception e )
{
mLog.error( "Error while processing audio buffers", e );
}
}

View File

@@ -5,7 +5,7 @@ import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -24,7 +24,8 @@ import audio.AudioEvent;
import audio.AudioFormats;
import audio.AudioPacket;
import audio.AudioPacket.Type;
import controller.NamingThreadFactory;
import controller.ThreadPoolManager;
import controller.ThreadPoolManager.ThreadType;
/**
* Mono Audio output in stereo format with automatic flow control based on the
@@ -56,9 +57,12 @@ public class StereoAudioOutput extends AudioOutput
private MixerChannel mMixerChannel;
public StereoAudioOutput( Mixer mixer, MixerChannel channel )
private ScheduledFuture<?> mProcessorTask;
public StereoAudioOutput( ThreadPoolManager threadPoolManager,
Mixer mixer, MixerChannel channel )
{
super();
super( threadPoolManager );
assert( channel == MixerChannel.LEFT || channel == MixerChannel.RIGHT );
@@ -113,12 +117,10 @@ 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 */
mExecutorService.scheduleAtFixedRate( new QueueProcessor(),
0, 40, TimeUnit.MILLISECONDS );
mProcessorTask = mThreadPoolManager.scheduleFixedRate(
ThreadType.AUDIO_PROCESSING, new QueueProcessor(),
40, TimeUnit.MILLISECONDS );
}
}
@@ -130,20 +132,30 @@ 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();
@@ -178,49 +190,56 @@ public class StereoAudioOutput extends AudioOutput
@Override
public void run()
{
/* The mProcessing flag ensures that only one instance of the
* processor can run at any given time */
if( mProcessing.compareAndSet( false, true ) )
try
{
List<AudioPacket> packets = new ArrayList<AudioPacket>();
mBuffer.drainTo( packets );
if( packets.size() > 0 )
/* The mProcessing flag ensures that only one instance of the
* processor can run at any given time */
if( mProcessing.compareAndSet( false, true ) )
{
broadcast( mAudioContinuationEvent );
List<AudioPacket> packets = new ArrayList<AudioPacket>();
for( AudioPacket packet: packets )
mBuffer.drainTo( packets );
if( packets.size() > 0 )
{
if( mCanProcessAudio )
broadcast( mAudioContinuationEvent );
for( AudioPacket packet: packets )
{
if( isSquelched() )
if( mCanProcessAudio )
{
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();
if( isSquelched() )
{
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 );
}
else
{
checkStop();
}
mProcessing.set( false );
}
catch( Exception e )
{
mLog.error( "Error while processing audio buffers", e );
}
}

View File

@@ -33,7 +33,6 @@ import module.decode.DecoderType;
import module.decode.config.DecodeConfigFactory;
import playlist.PlaylistNode;
import source.config.SourceConfigTuner;
import source.recording.RecordingGroupNode;
import source.tuner.Tuner;
import source.tuner.TunerGroupNode;
import source.tuner.TunerNode;
@@ -60,7 +59,7 @@ public class ConfigurationControllerModel extends DefaultTreeModel
private PlaylistNode mPlaylistNode;
private TunerGroupNode mTunerGroupNode;
private RecordingGroupNode mRecordingGroupNode;
// private RecordingGroupNode mRecordingGroupNode;
private IconManager mIconManagerFrame;
@@ -121,15 +120,15 @@ public class ConfigurationControllerModel extends DefaultTreeModel
/**
* Add the recording group node
*/
mRecordingGroupNode = new RecordingGroupNode();
insertNodeInto( mRecordingGroupNode, (MutableTreeNode)root, 1 );
mRecordingGroupNode.loadRecordings();
// mRecordingGroupNode = new RecordingGroupNode();
// insertNodeInto( mRecordingGroupNode, (MutableTreeNode)root, 1 );
// mRecordingGroupNode.loadRecordings();
/**
* Add the playlist node
*/
mPlaylistNode = new PlaylistNode();
insertNodeInto( mPlaylistNode, (MutableTreeNode)root, 2 );
insertNodeInto( mPlaylistNode, (MutableTreeNode)root, 1 );
mPlaylistNode.loadPlaylist();
}

View File

@@ -36,24 +36,20 @@ public class ThreadPoolManager
private HashMap<ScheduledFuture<?>,ThreadType> mTasks =
new HashMap<ScheduledFuture<?>,ThreadType>();
private ScheduledExecutorService mExecutor;
private ScheduledExecutorService mExecutor =
Executors.newScheduledThreadPool( THREAD_POOL_SIZE,
new NamingThreadFactory( "sdrtrunk" ) );
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 );
@@ -65,12 +61,6 @@ 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 );
}

View File

@@ -421,7 +421,7 @@ public class SDRTrunk
if( version != null )
{
sb.append( " V" );
sb.append( " v" );
sb.append( version );
}
}

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,52 +17,94 @@
******************************************************************************/
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 implements MessageDetailsProvider
public abstract class Message
{
protected long mTimeReceived;
protected MessageType mType;
public Message()
{
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;
}
public Date getDateReceived()
/**
* Map plottable
*/
public Plottable getPlottable()
{
return new Date( mTimeReceived );
}
public MessageType getType()
{
return mType;
//TODO: move this to an IPlottable interface that only gets implemented as needed
return null;
}
/**
* 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.
*/

View File

@@ -19,6 +19,7 @@ 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;
@@ -174,7 +175,7 @@ public class MessageActivityModel extends AbstractTableModel
switch( columnIndex )
{
case sTIME:
return mSDFTime.format( message.getDateReceived() );
return mSDFTime.format( new Date( message.getTimeReceived() ) );
case sPROTOCOL:
return message.getProtocol();
case sERROR_STATUS:

View File

@@ -1,80 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.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();
}

View File

@@ -51,17 +51,16 @@ public class LTRCallEvent extends CallEvent
*/
public boolean isMatchingTalkgroup( LTRNetMessage message )
{
String talkgroup = message.getTalkgroupID();
if( talkgroup != null && getToID() != null &&
talkgroup.contentEquals( getToID() ) )
{
return true;
}
return false;
return isMatchingTalkgroup( message.getTalkgroupID() );
}
public boolean isMatchingTalkgroup( String talkgroup )
{
return talkgroup != null &&
getToID() != null &&
talkgroup.contentEquals( getToID() );
}
private Alias getAlias( String talkgroup )
{
if( hasAliasList() )

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,21 +17,31 @@
******************************************************************************/
package module.decode.ltrstandard;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
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;
@@ -39,15 +49,17 @@ import audio.metadata.MetadataType;
public class LTRStandardDecoderState extends DecoderState
{
private HashMap<Integer,String> mActiveCalls = new HashMap<Integer,String>();
private HashSet<String> mTalkgroupsFirstHeard = new HashSet<String>();
private TreeSet<String> mTalkgroups = new TreeSet<String>();
private TreeSet<Integer> mActiveLCNs = new TreeSet<Integer>();
private final static Logger mLog =
LoggerFactory.getLogger( LTRStandardDecoderState.class );
private Map<Integer,LTRCallEvent> mActiveCalls = new HashMap<>();
private Set<String> mTalkgroupsFirstHeard = new HashSet<>();
private Set<String> mTalkgroups = new TreeSet<>();
private String mTalkgroup;
private Alias mTalkgroupAlias;
private int mChannelNumber;
private long mFrequency;
private LCNTracker mLCNTracker = new LCNTracker();
public LTRStandardDecoderState( AliasList aliasList )
{
@@ -70,171 +82,126 @@ public class LTRStandardDecoderState extends DecoderState
{
}
private LTRCallEvent getCurrentCallEvent()
{
return (LTRCallEvent)mCurrentCallEvent;
}
@Override
public void receive( Message message )
{
if( message.isValid() )
if( message.isValid() && message instanceof LTRStandardMessage )
{
if( message instanceof LTRStandardOSWMessage )
switch( ((LTRStandardMessage)message).getMessageType() )
{
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 );
}
case CA_STRT:
CallMessage start = (CallMessage)message;
int channel = start.getChannel();
mLCNTracker.processCallChannel( channel );
mLCNTracker.processFreeChannel( start.getFree() );
/* 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();
//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 );
}
/* 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();
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 );
broadcast( mCurrentCallEvent );
broadcast( new Metadata( MetadataType.TO,
mTalkgroup, mTalkgroupAlias, true ) );
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( 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 );
int repeater = end.getHomeRepeater();
mLCNTracker.processCallChannel( repeater );
if( mLCNTracker.isCurrentChannel( repeater ) )
{
mTalkgroup = end.getToID();
broadcast( ChangedAttribute.TO_TALKGROUP );
processTalkgroup( mTalkgroup );
mTalkgroupAlias = end.getToIDAlias();
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
mTalkgroupAlias = ltr.getTalkgroupIDAlias();
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
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;
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 );
}
}
int lcn = idle.getChannel();
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;
}
mLCNTracker.processCallChannel( lcn );
break;
case UN_KNWN:
default:
break;
}
}
}
/**
* Indicates if the talkgroup is different than the talkgroup specified in
* the current call event
*/
private boolean isDifferentTalkgroup( String talkgroup )
private void processTalkgroup( String talkgroup )
{
return talkgroup != null &&
mCurrentCallEvent != null &&
mCurrentCallEvent.getToID() != null &&
!mCurrentCallEvent.getToID().contentEquals( talkgroup );
if( mTalkgroupsFirstHeard.contains( talkgroup ) )
{
mTalkgroups.add( talkgroup );
}
else
{
mTalkgroupsFirstHeard.add( talkgroup );
}
}
/**
@@ -245,8 +212,7 @@ public class LTRStandardDecoderState extends DecoderState
mActiveCalls.clear();
mTalkgroupsFirstHeard.clear();
mTalkgroups.clear();
mActiveLCNs.clear();
mChannelNumber = 0;
mLCNTracker.reset();
resetState();
}
@@ -256,13 +222,20 @@ public class LTRStandardDecoderState extends DecoderState
*/
private void resetState()
{
if( mCurrentCallEvent != null )
for( Integer key: mActiveCalls.keySet() )
{
mCurrentCallEvent.setEnd( System.currentTimeMillis() );
broadcast( mCurrentCallEvent );
mCurrentCallEvent = null;
LTRCallEvent event = mActiveCalls.get( key );
if( event != null )
{
event.end();
broadcast( event );
}
}
mActiveCalls.clear();
mTalkgroup = null;
broadcast( ChangedAttribute.TO_TALKGROUP );
@@ -282,12 +255,12 @@ public class LTRStandardDecoderState extends DecoderState
public boolean hasChannelNumber()
{
return mChannelNumber != 0;
return mLCNTracker.getCurrentChannel() != 0;
}
public int getChannelNumber()
{
return mChannelNumber;
return mLCNTracker.getCurrentChannel();
}
@Override
@@ -322,28 +295,28 @@ public class LTRStandardDecoderState extends DecoderState
sb.append( "Monitored LCN: " );
if( mChannelNumber > 0 )
if( hasChannelNumber() )
{
sb.append( mChannelNumber );
sb.append( getChannelNumber() );
}
else
{
sb.append( "unknown" );
sb.append( "*Insufficient Data*" );
}
sb.append( "\n" );
Integer[] lcns = mActiveLCNs.toArray( new Integer[0] );
sb.append( "Active LCNs:\t" );
List<Integer> lcns = mLCNTracker.getActiveLCNs();
if( lcns.length > 0 )
if( lcns.size() > 0 )
{
sb.append( Arrays.toString( lcns ) );
sb.append( mLCNTracker.getActiveLCNs() );
}
else
{
sb.append( "none" );
sb.append( "*Insufficient Data*" );
}
sb.append( "\n\n" );
@@ -355,19 +328,15 @@ public class LTRStandardDecoderState extends DecoderState
}
else
{
Iterator<String> it = mTalkgroups.iterator();
while( it.hasNext() )
for( String talkgroup: mTalkgroups )
{
String tgid = it.next();
sb.append( " " );
sb.append( formatTalkgroup( tgid ) );
sb.append( talkgroup );
sb.append( " " );
if( hasAliasList() )
{
Alias alias = getAliasList().getTalkgroupAlias( tgid );
Alias alias = getAliasList().getTalkgroupAlias( talkgroup );
if( alias != null )
{
@@ -376,30 +345,147 @@ public class LTRStandardDecoderState extends DecoderState
}
sb.append( "\n" );
}
}
return sb.toString();
}
public static String formatTalkgroup( String talkgroup )
/**
* 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
{
StringBuilder sb = new StringBuilder();
private static final int DEFAULT_COUNT = 10;
private int[] mCallLCNCounts;
private int[] mFreeLCNCounts;
private int mCallHighestCount;
private int mFreeHighestCount;
private int mCurrentLCN;
if( talkgroup.length() == 6 )
public LCNTracker()
{
sb.append( talkgroup.substring( 0, 1 ) );
sb.append( "-" );
sb.append( talkgroup.substring( 1, 3 ) );
sb.append( "-" );
sb.append( talkgroup.substring( 3, 6 ) );
return sb.toString();
reset();
}
else
public void logStatistics()
{
return talkgroup;
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 );
mLog.debug( "Current LCN: " + mCurrentLCN );
}
public void reset()
{
//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<Integer> getActiveLCNs()
{
List<Integer> 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;
}
}
}

View File

@@ -1,238 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.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<Alias> getAliases()
{
List<Alias> aliases = new ArrayList<Alias>();
Alias from = getFromIDAlias();
if( from != null )
{
aliases.add( from );
}
Alias to = getToIDAlias();
if( to != null )
{
aliases.add( to );
}
return aliases;
}
}

View File

@@ -1,254 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.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<<x;
}
}
return retVal;
}
/**
* Pads an integer value with additional zeroes to make it decimalPlaces long
*/
public String format( int number, int decimalPlaces )
{
StringBuilder sb = new StringBuilder();
int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() );
for( int x = 0; x < paddingRequired; x++)
{
sb.append( "0" );
}
sb.append( number );
return sb.toString();
}
public String format( String val, int places )
{
StringBuilder sb = new StringBuilder();
sb.append( val );
while( sb.length() < places )
{
sb.append( " " );
}
return sb.toString();
}
public CRC getCRC()
{
return mCRC;
}
public int getArea()
{
return getInt( sAREA );
}
public int getChannel()
{
return getInt( sCHANNEL );
}
public int getHomeRepeater()
{
return getInt( sHOME_REPEATER );
}
public int getGroup()
{
return getInt( sGROUP );
}
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 int getFree()
{
return getInt( sFREE );
}
public int getCRCChecksum()
{
return getInt( sCRC );
}
@Override
public String getProtocol()
{
return DecoderType.LTR_STANDARD.getDisplayString();
}
@Override
public String getEventType()
{
if( mMessageType != null )
{
return mMessageType.getDisplayText();
}
else
{
return MessageType.UN_KNWN.getDisplayText();
}
}
@Override
public String getToID()
{
return null;
}
@Override
public Alias getToIDAlias()
{
return null;
}
@Override
public String getErrorStatus()
{
return mCRC.getDisplayText();
}
}

View File

@@ -6,6 +6,7 @@ import java.util.List;
import message.Message;
import message.MessageType;
import module.decode.ltrstandard.message.LTRStandardMessage;
import filter.Filter;
import filter.FilterElement;

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,9 +19,16 @@ package module.decode.ltrstandard;
import message.Message;
import message.MessageDirection;
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.ltrstandard.message.UnknownMessage;
import sample.Listener;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
import edac.CRCLTR;
public class LTRStandardMessageProcessor implements Listener<BinaryMessage>
{
@@ -29,6 +36,12 @@ public class LTRStandardMessageProcessor implements Listener<BinaryMessage>
private AliasList mAliasList;
private Listener<Message> 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;
@@ -36,27 +49,66 @@ public class LTRStandardMessageProcessor implements Listener<BinaryMessage>
}
@Override
public void receive( BinaryMessage buffer )
public void receive( BinaryMessage binaryMessage )
{
if( mMessageListener != null )
{
LTRStandardMessage message;
if( mDirection == MessageDirection.OSW )
//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 )
{
message = new LTRStandardOSWMessage( buffer, mAliasList );
binaryMessage.flip( 0, 40 );
}
else
CRC crc = CRCLTR.check( binaryMessage, mDirection );
if( crc != CRC.FAILED_CRC &&
crc != CRC.FAILED_PARITY )
{
buffer.flip( 0, 40 );
LTRStandardMessage message;
message = new LTRStandardISWMessage( buffer, mAliasList );
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<Message> listener )
{
mMessageListener = listener;

View File

@@ -1,248 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.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<Alias> getAliases()
{
List<Alias> aliases = new ArrayList<Alias>();
Alias from = getFromIDAlias();
if( from != null )
{
aliases.add( from );
}
Alias to = getToIDAlias();
if( to != null )
{
aliases.add( to );
}
return aliases;
}
}

View File

@@ -0,0 +1,67 @@
/*******************************************************************************
* 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 <http://www.gnu.org/licenses/>
******************************************************************************/
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();
}
}

View File

@@ -0,0 +1,68 @@
/*******************************************************************************
* 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 <http://www.gnu.org/licenses/>
******************************************************************************/
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();
}
}

View File

@@ -0,0 +1,58 @@
/*******************************************************************************
* 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 <http://www.gnu.org/licenses/>
******************************************************************************/
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();
}
}

View File

@@ -0,0 +1,213 @@
/*******************************************************************************
* 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 <http://www.gnu.org/licenses/>
******************************************************************************/
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<Alias> getAliases()
{
List<Alias> 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();
}
}

View File

@@ -0,0 +1,58 @@
/*******************************************************************************
* 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 <http://www.gnu.org/licenses/>
******************************************************************************/
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();
}
}

View File

@@ -118,7 +118,6 @@ 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;
@@ -1748,6 +1747,8 @@ public class P25DecoderState extends DecoderState
.to( pduc.getLogicalLinkID() )
.build() );
break;
case SN_RF_UNCONFIRMED_DATA:
break;
default:
mLog.debug( "PDUC - Unrecognized Message: " + pduc.toString() );
break;

View File

@@ -1,5 +1,7 @@
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;
@@ -19,6 +21,7 @@ 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;
@@ -56,8 +59,6 @@ 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
{
@@ -119,6 +120,8 @@ 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 );

View File

@@ -0,0 +1,159 @@
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;
}
}

View File

@@ -1,8 +1,10 @@
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.GregorianCalendar;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import module.decode.p25.message.tsbk.TSBKMessage;
@@ -12,20 +14,21 @@ import alias.AliasList;
import bits.BinaryMessage;
/**
* 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.
* 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.
*/
public class SyncBroadcast extends TSBKMessage
{
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[] 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 LOCAL_TIME_OFFSET_SIGN = 98;
public static final int[] LOCAL_TIME_OFFSET = { 99,100,101,102 };
public static final int LOCAL_TIME_OFFSET_HALF = 103;
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[] YEAR = { 104,105,106,107,108,109,110 };
public static final int[] MONTH = { 111,112,113,114 };
@@ -36,7 +39,10 @@ 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 )
@@ -56,24 +62,41 @@ public class SyncBroadcast extends TSBKMessage
sb.append( getMessageStub() );
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() );
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" );
}
return sb.toString();
}
/**
* Returns the time sync in java time (ie milliseconds since epoch )
* System Time (UTC) in milliseconds since java epoch
*/
public long getTime()
public long getSystemTime()
{
Calendar cal = new GregorianCalendar();
Calendar cal = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
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() );
@@ -84,102 +107,165 @@ public class SyncBroadcast extends TSBKMessage
return cal.getTimeInMillis();
}
public boolean isUS()
/**
* System Time not locked to external time reference indicator
*/
public boolean isSystemTimeNotLockedToExternalReference()
{
return mMessage.get( US );
return mMessage.get( SYSTEM_TIME_NOT_LOCKED_TO_EXTERNAL_REFERENCE );
}
/**
* Daylight Savings Time
* 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.
*
*/
public boolean isIST()
public boolean isMicroslotsLockedToMinuteRollover()
{
return mMessage.get( IST );
return !mMessage.get( MICRO_SLOTS_TO_MINUTE_ROLLOVER_UNLOCKED );
}
/**
* No idea ...
* 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
*/
public boolean isMMU()
public double getLeapSecondCorrection()
{
return mMessage.get( MMU );
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 );
}
/**
* Millenium ...?
* 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.
*/
public int getMC()
public TimeZone getTimeZone()
{
return mMessage.getInt( MC );
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;
}
}
/**
* No idea ....
* System time - year.
*
* @return year in range 2000-2127
*/
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 );
}
/**
* 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.
* 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
*/
public int getMicroSlots()
{

View File

@@ -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 ),
RESERVED_33( "RESERVED_33 ", "Reserved", 51 ),
IDENTIFIER_UPDATE_TDMA( "IDEN_UPDATE_TDMA", "Identifier Update for TDMA", 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 ),

View File

@@ -128,10 +128,13 @@ public class ChannelList extends JPanel implements ChannelEventListener
public void setSelectedChannel( Channel channel )
{
/* Send channel selection to each channel panel */
for( ChannelCollectionPanel panel: mDisplayedPanels.values() )
synchronized( mDisplayedPanels )
{
panel.setSelectedChannel( channel );
/* Send channel selection to each channel panel */
for( ChannelCollectionPanel panel: mDisplayedPanels.values() )
{
panel.setSelectedChannel( channel );
}
}
}

View File

@@ -17,6 +17,7 @@
******************************************************************************/
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;
@@ -182,8 +183,16 @@ public class ChannelState extends Module implements ICallEventProvider,
{
if( mRunningMonitor == null && mStateMonitor != null )
{
mRunningMonitor = mThreadPoolManager.scheduleFixedRate(
ThreadType.DECODER, mStateMonitor, 20, TimeUnit.MILLISECONDS );
try
{
mRunningMonitor = mThreadPoolManager.scheduleFixedRate(
ThreadType.DECODER, mStateMonitor, 20, TimeUnit.MILLISECONDS );
}
catch( RejectedExecutionException ree )
{
mLog.error( "state monitor scheduled task rejected", ree );
mMonitoring.set( false );
}
}
else
{
@@ -372,6 +381,10 @@ public class ChannelState extends Module implements ICallEventProvider,
break;
}
}
else
{
mLog.debug( "Can't change from [" + mState + "] to [" + state + "]" );
}
}
public class StateMonitor implements Runnable
@@ -379,23 +392,35 @@ public class ChannelState extends Module implements ICallEventProvider,
@Override
public void run()
{
if( getState() == State.FADE )
try
{
if( mResetTimeout <= System.currentTimeMillis() )
if( getState() == State.FADE )
{
setState( State.END );
if( mResetTimeout <= System.currentTimeMillis() )
{
setState( State.END );
}
}
else if( getState() == State.END )
{
setState( State.IDLE );
}
else
{
if( mFadeTimeout <= System.currentTimeMillis() )
{
setState( State.FADE );
}
}
}
else if( getState() == State.END )
catch( Exception e )
{
setState( State.IDLE );
}
else
{
if( mFadeTimeout <= System.currentTimeMillis() )
{
setState( State.FADE );
}
mLog.error( "Exception thrown while state monitor is running " +
"- state [" + getState() +
"] current [" + System.currentTimeMillis() +
"] mResetTimeout [" + mResetTimeout +
"] mFadeTimeout [" + mFadeTimeout +
"]", e );
}
}
}

View File

@@ -22,10 +22,9 @@ 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.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import org.jdesktop.swingx.mapviewer.GeoPosition;
@@ -126,13 +125,13 @@ public class JXMapViewer extends JPanel implements DesignMode
// make a dummy loading image
try
{
URL url = JXMapViewer.class.getResource("mapviewer/resources/loading.png");
this.setLoadingImage(ImageIO.read(url));
ImageIcon imageIcon = new ImageIcon( "images/loading.png" );
this.setLoadingImage( imageIcon.getImage() );
}
catch (Throwable ex)
{
mLog.error( "JXMapViewer could not load 'loading.png'" );
mLog.error( "could not load 'loading.png'" );
BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();

View File

@@ -100,7 +100,7 @@ public class PlaylistManager
}
catch( Exception e )
{
mLog.error( "PlaylistManager - couldn't create file to save "
mLog.error( "couldn't create file to save "
+ "playlist [" + filePath.toString() + "]" );
}
@@ -122,14 +122,12 @@ public class PlaylistManager
}
catch ( JAXBException e )
{
mLog.error( "PlaylistManager - jaxb exception while saving " +
"playlist: ", e );
mLog.error( "jaxb exception while saving playlist: ", e );
}
}
catch ( Exception e )
{
mLog.error( "PlaylistManager - coulcn't open outputstream to " +
"save playlist [" + filePath.toString() + "]", e );
mLog.error( "coulcn't open outputstream to save playlist [" + filePath.toString() + "]", e );
}
finally
{
@@ -155,8 +153,7 @@ public class PlaylistManager
{
if( Files.exists( playlistPath ) )
{
mLog.info( "PlaylistManager - loading playlist file [" +
playlistPath.toString() + "]" );
mLog.info( "loading playlist file [" + playlistPath.toString() + "]" );
JAXBContext context = null;
@@ -176,14 +173,12 @@ public class PlaylistManager
}
catch ( JAXBException e )
{
mLog.error( "PlaylistManager - jaxb exception while loading " +
"playlist: ", e );
mLog.error( "jaxb exception while loading playlist: ", e );
}
}
catch ( Exception e )
{
mLog.error( "PlaylistManager - coulcn't open inputstream to " +
"load playlist [" + playlistPath.toString() + "]", e );
mLog.error( "coulcn't open inputstream to load playlist [" + playlistPath.toString() + "]", e );
}
finally
{
@@ -195,7 +190,7 @@ public class PlaylistManager
}
catch ( IOException e )
{
mLog.error( "PlaylistManager - exception while closing " +
mLog.error( "exception while closing " +
"the playlist file inputstream reader", e );
}
}
@@ -203,8 +198,7 @@ public class PlaylistManager
}
else
{
mLog.info( "PlaylistManager - playlist does not exist [" +
playlistPath.toString() + "]" );
mLog.info( "playlist does not exist [" + playlistPath.toString() + "]" );
}
if( mPlaylist == null )

View File

@@ -82,18 +82,13 @@ 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( "SystemProperties - exception while saving " +
"application properties", e );
mLog.error( "exception while saving application properties", e );
}
finally
{
@@ -146,8 +141,7 @@ public class SystemProperties
}
catch ( IOException e )
{
mLog.error( "SystemProperties - exception while creating " +
"app folder [" + folder + "]", e );
mLog.error( "exception while creating app folder [" + folder + "]", e );
}
}
@@ -158,11 +152,11 @@ public class SystemProperties
{
if( mPropertiesPath == null )
{
mLog.info( "SystemProperties - no properties file loaded - using defaults" );
mLog.info( "no properties file loaded - using defaults" );
}
else
{
mLog.info( "SystemProperties - application properties loaded [" + mPropertiesPath.toString() + "]" );
mLog.info( "application properties loaded [" + mPropertiesPath.toString() + "]" );
}
}
@@ -184,7 +178,7 @@ public class SystemProperties
}
catch ( FileNotFoundException e )
{
mLog.error( "SDRTrunk - exception while opening inputstream on " +
mLog.error( "exception while opening inputstream on " +
"application properties file", e );
}
@@ -196,7 +190,7 @@ public class SystemProperties
}
catch ( IOException e )
{
mLog.error( "SDRTrunk - exception while loading properties " +
mLog.error( "exception while loading properties " +
"inputstream into SystemProperties", e );
}
finally
@@ -212,8 +206,7 @@ public class SystemProperties
}
}
mLog.info( "SystemProperties - loaded [" +
propertiesPath.toString() + "]" );
mLog.info( "loaded [" + propertiesPath.toString() + "]" );
}
/**

View File

@@ -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( Color.BLUE,
CHANNEL_STATE_GRADIENT_MIDDLE_DATA( new Color( 0xCC00CC ),
"Data Gradient Middle", "Channel Data State Gradient Middle" ),
CHANNEL_STATE_GRADIENT_TOP_FADE( Color.BLACK,
"Fade Gradient Top", "Channel Fade State Gradient Top" ),

View File

@@ -431,8 +431,7 @@ public class SettingsManager
}
/* Something happened ... the above should always return an icon */
mLog.error( "SettingsManager - couldn't return an icon named [" +
name + "] of heigh [" + height + "]" );
mLog.error( "couldn't return an icon named [" + name + "] of heigh [" + height + "]" );
}
return null;
@@ -630,8 +629,7 @@ public class SettingsManager
return r820TConfig;
default:
throw new IllegalArgumentException( "TunerConfiguration"
+ "Directory - tuner type is unrecognized [" +
throw new IllegalArgumentException( "tuner type is unrecognized [" +
type.toString() + "]" );
}
}
@@ -705,8 +703,7 @@ public class SettingsManager
}
catch( Exception e )
{
mLog.error( "SettingsManager - couldn't create file to save "
+ "settings [" + filePath.toString() + "]", e );
mLog.error( "couldn't create file to save settings [" + filePath.toString() + "]", e );
}
OutputStream out = null;
@@ -727,14 +724,12 @@ public class SettingsManager
}
catch ( JAXBException e )
{
mLog.error( "SettingsManager - jaxb exception while saving " +
"settings", e );
mLog.error( "jaxb exception while saving settings", e );
}
}
catch ( Exception e )
{
mLog.error( "SettingsManager - coulcn't open outputstream to " +
"save settings [" + filePath.toString() + "]" );
mLog.error( "couldn't open outputstream to save settings [" + filePath.toString() + "]" );
}
finally
{
@@ -760,8 +755,7 @@ public class SettingsManager
{
if( Files.exists( settingsPath ) )
{
mLog.info( "SettingsManager - loading settings file [" +
settingsPath.toString() + "]" );
mLog.info( "loading settings file [" + settingsPath.toString() + "]" );
JAXBContext context = null;
@@ -781,13 +775,13 @@ public class SettingsManager
}
catch ( JAXBException e )
{
mLog.error( "SettingsManager - jaxb exception while loading " +
mLog.error( "jaxb exception while loading " +
"settings", e );
}
}
catch ( Exception e )
{
mLog.error( "SettingsManager - coulcn't open inputstream to " +
mLog.error( "coulcn't open inputstream to " +
"load settings [" + settingsPath.toString() + "]", e );
}
finally
@@ -800,7 +794,7 @@ public class SettingsManager
}
catch ( IOException e )
{
mLog.error( "SettingsManager - exception while closing " +
mLog.error( "exception while closing " +
"the settings file inputstream reader", e );
}
}
@@ -808,8 +802,7 @@ public class SettingsManager
}
else
{
mLog.info( "SettingsManager - settings does not exist [" +
settingsPath.toString() + "]" );
mLog.info( "settings does not exist [" + settingsPath.toString() + "]" );
}
if( mSettings == null )

View File

@@ -23,6 +23,14 @@ 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();

View File

@@ -143,8 +143,7 @@ public class RecordingSourceManager
ArrayList<RecordingConfiguration> recordingConfigurations =
settingsManager.getRecordingConfigurations();
mLog.info( "RecordingSourceManager - discovered [" +
recordingConfigurations.size() + "] recording configurations" );
mLog.info( "discovered [" + recordingConfigurations.size() + "] recording configurations" );
for( RecordingConfiguration config: recordingConfigurations )
{

View File

@@ -17,8 +17,8 @@
******************************************************************************/
package source.tuner;
import java.util.ArrayList;
import java.util.Collections;
import java.util.SortedSet;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.RejectedExecutionException;
import org.slf4j.Logger;
@@ -36,8 +36,7 @@ public abstract class TunerController implements Tunable
LoggerFactory.getLogger( TunerController.class );
/* List of currently tuned channels being served to demod channels */
protected ArrayList<TunerChannel> mTunedChannels =
new ArrayList<TunerChannel>();
private SortedSet<TunerChannel> mTunedChannels = new ConcurrentSkipListSet<>();
protected FrequencyController mFrequencyController;
private int mMiddleUnusable;
private double mUsableBandwidthPercentage;
@@ -147,13 +146,9 @@ public abstract class TunerController implements Tunable
}
else
{
//Sort the existing locks and get the min/max locked frequencies
Collections.sort( mTunedChannels );
int usableBandwidth = getUsableBandwidth();
long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency();
long maxLockedFrequency = mTunedChannels
.get( mTunedChannels.size() - 1 ).getMaxFrequency();
long minLockedFrequency = mTunedChannels.first().getMinFrequency();
long maxLockedFrequency = mTunedChannels.last().getMaxFrequency();
//Requested channel is within current locked channel frequency range
if( minLockedFrequency <= channel.getMinFrequency() &&
@@ -282,8 +277,6 @@ public abstract class TunerController implements Tunable
*/
private void updateLOFrequency() throws SourceException
{
Collections.sort( mTunedChannels );
long frequency = getFrequency();
boolean frequencyValid = true;
@@ -291,13 +284,12 @@ public abstract class TunerController implements Tunable
//If there is only 1 channel, position it to the right of center
if( mTunedChannels.size() == 1 )
{
frequency = mTunedChannels.get( 0 ).getMinFrequency() - mMiddleUnusable;
frequency = mTunedChannels.first().getMinFrequency() - mMiddleUnusable;
}
else
{
long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency();
long maxLockedFrequency = mTunedChannels
.get( mTunedChannels.size() - 1 ).getMaxFrequency();
long minLockedFrequency = mTunedChannels.first().getMinFrequency();
long maxLockedFrequency = mTunedChannels.last().getMaxFrequency();
//Start by placing the highest frequency channel at the high end of
//the spectrum

View File

@@ -30,6 +30,7 @@ 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;
@@ -61,6 +62,8 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
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;
@@ -89,7 +92,16 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
public DFTProcessor( SampleType sampleType )
{
setSampleType( sampleType );
setFrameRate( 20 );
loadSettings();
}
private void loadSettings()
{
int frameRate = SystemProperties.getInstance()
.get( DFTProcessor.FRAME_RATE_PROPERTY, 20 );
setFrameRate( frameRate );
}
public void dispose()
@@ -171,6 +183,10 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
mFrameRate = framesPerSecond;
SystemProperties properties = SystemProperties.getInstance();
properties.set( FRAME_RATE_PROPERTY, String.valueOf( mFrameRate ) );
properties.save();
calculateConsumptionRate();
restart();

View File

@@ -55,6 +55,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import playlist.PlaylistManager;
import properties.SystemProperties;
import sample.Listener;
import sample.SampleType;
import sample.complex.ComplexBuffer;
@@ -99,8 +100,10 @@ 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;
@@ -150,6 +153,36 @@ 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()
@@ -194,6 +227,10 @@ 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 );
}

View File

@@ -121,6 +121,8 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener,
mMemoryImageSource.setAnimated( true );
mWaterfallImage = createImage( mMemoryImageSource );
repaint();
}
/**