mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-09 15:17:24 -05:00
Compare commits
44 Commits
v0.2.0-bet
...
v0.2.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc3390460a | ||
|
|
8db226cd7b | ||
|
|
b7a54598b1 | ||
|
|
dd22397f75 | ||
|
|
d8a20b5adb | ||
|
|
c670f1d855 | ||
|
|
1dc0125ef4 | ||
|
|
225e747d6e | ||
|
|
36f9953156 | ||
|
|
47c452e7e6 | ||
|
|
06c8bba97e | ||
|
|
a25bb83dea | ||
|
|
8e11cb4554 | ||
|
|
2892e4609e | ||
|
|
d6e161f3e9 | ||
|
|
ba952af721 | ||
|
|
d9be346d7f | ||
|
|
133adec27a | ||
|
|
981a8fec55 | ||
|
|
9c94266adb | ||
|
|
4460f1ee30 | ||
|
|
ddfefe4b85 | ||
|
|
debdc39a5f | ||
|
|
6f76f6fca4 | ||
|
|
1290b8c4d3 | ||
|
|
b95b3ff415 | ||
|
|
3e8db5b216 | ||
|
|
f0c11f2590 | ||
|
|
99a5fc9bcf | ||
|
|
f5fadc5004 | ||
|
|
c922029233 | ||
|
|
94d85f636d | ||
|
|
1db6779dec | ||
|
|
676d26f4d8 | ||
|
|
abca4ebbd7 | ||
|
|
cffc6b6eed | ||
|
|
39ebec78a8 | ||
|
|
4066a4b3c3 | ||
|
|
c12f535f3c | ||
|
|
810bb7384e | ||
|
|
0c5692cb1e | ||
|
|
663378ad20 | ||
|
|
5807bb191e | ||
|
|
ab9ef2d8cc |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/*.class
|
||||
/classes*
|
||||
/product*
|
||||
/product*
|
||||
/bin/
|
||||
|
||||
31
README.md
31
README.md
@@ -1,19 +1,22 @@
|
||||
# sdrtrunk
|
||||
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
||||
# sdrtrunk #
|
||||
A cross-platform java application for decoding, monitoring and recording trunked
|
||||
mobile and related radio protocols using Software Defined Radios (SDR).
|
||||
|
||||
# Documentation #
|
||||
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted)
|
||||
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/UserManual)
|
||||
* [Download](https://drive.google.com/folderview?id=0B7BHsssXUq8eOHBTNndCczZTd0k&usp=sharing)
|
||||
* [User's Manual (Version 0.2.0)](https://github.com/DSheirer/sdrtrunk/wiki/UserManual)
|
||||
* [Wiki](https://github.com/DSheirer/sdrtrunk/wiki)
|
||||
|
||||
# Support Group and Error Reporting #
|
||||
You can get help with setting up the sdrtrunk application or report any errors
|
||||
that you encounter while running the application in the sdrtrunk Google Group at:
|
||||
|
||||
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
|
||||
|
||||
# Updates #
|
||||
Subscribe to this group to receive updates about new releases and the ongoing
|
||||
application development effort.
|
||||
|
||||
* [Subscribe for Updates](https://groups.google.com/forum/#!forum/sdrtrunk)
|
||||
|
||||

|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-67489234-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
|
||||
</script>
|
||||
|
||||
116
build/build.xml
116
build/build.xml
@@ -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.6"/>
|
||||
<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>
|
||||
@@ -1 +1 @@
|
||||
java -XX:+UseG1GC -cp "SDRTrunk.jar;*;libs/*;config/*;images/*" gui.SDRTrunk
|
||||
java -XX:+UseG1GC -cp "sdrtrunk.jar;*;libs/*;config/*;images/*" gui.SDRTrunk
|
||||
@@ -27,7 +27,6 @@ import audio.inverted.AudioType;
|
||||
public class TalkgroupID extends AliasID
|
||||
{
|
||||
private String mTalkgroup;
|
||||
private AudioType mAudioType;
|
||||
|
||||
public TalkgroupID()
|
||||
{
|
||||
@@ -44,20 +43,9 @@ public class TalkgroupID extends AliasID
|
||||
mTalkgroup = talkgroup;
|
||||
}
|
||||
|
||||
@XmlAttribute
|
||||
public AudioType getAudioType()
|
||||
{
|
||||
return mAudioType;
|
||||
}
|
||||
|
||||
public void setAudioType( AudioType type )
|
||||
{
|
||||
mAudioType = type;
|
||||
}
|
||||
|
||||
public boolean hasAudioType()
|
||||
{
|
||||
return mAudioType != null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
@@ -66,13 +54,6 @@ public class TalkgroupID extends AliasID
|
||||
|
||||
sb.append( "Talkgroup: " + mTalkgroup );
|
||||
|
||||
if( mAudioType != null )
|
||||
{
|
||||
sb.append( " [Audio " );
|
||||
sb.append( mAudioType.getShortDisplayString() );
|
||||
sb.append( "]" );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -20,9 +20,7 @@ package alias.id.talkgroup;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
@@ -30,7 +28,6 @@ import javax.swing.JTextArea;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import audio.inverted.AudioType;
|
||||
import controller.ConfigurableNode;
|
||||
|
||||
public class TalkgroupIDEditor extends JPanel implements ActionListener
|
||||
@@ -38,19 +35,15 @@ public class TalkgroupIDEditor extends JPanel implements ActionListener
|
||||
private static final long serialVersionUID = 1L;
|
||||
private TalkgroupIDNode mTalkgroupIDNode;
|
||||
|
||||
private JLabel mLabelName;
|
||||
private JTextField mTextTalkgroupID;
|
||||
private JLabel mLabelAudio;
|
||||
private JComboBox<AudioType> mComboAudioTypes;
|
||||
|
||||
private String mHelpText = "Enter a formatted talkgroup identifier.\n\n"
|
||||
+ "P25 Talkgroup Format: hexadecimal value, 4 characters for "
|
||||
+ "talkgroup or 6 characters for a mobile radio\n\n"
|
||||
+ "LTR Talkgroup Format: A-HH-TTT (A=Area, H=Home Repeater, "
|
||||
+ "T=Talkgroup)\n\n"
|
||||
+ "Passport Talkgroup Format: xxxxx (up to 5-digit number)\n\n"
|
||||
+ "Wildcard: use one or more asterisks (*) for any talkgroup digits.\n\n"
|
||||
+ "Audio: if the talkgroup uses simple frequency inversion, specify"
|
||||
+ " the inversion frequency from the dropdown list to automatically"
|
||||
+ " un-invert audio for this specific talkgroup";
|
||||
+ "Wildcard: use one or more asterisks (*) for any talkgroup digits.";
|
||||
|
||||
public TalkgroupIDEditor( TalkgroupIDNode talkgroupIDNode )
|
||||
{
|
||||
@@ -72,22 +65,6 @@ public class TalkgroupIDEditor extends JPanel implements ActionListener
|
||||
|
||||
add( mTextTalkgroupID, "growx,push" );
|
||||
|
||||
add( new JLabel( "Audio:" ) );
|
||||
|
||||
mComboAudioTypes = new JComboBox<AudioType>();
|
||||
|
||||
mComboAudioTypes.setModel(
|
||||
new DefaultComboBoxModel<AudioType>( AudioType.values() ) );
|
||||
|
||||
AudioType audioType = mTalkgroupIDNode.getTalkgroupID().getAudioType();
|
||||
|
||||
if( audioType != null )
|
||||
{
|
||||
mComboAudioTypes.setSelectedItem( audioType );
|
||||
}
|
||||
|
||||
add( mComboAudioTypes, "growx,push" );
|
||||
|
||||
JButton btnSave = new JButton( "Save" );
|
||||
btnSave.addActionListener( TalkgroupIDEditor.this );
|
||||
add( btnSave, "growx,push" );
|
||||
@@ -117,14 +94,6 @@ public class TalkgroupIDEditor extends JPanel implements ActionListener
|
||||
|
||||
((ConfigurableNode)mTalkgroupIDNode.getParent()).sort();
|
||||
|
||||
AudioType selected = mComboAudioTypes
|
||||
.getItemAt( mComboAudioTypes.getSelectedIndex() );
|
||||
|
||||
if( selected != null )
|
||||
{
|
||||
mTalkgroupIDNode.getTalkgroupID().setAudioType( selected );
|
||||
}
|
||||
|
||||
mTalkgroupIDNode.save();
|
||||
|
||||
mTalkgroupIDNode.show();
|
||||
@@ -139,9 +108,6 @@ public class TalkgroupIDEditor extends JPanel implements ActionListener
|
||||
{
|
||||
mTextTalkgroupID.setText(
|
||||
mTalkgroupIDNode.getTalkgroupID().getTalkgroup() );
|
||||
|
||||
mComboAudioTypes.setSelectedItem(
|
||||
mTalkgroupIDNode.getTalkgroupID().getAudioType() );
|
||||
}
|
||||
|
||||
mTalkgroupIDNode.refresh();
|
||||
|
||||
@@ -48,8 +48,11 @@ public class AudioChannelPanel extends JPanel
|
||||
private JLabel mFromLabel = new JLabel( "FROM:" );
|
||||
private JLabel mFrom = new JLabel( "" );
|
||||
private JLabel mFromAlias = new JLabel( "" );
|
||||
|
||||
private enum AudioStatus { STARTED,STOPPED };
|
||||
private AudioStatus mAudioStatus = AudioStatus.STOPPED;
|
||||
|
||||
private boolean mConfigured = false;
|
||||
private AudioMetadata mAudioMetadata;
|
||||
|
||||
public AudioChannelPanel( SettingsManager settingsManager, AudioOutput audioOutput )
|
||||
{
|
||||
@@ -134,15 +137,19 @@ public class AudioChannelPanel extends JPanel
|
||||
{
|
||||
switch( audioEvent.getType() )
|
||||
{
|
||||
case AUDIO_STOPPED:
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
case AUDIO_STARTED:
|
||||
synchronized( mAudioStatus )
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
resetLabels();
|
||||
}
|
||||
} );
|
||||
mAudioStatus = AudioStatus.STARTED;
|
||||
updateLabels();
|
||||
}
|
||||
break;
|
||||
case AUDIO_STOPPED:
|
||||
synchronized( mAudioStatus )
|
||||
{
|
||||
mAudioStatus = AudioStatus.STOPPED;
|
||||
resetLabels();
|
||||
}
|
||||
break;
|
||||
case AUDIO_MUTED:
|
||||
case AUDIO_UNMUTED:
|
||||
@@ -166,13 +173,55 @@ public class AudioChannelPanel extends JPanel
|
||||
*/
|
||||
private void resetLabels()
|
||||
{
|
||||
mFrom.setText( "" );
|
||||
updateAlias( mFromAlias, null );
|
||||
|
||||
mTo.setText( "" );
|
||||
updateAlias( mToAlias, null );
|
||||
|
||||
mConfigured = false;
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mFrom.setText( "" );
|
||||
updateAlias( mFromAlias, null );
|
||||
|
||||
mTo.setText( "" );
|
||||
updateAlias( mToAlias, null );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
private void updateLabels()
|
||||
{
|
||||
if( mAudioMetadata != null )
|
||||
{
|
||||
final Metadata from = mAudioMetadata.getMetadata( MetadataType.FROM );
|
||||
|
||||
final Metadata to = mAudioMetadata.getMetadata( MetadataType.TO );
|
||||
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if( from != null )
|
||||
{
|
||||
mFrom.setText( from.getValue() );
|
||||
updateAlias( mFromAlias, from.getAlias() );
|
||||
}
|
||||
else
|
||||
{
|
||||
mFrom.setText( "-----" );
|
||||
}
|
||||
|
||||
if( to != null )
|
||||
{
|
||||
mTo.setText( to.getValue() );
|
||||
updateAlias( mToAlias, to.getAlias() );
|
||||
}
|
||||
else
|
||||
{
|
||||
mTo.setText( "-----" );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,32 +263,17 @@ public class AudioChannelPanel extends JPanel
|
||||
@Override
|
||||
public void receive( AudioMetadata audioMetadata )
|
||||
{
|
||||
if( !mConfigured || audioMetadata.isUpdated() )
|
||||
mAudioMetadata = audioMetadata;
|
||||
|
||||
if( audioMetadata.isUpdated() )
|
||||
{
|
||||
final Metadata from = audioMetadata.getMetadata( MetadataType.FROM );
|
||||
|
||||
final Metadata to = audioMetadata.getMetadata( MetadataType.TO );
|
||||
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
synchronized( mAudioStatus )
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
if( mAudioStatus == AudioStatus.STARTED )
|
||||
{
|
||||
if( from != null )
|
||||
{
|
||||
mFrom.setText( from.getValue() );
|
||||
updateAlias( mFromAlias, from.getAlias() );
|
||||
}
|
||||
|
||||
if( to != null )
|
||||
{
|
||||
mTo.setText( to.getValue() );
|
||||
updateAlias( mToAlias, to.getAlias() );
|
||||
}
|
||||
updateLabels();
|
||||
}
|
||||
} );
|
||||
|
||||
mConfigured = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -17,16 +17,12 @@ package audio;
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
|
||||
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,10 +33,12 @@ import javax.sound.sampled.Mixer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import properties.SystemProperties;
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import source.mixer.MixerChannel;
|
||||
import source.mixer.MixerChannelConfiguration;
|
||||
import source.mixer.MixerManager;
|
||||
import audio.AudioEvent.Type;
|
||||
import audio.output.AudioOutput;
|
||||
import audio.output.MonoAudioOutput;
|
||||
@@ -52,8 +50,10 @@ 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 int AUDIO_TIMEOUT = 1000; //1 second
|
||||
|
||||
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 +62,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 +85,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 +156,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 +173,44 @@ 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.isInactive() &&
|
||||
mChannelConnectionMap.containsKey( connection.getSource() ) )
|
||||
{
|
||||
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 +228,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 +263,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 +329,243 @@ 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()
|
||||
{
|
||||
try
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
mLog.error( "Encountered error while processing audio packets", e );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio output connection manages a connection between a source and an audio
|
||||
* output and maintains current state information about the audio activity
|
||||
* received form the source.
|
||||
*/
|
||||
public class AudioOutputConnection implements Listener<AudioEvent>
|
||||
{
|
||||
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 + AUDIO_TIMEOUT < 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package audio.output;
|
||||
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.sound.sampled.FloatControl;
|
||||
import javax.sound.sampled.LineListener;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -12,23 +11,26 @@ import org.slf4j.LoggerFactory;
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import audio.AudioEvent;
|
||||
import audio.AudioEvent.Type;
|
||||
import audio.AudioPacket;
|
||||
import audio.metadata.AudioMetadata;
|
||||
import controller.ThreadPoolManager;
|
||||
|
||||
public abstract class AudioOutput implements Listener<AudioPacket>
|
||||
public abstract class AudioOutput implements Listener<AudioPacket>, LineListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( AudioOutput.class );
|
||||
|
||||
private AtomicBoolean mSquelched = new AtomicBoolean( false );
|
||||
private Listener<AudioMetadata> mAudioMetadataListener;
|
||||
|
||||
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 +43,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();
|
||||
@@ -72,19 +71,6 @@ public abstract class AudioOutput implements Listener<AudioPacket>
|
||||
mAudioEventBroadcaster.broadcast( audioEvent );
|
||||
}
|
||||
|
||||
public void setSquelched( boolean squelched )
|
||||
{
|
||||
mSquelched.set( squelched );
|
||||
|
||||
broadcast( new AudioEvent( squelched ? Type.AUDIO_SQUELCHED :
|
||||
Type.AUDIO_UNSQUELCHED, getChannelName() ) );
|
||||
}
|
||||
|
||||
public boolean isSquelched()
|
||||
{
|
||||
return mSquelched.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a single listener to receive audio start and audio stop events
|
||||
*/
|
||||
|
||||
@@ -5,13 +5,14 @@ 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;
|
||||
|
||||
import javax.sound.sampled.BooleanControl;
|
||||
import javax.sound.sampled.Control;
|
||||
import javax.sound.sampled.FloatControl;
|
||||
import javax.sound.sampled.LineEvent;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.Mixer;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
@@ -24,8 +25,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
|
||||
@@ -50,14 +51,14 @@ public class MonoAudioOutput extends AudioOutput
|
||||
private SourceDataLine mOutput;
|
||||
private FloatControl mGainControl;
|
||||
private BooleanControl mMuteControl;
|
||||
|
||||
private AudioEvent mAudioStartEvent;
|
||||
private AudioEvent mAudioStopEvent;
|
||||
private AudioEvent mAudioContinuationEvent;
|
||||
private ScheduledFuture<?> mProcessorTask;
|
||||
|
||||
public MonoAudioOutput( Mixer mixer )
|
||||
public MonoAudioOutput( ThreadPoolManager threadPoolManager, Mixer mixer )
|
||||
{
|
||||
super();
|
||||
super( threadPoolManager );
|
||||
|
||||
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
|
||||
getChannelName() );
|
||||
@@ -74,15 +75,8 @@ public class MonoAudioOutput extends AudioOutput
|
||||
if( mOutput != null )
|
||||
{
|
||||
mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO, BUFFER_SIZE );
|
||||
|
||||
mOutput.addLineListener( this );
|
||||
mCanProcessAudio = true;
|
||||
|
||||
mExecutorService = Executors.newSingleThreadScheduledExecutor(
|
||||
new NamingThreadFactory( "audio (mono) output" ) );
|
||||
|
||||
/* Run the queue processor task every 40 milliseconds */
|
||||
mExecutorService.scheduleAtFixedRate( new QueueProcessor(),
|
||||
0, 40, TimeUnit.MILLISECONDS );
|
||||
}
|
||||
}
|
||||
catch ( LineUnavailableException e )
|
||||
@@ -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,102 +124,108 @@ 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 )
|
||||
for( AudioPacket packet: packets )
|
||||
{
|
||||
if( isSquelched() )
|
||||
broadcast( mAudioContinuationEvent );
|
||||
|
||||
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( packet.getType() == Type.AUDIO )
|
||||
{
|
||||
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
|
||||
}
|
||||
|
||||
mOutput.write( buffer.array(), 0,
|
||||
buffer.array().length );
|
||||
|
||||
checkStart();
|
||||
|
||||
broadcast( packet.getAudioMetadata() );
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
private void checkStart()
|
||||
{
|
||||
if( mCanProcessAudio && !isSquelched() &&
|
||||
if( mCanProcessAudio &&
|
||||
!mOutput.isRunning() && mOutput.available() <= START_THRESHOLD )
|
||||
{
|
||||
mOutput.start();
|
||||
|
||||
mAudioEventBroadcaster.broadcast( mAudioStartEvent );
|
||||
}
|
||||
}
|
||||
|
||||
private void checkStop()
|
||||
{
|
||||
if( mCanProcessAudio && mOutput.isRunning() &&
|
||||
( mOutput.available() >= STOP_THRESHOLD || isSquelched() ) )
|
||||
mOutput.available() >= STOP_THRESHOLD )
|
||||
{
|
||||
mOutput.drain();
|
||||
mOutput.stop();
|
||||
|
||||
mAudioEventBroadcaster.broadcast( mAudioStopEvent );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,4 +264,19 @@ public class MonoAudioOutput extends AudioOutput
|
||||
{
|
||||
return mGainControl != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update( LineEvent event )
|
||||
{
|
||||
LineEvent.Type type = event.getType();
|
||||
|
||||
if( type == LineEvent.Type.START )
|
||||
{
|
||||
mAudioEventBroadcaster.broadcast( mAudioStartEvent );
|
||||
}
|
||||
else if( type == LineEvent.Type.STOP )
|
||||
{
|
||||
mAudioEventBroadcaster.broadcast( mAudioStopEvent );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@ 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;
|
||||
|
||||
import javax.sound.sampled.BooleanControl;
|
||||
import javax.sound.sampled.Control;
|
||||
import javax.sound.sampled.FloatControl;
|
||||
import javax.sound.sampled.LineEvent;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.Mixer;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
@@ -24,7 +25,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
|
||||
@@ -39,9 +41,9 @@ public class StereoAudioOutput extends AudioOutput
|
||||
private final static int BUFFER_SIZE = 96000;
|
||||
|
||||
/* The queue processor will run every 20 milliseconds checking for inbound
|
||||
* audio and automatically starting or stopping audio playback. We set the
|
||||
* audio and automatically start or stop audio playback. We set the
|
||||
* data line available space threshold to start when we're 20 ms away from
|
||||
* being full, and the stop threshold when 20 ms away from being empty */
|
||||
* being full and the stop threshold when 20 ms away from being empty */
|
||||
private final static int SAMPLE_SIZE_40_MS = BUFFER_SIZE / 25;
|
||||
private final static int START_THRESHOLD = SAMPLE_SIZE_40_MS;
|
||||
private final static int STOP_THRESHOLD = BUFFER_SIZE - SAMPLE_SIZE_40_MS;
|
||||
@@ -49,16 +51,16 @@ public class StereoAudioOutput extends AudioOutput
|
||||
private SourceDataLine mOutput;
|
||||
private FloatControl mGainControl;
|
||||
private BooleanControl mMuteControl;
|
||||
|
||||
private AudioEvent mAudioStartEvent;
|
||||
private AudioEvent mAudioStopEvent;
|
||||
private AudioEvent mAudioContinuationEvent;
|
||||
|
||||
private MixerChannel mMixerChannel;
|
||||
private ScheduledFuture<?> mProcessorTask;
|
||||
|
||||
public StereoAudioOutput( Mixer mixer, MixerChannel channel )
|
||||
public StereoAudioOutput( ThreadPoolManager threadPoolManager,
|
||||
Mixer mixer, MixerChannel channel )
|
||||
{
|
||||
super();
|
||||
super( threadPoolManager );
|
||||
|
||||
assert( channel == MixerChannel.LEFT || channel == MixerChannel.RIGHT );
|
||||
|
||||
@@ -79,6 +81,7 @@ public class StereoAudioOutput extends AudioOutput
|
||||
if( mOutput != null )
|
||||
{
|
||||
mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO, BUFFER_SIZE );
|
||||
mOutput.addLineListener( this );
|
||||
mCanProcessAudio = true;
|
||||
}
|
||||
}
|
||||
@@ -113,12 +116,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 +131,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,72 +189,72 @@ 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 )
|
||||
|
||||
for( AudioPacket packet: packets )
|
||||
{
|
||||
if( isSquelched() )
|
||||
broadcast( mAudioContinuationEvent );
|
||||
|
||||
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( 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 );
|
||||
}
|
||||
}
|
||||
|
||||
private void checkStart()
|
||||
{
|
||||
if( mCanProcessAudio && !isSquelched() &&
|
||||
if( mCanProcessAudio &&
|
||||
!mOutput.isRunning() && mOutput.available() <= START_THRESHOLD )
|
||||
{
|
||||
mOutput.start();
|
||||
|
||||
mAudioEventBroadcaster.broadcast( mAudioStartEvent );
|
||||
}
|
||||
}
|
||||
|
||||
private void checkStop()
|
||||
{
|
||||
if( mCanProcessAudio && mOutput.isRunning() &&
|
||||
( mOutput.available() >= STOP_THRESHOLD || isSquelched() ) )
|
||||
mOutput.available() >= STOP_THRESHOLD )
|
||||
{
|
||||
mOutput.drain();
|
||||
mOutput.stop();
|
||||
|
||||
mAudioEventBroadcaster.broadcast( mAudioStopEvent );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,4 +293,20 @@ public class StereoAudioOutput extends AudioOutput
|
||||
{
|
||||
return mGainControl != null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void update( LineEvent event )
|
||||
{
|
||||
LineEvent.Type type = event.getType();
|
||||
|
||||
if( type == LineEvent.Type.START )
|
||||
{
|
||||
mAudioEventBroadcaster.broadcast( mAudioStartEvent );
|
||||
}
|
||||
else if( type == LineEvent.Type.STOP )
|
||||
{
|
||||
mAudioEventBroadcaster.broadcast( mAudioStopEvent );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,16 @@ public class MessageFramer implements Listener<Boolean>,
|
||||
mMessageLength = messageLength;
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
for( MessageAssembler assembler: mMessageAssemblers )
|
||||
{
|
||||
assembler.dispose();
|
||||
}
|
||||
|
||||
mMessageAssemblers.clear();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mBroadcaster.dispose();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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,20 +61,16 @@ public class ThreadPoolManager
|
||||
public void scheduleOnce( Runnable command, long delay, TimeUnit unit )
|
||||
throws RejectedExecutionException
|
||||
{
|
||||
if( mExecutor == null )
|
||||
{
|
||||
mExecutor = Executors.newScheduledThreadPool( THREAD_POOL_SIZE,
|
||||
new NamingThreadFactory( "sdrtrunk" ) );
|
||||
}
|
||||
|
||||
mExecutor.schedule( command, delay, unit );
|
||||
}
|
||||
|
||||
public void cancel( ScheduledFuture<?> task )
|
||||
public boolean cancel( ScheduledFuture<?> task )
|
||||
{
|
||||
task.cancel( true );
|
||||
boolean success = task.cancel( true );
|
||||
|
||||
mTasks.remove( task );
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public int getTaskCount( ThreadType type )
|
||||
|
||||
@@ -297,6 +297,9 @@ public class Channel extends Configuration
|
||||
|
||||
/* Broadcast channel deleted event */
|
||||
fireChannelEvent( Event.CHANNEL_DELETED );
|
||||
|
||||
mProcessingChain.dispose();
|
||||
mProcessingChain = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -809,8 +812,7 @@ public class Channel extends Configuration
|
||||
{
|
||||
if( mProcessingChain == null )
|
||||
{
|
||||
mProcessingChain = new ProcessingChain(
|
||||
mResourceManager.getThreadPoolManager() );
|
||||
mProcessingChain = new ProcessingChain( getName() );
|
||||
|
||||
/* Get the optional alias list for the decode modules to use */
|
||||
AliasList aliasList = mResourceManager.getPlaylistManager()
|
||||
@@ -901,6 +903,8 @@ public class Channel extends Configuration
|
||||
{
|
||||
if( !mEnabled && mProcessingChain != null )
|
||||
{
|
||||
fireChannelEvent( Event.CHANNEL_PROCESSING_STOPPED );
|
||||
|
||||
mProcessingChain.stop();
|
||||
|
||||
if( mChannelType == ChannelType.STANDARD )
|
||||
@@ -910,8 +914,6 @@ public class Channel extends Configuration
|
||||
}
|
||||
|
||||
mSelected = false;
|
||||
|
||||
fireChannelEvent( Event.CHANNEL_PROCESSING_STOPPED );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -421,7 +421,7 @@ public class SDRTrunk
|
||||
|
||||
if( version != null )
|
||||
{
|
||||
sb.append( " V" );
|
||||
sb.append( " v" );
|
||||
sb.append( version );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@ import source.Source;
|
||||
|
||||
public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable
|
||||
{
|
||||
public InstrumentableProcessingChain()
|
||||
public InstrumentableProcessingChain( String name )
|
||||
{
|
||||
super( name );
|
||||
}
|
||||
|
||||
public void setSource( Source source ) throws IllegalStateException
|
||||
|
||||
@@ -99,7 +99,7 @@ public class DecoderViewFrame extends JInternalFrame
|
||||
|
||||
mDecodeConfig = decodeConfig;
|
||||
|
||||
mProcessingChain = new InstrumentableProcessingChain();
|
||||
mProcessingChain = new InstrumentableProcessingChain( "instrumented" );
|
||||
|
||||
List<Module> modules = DecoderFactory
|
||||
.getModules( ChannelType.STANDARD,
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -19,7 +19,8 @@ package module;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import message.IMessageListener;
|
||||
@@ -61,7 +62,7 @@ import audio.metadata.Metadata;
|
||||
import audio.squelch.ISquelchStateListener;
|
||||
import audio.squelch.ISquelchStateProvider;
|
||||
import audio.squelch.SquelchState;
|
||||
import controller.ThreadPoolManager;
|
||||
import controller.NamingThreadFactory;
|
||||
import controller.channel.ChannelEvent;
|
||||
import controller.channel.IChannelEventListener;
|
||||
import controller.channel.IChannelEventProvider;
|
||||
@@ -99,24 +100,19 @@ public class ProcessingChain implements IChannelEventListener
|
||||
private Broadcaster<RealBuffer> mRealBufferBroadcaster = new Broadcaster<>();
|
||||
private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
|
||||
|
||||
private ThreadPoolManager mThreadPoolManager;
|
||||
private ScheduledFuture<?> mBufferProcessingTask;
|
||||
private ScheduledExecutorService mScheduledExecutorService;
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
|
||||
protected Source mSource;
|
||||
private List<Module> mModules = new ArrayList<>();
|
||||
private IFrequencyCorrectionController mFrequencyCorrectionController;
|
||||
|
||||
public ProcessingChain()
|
||||
public ProcessingChain( String name )
|
||||
{
|
||||
this( new ThreadPoolManager() );
|
||||
mScheduledExecutorService = Executors.newScheduledThreadPool( 1,
|
||||
new NamingThreadFactory( "channel " + name ) );
|
||||
}
|
||||
|
||||
public ProcessingChain( ThreadPoolManager threadManager )
|
||||
{
|
||||
mThreadPoolManager = threadManager;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
stop();
|
||||
@@ -128,14 +124,9 @@ public class ProcessingChain implements IChannelEventListener
|
||||
|
||||
mModules.clear();
|
||||
|
||||
// mComplexReceiver.dispose();
|
||||
// mComplexReceiver = null;
|
||||
//
|
||||
// mRealReceiver.dispose();
|
||||
// mRealReceiver = null;
|
||||
mScheduledExecutorService.shutdownNow();
|
||||
mScheduledExecutorService = null;
|
||||
|
||||
mThreadPoolManager = null;
|
||||
mBufferProcessingTask = null;
|
||||
mFrequencyCorrectionController = null;
|
||||
|
||||
mAudioPacketBroadcaster.dispose();
|
||||
@@ -447,9 +438,11 @@ public class ProcessingChain implements IChannelEventListener
|
||||
* of the decoder state's */
|
||||
if( mSource instanceof TunerChannelSource )
|
||||
{
|
||||
TunerChannelSource tunerChannelSource = (TunerChannelSource)mSource;
|
||||
|
||||
try
|
||||
{
|
||||
long frequency = ((TunerChannelSource)mSource).getFrequency();
|
||||
long frequency = tunerChannelSource.getFrequency();
|
||||
|
||||
mDecoderStateEventBroadcaster.broadcast(
|
||||
new DecoderStateEvent( this, Event.SOURCE_FREQUENCY,
|
||||
@@ -459,6 +452,8 @@ public class ProcessingChain implements IChannelEventListener
|
||||
{
|
||||
mLog.error( "Error getting frequency from tuner channel source", e );
|
||||
}
|
||||
|
||||
tunerChannelSource.start( mScheduledExecutorService );
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -496,12 +491,6 @@ public class ProcessingChain implements IChannelEventListener
|
||||
mSource = null;
|
||||
}
|
||||
|
||||
if( mBufferProcessingTask != null )
|
||||
{
|
||||
mThreadPoolManager.cancel( mBufferProcessingTask );
|
||||
mBufferProcessingTask = null;
|
||||
}
|
||||
|
||||
/* Stop each of the modules */
|
||||
for( Module module: mModules )
|
||||
{
|
||||
|
||||
@@ -228,7 +228,8 @@ public class DecoderFactory
|
||||
Sync sync = mptConfig.getSync();
|
||||
|
||||
modules.add( new MPT1327Decoder( aliasList, sync ) );
|
||||
modules.add( new MPT1327DecoderState( aliasList, channelMap, channelType ) );
|
||||
modules.add( new MPT1327DecoderState( aliasList, channelMap,
|
||||
channelType, mptConfig.getCallTimeout() * 1000 ) );
|
||||
|
||||
if( channelType == ChannelType.STANDARD )
|
||||
{
|
||||
|
||||
@@ -310,6 +310,11 @@ public abstract class CallEvent implements Comparable<CallEvent>
|
||||
@Override
|
||||
public int compareTo( CallEvent other )
|
||||
{
|
||||
if( other != null && other == this )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Long.compare( mEventStart, other.getEventStartTime() );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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() )
|
||||
|
||||
@@ -112,6 +112,7 @@ public class LTRNetDecoder extends Decoder implements IRealBufferListener, Instr
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
mLTRMessageFramer.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -76,6 +76,7 @@ public class LTRStandardDecoder extends Decoder implements IRealBufferListener
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
mLTRMessageFramer.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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,128 @@ 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 );
|
||||
//Home channel is 31 for call end -- use the free channel
|
||||
//as the call end channel
|
||||
int repeater = end.getFree();
|
||||
|
||||
mLCNTracker.processCallChannel( repeater );
|
||||
|
||||
if( mLCNTracker.isCurrentChannel( repeater ) )
|
||||
{
|
||||
mTalkgroup = end.getToID();
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||
|
||||
processTalkgroup( mTalkgroup );
|
||||
|
||||
mTalkgroupAlias = end.getToIDAlias();
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
||||
|
||||
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 +214,7 @@ public class LTRStandardDecoderState extends DecoderState
|
||||
mActiveCalls.clear();
|
||||
mTalkgroupsFirstHeard.clear();
|
||||
mTalkgroups.clear();
|
||||
mActiveLCNs.clear();
|
||||
mChannelNumber = 0;
|
||||
mLCNTracker.reset();
|
||||
|
||||
resetState();
|
||||
}
|
||||
@@ -256,13 +224,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 +257,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 +297,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 +330,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 +347,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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
67
src/module/decode/ltrstandard/message/CallEndMessage.java
Normal file
67
src/module/decode/ltrstandard/message/CallEndMessage.java
Normal 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();
|
||||
}
|
||||
}
|
||||
68
src/module/decode/ltrstandard/message/CallMessage.java
Normal file
68
src/module/decode/ltrstandard/message/CallMessage.java
Normal 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();
|
||||
}
|
||||
}
|
||||
58
src/module/decode/ltrstandard/message/IdleMessage.java
Normal file
58
src/module/decode/ltrstandard/message/IdleMessage.java
Normal 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();
|
||||
}
|
||||
}
|
||||
213
src/module/decode/ltrstandard/message/LTRStandardMessage.java
Normal file
213
src/module/decode/ltrstandard/message/LTRStandardMessage.java
Normal 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();
|
||||
}
|
||||
}
|
||||
58
src/module/decode/ltrstandard/message/UnknownMessage.java
Normal file
58
src/module/decode/ltrstandard/message/UnknownMessage.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -301,8 +301,8 @@ public class MPT1327Decoder extends Decoder
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
mControlMessageFramer.reset();
|
||||
mTrafficMessageFramer.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -26,9 +26,11 @@ import java.util.TreeSet;
|
||||
|
||||
import message.Message;
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
import module.decode.event.CallEvent;
|
||||
import module.decode.event.CallEvent.CallEventType;
|
||||
import module.decode.mpt1327.MPT1327Message.IdentType;
|
||||
import module.decode.state.ChangeChannelTimeoutEvent;
|
||||
import module.decode.state.ChangedAttribute;
|
||||
import module.decode.state.DecoderState;
|
||||
import module.decode.state.DecoderStateEvent;
|
||||
@@ -41,6 +43,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
import audio.metadata.Metadata;
|
||||
import audio.metadata.MetadataType;
|
||||
import controller.channel.Channel.ChannelType;
|
||||
import controller.channel.map.ChannelMap;
|
||||
|
||||
@@ -60,15 +64,18 @@ public class MPT1327DecoderState extends DecoderState
|
||||
private ChannelMap mChannelMap;
|
||||
|
||||
private long mFrequency = 0;
|
||||
private long mCallTimeout;
|
||||
|
||||
public MPT1327DecoderState( AliasList aliasList, ChannelMap channelMap,
|
||||
ChannelType channelType )
|
||||
public MPT1327DecoderState( AliasList aliasList,
|
||||
ChannelMap channelMap,
|
||||
ChannelType channelType,
|
||||
long callTimeout )
|
||||
{
|
||||
super( aliasList );
|
||||
|
||||
mChannelMap = channelMap;
|
||||
|
||||
mChannelType = channelType;
|
||||
mCallTimeout = callTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -252,8 +259,42 @@ public class MPT1327DecoderState extends DecoderState
|
||||
|
||||
/* Traffic Channel Events */
|
||||
case CLEAR:
|
||||
mChannelNumber = mpt.getChannel();
|
||||
|
||||
broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
|
||||
break;
|
||||
case MAINT:
|
||||
broadcast( new DecoderStateEvent( this, Event.END, State.END ) );
|
||||
if( mChannelType == ChannelType.STANDARD )
|
||||
{
|
||||
/**
|
||||
* When we receive a MAINT message and we're configured
|
||||
* as a standard channel, we need to apply the call
|
||||
* timeout specified by the user. Otherwise we'll
|
||||
* be using the shorter default call timeout
|
||||
*/
|
||||
broadcast( new ChangeChannelTimeoutEvent( this,
|
||||
mChannelType, mCallTimeout ) );
|
||||
|
||||
if( mCurrentCallEvent == null )
|
||||
{
|
||||
mCurrentCallEvent = new MPT1327CallEvent.Builder( CallEventType.CALL )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( String.valueOf( mChannelNumber ) )
|
||||
.details( "MONITORED TRAFFIC CHANNEL" )
|
||||
.frequency( mFrequency )
|
||||
.to( mpt.getToID() )
|
||||
.build();
|
||||
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
|
||||
broadcast(new Metadata(MetadataType.TO,
|
||||
mpt.getToID(), mpt.getToIDAlias(), true));
|
||||
|
||||
broadcast( new DecoderStateEvent( this, Event.START, State.CALL ) );
|
||||
|
||||
setToTalkgroup( mpt.getToID() );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -276,6 +317,25 @@ public class MPT1327DecoderState extends DecoderState
|
||||
|
||||
mToTalkgroup = null;
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||
|
||||
/**
|
||||
* If this is a standard channel, reset the fade timeout to the default
|
||||
* timeout. Once processing is underway, if we get a MAINT message,
|
||||
* this indicates we're processing a traffic channel as a standard
|
||||
* channel, so we'll issue a different call timeout then.
|
||||
*/
|
||||
if( mChannelType == ChannelType.STANDARD )
|
||||
{
|
||||
broadcast( new ChangeChannelTimeoutEvent( this, mChannelType,
|
||||
DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000 ) );
|
||||
|
||||
if( mCurrentCallEvent != null )
|
||||
{
|
||||
mCurrentCallEvent.end();
|
||||
broadcast( mCurrentCallEvent );
|
||||
mCurrentCallEvent = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getSite()
|
||||
|
||||
@@ -42,6 +42,8 @@ import module.decode.p25.message.ldu.lc.TelephoneInterconnectVoiceChannelUser;
|
||||
import module.decode.p25.message.ldu.lc.UnitToUnitVoiceChannelUser;
|
||||
import module.decode.p25.message.pdu.PDUMessage;
|
||||
import module.decode.p25.message.pdu.confirmed.PDUConfirmedMessage;
|
||||
import module.decode.p25.message.pdu.confirmed.PDUTypeUnknown;
|
||||
import module.decode.p25.message.pdu.confirmed.PacketData;
|
||||
import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextAccept;
|
||||
import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextReject;
|
||||
import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextRequest;
|
||||
@@ -118,7 +120,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;
|
||||
@@ -406,12 +407,12 @@ public class P25DecoderState extends DecoderState
|
||||
{
|
||||
if( hdu.isValid() )
|
||||
{
|
||||
State state = hdu.isEncrypted() ? State.ENCRYPTED : State.CALL;
|
||||
|
||||
broadcast( new DecoderStateEvent( this, Event.START, state ) );
|
||||
broadcast( new DecoderStateEvent( this, Event.START, State.CALL ) );
|
||||
|
||||
String to = hdu.getToID();
|
||||
|
||||
|
||||
updateTo( to );
|
||||
|
||||
if( mCurrentCallEvent == null )
|
||||
{
|
||||
mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.CALL )
|
||||
@@ -425,7 +426,6 @@ public class P25DecoderState extends DecoderState
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
|
||||
updateTo( to );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,7 +437,7 @@ public class P25DecoderState extends DecoderState
|
||||
{
|
||||
if( tdulc.getOpcode() == LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION )
|
||||
{
|
||||
broadcast( new DecoderStateEvent( this, Event.END, State.END ) );
|
||||
broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
|
||||
|
||||
if( mCurrentCallEvent != null )
|
||||
{
|
||||
@@ -615,12 +615,14 @@ public class P25DecoderState extends DecoderState
|
||||
module.decode.p25.message.tdu.lc.GroupVoiceChannelUser gvcuser =
|
||||
(module.decode.p25.message.tdu.lc.GroupVoiceChannelUser)tdulc;
|
||||
|
||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION,
|
||||
( gvcuser.isEncrypted() ? State.ENCRYPTED : State.CALL ) ) );
|
||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CALL ) );
|
||||
|
||||
String from = gvcuser.getSourceAddress();
|
||||
String to = gvcuser.getGroupAddress();
|
||||
|
||||
updateFrom( from );
|
||||
updateTo( to );
|
||||
|
||||
if( mCurrentCallEvent == null )
|
||||
{
|
||||
mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.GROUP_CALL )
|
||||
@@ -636,9 +638,6 @@ public class P25DecoderState extends DecoderState
|
||||
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
|
||||
updateFrom( from );
|
||||
updateTo( to );
|
||||
}
|
||||
break;
|
||||
case MESSAGE_UPDATE:
|
||||
@@ -845,6 +844,8 @@ public class P25DecoderState extends DecoderState
|
||||
|
||||
String to = tivcu.getAddress();
|
||||
|
||||
updateTo( to );
|
||||
|
||||
if( mCurrentCallEvent == null )
|
||||
{
|
||||
mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.TELEPHONE_INTERCONNECT )
|
||||
@@ -858,8 +859,6 @@ public class P25DecoderState extends DecoderState
|
||||
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
|
||||
updateTo( to );
|
||||
}
|
||||
break;
|
||||
case UNIT_AUTHENTICATION_COMMAND:
|
||||
@@ -922,7 +921,10 @@ public class P25DecoderState extends DecoderState
|
||||
(module.decode.p25.message.tdu.lc.UnitToUnitVoiceChannelUser)tdulc;
|
||||
|
||||
String from = uuvcu.getSourceAddress();
|
||||
updateFrom( from );
|
||||
|
||||
String to = uuvcu.getTargetAddress();
|
||||
updateTo( to );
|
||||
|
||||
if( mCurrentCallEvent != null )
|
||||
{
|
||||
@@ -936,9 +938,6 @@ public class P25DecoderState extends DecoderState
|
||||
.to( to )
|
||||
.build();
|
||||
}
|
||||
|
||||
updateFrom( from );
|
||||
updateTo( to );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -950,35 +949,14 @@ public class P25DecoderState extends DecoderState
|
||||
* Log optional vendor format messages that we don't yet support so that we
|
||||
* can understand those messages and eventually add support.
|
||||
*/
|
||||
private void logAlternateVendorMessage( P25Message p25 )
|
||||
private void logAlternateVendorMessage( P25Message message )
|
||||
{
|
||||
// if( p25 instanceof TDULinkControlMessage )
|
||||
// if( message.isValid() )
|
||||
// {
|
||||
// TDULinkControlMessage tdulc = (TDULinkControlMessage)p25;
|
||||
//
|
||||
// mLog.debug( "TDULC - VENDOR [" + tdulc.getVendor() +
|
||||
// "] SPECIFIC FORMAT FOR " + tdulc.getVendorOpcode().getLabel() +
|
||||
// " " + tdulc.getBinaryMessage() );
|
||||
// }
|
||||
// else if( p25 instanceof LDU1Message )
|
||||
// {
|
||||
// LDU1Message ldu1 = (LDU1Message)p25;
|
||||
//
|
||||
// mLog.debug( "LDU1 - VENDOR [" + ldu1.getVendor() +
|
||||
// "] SPECIFIC FORMAT FOR " + ldu1.getVendorOpcode().getLabel() +
|
||||
// " " + ldu1.getBinaryMessage() );
|
||||
// }
|
||||
// else if( p25 instanceof PDUMessage )
|
||||
// {
|
||||
// PDUMessage pdu = (PDUMessage)p25;
|
||||
//
|
||||
// mLog.debug( "PDU - VENDOR [" + pdu.getVendor() +
|
||||
// "] SPECIFIC FORMAT FOR " + pdu.getVendorOpcode().getLabel() +
|
||||
// " " + pdu.getBinaryMessage() );
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// mLog.debug( "P25 - ALTERNATE VENDOR MESSAGE [" + p25.getClass() + "]" );
|
||||
// mLog.info( "PLEASE NOTIFY DEVELOPER - UNRECOGNIZED P25 VENDOR FORMAT -"
|
||||
// + " DUID:" + message.getDUID()
|
||||
// + " MSG:" + message.getBinaryMessage()
|
||||
// + " CLASS:" + message.getClass() );
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -989,15 +967,11 @@ public class P25DecoderState extends DecoderState
|
||||
*/
|
||||
private void processLDU( LDUMessage ldu )
|
||||
{
|
||||
State state = ( ldu.isValid() && ldu.isEncrypted() ) ?
|
||||
State.ENCRYPTED : State.CALL;
|
||||
|
||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, state ) );
|
||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CALL ) );
|
||||
|
||||
if( mCurrentCallEvent == null )
|
||||
{
|
||||
mCurrentCallEvent = new P25CallEvent.Builder(
|
||||
( state == State.CALL ) ? CallEventType.CALL : CallEventType.ENCRYPTED_CALL )
|
||||
mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.CALL )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( mCurrentChannel )
|
||||
.frequency( mCurrentChannelFrequency )
|
||||
@@ -1057,7 +1031,7 @@ public class P25DecoderState extends DecoderState
|
||||
}
|
||||
break;
|
||||
case CALL_TERMINATION_OR_CANCELLATION:
|
||||
broadcast( new DecoderStateEvent( this, Event.END, State.END ) );
|
||||
broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
|
||||
mCurrentCallEvent = null;
|
||||
|
||||
if( !( ldu instanceof CallTermination ) )
|
||||
@@ -1115,42 +1089,43 @@ public class P25DecoderState extends DecoderState
|
||||
}
|
||||
break;
|
||||
case GROUP_VOICE_CHANNEL_UPDATE:
|
||||
if( mChannelType == ChannelType.STANDARD &&
|
||||
ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate )
|
||||
if( ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate )
|
||||
{
|
||||
module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate gvcu =
|
||||
(module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate)ldu;
|
||||
(module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate)ldu;
|
||||
|
||||
String userA = gvcu.getGroupAddressA();
|
||||
|
||||
if( !mCallDetectTalkgroups.contains( userA ) )
|
||||
{
|
||||
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( gvcu.getChannelA() )
|
||||
.details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) )
|
||||
.frequency( gvcu.getDownlinkFrequencyA() )
|
||||
.to( userA )
|
||||
.build() );
|
||||
|
||||
mCallDetectTalkgroups.add( userA );
|
||||
}
|
||||
|
||||
String userB = gvcu.getGroupAddressB();
|
||||
|
||||
if( userB != null &&
|
||||
!userB.contentEquals( "0000" ) &&
|
||||
!mCallDetectTalkgroups.contains( userB ) )
|
||||
if( mChannelType == ChannelType.STANDARD )
|
||||
{
|
||||
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( gvcu.getChannelB() )
|
||||
.details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) )
|
||||
.frequency( gvcu.getDownlinkFrequencyB() )
|
||||
.to( userB )
|
||||
.build() );
|
||||
if( !mCallDetectTalkgroups.contains( userA ) )
|
||||
{
|
||||
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( gvcu.getChannelA() )
|
||||
.details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) )
|
||||
.frequency( gvcu.getDownlinkFrequencyA() )
|
||||
.to( userA )
|
||||
.build() );
|
||||
|
||||
mCallDetectTalkgroups.add( userA );
|
||||
}
|
||||
|
||||
mCallDetectTalkgroups.add( userB );
|
||||
if( userB != null &&
|
||||
!userB.contentEquals( "0000" ) &&
|
||||
!mCallDetectTalkgroups.contains( userB ) )
|
||||
{
|
||||
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( gvcu.getChannelB() )
|
||||
.details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) )
|
||||
.frequency( gvcu.getDownlinkFrequencyB() )
|
||||
.to( userB )
|
||||
.build() );
|
||||
|
||||
mCallDetectTalkgroups.add( userB );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1159,25 +1134,27 @@ public class P25DecoderState extends DecoderState
|
||||
}
|
||||
break;
|
||||
case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT:
|
||||
if( mChannelType == ChannelType.STANDARD &&
|
||||
ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit )
|
||||
if( ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit )
|
||||
{
|
||||
module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit gvcue =
|
||||
(module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit)ldu;
|
||||
|
||||
String group = gvcue.getGroupAddress();
|
||||
|
||||
if( !mCallDetectTalkgroups.contains( group ) )
|
||||
if( mChannelType == ChannelType.STANDARD )
|
||||
{
|
||||
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( gvcue.getTransmitChannel() )
|
||||
.details( ( gvcue.isEncrypted() ? "ENCRYPTED" : "" ) )
|
||||
.frequency( gvcue.getDownlinkFrequency() )
|
||||
.to( group )
|
||||
.build() );
|
||||
|
||||
mCallDetectTalkgroups.add( group );
|
||||
if( !mCallDetectTalkgroups.contains( group ) )
|
||||
{
|
||||
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( gvcue.getTransmitChannel() )
|
||||
.details( ( gvcue.isEncrypted() ? "ENCRYPTED" : "" ) )
|
||||
.frequency( gvcue.getDownlinkFrequency() )
|
||||
.to( group )
|
||||
.build() );
|
||||
|
||||
mCallDetectTalkgroups.add( group );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1187,29 +1164,32 @@ public class P25DecoderState extends DecoderState
|
||||
break;
|
||||
case GROUP_VOICE_CHANNEL_USER:
|
||||
/* Indicates the current user of the current channel */
|
||||
if( mChannelType == ChannelType.STANDARD &&
|
||||
ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUser )
|
||||
if( ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUser )
|
||||
{
|
||||
module.decode.p25.message.ldu.lc.GroupVoiceChannelUser gvcuser =
|
||||
(module.decode.p25.message.ldu.lc.GroupVoiceChannelUser)ldu;
|
||||
|
||||
if( mCurrentCallEvent.getCallEventType() != CallEventType.GROUP_CALL )
|
||||
{
|
||||
mCurrentCallEvent.setCallEventType( CallEventType.GROUP_CALL );
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
|
||||
if( mCurrentCallEvent.getDetails() == null )
|
||||
{
|
||||
mCurrentCallEvent.setDetails(
|
||||
( gvcuser.isEncrypted() ? "ENCRYPTED " : "" ) +
|
||||
( gvcuser.isEmergency() ? "EMERGENCY " : "" ) );
|
||||
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
|
||||
updateFrom( gvcuser.getSourceAddress() );
|
||||
|
||||
updateTo( gvcuser.getGroupAddress() );
|
||||
|
||||
if( mChannelType == ChannelType.STANDARD )
|
||||
{
|
||||
if( mCurrentCallEvent.getCallEventType() != CallEventType.GROUP_CALL )
|
||||
{
|
||||
mCurrentCallEvent.setCallEventType( CallEventType.GROUP_CALL );
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
|
||||
if( mCurrentCallEvent.getDetails() == null )
|
||||
{
|
||||
mCurrentCallEvent.setDetails(
|
||||
( gvcuser.isEncrypted() ? "ENCRYPTED " : "" ) +
|
||||
( gvcuser.isEmergency() ? "EMERGENCY " : "" ) );
|
||||
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1405,6 +1385,8 @@ public class P25DecoderState extends DecoderState
|
||||
TelephoneInterconnectVoiceChannelUser tivcu =
|
||||
(TelephoneInterconnectVoiceChannelUser)ldu;
|
||||
|
||||
updateTo( tivcu.getAddress() );
|
||||
|
||||
if( mCurrentCallEvent.getCallEventType() != CallEventType.TELEPHONE_INTERCONNECT )
|
||||
{
|
||||
mCurrentCallEvent.setCallEventType( CallEventType.TELEPHONE_INTERCONNECT );
|
||||
@@ -1419,8 +1401,6 @@ public class P25DecoderState extends DecoderState
|
||||
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
|
||||
updateTo( tivcu.getAddress() );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1486,6 +1466,10 @@ public class P25DecoderState extends DecoderState
|
||||
UnitToUnitVoiceChannelUser uuvcu =
|
||||
(UnitToUnitVoiceChannelUser)ldu;
|
||||
|
||||
updateFrom( uuvcu.getSourceAddress() );
|
||||
|
||||
updateTo( uuvcu.getTargetAddress() );
|
||||
|
||||
if( mCurrentCallEvent.getCallEventType() != CallEventType.UNIT_TO_UNIT_CALL )
|
||||
{
|
||||
mCurrentCallEvent.setCallEventType( CallEventType.UNIT_TO_UNIT_CALL );
|
||||
@@ -1500,9 +1484,6 @@ public class P25DecoderState extends DecoderState
|
||||
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
|
||||
updateFrom( uuvcu.getSourceAddress() );
|
||||
updateTo( uuvcu.getTargetAddress() );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1521,7 +1502,7 @@ public class P25DecoderState extends DecoderState
|
||||
private void processTSBK( TSBKMessage tsbk )
|
||||
{
|
||||
/* Trunking Signalling Block Messages - indicates Control Channel */
|
||||
broadcast( new DecoderStateEvent( this, Event.START, State.CONTROL ) );
|
||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
|
||||
|
||||
if( tsbk.getVendor() == Vendor.STANDARD )
|
||||
{
|
||||
@@ -1650,7 +1631,11 @@ public class P25DecoderState extends DecoderState
|
||||
*/
|
||||
private void processPDU( PDUMessage pdu )
|
||||
{
|
||||
if( pdu instanceof PDUConfirmedMessage )
|
||||
if( pdu instanceof PacketData || pdu instanceof PDUTypeUnknown )
|
||||
{
|
||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.DATA ) );
|
||||
}
|
||||
else if( pdu instanceof PDUConfirmedMessage )
|
||||
{
|
||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.DATA ) );
|
||||
|
||||
@@ -1658,7 +1643,7 @@ public class P25DecoderState extends DecoderState
|
||||
|
||||
switch( pduc.getPDUType() )
|
||||
{
|
||||
case SN_ACTIVATE_TDS_CONTEXT_ACCEPT:
|
||||
case SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT:
|
||||
SNDCPActivateTDSContextAccept satca =
|
||||
(SNDCPActivateTDSContextAccept)pduc;
|
||||
|
||||
@@ -1670,7 +1655,7 @@ public class P25DecoderState extends DecoderState
|
||||
.to( satca.getLogicalLinkID() )
|
||||
.build() );
|
||||
break;
|
||||
case SN_ACTIVATE_TDS_CONTEXT_REJECT:
|
||||
case SNDCP_ACTIVATE_TDS_CONTEXT_REJECT:
|
||||
SNDCPActivateTDSContextReject satcr =
|
||||
(SNDCPActivateTDSContextReject)pduc;
|
||||
|
||||
@@ -1695,7 +1680,7 @@ public class P25DecoderState extends DecoderState
|
||||
.from( satcreq.getLogicalLinkID() )
|
||||
.build() );
|
||||
break;
|
||||
case SN_DEACTIVATE_TDS_CONTEXT_ACCEPT:
|
||||
case SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT:
|
||||
SNDCPDeactivateTDSContext sdtca =
|
||||
(SNDCPDeactivateTDSContext)pduc;
|
||||
|
||||
@@ -1707,7 +1692,7 @@ public class P25DecoderState extends DecoderState
|
||||
.from( sdtca.getLogicalLinkID() )
|
||||
.build() );
|
||||
break;
|
||||
case SN_DEACTIVATE_TDS_CONTEXT_REQUEST:
|
||||
case SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST:
|
||||
SNDCPDeactivateTDSContext sdtcreq =
|
||||
(SNDCPDeactivateTDSContext)pduc;
|
||||
|
||||
@@ -1719,7 +1704,7 @@ public class P25DecoderState extends DecoderState
|
||||
.from( sdtcreq.getLogicalLinkID() )
|
||||
.build() );
|
||||
break;
|
||||
case SN_RF_CONFIRMED_DATA:
|
||||
case SNDCP_RF_CONFIRMED_DATA:
|
||||
SNDCPUserData sud = (SNDCPUserData)pduc;
|
||||
|
||||
StringBuilder sbFrom = new StringBuilder();
|
||||
@@ -1748,8 +1733,10 @@ public class P25DecoderState extends DecoderState
|
||||
.to( pduc.getLogicalLinkID() )
|
||||
.build() );
|
||||
break;
|
||||
case SNDCP_RF_UNCONFIRMED_DATA:
|
||||
break;
|
||||
default:
|
||||
mLog.debug( "PDUC - Unrecognized Message: " + pduc.toString() );
|
||||
// mLog.debug( "PDUC - Unrecognized Message: " + pduc.toString() );
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -3100,16 +3087,9 @@ public class P25DecoderState extends DecoderState
|
||||
if( mToTalkgroup == null || !mToTalkgroup.contentEquals( to ) )
|
||||
{
|
||||
mToTalkgroup = to;
|
||||
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||
|
||||
if( mCurrentCallEvent != null &&
|
||||
( mCurrentCallEvent.getToID() == null ||
|
||||
!mCurrentCallEvent.getToID().contentEquals( to ) ) )
|
||||
{
|
||||
mCurrentCallEvent.setToID( to );
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
mToAlias = getAliasList().getTalkgroupAlias( mToTalkgroup );
|
||||
@@ -3118,12 +3098,20 @@ public class P25DecoderState extends DecoderState
|
||||
{
|
||||
mToAlias = null;
|
||||
}
|
||||
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
||||
}
|
||||
|
||||
/* Send audio metadata update */
|
||||
broadcast( new Metadata( MetadataType.TO, mToTalkgroup, mToAlias, true ) );
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
||||
|
||||
/* Send audio metadata update */
|
||||
broadcast( new Metadata( MetadataType.TO, mToTalkgroup, mToAlias, true ) );
|
||||
|
||||
if( mCurrentCallEvent != null &&
|
||||
( mCurrentCallEvent.getToID() == null ||
|
||||
!mCurrentCallEvent.getToID().contentEquals( to ) ) )
|
||||
{
|
||||
mCurrentCallEvent.setToID( to );
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3139,14 +3127,6 @@ public class P25DecoderState extends DecoderState
|
||||
mFromTalkgroup = from;
|
||||
broadcast( ChangedAttribute.FROM_TALKGROUP );
|
||||
|
||||
if( mCurrentCallEvent != null &&
|
||||
mCurrentCallEvent.getFromID() == null ||
|
||||
!mCurrentCallEvent.getFromID().contentEquals( from ) )
|
||||
{
|
||||
mCurrentCallEvent.setFromID( from );
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
mFromAlias = getAliasList().getTalkgroupAlias( mFromTalkgroup );
|
||||
@@ -3157,10 +3137,19 @@ public class P25DecoderState extends DecoderState
|
||||
}
|
||||
|
||||
broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS );
|
||||
|
||||
/* Send audio metadata update */
|
||||
broadcast( new Metadata( MetadataType.FROM, mFromTalkgroup, mFromAlias, true ) );
|
||||
|
||||
if( mCurrentCallEvent != null &&
|
||||
mCurrentCallEvent.getFromID() == null ||
|
||||
!mCurrentCallEvent.getFromID().contentEquals( from ) )
|
||||
{
|
||||
mCurrentCallEvent.setFromID( from );
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
}
|
||||
|
||||
/* Send audio metadata update */
|
||||
broadcast( new Metadata( MetadataType.FROM, mFromTalkgroup, mFromAlias, true ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -289,6 +289,7 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
else
|
||||
{
|
||||
mComplete = true;
|
||||
dispatch( new P25Message( mMessage.copy(), duid, mAliasList ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -87,6 +87,8 @@ public class P25Message extends Message
|
||||
sb.append( " " );
|
||||
sb.append( getDUID().getLabel() );
|
||||
|
||||
sb.append( " DATA UNIT ID:" + mMessage.getInt( DUID ) );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package module.decode.p25.message.pdu;
|
||||
|
||||
import module.decode.p25.message.pdu.confirmed.PacketData;
|
||||
import module.decode.p25.message.pdu.osp.control.AdjacentStatusBroadcastExtended;
|
||||
import module.decode.p25.message.pdu.osp.control.CallAlertExtended;
|
||||
import module.decode.p25.message.pdu.osp.control.GroupAffiliationQueryExtended;
|
||||
@@ -121,7 +122,8 @@ public class PDUMessageFactory
|
||||
}
|
||||
|
||||
return new PDUMessage( message, duid, aliasList );
|
||||
|
||||
case PACKET_DATA:
|
||||
return new PacketData( message, aliasList );
|
||||
case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL:
|
||||
return new PDUMessage( message, duid, aliasList );
|
||||
default:
|
||||
|
||||
@@ -48,19 +48,27 @@ public class PDUConfirmedMessageFactory
|
||||
case UNENCRYPTED_USER_DATA:
|
||||
switch( message.getPDUType() )
|
||||
{
|
||||
case SN_ACTIVATE_TDS_CONTEXT_ACCEPT:
|
||||
case SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT:
|
||||
return new SNDCPActivateTDSContextAccept( message );
|
||||
case SN_ACTIVATE_TDS_CONTEXT_REJECT:
|
||||
case SNDCP_ACTIVATE_TDS_CONTEXT_REJECT:
|
||||
return new SNDCPActivateTDSContextReject( message );
|
||||
case SN_ACTIVATE_TDS_CONTEXT_REQUEST:
|
||||
return new SNDCPActivateTDSContextRequest( message );
|
||||
case SN_DEACTIVATE_TDS_CONTEXT_ACCEPT:
|
||||
case SN_DEACTIVATE_TDS_CONTEXT_REQUEST:
|
||||
case SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT:
|
||||
case SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST:
|
||||
return new SNDCPDeactivateTDSContext( message );
|
||||
case SN_RF_CONFIRMED_DATA:
|
||||
case SNDCP_RF_CONFIRMED_DATA:
|
||||
return new SNDCPUserData( message );
|
||||
case SN_RF_UNCONFIRMED_DATA:
|
||||
case SNDCP_RF_UNCONFIRMED_DATA:
|
||||
break;
|
||||
case PDU_TYPE_6:
|
||||
case PDU_TYPE_7:
|
||||
case PDU_TYPE_8:
|
||||
case PDU_TYPE_9:
|
||||
case PDU_TYPE_10:
|
||||
case PDU_TYPE_11:
|
||||
return new PDUTypeUnknown( message );
|
||||
case UNKNOWN:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package module.decode.p25.message.pdu.confirmed;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PDUTypeUnknown extends PDUConfirmedMessage
|
||||
{
|
||||
public final static Logger mLog =
|
||||
LoggerFactory.getLogger( PDUTypeUnknown.class );
|
||||
|
||||
public PDUTypeUnknown( PDUConfirmedMessage message )
|
||||
{
|
||||
super( message );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "NAC:" );
|
||||
sb.append( getNAC() );
|
||||
sb.append( " LLID:" );
|
||||
sb.append( getLogicalLinkID() );
|
||||
sb.append( " PACKET DATA UNIT CONFIRMED - " );
|
||||
sb.append( getPDUType().getLabel() );
|
||||
sb.append( " CRC[" );
|
||||
sb.append( getErrorStatus() );
|
||||
sb.append( "]" );
|
||||
|
||||
sb.append( " PACKET #" );
|
||||
sb.append( getPacketSequenceNumber() );
|
||||
|
||||
if( isFinalFragment() && getFragmentSequenceNumber() == 0 )
|
||||
{
|
||||
sb.append( ".C" );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( "." );
|
||||
sb.append( getFragmentSequenceNumber() );
|
||||
|
||||
if( isFinalFragment() )
|
||||
{
|
||||
sb.append( "C" );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( " " );
|
||||
|
||||
sb.append( mMessage.toString() );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
66
src/module/decode/p25/message/pdu/confirmed/PacketData.java
Normal file
66
src/module/decode/p25/message/pdu/confirmed/PacketData.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package module.decode.p25.message.pdu.confirmed;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import alias.AliasList;
|
||||
import bits.BinaryMessage;
|
||||
|
||||
public class PacketData extends PDUConfirmedMessage
|
||||
{
|
||||
public final static Logger mLog =
|
||||
LoggerFactory.getLogger( PacketData.class );
|
||||
|
||||
public static final int DATA_BLOCK_START = 176;
|
||||
|
||||
public PacketData( BinaryMessage message, AliasList aliasList )
|
||||
{
|
||||
super( message, aliasList );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "NAC:" );
|
||||
sb.append( getNAC() );
|
||||
sb.append( " PDUC LLID:" );
|
||||
sb.append( getLogicalLinkID() );
|
||||
|
||||
|
||||
sb.append( " PACKET DATA" );
|
||||
sb.append( " CRC[" );
|
||||
sb.append( getErrorStatus() );
|
||||
sb.append( "]" );
|
||||
|
||||
if( !mMessage.get( FINAL_FRAGMENT_FLAG ) )
|
||||
{
|
||||
sb.append( " RESENDING" );
|
||||
}
|
||||
|
||||
sb.append( " PACKET #" );
|
||||
sb.append( getPacketSequenceNumber() );
|
||||
|
||||
if( isFinalFragment() && getFragmentSequenceNumber() == 0 )
|
||||
{
|
||||
sb.append( ".C" );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( "." );
|
||||
sb.append( getFragmentSequenceNumber() );
|
||||
|
||||
if( isFinalFragment() )
|
||||
{
|
||||
sb.append( "C" );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( " " );
|
||||
|
||||
sb.append( mMessage.toString() );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public class SNDCPDeactivateTDSContext extends PDUConfirmedMessage
|
||||
|
||||
public String getDeactivationType()
|
||||
{
|
||||
if( getPDUType() == PDUType.SN_DEACTIVATE_TDS_CONTEXT_REQUEST )
|
||||
if( getPDUType() == PDUType.SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST )
|
||||
{
|
||||
return mMessage.getInt( DEACTIVATION_TYPE ) == 0 ?
|
||||
"ALL NSAPIS" : "THIS NSAPI";
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -10,9 +10,11 @@ public enum DataUnitID
|
||||
HDU ( 0, 712, false, "HDU ", "Header Data Unit" ),
|
||||
TDU ( 3, 64, false, "TDU ", "Simple Terminator Data Unit" ),
|
||||
LDU1 ( 5, 1632, true, "LDU1 ", "Logical Link Data Unit 1" ),
|
||||
VSELP1( 6, 1680, false, "VSEL1", "Motorola VSELP Unit 1" ),
|
||||
TSBK1( 7, 260, false, "TSBK1", "Trunking Signaling Block" ),
|
||||
TSBK2( 7, 260, false, "TSBK2", "Trunking Signaling Block" ),
|
||||
TSBK3( 7, 260, false, "TSBK3", "Trunking Signaling Block" ),
|
||||
VSELP2( 9, 1680, false, "VSEL1", "Motorola VSELP Unit 1" ),
|
||||
LDU2 ( 10, 1632, true, "LDU2 ", "Logical Link Data Unit 2" ),
|
||||
PDU0 ( 12, 260, false, "PDU0 ", "Packet Header Data Unit" ),
|
||||
PDU1 ( 12, 356, false, "PDU1 ", "Packet Data Unit-Unconfirmed" ),
|
||||
|
||||
@@ -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 ),
|
||||
|
||||
@@ -3,12 +3,18 @@ package module.decode.p25.reference;
|
||||
public enum PDUType
|
||||
{
|
||||
/* Outbound */
|
||||
SN_ACTIVATE_TDS_CONTEXT_ACCEPT( "SN-ACTIVATE TDS CONTEXT ACCEPT", 0 ),
|
||||
SN_DEACTIVATE_TDS_CONTEXT_ACCEPT( "SN-DEACTIVATE TDS CONTEXT ACCEPT", 1 ),
|
||||
SN_DEACTIVATE_TDS_CONTEXT_REQUEST( "SN-DEACTIVATE TDS CONTEXT REQUEST", 2 ),
|
||||
SN_ACTIVATE_TDS_CONTEXT_REJECT( "SN-ACTIVATE TDS CONTEXT REJECT", 3 ),
|
||||
SN_RF_UNCONFIRMED_DATA( "SN-RF UNCONFIRMED DATA", 4 ),
|
||||
SN_RF_CONFIRMED_DATA( "SN-RF CONFIRMED DATA", 5 ),
|
||||
SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT( "SNDCP-ACTIVATE TDS CONTEXT ACCEPT", 0 ),
|
||||
SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT( "SNDCP-DEACTIVATE TDS CONTEXT ACCEPT", 1 ),
|
||||
SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST( "SNDCP-DEACTIVATE TDS CONTEXT REQUEST", 2 ),
|
||||
SNDCP_ACTIVATE_TDS_CONTEXT_REJECT( "SNDCP-ACTIVATE TDS CONTEXT REJECT", 3 ),
|
||||
SNDCP_RF_UNCONFIRMED_DATA( "SNDCP-RF UNCONFIRMED DATA", 4 ),
|
||||
SNDCP_RF_CONFIRMED_DATA( "SNDCP-RF CONFIRMED DATA", 5 ),
|
||||
PDU_TYPE_6( "PDU TYPE 6 UNKNOWN", 6 ),
|
||||
PDU_TYPE_7( "PDU TYPE 7 UNKNOWN", 7 ),
|
||||
PDU_TYPE_8( "PDU TYPE 8 UNKNOWN", 8 ),
|
||||
PDU_TYPE_9( "PDU TYPE 9 UNKNOWN", 9 ),
|
||||
PDU_TYPE_10( "PDU TYPE 10 UNKNOWN", 10 ),
|
||||
PDU_TYPE_11( "PDU TYPE 11 UNKNOWN", 11 ),
|
||||
|
||||
/* Inbound */
|
||||
SN_ACTIVATE_TDS_CONTEXT_REQUEST( "SN-ACTIVATE TDS CONTEXT REQUEST", 0 ),
|
||||
@@ -36,7 +42,7 @@ public enum PDUType
|
||||
|
||||
public static PDUType fromValue( int value, boolean outbound )
|
||||
{
|
||||
if( outbound && 0 <= value && value <= 5 )
|
||||
if( outbound && 0 <= value && value <= 11 )
|
||||
{
|
||||
return values()[ value ];
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package module.decode.p25.reference;
|
||||
|
||||
public enum ServiceAccessPoint
|
||||
{
|
||||
UNENCRYPTED_USER_DATA( "USER DATA", 0 ),
|
||||
UNENCRYPTED_USER_DATA( "UNENCRYPTED USER DATA", 0 ),
|
||||
ENCRYPTED_USER_DATA( "ENCRYPTED USER DATA", 1 ),
|
||||
CIRCUIT_DATA( "CIRCUIT DATA", 2 ),
|
||||
CIRCUIT_DATA_CONTROL( "CIRCUIT DATA CONTROL", 3 ),
|
||||
|
||||
@@ -103,6 +103,7 @@ public class PassportDecoder extends Decoder
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
mPassportMessageFramer.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
29
src/module/decode/state/ChangeChannelTimeoutEvent.java
Normal file
29
src/module/decode/state/ChangeChannelTimeoutEvent.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package module.decode.state;
|
||||
|
||||
import controller.channel.Channel.ChannelType;
|
||||
|
||||
/**
|
||||
* Allows decoders to change the call timeout setting in the channel state
|
||||
*/
|
||||
public class ChangeChannelTimeoutEvent extends DecoderStateEvent
|
||||
{
|
||||
private ChannelType mChannelType;
|
||||
private long mCallTimeout;
|
||||
|
||||
public ChangeChannelTimeoutEvent( Object source, ChannelType channelType, long timeout )
|
||||
{
|
||||
super( source, Event.CHANGE_CALL_TIMEOUT, State.IDLE );
|
||||
mChannelType = channelType;
|
||||
mCallTimeout = timeout;
|
||||
}
|
||||
|
||||
public ChannelType getChannelType()
|
||||
{
|
||||
return mChannelType;
|
||||
}
|
||||
|
||||
public long getCallTimeout()
|
||||
{
|
||||
return mCallTimeout;
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,20 +253,6 @@ public class ChannelList extends JPanel implements ChannelEventListener
|
||||
idleMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
|
||||
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_IDLE ) );
|
||||
|
||||
JMenu noTunerMenu = new JMenu( "No Tuner" );
|
||||
channelsMenu.add( noTunerMenu );
|
||||
|
||||
noTunerMenu.add( new ColorSettingMenuItem( mSettingsManager,
|
||||
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER ) );
|
||||
noTunerMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
|
||||
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER ) );
|
||||
noTunerMenu.add( new JSeparator() );
|
||||
|
||||
noTunerMenu.add( new ColorSettingMenuItem( mSettingsManager,
|
||||
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER ) );
|
||||
noTunerMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
|
||||
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER ) );
|
||||
|
||||
JMenu labelMenu = new JMenu( "Labels" );
|
||||
colorMenu.add( labelMenu );
|
||||
|
||||
|
||||
@@ -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,20 +17,14 @@
|
||||
******************************************************************************/
|
||||
package module.decode.state;
|
||||
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import module.Module;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
import module.decode.event.CallEvent;
|
||||
import module.decode.event.ICallEventProvider;
|
||||
import module.decode.state.DecoderStateEvent.Event;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
import audio.metadata.IMetadataProvider;
|
||||
import audio.metadata.Metadata;
|
||||
import audio.metadata.MetadataReset;
|
||||
@@ -40,38 +34,13 @@ import audio.squelch.SquelchState;
|
||||
import controller.ThreadPoolManager;
|
||||
import controller.ThreadPoolManager.ThreadType;
|
||||
import controller.channel.Channel.ChannelType;
|
||||
import module.Module;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
import module.decode.event.CallEvent;
|
||||
import module.decode.event.ICallEventProvider;
|
||||
import module.decode.state.DecoderStateEvent.Event;
|
||||
import sample.Listener;
|
||||
|
||||
/**
|
||||
* Channel state tracks the overall state of all processing modules and decoders
|
||||
* configured for the channel and provides squelch control events.
|
||||
*
|
||||
* Primary channel state provides a state machine for tracking decode events
|
||||
* during a voice or data call through it's life cycle of CALL/DATA, FADE, END,
|
||||
* and then back to an IDLE state. This class also provides squelch control
|
||||
* to support any audio processing.
|
||||
*
|
||||
* State Descriptions:
|
||||
* IDLE No voice or data call activity
|
||||
* CALL/DATA A voice or data call has started, or is continuing
|
||||
* CONTROL Control channel
|
||||
* FADE The phase after a voice or data call when either an explicit
|
||||
* call end has been received, or when no new signalling updates
|
||||
* have been received, and the fade timer has expired. This phase
|
||||
* allows for gui updates to signal to the user that the call is
|
||||
* ended, while continuing to display the call details for the user
|
||||
* END The phase after the voice or data call FADE, when the call is
|
||||
* now over, and the gui should be reset to IDLE.
|
||||
*
|
||||
* When a call is started, invoke mTimerService.startFadeTimer() to start the
|
||||
* call fade monitoring timer process that will automatically signal a call end
|
||||
* once no more call signalling has been detected after the fade timeout has
|
||||
* occurred, by invoking the fade() method.
|
||||
*
|
||||
* When a call ends, invoke the mTimerService.stopFadeTimer() and then invoke
|
||||
* the mTimerService.startResetTimer(). Once the reset timeout occurs,
|
||||
* the reset() method will be called, allowing the channel state to perform
|
||||
* any reset actions, like clearing status and id labels on any view panels.
|
||||
*/
|
||||
public class ChannelState extends Module implements ICallEventProvider,
|
||||
IChangedAttributeProvider, IDecoderStateEventListener,
|
||||
IDecoderStateEventProvider, IMetadataProvider, ISquelchStateProvider
|
||||
@@ -83,8 +52,6 @@ public class ChannelState extends Module implements ICallEventProvider,
|
||||
public static final long RESET_TIMEOUT_DELAY = 2000;
|
||||
|
||||
private State mState = State.IDLE;
|
||||
private long mTrafficChannelTimeout =
|
||||
DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS;
|
||||
|
||||
private Listener<CallEvent> mCallEventListener;
|
||||
private Listener<ChangedAttribute> mChangedAttributeListener;
|
||||
@@ -97,12 +64,15 @@ public class ChannelState extends Module implements ICallEventProvider,
|
||||
private boolean mSquelchLocked = false;
|
||||
private boolean mSelected = false;
|
||||
|
||||
private long mStandardChannelFadeTimeout = FADE_TIMEOUT_DELAY;
|
||||
private long mTrafficChannelFadeTimeout =
|
||||
DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000;
|
||||
private long mFadeTimeout;
|
||||
private long mResetTimeout;
|
||||
private long mEndTimeout;
|
||||
|
||||
private StateMonitor mStateMonitor = new StateMonitor();
|
||||
private AtomicBoolean mMonitoring = new AtomicBoolean();
|
||||
private ScheduledFuture<?> mRunningMonitor;
|
||||
private ScheduledFuture<?> mStateMonitorFuture;
|
||||
|
||||
private ThreadPoolManager mThreadPoolManager;
|
||||
private ChannelType mChannelType;
|
||||
@@ -110,32 +80,97 @@ public class ChannelState extends Module implements ICallEventProvider,
|
||||
private TrafficChannelStatusListener mTrafficChannelStatusListener;
|
||||
private CallEvent mTrafficChannelCallEvent;
|
||||
|
||||
/**
|
||||
* Channel state tracks the overall state of all processing modules and decoders
|
||||
* configured for the channel and provides squelch control and decoder state
|
||||
* reset events.
|
||||
*
|
||||
* Uses a state enumeration that defines allowable channel state transitions in
|
||||
* order to track a call or data decode event from start to finish. Uses a
|
||||
* timer to monitor for inactivity and to provide a FADE period that indicates
|
||||
* to the user that the activity has stopped while continuing to provide details
|
||||
* about the call, before the state is reset to IDLE.
|
||||
*
|
||||
* Since this class is multi-threaded between decoder events and the internal
|
||||
* timer that monitors for inactivity, this class uses the mState object for
|
||||
* thread synchronization.
|
||||
*
|
||||
* State Descriptions:
|
||||
* IDLE Normal state. No voice or data call activity
|
||||
* CALL/DATA/ENCRYPTED/CONTROL
|
||||
* Decoding states.
|
||||
* FADE The phase after a voice or data call when either an explicit
|
||||
* call end has been received, or when no new signalling updates
|
||||
* have been received, and the fade timer has expired. This phase
|
||||
* allows for gui updates to signal to the user that the call is
|
||||
* ended, while continuing to display the call details for the user
|
||||
* TEARDOWN Indicates a traffic channel that will be torn down for reuse.
|
||||
*/
|
||||
public ChannelState( ThreadPoolManager manager, ChannelType channelType )
|
||||
{
|
||||
mThreadPoolManager = manager;
|
||||
mChannelType = channelType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this channel state and prepares it for reuse.
|
||||
*/
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) );
|
||||
broadcast( new MetadataReset() );
|
||||
|
||||
mState = State.IDLE;
|
||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
//Start a monitoring task to check for FADE and END event timeouts
|
||||
if( mMonitoring.compareAndSet( false, true ) )
|
||||
{
|
||||
if( mStateMonitorFuture == null && mStateMonitor != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
mStateMonitorFuture = mThreadPoolManager.scheduleFixedRate(
|
||||
ThreadType.DECODER, mStateMonitor, 20, TimeUnit.MILLISECONDS );
|
||||
}
|
||||
catch( RejectedExecutionException ree )
|
||||
{
|
||||
mLog.error( "state monitor scheduled task rejected", ree );
|
||||
mMonitoring.set( false );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
mSquelchLocked = false;
|
||||
|
||||
//Shutdown the state montoring task
|
||||
if( mMonitoring.compareAndSet( true, false ) )
|
||||
{
|
||||
if( mStateMonitorFuture != null )
|
||||
{
|
||||
boolean success = mThreadPoolManager.cancel( mStateMonitorFuture );
|
||||
|
||||
if( !success )
|
||||
{
|
||||
mLog.error("Couldn't stop monitoring scheduled future" );
|
||||
}
|
||||
}
|
||||
|
||||
mStateMonitorFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
stopMonitor();
|
||||
|
||||
mCallEventListener = null;
|
||||
mChangedAttributeListener = null;
|
||||
mDecoderStateListener = null;
|
||||
@@ -144,9 +179,34 @@ public class ChannelState extends Module implements ICallEventProvider,
|
||||
mStateMonitor = null;
|
||||
}
|
||||
|
||||
private boolean isStandardChannel()
|
||||
{
|
||||
return mChannelType == ChannelType.STANDARD;
|
||||
}
|
||||
|
||||
private boolean isTrafficChannel()
|
||||
{
|
||||
return mChannelType == ChannelType.TRAFFIC;
|
||||
}
|
||||
|
||||
public void setStandardChannelTimeout( long milliseconds )
|
||||
{
|
||||
mStandardChannelFadeTimeout = milliseconds;
|
||||
|
||||
if( mChannelType == ChannelType.STANDARD )
|
||||
{
|
||||
mFadeTimeout = mStandardChannelFadeTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTrafficChannelTimeout( long milliseconds )
|
||||
{
|
||||
mTrafficChannelTimeout = milliseconds;
|
||||
mTrafficChannelFadeTimeout = milliseconds;
|
||||
|
||||
if( mChannelType == ChannelType.TRAFFIC )
|
||||
{
|
||||
mFadeTimeout = mTrafficChannelFadeTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelected( boolean selected )
|
||||
@@ -164,71 +224,18 @@ public class ChannelState extends Module implements ICallEventProvider,
|
||||
return mState;
|
||||
}
|
||||
|
||||
private boolean isIdleOrEndingState()
|
||||
{
|
||||
return mState == State.FADE ||
|
||||
mState == State.END ||
|
||||
mState == State.IDLE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts the state monitor running when we're not in IDLE state. Calls to
|
||||
* start an already started monitor are ignored.
|
||||
*/
|
||||
private void startMonitor()
|
||||
{
|
||||
if( mMonitoring.compareAndSet( false, true ) )
|
||||
{
|
||||
if( mRunningMonitor == null && mStateMonitor != null )
|
||||
{
|
||||
mRunningMonitor = mThreadPoolManager.scheduleFixedRate(
|
||||
ThreadType.DECODER, mStateMonitor, 20, TimeUnit.MILLISECONDS );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException( "Channel state monitor's scheduled "
|
||||
+ "future pointer was not null" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the state monitor when we're in an IDLE state. Calls to stop an
|
||||
* already stopped monitor are ignored.
|
||||
*/
|
||||
private void stopMonitor()
|
||||
{
|
||||
if( mMonitoring.compareAndSet( true, false ) )
|
||||
{
|
||||
if( mRunningMonitor != null )
|
||||
{
|
||||
mThreadPoolManager.cancel( mRunningMonitor );
|
||||
}
|
||||
|
||||
mRunningMonitor = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the fade timeout threshold to the current time plus delay
|
||||
*/
|
||||
private void updateFadeTimeout()
|
||||
{
|
||||
/* Standard channel uses the FADE_TIMEOUT_DELAY and traffic channels
|
||||
* use the TRAFFIC_FADE_TIMEOUT_DELAY. Some traffic channels (e.g. MPT)
|
||||
* don't have signaling on the traffic channel that can be used to
|
||||
* update the fade timeout, so we set the timeout value larger to ensure
|
||||
* the channel doesn't fade before the end of the call. */
|
||||
long current = System.currentTimeMillis();
|
||||
|
||||
long timeout = current +
|
||||
( mChannelType == ChannelType.STANDARD ? FADE_TIMEOUT_DELAY :
|
||||
mTrafficChannelTimeout );
|
||||
|
||||
if( timeout > mFadeTimeout )
|
||||
if( isTrafficChannel() )
|
||||
{
|
||||
mFadeTimeout = timeout;
|
||||
mFadeTimeout = System.currentTimeMillis() + mTrafficChannelFadeTimeout;
|
||||
}
|
||||
else
|
||||
{
|
||||
mFadeTimeout = System.currentTimeMillis() + mStandardChannelFadeTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,13 +244,13 @@ public class ChannelState extends Module implements ICallEventProvider,
|
||||
*/
|
||||
private void updateResetTimeout()
|
||||
{
|
||||
if( mChannelType == ChannelType.TRAFFIC )
|
||||
if( isTrafficChannel() )
|
||||
{
|
||||
mResetTimeout = System.currentTimeMillis();
|
||||
mEndTimeout = System.currentTimeMillis();
|
||||
}
|
||||
else
|
||||
{
|
||||
mResetTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY;
|
||||
mEndTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,120 +292,120 @@ public class ChannelState extends Module implements ICallEventProvider,
|
||||
*/
|
||||
protected void setState( State state )
|
||||
{
|
||||
if( mState == state )
|
||||
synchronized( mState )
|
||||
{
|
||||
switch( state )
|
||||
if( state == mState )
|
||||
{
|
||||
case CALL:
|
||||
case CONTROL:
|
||||
case DATA:
|
||||
case ENCRYPTED:
|
||||
updateFadeTimeout();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if( mState.canChangeTo( state ) )
|
||||
{
|
||||
switch( state )
|
||||
{
|
||||
case CONTROL:
|
||||
//Don't allow traffic channels to be control channels,
|
||||
//otherwise they won't get call tear down
|
||||
if( mChannelType != ChannelType.TRAFFIC )
|
||||
{
|
||||
broadcast( SquelchState.SQUELCH );
|
||||
updateFadeTimeout();
|
||||
startMonitor();
|
||||
mState = state;
|
||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||
}
|
||||
break;
|
||||
case DATA:
|
||||
case ENCRYPTED:
|
||||
broadcast( SquelchState.SQUELCH );
|
||||
updateFadeTimeout();
|
||||
startMonitor();
|
||||
mState = state;
|
||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||
break;
|
||||
case CALL:
|
||||
broadcast( SquelchState.UNSQUELCH );
|
||||
updateFadeTimeout();
|
||||
startMonitor();
|
||||
mState = state;
|
||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||
break;
|
||||
case FADE:
|
||||
broadcast( SquelchState.SQUELCH );
|
||||
updateResetTimeout();
|
||||
startMonitor();
|
||||
mState = state;
|
||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||
break;
|
||||
case END:
|
||||
broadcast( SquelchState.SQUELCH );
|
||||
broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) );
|
||||
broadcast( new MetadataReset() );
|
||||
mState = state;
|
||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||
break;
|
||||
case IDLE:
|
||||
broadcast( SquelchState.SQUELCH );
|
||||
stopMonitor();
|
||||
|
||||
if( mChannelType == ChannelType.TRAFFIC )
|
||||
{
|
||||
if( mTrafficChannelStatusListener != null )
|
||||
{
|
||||
mTrafficChannelStatusListener.callEnd();
|
||||
mTrafficChannelStatusListener = null;
|
||||
}
|
||||
|
||||
if( mTrafficChannelCallEvent != null )
|
||||
{
|
||||
mTrafficChannelCallEvent.end();
|
||||
|
||||
broadcast( mTrafficChannelCallEvent );
|
||||
mTrafficChannelCallEvent = null;
|
||||
}
|
||||
}
|
||||
|
||||
mState = state;
|
||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class StateMonitor implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if( getState() == State.FADE )
|
||||
{
|
||||
if( mResetTimeout <= System.currentTimeMillis() )
|
||||
if( State.CALL_STATES.contains( state ) )
|
||||
{
|
||||
setState( State.END );
|
||||
updateFadeTimeout();
|
||||
}
|
||||
}
|
||||
else if( getState() == State.END )
|
||||
else if( state != mState && mState.canChangeTo( state ) )
|
||||
{
|
||||
setState( State.IDLE );
|
||||
switch( state )
|
||||
{
|
||||
case CONTROL:
|
||||
//Don't allow traffic channels to be control channels,
|
||||
//otherwise they can't transition to call tear down
|
||||
if( isStandardChannel() )
|
||||
{
|
||||
broadcast( SquelchState.SQUELCH );
|
||||
updateFadeTimeout();
|
||||
mState = state;
|
||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||
}
|
||||
break;
|
||||
case DATA:
|
||||
case ENCRYPTED:
|
||||
broadcast( SquelchState.SQUELCH );
|
||||
updateFadeTimeout();
|
||||
mState = state;
|
||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||
break;
|
||||
case CALL:
|
||||
broadcast( SquelchState.UNSQUELCH );
|
||||
updateFadeTimeout();
|
||||
mState = state;
|
||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||
break;
|
||||
case FADE:
|
||||
processFadeState();
|
||||
break;
|
||||
case IDLE:
|
||||
processIdleState();
|
||||
break;
|
||||
case TEARDOWN:
|
||||
processTeardownState();
|
||||
break;
|
||||
case RESET:
|
||||
mState = State.IDLE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( mFadeTimeout <= System.currentTimeMillis() )
|
||||
{
|
||||
setState( State.FADE );
|
||||
}
|
||||
// mLog.debug( "Can't change from [" + mState + "] to [" + state + "]" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state and processes related actions
|
||||
*/
|
||||
private void processFadeState()
|
||||
{
|
||||
updateResetTimeout();
|
||||
mState = State.FADE;
|
||||
|
||||
broadcast( SquelchState.SQUELCH );
|
||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||
}
|
||||
|
||||
private void processIdleState()
|
||||
{
|
||||
broadcast( SquelchState.SQUELCH );
|
||||
|
||||
if( mState == State.FADE )
|
||||
{
|
||||
broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) );
|
||||
broadcast( new MetadataReset() );
|
||||
}
|
||||
|
||||
mState = State.IDLE;
|
||||
|
||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||
}
|
||||
|
||||
private void processTeardownState()
|
||||
{
|
||||
broadcast( SquelchState.SQUELCH );
|
||||
|
||||
mState = State.TEARDOWN;
|
||||
|
||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||
|
||||
if( mTrafficChannelCallEvent != null )
|
||||
{
|
||||
mTrafficChannelCallEvent.end();
|
||||
|
||||
broadcast( mTrafficChannelCallEvent );
|
||||
mTrafficChannelCallEvent = null;
|
||||
}
|
||||
|
||||
if( mTrafficChannelStatusListener != null )
|
||||
{
|
||||
mTrafficChannelStatusListener.callEnd();
|
||||
mTrafficChannelStatusListener = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error( "Traffic channel status listener was null - couldn't "
|
||||
+ "teardown the channel");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the call event to the registered listener
|
||||
@@ -590,35 +597,95 @@ public class ChannelState extends Module implements ICallEventProvider,
|
||||
broadcast( SquelchState.UNSQUELCH );
|
||||
mSquelchLocked = true;
|
||||
break;
|
||||
case CHANGE_CALL_TIMEOUT:
|
||||
if( event instanceof ChangeChannelTimeoutEvent )
|
||||
{
|
||||
ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent)event;
|
||||
|
||||
if( timeout.getChannelType() == ChannelType.STANDARD )
|
||||
{
|
||||
setStandardChannelTimeout( timeout.getCallTimeout() );
|
||||
}
|
||||
else
|
||||
{
|
||||
setTrafficChannelTimeout( timeout.getCallTimeout() );
|
||||
}
|
||||
}
|
||||
case CONTINUATION:
|
||||
if( isIdleOrEndingState() )
|
||||
{
|
||||
setState( event.getState() );
|
||||
}
|
||||
else
|
||||
{
|
||||
updateFadeTimeout();
|
||||
}
|
||||
break;
|
||||
case DECODE:
|
||||
if( getState() == State.IDLE )
|
||||
case START:
|
||||
if( State.CALL_STATES.contains( event.getState() ) )
|
||||
{
|
||||
setState( event.getState() );
|
||||
}
|
||||
break;
|
||||
case END:
|
||||
setState( State.FADE );
|
||||
if( isTrafficChannel() )
|
||||
{
|
||||
setState( State.TEARDOWN );
|
||||
}
|
||||
else
|
||||
{
|
||||
setState( State.FADE );
|
||||
}
|
||||
break;
|
||||
case RESET:
|
||||
/* Channel State does not respond to reset events */
|
||||
break;
|
||||
case START:
|
||||
setState( event.getState() );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors decoder state events to automatically transition the channel
|
||||
* state to IDLE (standard channel) or to TEARDOWN (traffic channel) when
|
||||
* decoding stops or the monitored channel returns to a no signal state.
|
||||
*
|
||||
* Provides a FADE transition state to allow for momentary decoding dropouts
|
||||
* and to allow the user access to call details for a fade period upon
|
||||
* call end.
|
||||
*/
|
||||
public class StateMonitor implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
synchronized( mState )
|
||||
{
|
||||
if( State.CALL_STATES.contains( mState ) &&
|
||||
mFadeTimeout <= System.currentTimeMillis() )
|
||||
{
|
||||
mState = State.FADE;
|
||||
processFadeState();
|
||||
}
|
||||
else if( mState == State.FADE &&
|
||||
mEndTimeout <= System.currentTimeMillis() )
|
||||
{
|
||||
if( isTrafficChannel() )
|
||||
{
|
||||
processTeardownState();
|
||||
}
|
||||
else
|
||||
{
|
||||
processIdleState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
mLog.error( "Exception thrown while state monitor is running " +
|
||||
"- state [" + getState() +
|
||||
"] current [" + System.currentTimeMillis() +
|
||||
"] mResetTimeout [" + mEndTimeout +
|
||||
"] mFadeTimeout [" + mFadeTimeout +
|
||||
"]", e );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,13 @@ import javax.swing.JLabel;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import module.decode.DecoderFactory;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import controller.channel.Channel;
|
||||
import controller.channel.ChannelUtils;
|
||||
import module.decode.DecoderFactory;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import playlist.PlaylistManager;
|
||||
import record.RecorderType;
|
||||
import record.config.RecordConfiguration;
|
||||
@@ -29,8 +30,6 @@ import settings.ColorSetting.ColorSettingName;
|
||||
import settings.Setting;
|
||||
import settings.SettingChangeListener;
|
||||
import settings.SettingsManager;
|
||||
import controller.channel.Channel;
|
||||
import controller.channel.ChannelUtils;
|
||||
|
||||
public class ChannelStatePanel extends JPanel
|
||||
implements Listener<ChangedAttribute>, SettingChangeListener
|
||||
@@ -236,26 +235,37 @@ public class ChannelStatePanel extends JPanel
|
||||
Graphics2D g2 = (Graphics2D)g.create();
|
||||
|
||||
Paint p = null;
|
||||
|
||||
final Channel channel = mChannel;
|
||||
|
||||
switch( mChannel.getChannelState().getState() )
|
||||
final ChannelState channelState = ( channel == null ? null : channel.getChannelState() );
|
||||
|
||||
if( channelState != null )
|
||||
{
|
||||
case CALL:
|
||||
p = getGradient( mColorTopCall, mColorMiddleCall );
|
||||
break;
|
||||
case CONTROL:
|
||||
p = getGradient( mColorTopControl, mColorMiddleControl );
|
||||
break;
|
||||
case DATA:
|
||||
p = getGradient( mColorTopData, mColorMiddleData );
|
||||
break;
|
||||
case FADE:
|
||||
p = getGradient( mColorTopFade, mColorMiddleFade );
|
||||
break;
|
||||
case END:
|
||||
case IDLE:
|
||||
default:
|
||||
p = getGradient( mColorTopIdle, mColorMiddleIdle );
|
||||
break;
|
||||
switch( channelState.getState() )
|
||||
{
|
||||
case CALL:
|
||||
p = getGradient( mColorTopCall, mColorMiddleCall );
|
||||
break;
|
||||
case CONTROL:
|
||||
p = getGradient( mColorTopControl, mColorMiddleControl );
|
||||
break;
|
||||
case DATA:
|
||||
p = getGradient( mColorTopData, mColorMiddleData );
|
||||
break;
|
||||
case FADE:
|
||||
p = getGradient( mColorTopFade, mColorMiddleFade );
|
||||
break;
|
||||
case IDLE:
|
||||
default:
|
||||
p = getGradient( mColorTopIdle, mColorMiddleIdle );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( p == null )
|
||||
{
|
||||
p = getGradient( mColorTopIdle, mColorMiddleIdle );
|
||||
}
|
||||
|
||||
g2.setPaint( p );
|
||||
|
||||
@@ -56,6 +56,7 @@ public class DecoderStateEvent
|
||||
public enum Event
|
||||
{
|
||||
ALWAYS_UNSQUELCH,
|
||||
CHANGE_CALL_TIMEOUT,
|
||||
CONTINUATION,
|
||||
DECODE,
|
||||
END,
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package module.decode.state;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Details the set of states for a channel and the allowable transition states
|
||||
*/
|
||||
public enum State
|
||||
{
|
||||
IDLE( "IDLE" )
|
||||
@@ -7,7 +12,8 @@ public enum State
|
||||
@Override
|
||||
public boolean canChangeTo( State state )
|
||||
{
|
||||
return true;
|
||||
return state != TEARDOWN &&
|
||||
state != RESET;
|
||||
}
|
||||
},
|
||||
CALL( "CALL" )
|
||||
@@ -15,11 +21,11 @@ public enum State
|
||||
@Override
|
||||
public boolean canChangeTo( State state )
|
||||
{
|
||||
return state == CALL ||
|
||||
state == CONTROL ||
|
||||
return state == CONTROL ||
|
||||
state == DATA ||
|
||||
state == ENCRYPTED ||
|
||||
state == FADE;
|
||||
state == FADE ||
|
||||
state == TEARDOWN;
|
||||
}
|
||||
},
|
||||
DATA( "DATA" )
|
||||
@@ -29,9 +35,9 @@ public enum State
|
||||
{
|
||||
return state == CALL ||
|
||||
state == CONTROL ||
|
||||
state == DATA ||
|
||||
state == State.ENCRYPTED ||
|
||||
state == FADE;
|
||||
state == ENCRYPTED ||
|
||||
state == FADE ||
|
||||
state == TEARDOWN;
|
||||
}
|
||||
},
|
||||
ENCRYPTED( "ENCRYPTED" )
|
||||
@@ -39,7 +45,7 @@ public enum State
|
||||
@Override
|
||||
public boolean canChangeTo( State state )
|
||||
{
|
||||
return state == FADE;
|
||||
return state == FADE || state == TEARDOWN;
|
||||
}
|
||||
},
|
||||
CONTROL( "CONTROL" )
|
||||
@@ -47,7 +53,8 @@ public enum State
|
||||
@Override
|
||||
public boolean canChangeTo( State state )
|
||||
{
|
||||
return true;
|
||||
return state == IDLE ||
|
||||
state == FADE;
|
||||
}
|
||||
},
|
||||
FADE( "FADE" )
|
||||
@@ -55,20 +62,36 @@ public enum State
|
||||
@Override
|
||||
public boolean canChangeTo( State state )
|
||||
{
|
||||
return state != FADE; //All states except fade allowed
|
||||
}
|
||||
return state != FADE &&
|
||||
state != RESET;
|
||||
}
|
||||
},
|
||||
END( "END" )
|
||||
RESET( "RESET" )
|
||||
{
|
||||
@Override
|
||||
public boolean canChangeTo( State state )
|
||||
{
|
||||
return state != FADE; //Fade is only disallowed state
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public boolean canChangeTo(State state)
|
||||
{
|
||||
return state == IDLE;
|
||||
}
|
||||
},
|
||||
|
||||
TEARDOWN( "TEARDOWN" )
|
||||
{
|
||||
@Override
|
||||
public boolean canChangeTo(State state)
|
||||
{
|
||||
return state == RESET;
|
||||
}
|
||||
};
|
||||
|
||||
private String mDisplayValue;
|
||||
|
||||
public static final EnumSet<State> CALL_STATES =
|
||||
EnumSet.of( CALL, CONTROL, DATA, ENCRYPTED );
|
||||
|
||||
public static final EnumSet<State> IDLE_STATES =
|
||||
EnumSet.of( IDLE, FADE );
|
||||
|
||||
private State( String displayValue )
|
||||
{
|
||||
mDisplayValue = displayValue;
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import record.config.RecordConfiguration;
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import source.config.SourceConfigTuner;
|
||||
import source.tuner.TunerChannel;
|
||||
@@ -90,16 +91,36 @@ public class TrafficChannelManager extends Module
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
mLog.debug( "dispose() on traffic channel manager" );
|
||||
for( Channel trafficChannel: mTrafficChannelPool )
|
||||
{
|
||||
trafficChannel.setEnabled( false );
|
||||
trafficChannel.dispose();
|
||||
}
|
||||
|
||||
mTrafficChannelPool.clear();
|
||||
|
||||
mCallEventListener = null;
|
||||
mResourceManager = null;
|
||||
mDecodeConfiguration = null;
|
||||
mPreviousDoNotMonitorCallEvent = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a channel by either reusing an existing channel or constructing
|
||||
* a new one, limited by the total number of constructed channels allowed.
|
||||
*
|
||||
* Note: you must enforce thread safety on the mTrafficChannelsInUse
|
||||
* external to this method.
|
||||
*
|
||||
* @param channelNumber
|
||||
* @param tunerChannel
|
||||
* @return
|
||||
*/
|
||||
private Channel getChannel( String channelNumber, TunerChannel tunerChannel )
|
||||
{
|
||||
Channel channel = null;
|
||||
|
||||
|
||||
if( !mTrafficChannelsInUse.containsKey( channelNumber ) )
|
||||
{
|
||||
for( Channel configuredChannel: mTrafficChannelPool )
|
||||
@@ -154,7 +175,7 @@ public class TrafficChannelManager extends Module
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
@@ -164,72 +185,79 @@ public class TrafficChannelManager extends Module
|
||||
*/
|
||||
private void process( TrafficChannelAllocationEvent event )
|
||||
{
|
||||
if( mCallEventListener != null )
|
||||
{
|
||||
synchronized( mCallEventListener )
|
||||
{
|
||||
CallEvent callEvent = event.getCallEvent();
|
||||
CallEvent callEvent = event.getCallEvent();
|
||||
|
||||
/* Check for duplicate events and suppress */
|
||||
if( mTrafficChannelsInUse.containsKey( callEvent.getChannel() ) )
|
||||
/* Check for duplicate events and suppress */
|
||||
synchronized( mTrafficChannelsInUse )
|
||||
{
|
||||
if( mTrafficChannelsInUse.containsKey( callEvent.getChannel() ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long frequency = callEvent.getFrequency();
|
||||
|
||||
/* Check the from/to aliases for do not monitor priority */
|
||||
if( isDoNotMonitor( callEvent ) )
|
||||
{
|
||||
callEvent.setCallEventType( CallEventType.CALL_DO_NOT_MONITOR );
|
||||
|
||||
if( isSameCallEvent( mPreviousDoNotMonitorCallEvent, callEvent ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long frequency = callEvent.getFrequency();
|
||||
|
||||
/* Check the from/to aliases for do not monitor priority */
|
||||
if( isDoNotMonitor( callEvent ) )
|
||||
else
|
||||
{
|
||||
callEvent.setCallEventType( CallEventType.CALL_DO_NOT_MONITOR );
|
||||
|
||||
if( isSameCallEvent( mPreviousDoNotMonitorCallEvent, callEvent ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
mPreviousDoNotMonitorCallEvent = callEvent;
|
||||
}
|
||||
mPreviousDoNotMonitorCallEvent = callEvent;
|
||||
}
|
||||
else if( frequency > 0 )
|
||||
{
|
||||
Channel channel = getChannel( callEvent.getChannel(),
|
||||
new TunerChannel( Type.TRAFFIC, frequency,
|
||||
mDecodeConfiguration.getDecoderType()
|
||||
.getChannelBandwidth() ) );
|
||||
}
|
||||
else if( frequency > 0 )
|
||||
{
|
||||
Channel channel = getChannel( callEvent.getChannel(),
|
||||
new TunerChannel( Type.TRAFFIC, frequency,
|
||||
mDecodeConfiguration.getDecoderType()
|
||||
.getChannelBandwidth() ) );
|
||||
|
||||
if( channel != null )
|
||||
if( channel != null )
|
||||
{
|
||||
if( channel.isProcessing() )
|
||||
{
|
||||
if( channel.isProcessing() )
|
||||
{
|
||||
ChannelState state = channel.getChannelState();
|
||||
|
||||
/* Register as a listener to be notified when the call
|
||||
* is completed */
|
||||
state.configureAsTrafficChannel(
|
||||
new TrafficChannelStatusListener( this,
|
||||
callEvent.getChannel() ), event );
|
||||
|
||||
/* Set state to call so that audio squelch state is correct */
|
||||
state.setState( State.CALL );
|
||||
}
|
||||
else
|
||||
{
|
||||
callEvent.setCallEventType( CallEventType.CALL_DETECT );
|
||||
}
|
||||
callEvent.setDetails( "TRAFFIC CHANNEL ALLOCATED" );
|
||||
|
||||
ChannelState state = channel.getChannelState();
|
||||
|
||||
/* Register as a listener to be notified when the call
|
||||
* is completed */
|
||||
state.configureAsTrafficChannel(
|
||||
new TrafficChannelStatusListener( this,
|
||||
callEvent.getChannel() ), event );
|
||||
|
||||
/* Set state to call so that audio squelch state is correct */
|
||||
state.setState( State.CALL );
|
||||
}
|
||||
else
|
||||
{
|
||||
callEvent.setCallEventType( CallEventType.CALL_DETECT );
|
||||
callEvent.setDetails( "CHANNEL PROCESSING ERROR" );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
callEvent.setCallEventType( CallEventType.CALL_DETECT );
|
||||
callEvent.setDetails( "NO TUNER AVAILABLE" );
|
||||
}
|
||||
|
||||
mCallEventListener.receive( callEvent );
|
||||
}
|
||||
else
|
||||
{
|
||||
callEvent.setCallEventType( CallEventType.CALL_DETECT );
|
||||
callEvent.setDetails( "UNKNOWN FREQUENCY" );
|
||||
}
|
||||
|
||||
final Listener<CallEvent> listener = mCallEventListener;
|
||||
|
||||
if( listener != null )
|
||||
{
|
||||
listener.receive( callEvent );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,14 +398,18 @@ public class TrafficChannelManager extends Module
|
||||
*/
|
||||
public void callEnd( String channelNumber )
|
||||
{
|
||||
if( channelNumber != null && mTrafficChannelsInUse.containsKey( channelNumber ) )
|
||||
synchronized( mTrafficChannelsInUse )
|
||||
{
|
||||
Channel channel = mTrafficChannelsInUse.get( channelNumber );
|
||||
|
||||
mTrafficChannelsInUse.remove( channelNumber );
|
||||
if( channelNumber != null &&
|
||||
mTrafficChannelsInUse.containsKey( channelNumber ) )
|
||||
{
|
||||
Channel channel = mTrafficChannelsInUse.get( channelNumber );
|
||||
|
||||
mTrafficChannelsInUse.remove( channelNumber );
|
||||
|
||||
/* Disable the channel and broadcast a notification */
|
||||
channel.setEnabled( false, true );
|
||||
/* Disable the channel and broadcast a notification */
|
||||
channel.setEnabled( false, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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() + "]" );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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" ),
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -63,9 +63,8 @@ public class Recording implements Comparable<Recording>,
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
||||
TunerChannel channel ) throws RejectedExecutionException,
|
||||
SourceException
|
||||
public TunerChannelSource getChannel( TunerChannel channel )
|
||||
throws RejectedExecutionException, SourceException
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
|
||||
@@ -71,9 +71,7 @@ public class RecordingSourceManager
|
||||
|
||||
if( recording != null )
|
||||
{
|
||||
retVal = recording.getChannel(
|
||||
mResourceManager.getThreadPoolManager(),
|
||||
tunerChannel );
|
||||
retVal = recording.getChannel( tunerChannel );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -143,8 +141,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 )
|
||||
{
|
||||
|
||||
@@ -30,10 +30,9 @@ import sample.complex.ComplexBuffer;
|
||||
import source.SourceException;
|
||||
import source.tuner.frequency.FrequencyChangeBroadcaster;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
import source.tuner.frequency.FrequencyChangeListener;
|
||||
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
||||
import source.tuner.frequency.FrequencyChangeListener;
|
||||
import controller.ResourceManager;
|
||||
import controller.ThreadPoolManager;
|
||||
|
||||
/**
|
||||
* Tuner - provides tuner channel sources, representing a channel frequency
|
||||
@@ -147,8 +146,8 @@ public abstract class Tuner implements FrequencyChangeBroadcaster,
|
||||
*
|
||||
* @return - source for 48k sample rate
|
||||
*/
|
||||
public abstract TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
||||
TunerChannel channel ) throws RejectedExecutionException, SourceException;
|
||||
public abstract TunerChannelSource getChannel( TunerChannel channel )
|
||||
throws RejectedExecutionException, SourceException;
|
||||
|
||||
/**
|
||||
* Releases the tuned channel resources
|
||||
|
||||
@@ -97,9 +97,13 @@ public class TunerChannel implements Comparable<TunerChannel>
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo( TunerChannel otherLock )
|
||||
public int compareTo( TunerChannel otherTunerChannel )
|
||||
{
|
||||
if( mFrequency < otherLock.getFrequency() )
|
||||
if( this.equals( otherTunerChannel ) )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if( mFrequency < otherTunerChannel.getFrequency() )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ public interface TunerChannelProvider
|
||||
*
|
||||
* @return - source for 48k sample rate
|
||||
*/
|
||||
public abstract TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
||||
TunerChannel channel ) throws RejectedExecutionException, SourceException;
|
||||
public abstract TunerChannelSource getChannel( TunerChannel channel )
|
||||
throws RejectedExecutionException, SourceException;
|
||||
|
||||
/**
|
||||
* Releases the tuned channel resources
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -37,8 +38,6 @@ import source.SourceException;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
||||
import source.tuner.frequency.FrequencyChangeListener;
|
||||
import controller.ThreadPoolManager;
|
||||
import controller.ThreadPoolManager.ThreadType;
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.Window.WindowType;
|
||||
import dsp.filter.cic.ComplexPrimeCICDecimate;
|
||||
@@ -62,7 +61,6 @@ public class TunerChannelSource extends ComplexSource
|
||||
private ComplexPrimeCICDecimate mDecimationFilter;
|
||||
private Listener<ComplexBuffer> mListener;
|
||||
private FrequencyChangeListener mFrequencyChangeListener;
|
||||
private ThreadPoolManager mThreadPoolManager;
|
||||
private ScheduledFuture<?> mTaskHandle;
|
||||
|
||||
private long mTunerFrequency = 0;
|
||||
@@ -71,15 +69,11 @@ public class TunerChannelSource extends ComplexSource
|
||||
|
||||
private DecimationProcessor mDecimationProcessor = new DecimationProcessor();
|
||||
|
||||
public TunerChannelSource( ThreadPoolManager threadPoolManager,
|
||||
Tuner tuner,
|
||||
TunerChannel tunerChannel )
|
||||
throws RejectedExecutionException, SourceException
|
||||
public TunerChannelSource( Tuner tuner, TunerChannel tunerChannel )
|
||||
throws RejectedExecutionException, SourceException
|
||||
{
|
||||
super( "Tuner Channel Source" );
|
||||
|
||||
mThreadPoolManager = threadPoolManager;
|
||||
|
||||
mTuner = tuner;
|
||||
mTuner.addListener( (FrequencyChangeListener)this );
|
||||
|
||||
@@ -95,14 +89,17 @@ public class TunerChannelSource extends ComplexSource
|
||||
/* Fire a sample rate change event to setup the decimation chain */
|
||||
frequencyChanged( new FrequencyChangeEvent(
|
||||
Event.SAMPLE_RATE_CHANGE_NOTIFICATION, mTuner.getSampleRate() ) );
|
||||
|
||||
/* Schedule the decimation task to run every 20 ms (50 iterations/second) */
|
||||
mTaskHandle = mThreadPoolManager.scheduleFixedRate( ThreadType.DECIMATION,
|
||||
mDecimationProcessor, 20, TimeUnit.MILLISECONDS );
|
||||
}
|
||||
|
||||
public void start( ScheduledExecutorService scheduledExecutorService )
|
||||
{
|
||||
/* Schedule the decimation task to run every 10 ms (100 iterations/second) */
|
||||
mTaskHandle = scheduledExecutorService.scheduleAtFixedRate(
|
||||
mDecimationProcessor, 0, 10, TimeUnit.MILLISECONDS );
|
||||
|
||||
/* Finally, register to receive samples from the tuner */
|
||||
mTuner.addListener( (Listener<ComplexBuffer>)this );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
@@ -129,12 +126,10 @@ public class TunerChannelSource extends ComplexSource
|
||||
{
|
||||
if( mTaskHandle != null )
|
||||
{
|
||||
mThreadPoolManager.cancel( mTaskHandle );
|
||||
mThreadPoolManager = null;
|
||||
mTaskHandle.cancel( true );
|
||||
mTaskHandle = null;
|
||||
}
|
||||
|
||||
|
||||
mBuffer.clear();
|
||||
mBuffer = null;
|
||||
|
||||
@@ -290,7 +285,7 @@ public class TunerChannelSource extends ComplexSource
|
||||
{
|
||||
if( mBuffer != null )
|
||||
{
|
||||
mBuffer.drainTo( mSampleBuffers, 4 );
|
||||
mBuffer.drainTo( mSampleBuffers, 8 );
|
||||
|
||||
for( Buffer buffer: mSampleBuffers )
|
||||
{
|
||||
|
||||
@@ -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() &&
|
||||
@@ -199,8 +194,8 @@ public abstract class TunerController implements Tunable
|
||||
*
|
||||
* @throws RejectedExecutionException if the decimation processor has an error
|
||||
*/
|
||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
||||
Tuner tuner, TunerChannel channel ) throws RejectedExecutionException
|
||||
public TunerChannelSource getChannel( Tuner tuner, TunerChannel channel )
|
||||
throws RejectedExecutionException
|
||||
{
|
||||
TunerChannelSource source = null;
|
||||
|
||||
@@ -215,8 +210,7 @@ public abstract class TunerController implements Tunable
|
||||
updateLOFrequency();
|
||||
}
|
||||
|
||||
source = new TunerChannelSource( threadPoolManager,
|
||||
tuner, channel );
|
||||
source = new TunerChannelSource( tuner, channel );
|
||||
}
|
||||
catch( SourceException se )
|
||||
{
|
||||
@@ -251,10 +245,6 @@ public abstract class TunerController implements Tunable
|
||||
{
|
||||
mTunedChannels.remove( tunerChannelSource.getTunerChannel() );
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error( "Request to release channel that is not in current tuned channels set" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,8 +272,6 @@ public abstract class TunerController implements Tunable
|
||||
*/
|
||||
private void updateLOFrequency() throws SourceException
|
||||
{
|
||||
Collections.sort( mTunedChannels );
|
||||
|
||||
long frequency = getFrequency();
|
||||
|
||||
boolean frequencyValid = true;
|
||||
@@ -291,13 +279,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
|
||||
|
||||
@@ -100,8 +100,7 @@ public class TunerManager
|
||||
|
||||
try
|
||||
{
|
||||
retVal = tuner.getChannel(
|
||||
mResourceManager.getThreadPoolManager(), tunerChannel );
|
||||
retVal = tuner.getChannel( tunerChannel );
|
||||
}
|
||||
catch ( RejectedExecutionException ree )
|
||||
{
|
||||
|
||||
@@ -128,8 +128,8 @@ public class AirspyTuner extends Tuner
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
||||
TunerChannel channel ) throws RejectedExecutionException, SourceException
|
||||
public TunerChannelSource getChannel( TunerChannel channel )
|
||||
throws RejectedExecutionException, SourceException
|
||||
{
|
||||
//TODO: this channel has a decimated sample rate of:
|
||||
// 10.0 MSps = 10,000,000 / 208 = 48076.923
|
||||
@@ -138,7 +138,7 @@ public class AirspyTuner extends Tuner
|
||||
//Consider implementing a fractional resampler to get a correct 48 kHz
|
||||
//output sample rate
|
||||
|
||||
return mController.getChannel( threadPoolManager, this, channel );
|
||||
return mController.getChannel( this, channel );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -99,8 +99,8 @@ public class B100Tuner extends Tuner
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
||||
TunerChannel channel ) throws RejectedExecutionException, SourceException
|
||||
public TunerChannelSource getChannel( TunerChannel channel )
|
||||
throws RejectedExecutionException, SourceException
|
||||
{
|
||||
//TODO: pass this call to the tuner controller
|
||||
return null;
|
||||
|
||||
@@ -112,11 +112,10 @@ public class FCDTuner extends MixerTuner
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
||||
TunerChannel tunerChannel ) throws RejectedExecutionException,
|
||||
SourceException
|
||||
public TunerChannelSource getChannel( TunerChannel tunerChannel )
|
||||
throws RejectedExecutionException, SourceException
|
||||
{
|
||||
return mController.getChannel( threadPoolManager, this, tunerChannel );
|
||||
return mController.getChannel( this, tunerChannel );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -106,10 +106,10 @@ public class HackRFTuner extends Tuner
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
||||
TunerChannel channel ) throws RejectedExecutionException, SourceException
|
||||
public TunerChannelSource getChannel( TunerChannel channel )
|
||||
throws RejectedExecutionException, SourceException
|
||||
{
|
||||
return mController.getChannel( threadPoolManager, this, channel );
|
||||
return mController.getChannel( this, channel );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -37,7 +37,6 @@ import source.tuner.TunerType;
|
||||
import source.tuner.frequency.FrequencyChangeListener;
|
||||
import source.tuner.rtl.RTL2832TunerController.SampleRate;
|
||||
import controller.ResourceManager;
|
||||
import controller.ThreadPoolManager;
|
||||
|
||||
public class RTL2832Tuner extends Tuner
|
||||
{
|
||||
@@ -148,16 +147,14 @@ public class RTL2832Tuner extends Tuner
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
||||
TunerChannel channel )
|
||||
throws RejectedExecutionException,
|
||||
SourceException
|
||||
public TunerChannelSource getChannel( TunerChannel channel )
|
||||
throws RejectedExecutionException, SourceException
|
||||
{
|
||||
TunerChannelSource source = null;
|
||||
|
||||
try
|
||||
{
|
||||
source = mController.getChannel( threadPoolManager, this, channel );
|
||||
source = mController.getChannel( this, channel );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -121,6 +121,8 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener,
|
||||
mMemoryImageSource.setAnimated( true );
|
||||
|
||||
mWaterfallImage = createImage( mMemoryImageSource );
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user