mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
Initial commit of redesigned internal processing and audio management.
Note: Several of the decoders are currently non-functioning in this commit. If you want the latest functioning codebase, please checkout the commit prior to this one. All decoders will eventually be updated with this new architecture. Currently broken: - Logging and recording - Aux decoders (Fleetsync/MDC/LoJack/Tait) - LTR/Passport decoders Changes in this commit: - Module architecture for decoders and components with central provisioning by the DecoderFactory - Transition from Complex sample stream processing to float buffer processing for improved memory management - Redesigned filter structures - to support buffers and streams - Resolved several memory leak issues - Universal sharing of the thread pool manager - Added centralized audio management with call/user prioritization and new gui with mute/volume controls - Embedded audio metadata for eventual streaming support - Redesigned ProcessingChain to support independent re-use - Added new TrafficChannelManager to handle traffic channel allocations - Traffic Channel pool size and Call timeout available in P25 and MPT1327 configs - Redesigned channel and decoder gui components
This commit is contained in:
112
src/module/decode/AuxDecodeComponentEditor.java
Normal file
112
src/module/decode/AuxDecodeComponentEditor.java
Normal file
@@ -0,0 +1,112 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import module.decode.config.AuxDecodeConfiguration;
|
||||
import controller.Editor;
|
||||
import controller.channel.AbstractChannelEditor;
|
||||
import controller.channel.ChannelNode;
|
||||
import controller.channel.ChannelValidationException;
|
||||
|
||||
public class AuxDecodeComponentEditor extends AbstractChannelEditor
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private HashMap<DecoderType,AuxDecoderCheckBox> mControls =
|
||||
new HashMap<DecoderType,AuxDecoderCheckBox>();
|
||||
|
||||
public AuxDecodeComponentEditor( ChannelNode channelNode )
|
||||
{
|
||||
super( channelNode );
|
||||
|
||||
List<DecoderType> decoders = DecoderType.getAuxDecoders();
|
||||
|
||||
Collections.sort( decoders );
|
||||
|
||||
for( DecoderType decoder: decoders )
|
||||
{
|
||||
AuxDecoderCheckBox control = new AuxDecoderCheckBox( decoder );
|
||||
|
||||
if( getConfig().getAuxDecoders().contains( decoder ) )
|
||||
{
|
||||
control.setSelected( true );
|
||||
}
|
||||
|
||||
add( control, "wrap" );
|
||||
|
||||
mControls.put( decoder, control );
|
||||
}
|
||||
}
|
||||
|
||||
public AuxDecodeConfiguration getConfig()
|
||||
{
|
||||
return getChannelNode().getChannel().getAuxDecodeConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
getConfig().clearAuxDecoders();
|
||||
|
||||
for( DecoderType decoder: mControls.keySet() )
|
||||
{
|
||||
if( mControls.get( decoder ) .isSelected() )
|
||||
{
|
||||
getConfig().addAuxDecoder( decoder );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
SwingUtilities.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
for( DecoderType decoder: mControls.keySet() )
|
||||
{
|
||||
mControls.get( decoder ).setSelected(
|
||||
getConfig().getAuxDecoders().contains( decoder ) );
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public class AuxDecoderCheckBox extends JCheckBox
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private DecoderType mDecoderType;
|
||||
|
||||
public AuxDecoderCheckBox( DecoderType decoder )
|
||||
{
|
||||
super( decoder.getDisplayString() );
|
||||
|
||||
mDecoderType = decoder;
|
||||
}
|
||||
}
|
||||
}
|
||||
154
src/module/decode/DecodeComponentEditor.java
Normal file
154
src/module/decode/DecodeComponentEditor.java
Normal file
@@ -0,0 +1,154 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import module.decode.config.DecodeConfigFactory;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
import controller.Editor;
|
||||
import controller.channel.AbstractChannelEditor;
|
||||
import controller.channel.ChannelNode;
|
||||
import controller.channel.ChannelValidationException;
|
||||
|
||||
public class DecodeComponentEditor extends AbstractChannelEditor
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JComboBox<DecoderType> mComboDecoders;
|
||||
|
||||
private DecodeEditor mEditor;
|
||||
|
||||
public DecodeComponentEditor( ChannelNode channelNode )
|
||||
{
|
||||
super( channelNode );
|
||||
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[right,grow][grow]", "[][][grow]" ) );
|
||||
|
||||
/**
|
||||
* ComboBox: Decoders
|
||||
*/
|
||||
mComboDecoders = new JComboBox<DecoderType>();
|
||||
|
||||
DefaultComboBoxModel<DecoderType> model =
|
||||
new DefaultComboBoxModel<DecoderType>();
|
||||
|
||||
for( DecoderType type: DecoderType.getPrimaryDecoders() )
|
||||
{
|
||||
model.addElement( type );
|
||||
}
|
||||
|
||||
mComboDecoders.setModel( model );
|
||||
|
||||
mComboDecoders.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
DecoderType selected = mComboDecoders
|
||||
.getItemAt( mComboDecoders.getSelectedIndex() );
|
||||
|
||||
if( selected != null )
|
||||
{
|
||||
DecodeConfiguration config;
|
||||
|
||||
if( mChannelNode.getChannel()
|
||||
.getDecodeConfiguration().getDecoderType() ==
|
||||
selected )
|
||||
{
|
||||
config = mChannelNode.getChannel()
|
||||
.getDecodeConfiguration();
|
||||
}
|
||||
else
|
||||
{
|
||||
config = DecodeConfigFactory
|
||||
.getDecodeConfiguration( selected );
|
||||
}
|
||||
|
||||
//Remove the existing one
|
||||
if( mEditor != null )
|
||||
{
|
||||
remove( mEditor );
|
||||
}
|
||||
|
||||
//Change to the new one
|
||||
mEditor = DecoderFactory.getPanel( config, mChannelNode );
|
||||
|
||||
add( mEditor, "span 2,center" );
|
||||
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
});
|
||||
add( new JLabel( "Decoder:" ) );
|
||||
add( mComboDecoders, "wrap" );
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
final DecoderType decoder = mChannelNode.getChannel()
|
||||
.getDecodeConfiguration().getDecoderType();
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run() {
|
||||
mComboDecoders.setSelectedItem( decoder );
|
||||
|
||||
mComboDecoders.requestFocus();
|
||||
|
||||
mComboDecoders.requestFocusInWindow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if( mEditor != null )
|
||||
{
|
||||
mEditor.save();
|
||||
}
|
||||
|
||||
//The component that calls save will invoke the change broadcast
|
||||
mChannelNode.getChannel()
|
||||
.setDecodeConfiguration( mEditor.getConfig(), false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the editor against the current decode editor
|
||||
*/
|
||||
@Override
|
||||
public void validate( Editor editor ) throws ChannelValidationException
|
||||
{
|
||||
super.validate( editor );
|
||||
|
||||
mEditor.validate( editor );
|
||||
}
|
||||
}
|
||||
118
src/module/decode/DecodeEditor.java
Normal file
118
src/module/decode/DecodeEditor.java
Normal file
@@ -0,0 +1,118 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.SpinnerModel;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
import controller.Editor;
|
||||
import controller.channel.Channel;
|
||||
import controller.channel.ChannelValidationException;
|
||||
|
||||
public class DecodeEditor extends Editor
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected DecodeConfiguration mConfig;
|
||||
|
||||
protected JCheckBox mAFC;
|
||||
protected JSpinner mAFCMaximumCorrection;
|
||||
|
||||
public DecodeEditor( DecodeConfiguration config )
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[right,grow][grow]", "[][][][][grow]" ) );
|
||||
|
||||
mConfig = config;
|
||||
|
||||
if( config.supportsAFC() )
|
||||
{
|
||||
mAFC = new JCheckBox( "Automatic Frequency Control" );
|
||||
mAFC.setSelected( mConfig.isAFCEnabled() );
|
||||
mAFC.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent arg0 )
|
||||
{
|
||||
mConfig.setAFC( mAFC.isSelected() );
|
||||
}
|
||||
} );
|
||||
|
||||
mAFC.setToolTipText( "AFC automatically adjusts the center "
|
||||
+ "frequency of the channel to correct/compensate for "
|
||||
+ "inaccuracies and frequency drift in the tuner" );
|
||||
|
||||
add( mAFC, "span" );
|
||||
|
||||
int initial = mConfig.getAFCMaximumCorrection();
|
||||
|
||||
SpinnerModel model = new SpinnerNumberModel( initial, 0, 15000, 1 );
|
||||
|
||||
mAFCMaximumCorrection = new JSpinner( model );
|
||||
|
||||
JSpinner.NumberEditor editor =
|
||||
(JSpinner.NumberEditor)mAFCMaximumCorrection.getEditor();
|
||||
editor.getTextField().setHorizontalAlignment( SwingConstants.CENTER );
|
||||
|
||||
mAFCMaximumCorrection.setToolTipText( "Sets the maximum frequency "
|
||||
+ "correction value in hertz, 0 - 15kHz" );
|
||||
|
||||
mAFCMaximumCorrection.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent e )
|
||||
{
|
||||
int value = ((SpinnerNumberModel)mAFCMaximumCorrection
|
||||
.getModel()).getNumber().intValue();
|
||||
|
||||
mConfig.setAFCMaximumCorrection( value );
|
||||
|
||||
save();
|
||||
}
|
||||
} );
|
||||
|
||||
add( new JLabel( "Correction Limit Hz:" ) );
|
||||
add( mAFCMaximumCorrection, "wrap" );
|
||||
}
|
||||
}
|
||||
|
||||
public DecodeConfiguration getConfig()
|
||||
{
|
||||
return mConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
}
|
||||
}
|
||||
143
src/module/decode/Decoder.java
Normal file
143
src/module/decode/Decoder.java
Normal file
@@ -0,0 +1,143 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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;
|
||||
|
||||
import message.IMessageProvider;
|
||||
import message.Message;
|
||||
import module.Module;
|
||||
import module.decode.event.CallEvent;
|
||||
import module.decode.event.ICallEventProvider;
|
||||
import module.decode.state.DecoderState;
|
||||
import module.decode.state.DecoderStateEvent;
|
||||
import module.decode.state.IDecoderStateEventListener;
|
||||
import module.decode.state.IDecoderStateEventProvider;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
|
||||
public abstract class Decoder extends Module
|
||||
implements ICallEventProvider,
|
||||
IDecoderStateEventListener,
|
||||
IDecoderStateEventProvider,
|
||||
IMessageProvider
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( Decoder.class );
|
||||
|
||||
/**
|
||||
* Broadcasts decoded messages both to the internal decoder state and any
|
||||
* external registered message listeners
|
||||
*/
|
||||
protected Broadcaster<Message> mMessageBroadcaster =
|
||||
new Broadcaster<Message>();
|
||||
|
||||
protected DecoderState mDecoderState;
|
||||
|
||||
/**
|
||||
* Decoder - parent class for all decoders, demodulators and components.
|
||||
*/
|
||||
public Decoder( DecoderState decoderState )
|
||||
{
|
||||
assert( decoderState != null );
|
||||
|
||||
mDecoderState = decoderState;
|
||||
mMessageBroadcaster.addListener( mDecoderState );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
mMessageBroadcaster.dispose();
|
||||
mMessageBroadcaster = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows decoder to broadcast initialization messages/events so that
|
||||
* external modules can be correctly configured. This method is invoked
|
||||
* prior to processing start.
|
||||
*/
|
||||
public void init()
|
||||
{
|
||||
mDecoderState.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the decoder type (ie protocol)
|
||||
*/
|
||||
public abstract DecoderType getDecoderType();
|
||||
|
||||
|
||||
/**
|
||||
* Adds a listener for receiving decoded messages from this decoder
|
||||
*/
|
||||
@Override
|
||||
public void addMessageListener( Listener<Message> listener )
|
||||
{
|
||||
mMessageBroadcaster.addListener( listener );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving decoded messages from all attached
|
||||
* decoders
|
||||
*/
|
||||
@Override
|
||||
public void removeMessageListener( Listener<Message> listener )
|
||||
{
|
||||
mMessageBroadcaster.removeListener( listener );
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoder State - tracks the state of the decoder
|
||||
*/
|
||||
public DecoderState getDecoderState()
|
||||
{
|
||||
return mDecoderState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<DecoderStateEvent> getDecoderStateListener()
|
||||
{
|
||||
return mDecoderState.getDecoderStateListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDecoderStateListener( Listener<DecoderStateEvent> listener )
|
||||
{
|
||||
mDecoderState.setDecoderStateListener( listener );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDecoderStateListener()
|
||||
{
|
||||
mDecoderState.removeDecoderStateListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCallEventListener( Listener<CallEvent> listener )
|
||||
{
|
||||
mDecoderState.setCallEventListener( listener );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCallEventListener()
|
||||
{
|
||||
mDecoderState.removeCallEventListener();
|
||||
}
|
||||
}
|
||||
416
src/module/decode/DecoderFactory.java
Normal file
416
src/module/decode/DecoderFactory.java
Normal file
@@ -0,0 +1,416 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import message.Message;
|
||||
import message.MessageDirection;
|
||||
import module.Module;
|
||||
import module.decode.config.AuxDecodeConfiguration;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
import module.decode.fleetsync2.Fleetsync2Decoder;
|
||||
import module.decode.fleetsync2.FleetsyncMessageFilter;
|
||||
import module.decode.lj1200.LJ1200Decoder;
|
||||
import module.decode.lj1200.LJ1200MessageFilter;
|
||||
import module.decode.ltrnet.DecodeConfigLTRNet;
|
||||
import module.decode.ltrnet.LTRNetDecoder;
|
||||
import module.decode.ltrnet.LTRNetDecoderPanel;
|
||||
import module.decode.ltrnet.LTRNetEditor;
|
||||
import module.decode.ltrnet.LTRNetMessageFilter;
|
||||
import module.decode.ltrstandard.DecodeConfigLTRStandard;
|
||||
import module.decode.ltrstandard.LTRStandardDecoder;
|
||||
import module.decode.ltrstandard.LTRStandardDecoderPanel;
|
||||
import module.decode.ltrstandard.LTRStandardEditor;
|
||||
import module.decode.ltrstandard.LTRStandardMessageFilter;
|
||||
import module.decode.mdc1200.MDCDecoder;
|
||||
import module.decode.mdc1200.MDCMessageFilter;
|
||||
import module.decode.mpt1327.DecodeConfigMPT1327;
|
||||
import module.decode.mpt1327.MPT1327ConfigEditor;
|
||||
import module.decode.mpt1327.MPT1327Decoder;
|
||||
import module.decode.mpt1327.MPT1327Decoder.Sync;
|
||||
import module.decode.mpt1327.MPT1327DecoderPanel;
|
||||
import module.decode.mpt1327.MPT1327MessageFilter;
|
||||
import module.decode.nbfm.NBFMDecoder;
|
||||
import module.decode.nbfm.NBFMDecoderPanel;
|
||||
import module.decode.nbfm.NBFMEditor;
|
||||
import module.decode.p25.DecodeConfigP25Phase1;
|
||||
import module.decode.p25.P25Decoder;
|
||||
import module.decode.p25.P25Decoder.Modulation;
|
||||
import module.decode.p25.P25DecoderPanel;
|
||||
import module.decode.p25.P25Editor;
|
||||
import module.decode.p25.P25_C4FMDecoder;
|
||||
import module.decode.p25.P25_LSMDecoder;
|
||||
import module.decode.p25.audio.P25AudioModule;
|
||||
import module.decode.p25.message.filter.P25MessageFilterSet;
|
||||
import module.decode.passport.PassportEditor;
|
||||
import module.decode.passport.PassportMessageFilter;
|
||||
import module.decode.state.DecoderPanel;
|
||||
import module.decode.state.TrafficChannelManager;
|
||||
import module.decode.tait.Tait1200Decoder;
|
||||
import module.demodulate.fm.FMDemodulatorModule;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import properties.SystemProperties;
|
||||
import settings.SettingsManager;
|
||||
import util.TimeStamp;
|
||||
import alias.AliasList;
|
||||
import audio.AudioModule;
|
||||
import controller.ResourceManager;
|
||||
import controller.channel.Channel.ChannelType;
|
||||
import controller.channel.ChannelNode;
|
||||
import controller.channel.map.ChannelMap;
|
||||
import filter.AllPassFilter;
|
||||
import filter.FilterSet;
|
||||
import filter.IFilter;
|
||||
|
||||
public class DecoderFactory
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( DecoderFactory.class );
|
||||
|
||||
public static final boolean NO_DC_REMOVAL = false;
|
||||
public static final boolean REMOVE_DC = true;
|
||||
|
||||
public static final int NO_FREQUENCY_CORRECTION = 0;
|
||||
|
||||
/**
|
||||
* Returns a list of one primary decoder and any auxiliary decoders, as
|
||||
* specified in the configurations.
|
||||
*
|
||||
* @param config - primary decoder configuration
|
||||
* @param auxConfig - auxiliary decoder(s) configuration
|
||||
*
|
||||
* @return list of configured decoders
|
||||
*/
|
||||
public static List<Module> getModules( ChannelType channelType,
|
||||
ResourceManager resourceManager,
|
||||
DecodeConfiguration config,
|
||||
AuxDecodeConfiguration auxConfig,
|
||||
AliasList aliasList )
|
||||
{
|
||||
List<Module> modules = getPrimaryModules( channelType, config,
|
||||
aliasList, resourceManager );
|
||||
|
||||
modules.addAll( getAuxiliaryDecoders( auxConfig, aliasList ) );
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
public static FMDemodulatorModule getFMDemodulator( DecodeConfiguration config, int pass, int stop, boolean removeDC )
|
||||
{
|
||||
if( config.isAFCEnabled() )
|
||||
{
|
||||
return new FMDemodulatorModule( pass, stop, config.getAFCMaximumCorrection(), removeDC );
|
||||
}
|
||||
else
|
||||
{
|
||||
return new FMDemodulatorModule( pass, stop, NO_FREQUENCY_CORRECTION, removeDC );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a primary decoder as specified in the decode configuration
|
||||
* @param config - primary decoder configuration
|
||||
* @param aliasList - optional alias list
|
||||
* @return configured decoder or null
|
||||
*/
|
||||
public static List<Module> getPrimaryModules( ChannelType channelType,
|
||||
DecodeConfiguration config,
|
||||
AliasList aliasList,
|
||||
ResourceManager resourceManager )
|
||||
{
|
||||
List<Module> modules = new ArrayList<Module>();
|
||||
|
||||
/* Baseband low-pass filter pass and stop frequencies */
|
||||
int pass = config.getDecoderType().getChannelBandwidth() / 2;
|
||||
int stop = pass + 1250;
|
||||
|
||||
switch( config.getDecoderType() )
|
||||
{
|
||||
case AM:
|
||||
// decoder = new AMDecoder();
|
||||
break;
|
||||
case NBFM:
|
||||
modules.add( new AudioModule() );
|
||||
modules.add( new NBFMDecoder( config ) );
|
||||
modules.add( getFMDemodulator( config, pass, stop, REMOVE_DC ) );
|
||||
break;
|
||||
case LTR_STANDARD:
|
||||
modules.add( new AudioModule( REMOVE_DC ) );
|
||||
MessageDirection direction = ((DecodeConfigLTRStandard)config).getMessageDirection();
|
||||
modules.add( new LTRStandardDecoder( aliasList, direction ) );
|
||||
modules.add( getFMDemodulator( config, pass, stop, NO_DC_REMOVAL ) );
|
||||
break;
|
||||
case LTR_NET:
|
||||
modules.add( new AudioModule( REMOVE_DC ) );
|
||||
modules.add( new LTRNetDecoder( (DecodeConfigLTRNet)config, aliasList ) );
|
||||
// modules.add( new ComplexBufferRecorderModule( getRecordingFilename( "decimated_baseband" ) ) );
|
||||
modules.add( getFMDemodulator( config, pass, stop, NO_DC_REMOVAL ) );
|
||||
break;
|
||||
case MPT1327:
|
||||
DecodeConfigMPT1327 mptConfig = (DecodeConfigMPT1327)config;
|
||||
|
||||
modules.add( new AudioModule() );
|
||||
|
||||
ChannelMap channelMap = resourceManager.getPlaylistManager().getPlayist()
|
||||
.getChannelMapList().getChannelMap( mptConfig.getChannelMapName() );
|
||||
Sync sync = mptConfig.getSync();
|
||||
modules.add( new MPT1327Decoder( channelMap, channelType, aliasList, sync ) );
|
||||
|
||||
if( channelType == ChannelType.STANDARD )
|
||||
{
|
||||
long timeout = mptConfig.getCallTimeout() * 1000; //convert to milliseconds
|
||||
|
||||
modules.add( new TrafficChannelManager( resourceManager,
|
||||
config, timeout,
|
||||
mptConfig.getTrafficChannelPoolSize() ) );
|
||||
}
|
||||
|
||||
// modules.add( new RealBufferRecorderModule( getRecordingFilename( "demodulated_mpt1327" ) ) );
|
||||
modules.add( getFMDemodulator( config, pass, stop, REMOVE_DC ) );
|
||||
break;
|
||||
case PASSPORT:
|
||||
// decoder = new PassportDecoder( config, aliasList );
|
||||
break;
|
||||
case P25_PHASE1:
|
||||
DecodeConfigP25Phase1 p25Config = (DecodeConfigP25Phase1)config;
|
||||
|
||||
Modulation modulation = p25Config.getModulation();
|
||||
|
||||
switch( modulation )
|
||||
{
|
||||
case C4FM:
|
||||
modules.add( getFMDemodulator( config, 6750, 7500, REMOVE_DC ) );
|
||||
modules.add( new P25_C4FMDecoder( aliasList, channelType ) );
|
||||
break;
|
||||
case CQPSK:
|
||||
modules.add( new P25_LSMDecoder( aliasList, channelType ) );
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException( "Unrecognized P25 "
|
||||
+ "Phase 1 Modulation [" + modulation + "]" );
|
||||
}
|
||||
|
||||
if( channelType == ChannelType.STANDARD )
|
||||
{
|
||||
long timeout = p25Config.getCallTimeout() * 1000; //convert to milliseconds
|
||||
|
||||
modules.add( new TrafficChannelManager( resourceManager,
|
||||
config, timeout,
|
||||
p25Config.getTrafficChannelPoolSize() ) );
|
||||
}
|
||||
|
||||
modules.add( new P25AudioModule() );
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
"Unknown decoder type [" +
|
||||
config.getDecoderType().toString() + "]" );
|
||||
}
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a list of auxiliary decoders, as specified in the configuration
|
||||
*
|
||||
* @param config - auxiliary configuration
|
||||
* @param aliasList - optional alias list
|
||||
* @return - list of auxiliary decoders
|
||||
*/
|
||||
public static List<Decoder> getAuxiliaryDecoders( AuxDecodeConfiguration config,
|
||||
AliasList aliasList )
|
||||
{
|
||||
List<Decoder> decoders = new ArrayList<Decoder>();
|
||||
|
||||
for( DecoderType auxDecoder: config.getAuxDecoders() )
|
||||
{
|
||||
switch( auxDecoder )
|
||||
{
|
||||
case FLEETSYNC2:
|
||||
decoders.add( new Fleetsync2Decoder( aliasList ) );
|
||||
break;
|
||||
case MDC1200:
|
||||
decoders.add( new MDCDecoder( aliasList ) );
|
||||
break;
|
||||
case LJ_1200:
|
||||
decoders.add( new LJ1200Decoder( aliasList ) );
|
||||
break;
|
||||
case TAIT_1200:
|
||||
decoders.add( new Tait1200Decoder( aliasList ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return decoders;
|
||||
}
|
||||
|
||||
public static DecoderPanel getDecoderPanel( SettingsManager settingsManager,
|
||||
Decoder decoder )
|
||||
{
|
||||
switch( decoder.getDecoderType() )
|
||||
{
|
||||
// case AM:
|
||||
// break;
|
||||
// case FLEETSYNC2:
|
||||
// break;
|
||||
// case LJ_1200:
|
||||
// break;
|
||||
case LTR_NET:
|
||||
return new LTRNetDecoderPanel( settingsManager, (LTRNetDecoder)decoder );
|
||||
case LTR_STANDARD:
|
||||
return new LTRStandardDecoderPanel( settingsManager, (LTRStandardDecoder)decoder );
|
||||
// case MDC1200:
|
||||
// break;
|
||||
case MPT1327:
|
||||
return new MPT1327DecoderPanel( settingsManager, (MPT1327Decoder)decoder );
|
||||
case NBFM:
|
||||
return new NBFMDecoderPanel( settingsManager, decoder );
|
||||
case P25_PHASE1:
|
||||
return new P25DecoderPanel( settingsManager, (P25Decoder)decoder );
|
||||
// case PASSPORT:
|
||||
// break;
|
||||
case TAIT_1200:
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException( "Unrecognized decoder: " +
|
||||
decoder.getDecoderType().getDisplayString() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles a filter set containing filters for the primary channel
|
||||
* decoder and each of the auxiliary decoders
|
||||
*/
|
||||
public static FilterSet<Message> getMessageFilters( List<Module> modules )
|
||||
{
|
||||
FilterSet<Message> filterSet = new FilterSet<>();
|
||||
|
||||
for( Module module: modules )
|
||||
{
|
||||
if( module instanceof Decoder )
|
||||
{
|
||||
filterSet.addFilters( getMessageFilter(
|
||||
((Decoder)module).getDecoderType() ) );
|
||||
}
|
||||
}
|
||||
|
||||
/* If we don't have any filters, add an ALL-PASS filter */
|
||||
if( filterSet.getFilters().isEmpty() )
|
||||
{
|
||||
filterSet.addFilter( new AllPassFilter<Message>() );
|
||||
}
|
||||
|
||||
return filterSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of IMessageFilter objects (FilterSets or Filters) that
|
||||
* can process all of the messages produced by the specified decoder type.
|
||||
*/
|
||||
public static List<IFilter<Message>> getMessageFilter( DecoderType decoder )
|
||||
{
|
||||
ArrayList<IFilter<Message>> filters = new ArrayList<IFilter<Message>>();
|
||||
|
||||
switch( decoder )
|
||||
{
|
||||
case FLEETSYNC2:
|
||||
filters.add( new FleetsyncMessageFilter() );
|
||||
break;
|
||||
case LJ_1200:
|
||||
filters.add( new LJ1200MessageFilter() );
|
||||
break;
|
||||
case LTR_NET:
|
||||
filters.add( new LTRNetMessageFilter() );
|
||||
break;
|
||||
case LTR_STANDARD:
|
||||
filters.add( new LTRStandardMessageFilter() );
|
||||
break;
|
||||
case MDC1200:
|
||||
filters.add( new MDCMessageFilter() );
|
||||
break;
|
||||
case MPT1327:
|
||||
filters.add( new MPT1327MessageFilter() );
|
||||
break;
|
||||
case P25_PHASE1:
|
||||
case P25_PHASE2:
|
||||
filters.add( new P25MessageFilterSet() );
|
||||
break;
|
||||
case PASSPORT:
|
||||
filters.add( new PassportMessageFilter() );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
public static DecodeEditor getPanel( DecodeConfiguration config,
|
||||
ChannelNode channelNode )
|
||||
{
|
||||
DecodeEditor configuredPanel;
|
||||
|
||||
switch( config.getDecoderType() )
|
||||
{
|
||||
case NBFM:
|
||||
configuredPanel = new NBFMEditor( config );
|
||||
break;
|
||||
case LTR_STANDARD:
|
||||
configuredPanel = new LTRStandardEditor( config );
|
||||
break;
|
||||
case LTR_NET:
|
||||
configuredPanel = new LTRNetEditor( config );
|
||||
break;
|
||||
case MPT1327:
|
||||
configuredPanel = new MPT1327ConfigEditor( config, channelNode );
|
||||
break;
|
||||
case PASSPORT:
|
||||
configuredPanel = new PassportEditor( config );
|
||||
break;
|
||||
case P25_PHASE1:
|
||||
configuredPanel = new P25Editor( config );
|
||||
break;
|
||||
default:
|
||||
configuredPanel = new DecodeEditor( config );
|
||||
break;
|
||||
}
|
||||
|
||||
return configuredPanel;
|
||||
}
|
||||
|
||||
public static String getRecordingFilename( String token )
|
||||
{
|
||||
//Get the base recording filename
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( SystemProperties.getInstance()
|
||||
.getApplicationFolder( "recordings" ) );
|
||||
sb.append( File.separator );
|
||||
sb.append( TimeStamp.getTimeStamp( "_" ) );
|
||||
sb.append( "_" );
|
||||
sb.append( token );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
130
src/module/decode/DecoderType.java
Normal file
130
src/module/decode/DecoderType.java
Normal file
@@ -0,0 +1,130 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Enumeration of decoder types
|
||||
*/
|
||||
public enum DecoderType
|
||||
{
|
||||
ACARS( "ACARS", "ACARS", "images/acars.png", 12500 ),
|
||||
AIS( "AIS", "AIS", "images/ais.png", 12500 ),
|
||||
AM( "AM", "AM", "images/am.png", 10000 ),
|
||||
APRS( "APRS", "APRS", "images/aprs.png", 12500 ),
|
||||
DMR( "DMR", "DMR", "images/dmr.png", 12500 ),
|
||||
FLEETSYNC2( "Fleetsync II", "Fsync2", "images/fm.png", 12500 ),
|
||||
LJ_1200( "LJ1200 173.075", "LJ1200", "images/lj1200.png", 12500 ),
|
||||
LTR_STANDARD( "LTR-Standard", "LTR", "images/ltr.png", 10000 ),
|
||||
LTR_NET( "LTR-Net", "LTR-Net", "images/ltr_net.png", 12500 ),
|
||||
MDC1200( "MDC1200", "MDC1200", "images/fm.png", 12500 ),
|
||||
MOTOROLA_TYPE_2( "MOTO T2", "MOTO T2", "images/motorola_type_2.png", 15000 ),
|
||||
MPT1327( "MPT1327", "MPT1327", "images/mpt1327.png", 12500 ),
|
||||
NXDN( "NXDN", "NXDN", "images/nxdn.png", 8000 ),
|
||||
NBFM( "NBFM", "NBFM", "images/fm.png", 12500 ),
|
||||
PASSPORT( "Passport", "Passport", "images/passport.png", 12500 ),
|
||||
P25_PHASE1( "P25 Phase I", "P25-1", "images/p25_1.png", 12500 ),
|
||||
P25_PHASE2( "P25 Phase II", "P25-2", "images/p25_2.png", 15000 ),
|
||||
TAIT_1200( "Tait 1200", "Tait 1200", "images/tait.png", 12500 );
|
||||
|
||||
private String mDisplayString;
|
||||
private String mShortDisplayString;
|
||||
private String mIconFilename;
|
||||
private int mChannelBandwidth;
|
||||
|
||||
DecoderType( String displayString, String shortDisplayString,
|
||||
String iconFilename, int bandwidth )
|
||||
{
|
||||
mDisplayString = displayString;
|
||||
mShortDisplayString = shortDisplayString;
|
||||
mIconFilename = iconFilename;
|
||||
mChannelBandwidth = bandwidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary decoders
|
||||
*/
|
||||
public static EnumSet<DecoderType> getPrimaryDecoders()
|
||||
{
|
||||
return EnumSet.of( DecoderType.AM,
|
||||
DecoderType.LTR_NET,
|
||||
DecoderType.LTR_STANDARD,
|
||||
DecoderType.MPT1327,
|
||||
DecoderType.NBFM,
|
||||
DecoderType.P25_PHASE1,
|
||||
DecoderType.PASSPORT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoders that can be used in the viewer application
|
||||
*/
|
||||
public static EnumSet<DecoderType> getInstrumentableDecoders()
|
||||
{
|
||||
return EnumSet.of( DecoderType.FLEETSYNC2,
|
||||
DecoderType.LJ_1200,
|
||||
DecoderType.LTR_NET,
|
||||
DecoderType.MDC1200,
|
||||
DecoderType.MPT1327,
|
||||
DecoderType.PASSPORT,
|
||||
DecoderType.P25_PHASE1,
|
||||
DecoderType.TAIT_1200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Available auxiliary decoders.
|
||||
*/
|
||||
public static ArrayList<DecoderType> getAuxDecoders()
|
||||
{
|
||||
ArrayList<DecoderType> decoders = new ArrayList<DecoderType>();
|
||||
|
||||
decoders.add( DecoderType.FLEETSYNC2 );
|
||||
decoders.add( DecoderType.LJ_1200 );
|
||||
decoders.add( DecoderType.MDC1200 );
|
||||
decoders.add( DecoderType.TAIT_1200 );
|
||||
|
||||
return decoders;
|
||||
}
|
||||
|
||||
public String getDisplayString()
|
||||
{
|
||||
return mDisplayString;
|
||||
}
|
||||
|
||||
public String getShortDisplayString()
|
||||
{
|
||||
return mShortDisplayString;
|
||||
}
|
||||
|
||||
public String getIconFilename()
|
||||
{
|
||||
return mIconFilename;
|
||||
}
|
||||
|
||||
public int getChannelBandwidth()
|
||||
{
|
||||
return mChannelBandwidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mDisplayString;
|
||||
}
|
||||
}
|
||||
79
src/module/decode/PrimaryDecoder.java
Normal file
79
src/module/decode/PrimaryDecoder.java
Normal file
@@ -0,0 +1,79 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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;
|
||||
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
import module.decode.state.DecoderState;
|
||||
import sample.Listener;
|
||||
import audio.squelch.ISquelchStateProvider;
|
||||
import audio.squelch.SquelchState;
|
||||
|
||||
/**
|
||||
* Primary decoder adds the following functionality over the basic decoder:
|
||||
*
|
||||
* - Provides audio squelch control
|
||||
*/
|
||||
|
||||
public abstract class PrimaryDecoder extends Decoder implements ISquelchStateProvider
|
||||
{
|
||||
protected Listener<SquelchState> mSquelchStateListener;
|
||||
|
||||
protected DecodeConfiguration mDecodeConfiguration;
|
||||
|
||||
public PrimaryDecoder( DecodeConfiguration config, DecoderState decoderState )
|
||||
{
|
||||
super( decoderState );
|
||||
|
||||
mDecodeConfiguration = config;
|
||||
}
|
||||
|
||||
public DecodeConfiguration getDecodeConfiguration()
|
||||
{
|
||||
return mDecodeConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mSquelchStateListener = null;
|
||||
mDecodeConfiguration = null;
|
||||
}
|
||||
|
||||
public void broadcast( SquelchState state )
|
||||
{
|
||||
if( mSquelchStateListener != null )
|
||||
{
|
||||
mSquelchStateListener.receive( state );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSquelchStateListener( Listener<SquelchState> listener )
|
||||
{
|
||||
mSquelchStateListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSquelchStateListener()
|
||||
{
|
||||
mSquelchStateListener = null;
|
||||
}
|
||||
}
|
||||
38
src/module/decode/am/AMDecoder.java
Normal file
38
src/module/decode/am/AMDecoder.java
Normal file
@@ -0,0 +1,38 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.am;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.PrimaryDecoder;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
import module.decode.state.AlwaysUnsquelchedDecoderState;
|
||||
|
||||
public class AMDecoder extends PrimaryDecoder
|
||||
{
|
||||
public AMDecoder( DecodeConfiguration config )
|
||||
{
|
||||
super( config, new AlwaysUnsquelchedDecoderState( "AM" ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.NBFM;
|
||||
}
|
||||
}
|
||||
75
src/module/decode/am/AMDecoderPanel.java
Normal file
75
src/module/decode/am/AMDecoderPanel.java
Normal file
@@ -0,0 +1,75 @@
|
||||
/*******************************************************************************
|
||||
* 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.am;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import module.decode.state.ChangedAttribute;
|
||||
import module.decode.state.DecoderPanel;
|
||||
import settings.ColorSetting;
|
||||
import settings.Setting;
|
||||
import settings.SettingsManager;
|
||||
|
||||
public class AMDecoderPanel extends DecoderPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JLabel mProtocol = new JLabel( "AM" );
|
||||
|
||||
public AMDecoderPanel( SettingsManager settingsManager, AMDecoder decoder )
|
||||
{
|
||||
super( settingsManager, decoder );
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
mProtocol.setFont( mFontDecoder );
|
||||
|
||||
add( mProtocol, "wrap" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( final ChangedAttribute changedAttribute )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingChanged( Setting setting )
|
||||
{
|
||||
super.settingChanged( setting );
|
||||
|
||||
if( setting instanceof ColorSetting )
|
||||
{
|
||||
ColorSetting colorSetting = (ColorSetting)setting;
|
||||
|
||||
switch( colorSetting.getColorSettingName() )
|
||||
{
|
||||
case CHANNEL_STATE_LABEL_DECODER:
|
||||
if( mProtocol != null )
|
||||
{
|
||||
mProtocol.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/module/decode/am/AMEditor.java
Normal file
31
src/module/decode/am/AMEditor.java
Normal file
@@ -0,0 +1,31 @@
|
||||
/*******************************************************************************
|
||||
* 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.am;
|
||||
|
||||
import module.decode.DecodeEditor;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
|
||||
public class AMEditor extends DecodeEditor
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public AMEditor( DecodeConfiguration config )
|
||||
{
|
||||
super( config );
|
||||
}
|
||||
}
|
||||
36
src/module/decode/am/DecodeConfigAM.java
Normal file
36
src/module/decode/am/DecodeConfigAM.java
Normal file
@@ -0,0 +1,36 @@
|
||||
/*******************************************************************************
|
||||
* 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.am;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
|
||||
public class DecodeConfigAM extends DecodeConfiguration
|
||||
{
|
||||
public DecodeConfigAM()
|
||||
{
|
||||
super( DecoderType.AM );
|
||||
|
||||
setAFC( false );
|
||||
}
|
||||
|
||||
public boolean supportsAFC()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
60
src/module/decode/config/AuxDecodeConfiguration.java
Normal file
60
src/module/decode/config/AuxDecodeConfiguration.java
Normal file
@@ -0,0 +1,60 @@
|
||||
/*******************************************************************************
|
||||
* 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.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import controller.config.Configuration;
|
||||
|
||||
public class AuxDecodeConfiguration extends Configuration
|
||||
{
|
||||
ArrayList<DecoderType> mAuxDecoders = new ArrayList<DecoderType>();
|
||||
|
||||
public AuxDecodeConfiguration()
|
||||
{
|
||||
}
|
||||
|
||||
@XmlElement( name="aux_decoder" )
|
||||
public ArrayList<DecoderType> getAuxDecoders()
|
||||
{
|
||||
return mAuxDecoders;
|
||||
}
|
||||
|
||||
public void setAuxDecoders( ArrayList<DecoderType> decoders )
|
||||
{
|
||||
mAuxDecoders = decoders;
|
||||
}
|
||||
|
||||
public void addAuxDecoder( DecoderType decoder )
|
||||
{
|
||||
mAuxDecoders.add( decoder );
|
||||
}
|
||||
|
||||
public void removeAuxDecoder( DecoderType decoder )
|
||||
{
|
||||
mAuxDecoders.remove( decoder );
|
||||
}
|
||||
|
||||
public void clearAuxDecoders()
|
||||
{
|
||||
mAuxDecoders.clear();
|
||||
}
|
||||
}
|
||||
71
src/module/decode/config/DecodeConfigFactory.java
Normal file
71
src/module/decode/config/DecodeConfigFactory.java
Normal file
@@ -0,0 +1,71 @@
|
||||
/*******************************************************************************
|
||||
* 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.config;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.am.DecodeConfigAM;
|
||||
import module.decode.ltrnet.DecodeConfigLTRNet;
|
||||
import module.decode.ltrstandard.DecodeConfigLTRStandard;
|
||||
import module.decode.mpt1327.DecodeConfigMPT1327;
|
||||
import module.decode.nbfm.DecodeConfigNBFM;
|
||||
import module.decode.p25.DecodeConfigP25Phase1;
|
||||
import module.decode.passport.DecodeConfigPassport;
|
||||
|
||||
public class DecodeConfigFactory
|
||||
{
|
||||
public static DecodeConfiguration getDefaultDecodeConfiguration()
|
||||
{
|
||||
return getDecodeConfiguration( DecoderType.NBFM );
|
||||
}
|
||||
|
||||
public static DecodeConfiguration
|
||||
getDecodeConfiguration( DecoderType decoder )
|
||||
{
|
||||
DecodeConfiguration retVal;
|
||||
|
||||
switch( decoder )
|
||||
{
|
||||
case AM:
|
||||
retVal = new DecodeConfigAM();
|
||||
break;
|
||||
case NBFM:
|
||||
retVal = new DecodeConfigNBFM();
|
||||
break;
|
||||
case LTR_STANDARD:
|
||||
retVal = new DecodeConfigLTRStandard();
|
||||
break;
|
||||
case LTR_NET:
|
||||
retVal = new DecodeConfigLTRNet();
|
||||
break;
|
||||
case MPT1327:
|
||||
retVal = new DecodeConfigMPT1327();
|
||||
break;
|
||||
case PASSPORT:
|
||||
retVal = new DecodeConfigPassport();
|
||||
break;
|
||||
case P25_PHASE1:
|
||||
retVal = new DecodeConfigP25Phase1();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException( "DecodeConfigFactory - "
|
||||
+ "unknown decoder type [" + decoder.toString() + "]" );
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
101
src/module/decode/config/DecodeConfiguration.java
Normal file
101
src/module/decode/config/DecodeConfiguration.java
Normal file
@@ -0,0 +1,101 @@
|
||||
/*******************************************************************************
|
||||
* 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.config;
|
||||
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlSeeAlso;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.am.DecodeConfigAM;
|
||||
import module.decode.ltrnet.DecodeConfigLTRNet;
|
||||
import module.decode.ltrstandard.DecodeConfigLTRStandard;
|
||||
import module.decode.mpt1327.DecodeConfigMPT1327;
|
||||
import module.decode.nbfm.DecodeConfigNBFM;
|
||||
import module.decode.p25.DecodeConfigP25Phase1;
|
||||
import module.decode.passport.DecodeConfigPassport;
|
||||
import controller.config.Configuration;
|
||||
|
||||
@XmlSeeAlso( { DecodeConfigAM.class,
|
||||
DecodeConfigNBFM.class,
|
||||
DecodeConfigLTRNet.class,
|
||||
DecodeConfigLTRStandard.class,
|
||||
DecodeConfigMPT1327.class,
|
||||
DecodeConfigPassport.class,
|
||||
DecodeConfigP25Phase1.class } )
|
||||
@XmlRootElement( name = "decode_configuration" )
|
||||
public abstract class DecodeConfiguration extends Configuration
|
||||
{
|
||||
public static final int DEFAULT_CALL_TIMEOUT_SECONDS = 45;
|
||||
public static final int CALL_TIMEOUT_MINIMUM = 10;
|
||||
public static final int CALL_TIMEOUT_MAXIMUM = 300; //5 minutes
|
||||
|
||||
public static final int TRAFFIC_CHANNEL_LIMIT_DEFAULT = 3;
|
||||
public static final int TRAFFIC_CHANNEL_LIMIT_MINIMUM = 0;
|
||||
public static final int TRAFFIC_CHANNEL_LIMIT_MAXIMUM = 30;
|
||||
|
||||
private DecoderType mDecoderType = DecoderType.NBFM;
|
||||
private boolean mAFCEnabled = true;
|
||||
private int mAFCMaxCorrection = 3000;
|
||||
|
||||
public DecodeConfiguration()
|
||||
{
|
||||
this( DecoderType.NBFM );
|
||||
}
|
||||
|
||||
public DecodeConfiguration( DecoderType type )
|
||||
{
|
||||
mDecoderType = type;
|
||||
}
|
||||
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return mDecoderType;
|
||||
}
|
||||
|
||||
@XmlElement( name = "afc" )
|
||||
public boolean getAFC()
|
||||
{
|
||||
return mAFCEnabled;
|
||||
}
|
||||
|
||||
public boolean isAFCEnabled()
|
||||
{
|
||||
return mAFCEnabled;
|
||||
}
|
||||
|
||||
public void setAFC( boolean enabled )
|
||||
{
|
||||
mAFCEnabled = enabled;
|
||||
}
|
||||
|
||||
public int getAFCMaximumCorrection()
|
||||
{
|
||||
return mAFCMaxCorrection;
|
||||
}
|
||||
|
||||
public void setAFCMaximumCorrection( int max )
|
||||
{
|
||||
mAFCMaxCorrection = max;
|
||||
}
|
||||
|
||||
public boolean supportsAFC()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
77
src/module/decode/event/ActivitySummaryFrame.java
Normal file
77
src/module/decode/event/ActivitySummaryFrame.java
Normal file
@@ -0,0 +1,77 @@
|
||||
/*******************************************************************************
|
||||
* 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 java.awt.Component;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextArea;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
public class ActivitySummaryFrame extends JFrame
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ActivitySummaryFrame( String summary )
|
||||
{
|
||||
this( summary, null );
|
||||
}
|
||||
|
||||
public ActivitySummaryFrame( String summary, Component displayOver )
|
||||
{
|
||||
setTitle( "Activity Summary" );
|
||||
setLocationRelativeTo( displayOver );
|
||||
setSize( 400, 400 );
|
||||
setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
|
||||
setLayout( new MigLayout( "", "[grow,fill]", "[grow,fill][]" ) );
|
||||
|
||||
JTextArea summaryText = new JTextArea( summary );
|
||||
|
||||
JScrollPane scroller = new JScrollPane( summaryText );
|
||||
scroller.setViewportView( summaryText );
|
||||
|
||||
add( scroller, "wrap" );
|
||||
|
||||
JButton close = new JButton( "Close" );
|
||||
close.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
dispose();
|
||||
}
|
||||
} );
|
||||
|
||||
add( close );
|
||||
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
setVisible( true );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
26
src/module/decode/event/ActivitySummaryProvider.java
Normal file
26
src/module/decode/event/ActivitySummaryProvider.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* 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 message.Message;
|
||||
import sample.Listener;
|
||||
|
||||
public interface ActivitySummaryProvider extends Listener<Message>
|
||||
{
|
||||
public String getActivitySummary();
|
||||
}
|
||||
312
src/module/decode/event/CallEvent.java
Normal file
312
src/module/decode/event/CallEvent.java
Normal file
@@ -0,0 +1,312 @@
|
||||
/*******************************************************************************
|
||||
* 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 java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
import alias.MetadataType;
|
||||
|
||||
public abstract class CallEvent implements Comparable<CallEvent>
|
||||
{
|
||||
protected long mEventStart = System.currentTimeMillis();
|
||||
protected long mEventEnd;
|
||||
|
||||
private SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd','HHmmss" );
|
||||
|
||||
protected DecoderType mDecoderType;
|
||||
protected CallEventType mCallEventType;
|
||||
protected AliasList mAliasList;
|
||||
protected String mFromID;
|
||||
protected String mToID;
|
||||
protected String mDetails;
|
||||
|
||||
private boolean mValid = true;
|
||||
|
||||
public CallEvent( DecoderType decoder,
|
||||
CallEventType callEventType,
|
||||
AliasList aliasList,
|
||||
String fromID,
|
||||
String toID,
|
||||
String details )
|
||||
{
|
||||
mDecoderType = decoder;
|
||||
mCallEventType = callEventType;
|
||||
mAliasList = aliasList;
|
||||
mFromID = fromID;
|
||||
mToID = toID;
|
||||
mDetails = details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this event is valid. Use this method to mark a previously
|
||||
* dispatched event as invalid and resend it so that any downstream
|
||||
* processing can remove the invalid event.
|
||||
*/
|
||||
public void setValid( boolean valid )
|
||||
{
|
||||
mValid = valid;
|
||||
}
|
||||
|
||||
public boolean isValid()
|
||||
{
|
||||
return mValid;
|
||||
}
|
||||
|
||||
public void setEnd( long end )
|
||||
{
|
||||
mEventEnd = end;
|
||||
}
|
||||
|
||||
public void end()
|
||||
{
|
||||
setEnd( System.currentTimeMillis() );
|
||||
}
|
||||
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return mDecoderType;
|
||||
}
|
||||
|
||||
public CallEventType getCallEventType()
|
||||
{
|
||||
return mCallEventType;
|
||||
}
|
||||
|
||||
public void setCallEventType( CallEventType type )
|
||||
{
|
||||
mCallEventType = type;
|
||||
}
|
||||
|
||||
public AliasList getAliasList()
|
||||
{
|
||||
return mAliasList;
|
||||
}
|
||||
|
||||
public void setAliasList( AliasList aliasList )
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
}
|
||||
|
||||
public boolean hasAliasList()
|
||||
{
|
||||
return mAliasList != null;
|
||||
}
|
||||
|
||||
public long getEventStartTime()
|
||||
{
|
||||
return mEventStart;
|
||||
}
|
||||
|
||||
public long getEventEndTime()
|
||||
{
|
||||
return mEventEnd;
|
||||
}
|
||||
|
||||
public String getFromID()
|
||||
{
|
||||
return mFromID;
|
||||
}
|
||||
|
||||
public boolean hasFromID()
|
||||
{
|
||||
return mFromID != null;
|
||||
}
|
||||
|
||||
public MetadataType getFromIDType()
|
||||
{
|
||||
return MetadataType.FROM_TALKGROUP;
|
||||
}
|
||||
|
||||
public void setFromID( String id )
|
||||
{
|
||||
mFromID = id;
|
||||
}
|
||||
|
||||
public String getToID()
|
||||
{
|
||||
return mToID;
|
||||
}
|
||||
|
||||
public boolean hasToID()
|
||||
{
|
||||
return mToID != null;
|
||||
}
|
||||
|
||||
public void setToID( String id )
|
||||
{
|
||||
mToID = id;
|
||||
}
|
||||
|
||||
public MetadataType getToIDType()
|
||||
{
|
||||
return MetadataType.TO;
|
||||
}
|
||||
|
||||
public String getDetails()
|
||||
{
|
||||
return mDetails;
|
||||
}
|
||||
|
||||
public void setDetails( String details )
|
||||
{
|
||||
mDetails = details;
|
||||
}
|
||||
|
||||
public abstract Alias getFromIDAlias();
|
||||
|
||||
public abstract Alias getToIDAlias();
|
||||
|
||||
public abstract String getChannel();
|
||||
|
||||
public abstract long getFrequency();
|
||||
|
||||
public static String getCSVHeader()
|
||||
{
|
||||
return "START_DATE,START_TIME,END_DATE,END_TIME,"
|
||||
+ "DECODER,EVENT,FROM,FROM_ALIAS,TO,TO_ALIAS,"
|
||||
+ "CHANNEL,FREQUENCY,DETAILS";
|
||||
}
|
||||
|
||||
public String toCSV()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "'" );
|
||||
sb.append( mSDF.format( new Date( getEventStartTime() ) ) );
|
||||
sb.append( "','" );
|
||||
if( mEventEnd != 0 )
|
||||
{
|
||||
sb.append( mSDF.format( new Date( getEventEndTime() ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( "','" );
|
||||
}
|
||||
sb.append( "','" );
|
||||
sb.append( getDecoderType().toString() );
|
||||
sb.append( "','" );
|
||||
sb.append( getCallEventType().toString() );
|
||||
sb.append( "'," );
|
||||
if( getFromID() != null )
|
||||
{
|
||||
sb.append( "'" );
|
||||
sb.append( getFromID() );
|
||||
sb.append( "'" );
|
||||
}
|
||||
sb.append( "," );
|
||||
|
||||
Alias fromAlias = getFromIDAlias();
|
||||
|
||||
if( fromAlias != null )
|
||||
{
|
||||
sb.append( "'" );
|
||||
sb.append( fromAlias.getName() );
|
||||
sb.append( "'" );
|
||||
}
|
||||
sb.append( "," );
|
||||
|
||||
if( getToID() != null )
|
||||
{
|
||||
sb.append( "'" );
|
||||
sb.append( getToID() );
|
||||
sb.append( "'" );
|
||||
}
|
||||
sb.append( "," );
|
||||
|
||||
Alias toAlias = getToIDAlias();
|
||||
|
||||
if( toAlias != null )
|
||||
{
|
||||
sb.append( "'" );
|
||||
sb.append( toAlias.getName() );
|
||||
sb.append( "'" );
|
||||
}
|
||||
sb.append( ",'" );
|
||||
sb.append( getChannel() );
|
||||
sb.append( "','" );
|
||||
sb.append( getFrequency() );
|
||||
sb.append( "'," );
|
||||
if( getDetails() != null )
|
||||
{
|
||||
sb.append( "'" );
|
||||
sb.append( getDetails() );
|
||||
sb.append( "'" );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public enum CallEventType
|
||||
{
|
||||
ANNOUNCEMENT( "Announcement" ),
|
||||
CALL( "Call" ),
|
||||
CALL_ALERT( "Call Alert" ),
|
||||
CALL_END( "Call End" ),
|
||||
CALL_DETECT( "Call Detect" ),
|
||||
CALL_UNIQUE_ID( "UID Call" ),
|
||||
CALL_NO_TUNER( "Call - No Tuner" ),
|
||||
CALL_TIMEOUT( "Call Timeout" ),
|
||||
COMMAND( "Command" ),
|
||||
DATA_CALL( "Data Call" ),
|
||||
DEREGISTER( "Deregister" ),
|
||||
EMERGENCY( "EMERGENCY" ),
|
||||
ENCRYPTED_CALL( "Encrypted Call" ),
|
||||
FUNCTION( "Function" ),
|
||||
GPS( "GPS" ),
|
||||
ID_ANI( "ANI" ),
|
||||
ID_UNIQUE( "Unique ID" ),
|
||||
PAGE( "Page" ),
|
||||
QUERY( "Query" ),
|
||||
REGISTER( "Register" ),
|
||||
REGISTER_ESN( "ESN" ),
|
||||
RESPONSE( "Response" ),
|
||||
SDM( "Short Data Message" ),
|
||||
STATION_ID( "Station ID" ),
|
||||
STATUS( "Status" ),
|
||||
TELEPHONE_CALL( "Telephone Call" ),
|
||||
UNKNOWN( "Unknown" ),
|
||||
INVALID( "Invalid" );
|
||||
|
||||
private String mLabel;
|
||||
|
||||
private CallEventType( String label )
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo( CallEvent other )
|
||||
{
|
||||
return Long.compare( mEventStart, other.getEventStartTime() );
|
||||
}
|
||||
}
|
||||
72
src/module/decode/event/CallEventAliasCellRenderer.java
Normal file
72
src/module/decode/event/CallEventAliasCellRenderer.java
Normal file
@@ -0,0 +1,72 @@
|
||||
/*******************************************************************************
|
||||
* 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 javax.swing.ImageIcon;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
|
||||
import settings.SettingsManager;
|
||||
import alias.Alias;
|
||||
|
||||
/**
|
||||
* Custom renderer for JTable cells that contain Alias objects. Renders the
|
||||
* alias name, and if there is an icon name, attempts to get that icon from
|
||||
* the settings manager and render it.
|
||||
*/
|
||||
public class CallEventAliasCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
private SettingsManager mSettingsManager;
|
||||
|
||||
public CallEventAliasCellRenderer( SettingsManager settingsManager )
|
||||
{
|
||||
super();
|
||||
mSettingsManager = settingsManager;
|
||||
}
|
||||
|
||||
public void setValue( Object obj )
|
||||
{
|
||||
if( obj != null && obj instanceof Alias )
|
||||
{
|
||||
Alias alias = (Alias)obj;
|
||||
|
||||
setText( alias.getName() );
|
||||
|
||||
ImageIcon icon = getIcon( alias, 12 );
|
||||
|
||||
if( icon != null )
|
||||
{
|
||||
setIcon( icon );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setText( " " );
|
||||
setIcon( null );
|
||||
}
|
||||
}
|
||||
|
||||
private ImageIcon getIcon( Alias alias, int height )
|
||||
{
|
||||
if( mSettingsManager != null && alias != null )
|
||||
{
|
||||
return mSettingsManager.getImageIcon( alias.getIconName(), height );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
223
src/module/decode/event/CallEventModel.java
Normal file
223
src/module/decode/event/CallEventModel.java
Normal file
@@ -0,0 +1,223 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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 java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
import module.decode.event.CallEvent.CallEventType;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
|
||||
public class CallEventModel extends AbstractTableModel implements Listener<CallEvent>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final static Logger mLog = LoggerFactory.getLogger( CallEventModel.class );
|
||||
|
||||
private static DecimalFormat mFrequencyFormatter =
|
||||
new DecimalFormat( "0.000000" );
|
||||
|
||||
public static final int TIME = 0;
|
||||
public static final int EVENT = 1;
|
||||
public static final int FROM_ID = 2;
|
||||
public static final int FROM_ALIAS = 3;
|
||||
public static final int TO_ID = 4;
|
||||
public static final int TO_ALIAS = 5;
|
||||
public static final int CHANNEL = 6;
|
||||
public static final int FREQUENCY = 7;
|
||||
public static final int DETAILS = 8;
|
||||
|
||||
protected int mMaxMessages = 500;
|
||||
|
||||
protected List<CallEvent> mEvents = new ArrayList<CallEvent>();
|
||||
|
||||
protected String[] mHeaders = new String[] { "Time",
|
||||
"Event",
|
||||
"From",
|
||||
"Alias",
|
||||
"To",
|
||||
"Alias",
|
||||
"Channel",
|
||||
"Frequency",
|
||||
"Details" };
|
||||
|
||||
private SimpleDateFormat mSDFTime = new SimpleDateFormat( "HH:mm:ss" );
|
||||
|
||||
private boolean mNewMessagesFirst = true;
|
||||
|
||||
public CallEventModel()
|
||||
{
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mEvents.clear();
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
mEvents.clear();
|
||||
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
public int getMaxMessageCount()
|
||||
{
|
||||
return mMaxMessages;
|
||||
}
|
||||
|
||||
public void setMaxMessageCount( int count )
|
||||
{
|
||||
mMaxMessages = count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds, updates or deletes the event from the model. Producers can send
|
||||
* the same call event multiple times to indicate that information in the
|
||||
* event is updated. Producers can also mark the event as invalid and the
|
||||
* event will be removed from the model.
|
||||
*/
|
||||
public void receive( final CallEvent event )
|
||||
{
|
||||
//TODO: reverse this so that the new events go to the end and the oldest
|
||||
//events are at the beginning ... use the getObjectAt() to control
|
||||
//correct access to the ordering imposed.
|
||||
|
||||
if( event.isValid() )
|
||||
{
|
||||
if( !mEvents.contains( event ) )
|
||||
{
|
||||
mEvents.add( 0, event );
|
||||
|
||||
fireTableRowsInserted( 0, 0 );
|
||||
|
||||
prune();
|
||||
}
|
||||
else
|
||||
{
|
||||
int row = mEvents.indexOf( event );
|
||||
|
||||
fireTableRowsUpdated( row, row );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( mEvents.contains( event ) )
|
||||
{
|
||||
int row = mEvents.indexOf( event );
|
||||
|
||||
mEvents.remove( event );
|
||||
|
||||
fireTableRowsDeleted( row, row );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void prune()
|
||||
{
|
||||
while( mEvents.size() > mMaxMessages )
|
||||
{
|
||||
int index = mEvents.size() - 1;
|
||||
mEvents.remove( index );
|
||||
|
||||
fireTableRowsDeleted( index, index );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
{
|
||||
return mEvents.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount()
|
||||
{
|
||||
return mHeaders.length;
|
||||
}
|
||||
|
||||
public String getColumnName( int column )
|
||||
{
|
||||
return mHeaders[ column ];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt( int rowIndex, int columnIndex )
|
||||
{
|
||||
synchronized( mEvents )
|
||||
{
|
||||
switch( columnIndex )
|
||||
{
|
||||
case TIME:
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( mSDFTime.format(
|
||||
mEvents.get( rowIndex ).getEventStartTime() ) );
|
||||
|
||||
if( mEvents.get( rowIndex ).getEventEndTime() != 0 )
|
||||
{
|
||||
sb.append( " - " );
|
||||
sb.append( mSDFTime.format(
|
||||
mEvents.get( rowIndex ).getEventEndTime() ) );
|
||||
}
|
||||
else if( mEvents.get( rowIndex )
|
||||
.getCallEventType() == CallEventType.CALL )
|
||||
{
|
||||
sb.append( " - In Progress" );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
case EVENT:
|
||||
return mEvents.get( rowIndex ).getCallEventType();
|
||||
case FROM_ID:
|
||||
return mEvents.get( rowIndex ).getFromID();
|
||||
case FROM_ALIAS:
|
||||
return mEvents.get( rowIndex ).getFromIDAlias();
|
||||
case TO_ID:
|
||||
return mEvents.get( rowIndex ).getToID();
|
||||
case TO_ALIAS:
|
||||
return mEvents.get( rowIndex ).getToIDAlias();
|
||||
case CHANNEL:
|
||||
return mEvents.get( rowIndex ).getChannel();
|
||||
case FREQUENCY:
|
||||
long frequency = mEvents.get( rowIndex ).getFrequency();
|
||||
|
||||
if( frequency != 0 )
|
||||
{
|
||||
return mFrequencyFormatter.format( (double)frequency / 1E6d );
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case DETAILS:
|
||||
return mEvents.get( rowIndex ).getDetails();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
105
src/module/decode/event/CallEventPanel.java
Normal file
105
src/module/decode/event/CallEventPanel.java
Normal file
@@ -0,0 +1,105 @@
|
||||
/*******************************************************************************
|
||||
* 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 javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import controller.channel.Channel;
|
||||
import controller.channel.ChannelEvent;
|
||||
import controller.channel.ChannelEvent.Event;
|
||||
import controller.channel.ChannelEventListener;
|
||||
|
||||
public class CallEventPanel extends JPanel implements ChannelEventListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JScrollPane mEmptyScroller;
|
||||
private Channel mDisplayedChannel;
|
||||
|
||||
public CallEventPanel()
|
||||
{
|
||||
setLayout( new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]") );
|
||||
|
||||
JTable table = new JTable( new CallEventModel() );
|
||||
table.setAutoCreateRowSorter( true );
|
||||
table.setAutoResizeMode( JTable.AUTO_RESIZE_LAST_COLUMN );
|
||||
mEmptyScroller = new JScrollPane( table );
|
||||
|
||||
add( mEmptyScroller );
|
||||
}
|
||||
// if( mCallEventTable == null )
|
||||
// {
|
||||
// mCallEventTable = new JTable( mCallEventModel );
|
||||
// mCallEventTable.setAutoCreateRowSorter( true );
|
||||
// mCallEventTable.setAutoResizeMode( JTable.AUTO_RESIZE_LAST_COLUMN );
|
||||
//
|
||||
// CallEventAliasCellRenderer renderer =
|
||||
// new CallEventAliasCellRenderer( mProcessingChain
|
||||
// .getResourceManager().getSettingsManager() );
|
||||
//
|
||||
// mCallEventTable.getColumnModel().getColumn( 3 )
|
||||
// .setCellRenderer( renderer );
|
||||
//
|
||||
// mCallEventTable.getColumnModel().getColumn( 5 )
|
||||
// .setCellRenderer( renderer );
|
||||
// }
|
||||
//
|
||||
// return mCallEventTable;
|
||||
|
||||
@Override
|
||||
public void channelChanged( ChannelEvent event )
|
||||
{
|
||||
if( event.getEvent() == Event.CHANGE_SELECTED &&
|
||||
event.getChannel().isSelected() )
|
||||
{
|
||||
if( mDisplayedChannel == null ||
|
||||
( mDisplayedChannel != null &&
|
||||
mDisplayedChannel != event.getChannel() ) )
|
||||
{
|
||||
removeAll();
|
||||
|
||||
JScrollPane scroller = new JScrollPane( new JTable(
|
||||
event.getChannel().getCallEventModel() ) );
|
||||
|
||||
add( scroller );
|
||||
|
||||
mDisplayedChannel = event.getChannel();
|
||||
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
else if( event.getEvent() == Event.CHANNEL_PROCESSING_STOPPED ||
|
||||
event.getEvent() == Event.CHANNEL_DISABLED )
|
||||
{
|
||||
if( mDisplayedChannel != null &&
|
||||
mDisplayedChannel == event.getChannel() )
|
||||
{
|
||||
mDisplayedChannel = null;
|
||||
removeAll();
|
||||
add( mEmptyScroller );
|
||||
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/module/decode/event/ICallEventProvider.java
Normal file
9
src/module/decode/event/ICallEventProvider.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package module.decode.event;
|
||||
|
||||
import sample.Listener;
|
||||
|
||||
public interface ICallEventProvider
|
||||
{
|
||||
public void setCallEventListener( Listener<CallEvent> listener );
|
||||
public void removeCallEventListener();
|
||||
}
|
||||
190
src/module/decode/event/MessageActivityModel.java
Normal file
190
src/module/decode/event/MessageActivityModel.java
Normal file
@@ -0,0 +1,190 @@
|
||||
/*******************************************************************************
|
||||
* 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 java.awt.EventQueue;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
import message.Message;
|
||||
import sample.Listener;
|
||||
import filter.FilterSet;
|
||||
|
||||
public class MessageActivityModel extends AbstractTableModel
|
||||
implements Listener<Message>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final int sTIME = 0;
|
||||
private static final int sPROTOCOL = 1;
|
||||
private static final int sERROR_STATUS = 2;
|
||||
private static final int sMESSAGE = 3;
|
||||
private static final int sMESSAGE_BITS = 4;
|
||||
|
||||
protected int mMaxMessages = 500;
|
||||
protected LinkedList<Message> mMessages = new LinkedList<Message>();
|
||||
|
||||
protected int[] mColumnWidths = { 110, 110, 110, -1, -1 };
|
||||
|
||||
protected String[] mHeaders = new String[] { "Time",
|
||||
"Protocol",
|
||||
"Error Check",
|
||||
"Message",
|
||||
"Binary" };
|
||||
|
||||
private SimpleDateFormat mSDFTime = new SimpleDateFormat( "HH:mm:ss" );
|
||||
|
||||
private boolean mNewMessagesFirst = true;
|
||||
|
||||
private FilterSet<Message> mMessageFilter;
|
||||
|
||||
public MessageActivityModel( FilterSet<Message> messageFilter )
|
||||
{
|
||||
mMessageFilter = messageFilter;
|
||||
}
|
||||
|
||||
public MessageActivityModel()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all messages from history
|
||||
*/
|
||||
public void clear()
|
||||
{
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
int messageCount = mMessages.size();
|
||||
|
||||
mMessages.clear();
|
||||
|
||||
fireTableRowsDeleted( 0, messageCount - 1 );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public FilterSet<Message> getMessageFilter()
|
||||
{
|
||||
return mMessageFilter;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mMessages.clear();
|
||||
}
|
||||
|
||||
public int[] getColumnWidths()
|
||||
{
|
||||
return mColumnWidths;
|
||||
}
|
||||
|
||||
public void setColumnWidths( int[] widths )
|
||||
{
|
||||
if( widths.length != 5 )
|
||||
{
|
||||
throw new IllegalArgumentException( "MessageActivityModel - "
|
||||
+ "column widths array should have 5 elements" );
|
||||
}
|
||||
else
|
||||
{
|
||||
mColumnWidths = widths;
|
||||
}
|
||||
}
|
||||
|
||||
public int getMaxMessageCount()
|
||||
{
|
||||
return mMaxMessages;
|
||||
}
|
||||
|
||||
public void setMaxMessageCount( int count )
|
||||
{
|
||||
mMaxMessages = count;
|
||||
}
|
||||
|
||||
public void receive( final Message message )
|
||||
{
|
||||
if( message.isValid() && mMessageFilter.passes( message ) )
|
||||
{
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mMessages.addFirst( message );
|
||||
|
||||
MessageActivityModel.this.fireTableRowsInserted( 0, 0 );
|
||||
|
||||
prune();
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
private void prune()
|
||||
{
|
||||
while( mMessages.size() > mMaxMessages )
|
||||
{
|
||||
mMessages.removeLast();
|
||||
|
||||
super.fireTableRowsDeleted( mMessages.size() - 1, mMessages.size() - 1 );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
{
|
||||
return mMessages.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount()
|
||||
{
|
||||
return mHeaders.length;
|
||||
}
|
||||
|
||||
public String getColumnName( int column )
|
||||
{
|
||||
return mHeaders[ column ];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt( int rowIndex, int columnIndex )
|
||||
{
|
||||
Message message = mMessages.get( rowIndex );
|
||||
|
||||
switch( columnIndex )
|
||||
{
|
||||
case sTIME:
|
||||
return mSDFTime.format( message.getDateReceived() );
|
||||
case sPROTOCOL:
|
||||
return message.getProtocol();
|
||||
case sERROR_STATUS:
|
||||
return message.getErrorStatus();
|
||||
case sMESSAGE:
|
||||
return message.getMessage();
|
||||
case sMESSAGE_BITS:
|
||||
return message.getBinaryMessage();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
310
src/module/decode/event/MessageActivityPanel.java
Normal file
310
src/module/decode/event/MessageActivityPanel.java
Normal file
@@ -0,0 +1,310 @@
|
||||
/*******************************************************************************
|
||||
* 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 java.awt.EventQueue;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import message.Message;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.jidesoft.swing.JideButton;
|
||||
import com.jidesoft.swing.JideSplitButton;
|
||||
|
||||
import controller.channel.Channel;
|
||||
import controller.channel.ChannelEvent;
|
||||
import controller.channel.ChannelEvent.Event;
|
||||
import controller.channel.ChannelEventListener;
|
||||
import filter.FilterEditorPanel;
|
||||
import filter.FilterSet;
|
||||
|
||||
public class MessageActivityPanel extends JPanel implements ChannelEventListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( MessageActivityPanel.class );
|
||||
|
||||
private static MessageActivityModel EMPTY_MODEL = new MessageActivityModel();
|
||||
|
||||
private JTable mMessageTable;
|
||||
|
||||
private Channel mDisplayedChannel;
|
||||
|
||||
private MessageManagementPanel mManagementPanel = new MessageManagementPanel();
|
||||
|
||||
public MessageActivityPanel()
|
||||
{
|
||||
setLayout( new MigLayout("insets 0 0 0 0", "[][grow,fill]", "[]0[grow,fill]") );
|
||||
|
||||
add( mManagementPanel, "span,growx" );
|
||||
|
||||
mMessageTable = new JTable( EMPTY_MODEL );
|
||||
|
||||
add( new JScrollPane( mMessageTable ), "span,grow" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelChanged( ChannelEvent event )
|
||||
{
|
||||
boolean changed = false;
|
||||
|
||||
if( event.getEvent() == Event.CHANGE_SELECTED &&
|
||||
event.getChannel().isSelected() )
|
||||
{
|
||||
if( mDisplayedChannel == null ||
|
||||
( mDisplayedChannel != null &&
|
||||
mDisplayedChannel != event.getChannel() ) )
|
||||
{
|
||||
mDisplayedChannel = event.getChannel();
|
||||
|
||||
mMessageTable.setModel( event.getChannel().getMessageActivityModel() );
|
||||
|
||||
if( mDisplayedChannel != null )
|
||||
{
|
||||
mManagementPanel.enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
mManagementPanel.disable();
|
||||
}
|
||||
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
else if( event.getEvent() == Event.CHANNEL_PROCESSING_STOPPED ||
|
||||
event.getEvent() == Event.CHANNEL_DISABLED )
|
||||
{
|
||||
if( mDisplayedChannel != null &&
|
||||
mDisplayedChannel == event.getChannel() )
|
||||
{
|
||||
mDisplayedChannel = null;
|
||||
|
||||
mMessageTable.setModel( EMPTY_MODEL );
|
||||
|
||||
mManagementPanel.disable();
|
||||
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if( changed )
|
||||
{
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
public class MessageManagementPanel extends JPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private MessageHistoryButton mHistoryButton = new MessageHistoryButton();
|
||||
private MessageFilterButton mFilterButton = new MessageFilterButton();
|
||||
|
||||
public MessageManagementPanel()
|
||||
{
|
||||
setLayout( new MigLayout("insets 2 2 5 5", "[]5[left,grow]", "") );
|
||||
|
||||
disable();
|
||||
|
||||
add( mFilterButton );
|
||||
add( mHistoryButton );
|
||||
}
|
||||
|
||||
public void enable()
|
||||
{
|
||||
mHistoryButton.setEnabled( true );
|
||||
mFilterButton.setEnabled( true );
|
||||
}
|
||||
|
||||
public void disable()
|
||||
{
|
||||
mHistoryButton.setEnabled( false );
|
||||
mFilterButton.setEnabled( false );
|
||||
}
|
||||
}
|
||||
|
||||
public class MessageHistoryButton extends JideSplitButton
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MessageHistoryButton()
|
||||
{
|
||||
super( "Clear" );
|
||||
|
||||
JPanel historyPanel = new JPanel();
|
||||
|
||||
historyPanel.add( new JLabel( "Message History:" ) );
|
||||
|
||||
final JSlider slider = new JSlider();
|
||||
slider.setMinimum( 0 );
|
||||
slider.setMaximum( 2000 );
|
||||
slider.setMajorTickSpacing( 500 );
|
||||
slider.setPaintTicks( true );
|
||||
slider.setPaintLabels( true );
|
||||
|
||||
slider.addMouseListener( new MouseListener()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent arg0 )
|
||||
{
|
||||
if( SwingUtilities.isLeftMouseButton( arg0 ) &&
|
||||
arg0.getClickCount() == 2 )
|
||||
{
|
||||
slider.setValue( 500 ); //default
|
||||
}
|
||||
}
|
||||
|
||||
public void mouseEntered( MouseEvent arg0 ) {}
|
||||
public void mouseExited( MouseEvent arg0 ) {}
|
||||
public void mousePressed( MouseEvent arg0 ) {}
|
||||
public void mouseReleased( MouseEvent arg0 ) {}
|
||||
} );
|
||||
|
||||
if( mDisplayedChannel != null )
|
||||
{
|
||||
slider.setValue( mDisplayedChannel.getMessageActivityModel()
|
||||
.getMaxMessageCount() );
|
||||
}
|
||||
else
|
||||
{
|
||||
slider.setValue( 500 );
|
||||
}
|
||||
|
||||
slider.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent arg0 )
|
||||
{
|
||||
if( mDisplayedChannel != null )
|
||||
{
|
||||
mDisplayedChannel.getMessageActivityModel()
|
||||
.setMaxMessageCount( slider.getValue() );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
historyPanel.add( slider );
|
||||
|
||||
add( historyPanel );
|
||||
|
||||
/* Clear messages */
|
||||
addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
if( mDisplayedChannel != null )
|
||||
{
|
||||
mDisplayedChannel.getMessageActivityModel().clear();
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
public class MessageFilterButton extends JideButton
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MessageFilterButton()
|
||||
{
|
||||
super( "Filter" );
|
||||
|
||||
addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent arg0 )
|
||||
{
|
||||
if( mDisplayedChannel == null )
|
||||
{
|
||||
JOptionPane.showMessageDialog( MessageFilterButton.this,
|
||||
"Please enable and select a decoding channel to "
|
||||
+ "adjust the channel's message display filter" );
|
||||
}
|
||||
else
|
||||
{
|
||||
final JFrame editor = new JFrame();
|
||||
|
||||
editor.setTitle( "Message Filter Editor" );
|
||||
editor.setSize( 600, 400 );
|
||||
editor.setLocationRelativeTo( MessageFilterButton.this );
|
||||
editor.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
|
||||
editor.setLayout( new MigLayout( "", "[grow,fill]",
|
||||
"[grow,fill][][]" ) );
|
||||
|
||||
@SuppressWarnings( "unchecked" )
|
||||
FilterSet<Message> filter = (FilterSet<Message>)
|
||||
mDisplayedChannel.getMessageActivityModel()
|
||||
.getMessageFilter();
|
||||
|
||||
FilterEditorPanel<Message> panel =
|
||||
new FilterEditorPanel<Message>( filter );
|
||||
|
||||
JScrollPane scroller = new JScrollPane( panel );
|
||||
scroller.setViewportView( panel );
|
||||
|
||||
editor.add( scroller, "wrap" );
|
||||
|
||||
editor.add( new JLabel( "Right-click to select/deselect "
|
||||
+ "all nodes" ), "wrap" );
|
||||
|
||||
JButton close = new JButton( "Close" );
|
||||
close.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
editor.dispose();
|
||||
}
|
||||
} );
|
||||
|
||||
editor.add( close );
|
||||
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
editor.setVisible( true );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
80
src/module/decode/event/MessageDetailsProvider.java
Normal file
80
src/module/decode/event/MessageDetailsProvider.java
Normal file
@@ -0,0 +1,80 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <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();
|
||||
|
||||
}
|
||||
202
src/module/decode/fleetsync1/Fleetsync1Message.java
Normal file
202
src/module/decode/fleetsync1/Fleetsync1Message.java
Normal file
@@ -0,0 +1,202 @@
|
||||
/*******************************************************************************
|
||||
* 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.fleetsync1;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import map.Plottable;
|
||||
import message.Message;
|
||||
import alias.Alias;
|
||||
import bits.BinaryMessage;
|
||||
|
||||
public class Fleetsync1Message extends Message
|
||||
{
|
||||
private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
|
||||
|
||||
private BinaryMessage mMessage;
|
||||
|
||||
public Fleetsync1Message( BinaryMessage message )
|
||||
{
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
public boolean isValid()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( mSDF.format( new Date( System.currentTimeMillis() ) ) );
|
||||
sb.append( " " );
|
||||
sb.append( "FS1 [" + mMessage.toString() + "]" );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads spaces onto the end of the value to make it 'places' long
|
||||
*/
|
||||
public String pad( String value, int places, String padCharacter )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( value );
|
||||
|
||||
while( sb.length() < places )
|
||||
{
|
||||
sb.append( padCharacter );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
@SuppressWarnings( "unused" )
|
||||
private 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBinaryMessage()
|
||||
{
|
||||
return mMessage.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFromID()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToID()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getToIDAlias()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorStatus()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plottable getPlottable()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
53
src/module/decode/fleetsync1/Fleetsync1MessageProcessor.java
Normal file
53
src/module/decode/fleetsync1/Fleetsync1MessageProcessor.java
Normal file
@@ -0,0 +1,53 @@
|
||||
/*******************************************************************************
|
||||
* 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.fleetsync1;
|
||||
|
||||
import message.Message;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import bits.BinaryMessage;
|
||||
|
||||
public class Fleetsync1MessageProcessor implements Listener<BinaryMessage>
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( Fleetsync1MessageProcessor.class );
|
||||
|
||||
private Broadcaster<Message> mBroadcaster = new Broadcaster<Message>();
|
||||
|
||||
@Override
|
||||
public void receive( BinaryMessage buffer )
|
||||
{
|
||||
Fleetsync1Message message = new Fleetsync1Message( buffer );
|
||||
|
||||
mBroadcaster.receive( message );
|
||||
}
|
||||
|
||||
public void addMessageListener( Listener<Message> listener )
|
||||
{
|
||||
mBroadcaster.addListener( listener );
|
||||
}
|
||||
|
||||
public void removeMessageListener( Listener<Message> listener )
|
||||
{
|
||||
mBroadcaster.removeListener( listener );
|
||||
}
|
||||
}
|
||||
173
src/module/decode/fleetsync2/Fleetsync2Decoder.java
Normal file
173
src/module/decode/fleetsync2/Fleetsync2Decoder.java
Normal file
@@ -0,0 +1,173 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.fleetsync2;
|
||||
|
||||
import instrument.Instrumentable;
|
||||
import instrument.tap.Tap;
|
||||
import instrument.tap.stream.BinaryTap;
|
||||
import instrument.tap.stream.FloatBufferTap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import module.decode.Decoder;
|
||||
import module.decode.DecoderType;
|
||||
import sample.Listener;
|
||||
import sample.real.IRealBufferListener;
|
||||
import sample.real.RealBuffer;
|
||||
import alias.AliasList;
|
||||
import bits.MessageFramer;
|
||||
import bits.SyncPattern;
|
||||
import dsp.filter.Filters;
|
||||
import dsp.filter.fir.real.RealFIRFilter_RB_RB;
|
||||
import dsp.filter.halfband.real.HalfBandFilter_RB_RB;
|
||||
import dsp.fsk.FSK2Decoder;
|
||||
import dsp.fsk.FSK2Decoder.Output;
|
||||
|
||||
/**
|
||||
* Fleetsync II Decoder - 1200 baud 2FSK decoder that can process 48k sample rate
|
||||
* floating point samples and output fully framed Fleetsync II messages
|
||||
*/
|
||||
public class Fleetsync2Decoder extends Decoder implements IRealBufferListener,
|
||||
Instrumentable
|
||||
{
|
||||
/* Decimated sample rate ( 48,000 / 2 = 24,000 ) feeding the decoder */
|
||||
private static final int sDECIMATED_SAMPLE_RATE = 24000;
|
||||
|
||||
/* Baud or Symbol Rate */
|
||||
private static final int sSYMBOL_RATE = 1200;
|
||||
|
||||
/* Message length - 5 x REVS + 16 x SYNC + 8 x 64Bit Blocks */
|
||||
private static final int sMESSAGE_LENGTH = 537;
|
||||
|
||||
/* Instrumentation Taps */
|
||||
private ArrayList<Tap> mAvailableTaps;
|
||||
private static final String INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD =
|
||||
"Tap Point: Bandpass Filter > < FSK2 Decoder";
|
||||
private static final String INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER =
|
||||
"Tap Point: FSK2 Decoder > < Message Framer";
|
||||
|
||||
private FSK2Decoder mFSKDecoder;
|
||||
private HalfBandFilter_RB_RB mDecimationFilter;
|
||||
private RealFIRFilter_RB_RB mBandPassFilter;
|
||||
private MessageFramer mMessageFramer;
|
||||
private Fleetsync2MessageProcessor mMessageProcessor;
|
||||
|
||||
public Fleetsync2Decoder( AliasList aliasList )
|
||||
{
|
||||
super( new FleetsyncDecoderState( aliasList ) );
|
||||
|
||||
mDecimationFilter = new HalfBandFilter_RB_RB(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0f, true );
|
||||
|
||||
mBandPassFilter = new RealFIRFilter_RB_RB(
|
||||
Filters.FIRBP_1200FSK_24000FS.getCoefficients(), 1.02f );
|
||||
|
||||
mDecimationFilter.setListener( mBandPassFilter );
|
||||
|
||||
mFSKDecoder = new FSK2Decoder( sDECIMATED_SAMPLE_RATE,
|
||||
sSYMBOL_RATE, Output.INVERTED );
|
||||
mBandPassFilter.setListener( mFSKDecoder );
|
||||
|
||||
mMessageFramer = new MessageFramer(
|
||||
SyncPattern.FLEETSYNC2.getPattern(), sMESSAGE_LENGTH );
|
||||
mFSKDecoder.setListener( mMessageFramer );
|
||||
|
||||
mMessageProcessor = new Fleetsync2MessageProcessor( aliasList );
|
||||
mMessageFramer.addMessageListener( mMessageProcessor );
|
||||
|
||||
mMessageProcessor.setMessageListener( mMessageBroadcaster );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mBandPassFilter.dispose();
|
||||
mDecimationFilter.dispose();
|
||||
mDecoderState.dispose();
|
||||
mFSKDecoder.dispose();
|
||||
mMessageFramer.dispose();
|
||||
mMessageProcessor.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.FLEETSYNC2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Tap> getTaps()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<Tap>();
|
||||
|
||||
mAvailableTaps.add( new FloatBufferTap(
|
||||
INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD, 0, 0.5f ) );
|
||||
mAvailableTaps.addAll( mFSKDecoder.getTaps() );
|
||||
mAvailableTaps.add( new BinaryTap(
|
||||
INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER, 0, 0.025f ) );
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTap( Tap tap )
|
||||
{
|
||||
mFSKDecoder.addTap( tap );
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
|
||||
FloatBufferTap bpTap = (FloatBufferTap)tap;
|
||||
mBandPassFilter.setListener( bpTap );
|
||||
bpTap.setListener( mFSKDecoder );
|
||||
break;
|
||||
case INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER:
|
||||
BinaryTap decoderTap = (BinaryTap)tap;
|
||||
mFSKDecoder.setListener( decoderTap );
|
||||
decoderTap.setListener( mMessageFramer );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTap( Tap tap )
|
||||
{
|
||||
mFSKDecoder.removeTap( tap );
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
|
||||
mBandPassFilter.setListener( mFSKDecoder );
|
||||
break;
|
||||
case INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER:
|
||||
mFSKDecoder.setListener( mMessageFramer );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<RealBuffer> getRealBufferListener()
|
||||
{
|
||||
return mDecimationFilter;
|
||||
}
|
||||
}
|
||||
189
src/module/decode/fleetsync2/Fleetsync2DecoderPanel.java
Normal file
189
src/module/decode/fleetsync2/Fleetsync2DecoderPanel.java
Normal file
@@ -0,0 +1,189 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.fleetsync2;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import alias.Alias;
|
||||
import module.decode.state.ChangedAttribute;
|
||||
import module.decode.state.DecoderPanel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import settings.ColorSetting;
|
||||
import settings.ColorSetting.ColorSettingName;
|
||||
import settings.Setting;
|
||||
import settings.SettingsManager;
|
||||
|
||||
public class Fleetsync2DecoderPanel extends DecoderPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String PROTOCOL = "FSync II";
|
||||
|
||||
private JLabel mFromLabel = new JLabel( " " );
|
||||
private JLabel mFrom = new JLabel();
|
||||
private JLabel mFromAlias = new JLabel();
|
||||
|
||||
private JLabel mToLabel = new JLabel();
|
||||
private JLabel mTo = new JLabel( " " );
|
||||
private JLabel mToAlias = new JLabel();
|
||||
|
||||
private JLabel mProtocol = new JLabel( PROTOCOL );
|
||||
private JLabel mMessage = new JLabel();
|
||||
private JLabel mMessageType = new JLabel();
|
||||
|
||||
public Fleetsync2DecoderPanel( SettingsManager settingsManager,
|
||||
Fleetsync2Decoder decoder )
|
||||
{
|
||||
super( settingsManager, decoder );
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private FleetsyncDecoderState getState()
|
||||
{
|
||||
return (FleetsyncDecoderState)mDecoder.getDecoderState();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
protected void init()
|
||||
{
|
||||
/* Calling super init will get and broadcast color settings to properly
|
||||
* setup the jlabel colors */
|
||||
super.init();
|
||||
|
||||
setLayout( new MigLayout( "insets 1 0 0 0", "[grow,fill]", "[]0[]0[]") );
|
||||
|
||||
mFromLabel.setFont( mFontAuxDecoder );
|
||||
add( mFromLabel );
|
||||
mFrom.setFont( mFontAuxDecoder );
|
||||
add( mFrom );
|
||||
mFromAlias.setFont( mFontAuxDecoder );
|
||||
add( mFromAlias, "wrap" );
|
||||
|
||||
mToLabel.setFont( mFontAuxDecoder );
|
||||
add( mToLabel );
|
||||
mTo.setFont( mFontAuxDecoder );
|
||||
add( mTo );
|
||||
mToAlias.setFont( mFontAuxDecoder );
|
||||
add( mToAlias, "wrap" );
|
||||
|
||||
mProtocol.setFont( mFontAuxDecoder );
|
||||
add( mProtocol );
|
||||
mMessageType.setFont( mFontAuxDecoder );
|
||||
add( mMessageType );
|
||||
mMessage.setFont( mFontAuxDecoder );
|
||||
add( mMessage, "wrap" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( final ChangedAttribute changedAttribute )
|
||||
{
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
switch( changedAttribute )
|
||||
{
|
||||
case FROM_TALKGROUP:
|
||||
String from = getState().getFromID();
|
||||
|
||||
if( from != null )
|
||||
{
|
||||
mFromLabel.setText( "FM:" );
|
||||
}
|
||||
else
|
||||
{
|
||||
mFromLabel.setText( " " );
|
||||
}
|
||||
mFrom.setText( from );
|
||||
break;
|
||||
case FROM_TALKGROUP_ALIAS:
|
||||
Alias fromAlias = getState().getFromIDAlias();
|
||||
|
||||
if( fromAlias != null )
|
||||
{
|
||||
mFromAlias.setText( fromAlias.getName() );
|
||||
}
|
||||
break;
|
||||
case TO_TALKGROUP:
|
||||
String to = getState().getToID();
|
||||
|
||||
if( to != null )
|
||||
{
|
||||
mToLabel.setText( "TO:" );
|
||||
}
|
||||
else
|
||||
{
|
||||
mToLabel.setText( " " );
|
||||
}
|
||||
mTo.setText( to );
|
||||
break;
|
||||
case TO_TALKGROUP_ALIAS:
|
||||
Alias toAlias = getState().getToIDAlias();
|
||||
|
||||
if( toAlias != null )
|
||||
{
|
||||
mToAlias.setText( toAlias.getName() );
|
||||
}
|
||||
break;
|
||||
case MESSAGE:
|
||||
mMessage.setText( getState().getMessage() );
|
||||
break;
|
||||
case MESSAGE_TYPE:
|
||||
mMessageType.setText( getState().getMessageType() );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingChanged( Setting setting )
|
||||
{
|
||||
super.settingChanged( setting );
|
||||
|
||||
if( setting instanceof ColorSetting )
|
||||
{
|
||||
ColorSetting color = (ColorSetting)setting;
|
||||
|
||||
if( color.getColorSettingName() ==
|
||||
ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER )
|
||||
{
|
||||
mFromLabel.setForeground( color.getColor() );
|
||||
mFrom.setForeground( color.getColor() );
|
||||
mFromAlias.setForeground( color.getColor() );
|
||||
mToLabel.setForeground( color.getColor() );
|
||||
mTo.setForeground( color.getColor() );
|
||||
mToAlias.setForeground( color.getColor() );
|
||||
mProtocol.setForeground( color.getColor() );
|
||||
mMessageType.setForeground( color.getColor() );
|
||||
mMessage.setForeground( color.getColor() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/module/decode/fleetsync2/Fleetsync2MessageProcessor.java
Normal file
62
src/module/decode/fleetsync2/Fleetsync2MessageProcessor.java
Normal file
@@ -0,0 +1,62 @@
|
||||
/*******************************************************************************
|
||||
* 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.fleetsync2;
|
||||
|
||||
import message.Message;
|
||||
import sample.Listener;
|
||||
import alias.AliasList;
|
||||
import bits.BinaryMessage;
|
||||
|
||||
public class Fleetsync2MessageProcessor implements Listener<BinaryMessage>
|
||||
{
|
||||
private Listener<Message> mMessageListener;
|
||||
|
||||
private AliasList mAliasList;
|
||||
|
||||
public Fleetsync2MessageProcessor( AliasList list )
|
||||
{
|
||||
mAliasList = list;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mMessageListener = null;
|
||||
mAliasList = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( BinaryMessage buffer )
|
||||
{
|
||||
FleetsyncMessage message = new FleetsyncMessage( buffer, mAliasList );
|
||||
|
||||
if( mMessageListener != null )
|
||||
{
|
||||
mMessageListener.receive( message );
|
||||
}
|
||||
}
|
||||
|
||||
public void setMessageListener( Listener<Message> listener )
|
||||
{
|
||||
mMessageListener = listener;
|
||||
}
|
||||
|
||||
public void removeMessageListener()
|
||||
{
|
||||
mMessageListener = null;
|
||||
}
|
||||
}
|
||||
189
src/module/decode/fleetsync2/FleetsyncCallEvent.java
Normal file
189
src/module/decode/fleetsync2/FleetsyncCallEvent.java
Normal file
@@ -0,0 +1,189 @@
|
||||
/*******************************************************************************
|
||||
* 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.fleetsync2;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.event.CallEvent;
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
|
||||
public class FleetsyncCallEvent extends CallEvent
|
||||
{
|
||||
public FleetsyncCallEvent( CallEventType callEventType,
|
||||
AliasList aliasList,
|
||||
String fromID,
|
||||
String toID,
|
||||
String details )
|
||||
{
|
||||
super( DecoderType.FLEETSYNC2, callEventType,
|
||||
aliasList, fromID, toID, details );
|
||||
}
|
||||
|
||||
private Alias getAlias( String ident )
|
||||
{
|
||||
if( hasAliasList() )
|
||||
{
|
||||
return getAliasList().getFleetsyncAlias( ident );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
return getAlias( getFromID() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getToIDAlias()
|
||||
{
|
||||
return getAlias( getToID() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannel()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrequency()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static class Builder
|
||||
{
|
||||
/* Required parameters */
|
||||
private CallEventType mCallEventType;
|
||||
|
||||
/* Optional parameters */
|
||||
private AliasList mAliasList;
|
||||
private String mFromID;
|
||||
private String mToID;
|
||||
private String mDetails;
|
||||
|
||||
public Builder( CallEventType callEventType )
|
||||
{
|
||||
mCallEventType = callEventType;
|
||||
}
|
||||
|
||||
public Builder aliasList( AliasList aliasList )
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder from( String val )
|
||||
{
|
||||
mFromID = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder details( String details )
|
||||
{
|
||||
mDetails = details;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder to( String toID )
|
||||
{
|
||||
mToID = toID;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FleetsyncCallEvent build()
|
||||
{
|
||||
return new FleetsyncCallEvent( this );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor for the builder
|
||||
*/
|
||||
private FleetsyncCallEvent( Builder builder )
|
||||
{
|
||||
this( builder.mCallEventType,
|
||||
builder.mAliasList,
|
||||
builder.mFromID,
|
||||
builder.mToID,
|
||||
builder.mDetails );
|
||||
}
|
||||
|
||||
public static FleetsyncCallEvent
|
||||
getFleetsync2Event( FleetsyncMessage message )
|
||||
{
|
||||
CallEventType type = CallEventType.UNKNOWN;
|
||||
StringBuilder sbDetails = new StringBuilder();
|
||||
sbDetails.append( "Fleetsync " );
|
||||
|
||||
switch( message.getMessageType() )
|
||||
{
|
||||
case ACKNOWLEDGE:
|
||||
type = CallEventType.RESPONSE;
|
||||
sbDetails.append( "ACK" );
|
||||
break;
|
||||
case ANI:
|
||||
type = CallEventType.ID_ANI;
|
||||
break;
|
||||
case EMERGENCY:
|
||||
type = CallEventType.EMERGENCY;
|
||||
break;
|
||||
case GPS:
|
||||
type = CallEventType.GPS;
|
||||
sbDetails.append( message.getGPSLocation() );
|
||||
break;
|
||||
case LONE_WORKER_EMERGENCY:
|
||||
type = CallEventType.EMERGENCY;
|
||||
sbDetails.append( "LONE WORKER EMERGENCY ALERT" );
|
||||
break;
|
||||
case PAGING:
|
||||
type = CallEventType.PAGE;
|
||||
break;
|
||||
case STATUS:
|
||||
type = CallEventType.STATUS;
|
||||
sbDetails.append( "Status: " );
|
||||
sbDetails.append( message.getStatus() );
|
||||
|
||||
if( message.getStatusAlias() != null )
|
||||
{
|
||||
sbDetails.append( "/" );
|
||||
sbDetails.append( message.getStatusAlias().getName() );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if( type == CallEventType.ID_ANI ||
|
||||
type == CallEventType.EMERGENCY )
|
||||
{
|
||||
return new FleetsyncCallEvent.Builder( type )
|
||||
.details( sbDetails.toString() )
|
||||
.from( message.getFromID() )
|
||||
.build();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new FleetsyncCallEvent.Builder( type )
|
||||
.details( sbDetails.toString() )
|
||||
.from( message.getFromID() )
|
||||
.to( message.getToID() )
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
328
src/module/decode/fleetsync2/FleetsyncDecoderState.java
Normal file
328
src/module/decode/fleetsync2/FleetsyncDecoderState.java
Normal file
@@ -0,0 +1,328 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.fleetsync2;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import message.Message;
|
||||
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 alias.Alias;
|
||||
import alias.AliasList;
|
||||
|
||||
public class FleetsyncDecoderState extends DecoderState
|
||||
{
|
||||
private TreeSet<String> mIdents = new TreeSet<String>();
|
||||
private TreeSet<String> mEmergencyIdents = new TreeSet<String>();
|
||||
|
||||
private String mFrom;
|
||||
private Alias mFromAlias;
|
||||
|
||||
private String mTo;
|
||||
private Alias mToAlias;
|
||||
|
||||
private String mMessage;
|
||||
private String mMessageType;
|
||||
|
||||
public FleetsyncDecoderState( AliasList aliasList )
|
||||
{
|
||||
super( aliasList );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the overall decoder state and clears any accumulated event details
|
||||
*/
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
mIdents.clear();
|
||||
mEmergencyIdents.clear();
|
||||
|
||||
resetState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this decoder state
|
||||
*/
|
||||
public void resetState()
|
||||
{
|
||||
setFromID( null );
|
||||
setFromIDAlias( null );
|
||||
setToID( null );
|
||||
setToIDAlias( null );
|
||||
setMessage( null );
|
||||
setMessageType( null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Message message )
|
||||
{
|
||||
if( message instanceof FleetsyncMessage )
|
||||
{
|
||||
FleetsyncMessage fleetsync = (FleetsyncMessage)message;
|
||||
|
||||
if( fleetsync.isValid() )
|
||||
{
|
||||
setFromID( fleetsync.getFromID() );
|
||||
setFromIDAlias( fleetsync.getFromIDAlias() );
|
||||
mIdents.add( fleetsync.getFromID() );
|
||||
|
||||
FleetsyncMessageType type = fleetsync.getMessageType();
|
||||
|
||||
if( type != FleetsyncMessageType.ANI )
|
||||
{
|
||||
setToID( fleetsync.getToID() );
|
||||
setToIDAlias( fleetsync.getToIDAlias() );
|
||||
mIdents.add( fleetsync.getToID() );
|
||||
}
|
||||
|
||||
setMessageType( type.getLabel() );
|
||||
|
||||
switch( type )
|
||||
{
|
||||
case GPS:
|
||||
setMessage( fleetsync.getGPSLocation() );
|
||||
break;
|
||||
case STATUS:
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( fleetsync.getStatus() );
|
||||
|
||||
Alias status = fleetsync.getStatusAlias();
|
||||
|
||||
if( status != null )
|
||||
{
|
||||
sb.append( "/" );
|
||||
sb.append( status.getName() );
|
||||
}
|
||||
|
||||
setMessage( sb.toString() );
|
||||
break;
|
||||
case EMERGENCY:
|
||||
case LONE_WORKER_EMERGENCY:
|
||||
mEmergencyIdents.add( fleetsync.getFromID() );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
FleetsyncCallEvent fsCallEvent =
|
||||
FleetsyncCallEvent.getFleetsync2Event( fleetsync );
|
||||
|
||||
fsCallEvent.setAliasList( getAliasList() );
|
||||
|
||||
broadcast( fsCallEvent );
|
||||
|
||||
/* Broadcast decode event. If the primary state is not already
|
||||
* in a call state, then we'll use a default decoded data state */
|
||||
broadcast( new DecoderStateEvent( this, Event.DECODE, State.DATA ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getFromID()
|
||||
{
|
||||
return mFrom;
|
||||
}
|
||||
|
||||
public void setFromID( String from )
|
||||
{
|
||||
mFrom = from;
|
||||
broadcast( ChangedAttribute.FROM_TALKGROUP );
|
||||
}
|
||||
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
return mFromAlias;
|
||||
}
|
||||
|
||||
public void setFromIDAlias( Alias alias )
|
||||
{
|
||||
mFromAlias = alias;
|
||||
broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS );
|
||||
}
|
||||
|
||||
public String getToID()
|
||||
{
|
||||
return mTo;
|
||||
}
|
||||
|
||||
public void setToID( String to )
|
||||
{
|
||||
mTo = to;
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||
}
|
||||
|
||||
public Alias getToIDAlias()
|
||||
{
|
||||
return mToAlias;
|
||||
}
|
||||
|
||||
public void setToIDAlias( Alias alias )
|
||||
{
|
||||
mToAlias = alias;
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
||||
}
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
public void setMessage( String message )
|
||||
{
|
||||
mMessage = message;
|
||||
broadcast( ChangedAttribute.MESSAGE );
|
||||
}
|
||||
|
||||
public String getMessageType()
|
||||
{
|
||||
return mMessageType;
|
||||
}
|
||||
|
||||
public void setMessageType( String type )
|
||||
{
|
||||
mMessageType = type;
|
||||
broadcast( ChangedAttribute.MESSAGE_TYPE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to reset events issued by the channel state
|
||||
*/
|
||||
@Override
|
||||
public void receiveDecoderStateEvent( DecoderStateEvent event )
|
||||
{
|
||||
switch( event.getEvent() )
|
||||
{
|
||||
case RESET:
|
||||
resetState();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActivitySummary()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "=============================\n" );
|
||||
sb.append( "Decoder:\tFleetsync II\n\n" );
|
||||
sb.append( "Fleetsync Idents\n" );
|
||||
|
||||
if( mIdents.isEmpty() )
|
||||
{
|
||||
sb.append( " None\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Iterator<String> it = mIdents.iterator();
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
String ident = it.next();
|
||||
|
||||
sb.append( " " );
|
||||
sb.append( formatIdent( ident ) );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias alias = getAliasList().getFleetsyncAlias( ident );
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
sb.append( " " );
|
||||
sb.append( alias.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\nFleetsync Emergency Activations\n" );
|
||||
|
||||
if( mEmergencyIdents.isEmpty() )
|
||||
{
|
||||
sb.append( " None\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Iterator<String> it = mEmergencyIdents.iterator();
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
String ident = it.next();
|
||||
|
||||
sb.append( " " );
|
||||
sb.append( formatIdent( ident ) );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias alias = getAliasList().getFleetsyncAlias( ident );
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
sb.append( " " );
|
||||
sb.append( alias.getName() );
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String formatIdent( String ident )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if( ident.length() == 7 )
|
||||
{
|
||||
sb.append( ident.substring( 0, 3 ) );
|
||||
sb.append( "-" );
|
||||
sb.append( ident.substring( 3, 7 ) );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return ident;
|
||||
}
|
||||
}
|
||||
}
|
||||
896
src/module/decode/fleetsync2/FleetsyncMessage.java
Normal file
896
src/module/decode/fleetsync2/FleetsyncMessage.java
Normal file
@@ -0,0 +1,896 @@
|
||||
/*******************************************************************************
|
||||
* 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.fleetsync2;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
|
||||
import map.Plottable;
|
||||
import message.Message;
|
||||
|
||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
import bits.BinaryMessage;
|
||||
import edac.CRC;
|
||||
import edac.CRCFleetsync;
|
||||
|
||||
public class FleetsyncMessage extends Message
|
||||
{
|
||||
private static DecimalFormat mDecimalFormatter = new DecimalFormat("#.#####");
|
||||
private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" );
|
||||
|
||||
//Calendar to use in calculating time hacks
|
||||
Calendar mCalendar = new GregorianCalendar();
|
||||
|
||||
//Message parts are identified in big-endian order for correct translation
|
||||
|
||||
//Message Header
|
||||
private static int[] sRevs = { 4,3,2,1,0 };
|
||||
private static int[] sSync = { 20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5 };
|
||||
|
||||
//Message Block 1
|
||||
private static int[] sStatusMessage = { 27,26,25,24,23,22,21 };
|
||||
private static int[] sMessageType = { 33,32,31,30,29 };
|
||||
|
||||
private static int sEmergencyFlag = 22;
|
||||
private static int sLoneWorkerFlag = 24;
|
||||
private static int sPagingFlag = 26;
|
||||
private static int sEndOfTransmissionFlag = 27;
|
||||
private static int sManualFlag = 28;
|
||||
private static int sANIFlag = 29;
|
||||
private static int sStatusFlag = 30;
|
||||
private static int sAcknowledgeFlag = 31;
|
||||
//32 - unknown - always 0
|
||||
//33 - unknown - always 0
|
||||
//34 - unknown - set for ACKNOWLEDGE
|
||||
private static int sGPSExtensionFlag = 35;
|
||||
private static int sFleetExtensionFlag = 36;
|
||||
private static int[] sFleetFrom = { 44,43,42,41,40,39,38,37 };
|
||||
private static int[] sIdentFrom = { 56,55,54,53,52,51,50,49,48,47,46,45 };
|
||||
private static int[] sIdentTo = { 68,67,66,65,64,63,62,61,60,59,58,57 };
|
||||
private static int[] sCRC1 = { 84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69 };
|
||||
|
||||
//Message Block 2
|
||||
private static int[] sFleetTo = { 92,91,90,89,88,87,86,85 };
|
||||
private static int[] sCRC2 = { 148,147,146,145,144,143,142,141,140,139,138,137,136,135,134,132 };
|
||||
|
||||
//Message Block 3
|
||||
private static int[] sGPSHours = { 176,175,174,173,172 };
|
||||
private static int[] sGPSMinutes = { 182,181,180,179,178,177 };
|
||||
private static int[] sGPSSeconds = { 188,187,186,185,184,183 };
|
||||
private static int[] sCRC3 = { 212,211,210,209,208,207,206,205,204,203,202,201,200,199,198,197 };
|
||||
|
||||
//Message Block 4
|
||||
private static int[] sGPSChecksum = { 220,219,218,217,216,215,214,213 };
|
||||
private static int[] sLatitudeDegreesMinutes = { 236,235,234,233,232,231,230,229,228,227,226,225,224,223,222,221 };
|
||||
private static int[] sLatitudeDecimalMinutes = { 251,250,249,248,247,246,245,244,243,242,241,240,239,238 };
|
||||
private static int[] sSpeed = { 276,275,274,273,272,271,270,269,268,267,266,265,264,263,262,261,260,259,258,257,256,255,254,253,252 };
|
||||
private static int[] sCRC4 = { 276,275,274,273,272,271,270,269,268,267,266,265,264,263,262,261 };
|
||||
|
||||
//Message Block 5
|
||||
private static int[] sGPSCentury = { 284,283,282,281,280,279,278,277 };
|
||||
private static int[] sGPSYear = { 291,290,289,288,287,286,285 };
|
||||
private static int[] sGPSMonth = { 295,294,293,292 };
|
||||
private static int[] sGPSDay = { 300,299,298,297,296 };
|
||||
private static int[] sLongitudeDegreesMinutes = { 316,315,314,313,312,311,310,309,308,307,306,305,304,303,302,301 };
|
||||
private static int[] sLongitudeDecimalMinutes = { 331,330,329,328,327,326,325,324,323,322,321,320,319,318 };
|
||||
private static int[] sCRC5 = { 340,339,338,337,336,335,334,333,332,331,330,329,328,327,326,325 };
|
||||
|
||||
//Message Block 6
|
||||
private static int[] sGPSUnknown1 = { 352,351,350,349 };
|
||||
private static int[] sGPSHeading = { 365,363,362,361,360,359,358,357,356,355,354,353 };
|
||||
private static int[] sCRC6 = { 404,403,402,401,400,399,398,397,396,395,394,393,392,391,390,389 };
|
||||
|
||||
//Message Block 7
|
||||
private static int[] sBLK7WhatIsIt = { 444,443,442,441,440,439,438,437,436,435,434,433,432,431,430,429 };
|
||||
private static int[] sCRC7 = { 468,467,466,465,464,463,462,461,460,459,458,457,456,455,454,453 };
|
||||
|
||||
//Message Block 8
|
||||
private static int[] sGPSSpeed = { 491,490,489,488,487,486,485,484 };
|
||||
private static int[] sGPSSpeedFractional = { 499,498,497,496,495,494,493,492 };
|
||||
private static int[] sCRC8 = { 532,531,530,529,528,527,526,525,524,523,522,521,520,519,518,517 };
|
||||
|
||||
private BinaryMessage mMessage;
|
||||
private CRC[] mCRC = new CRC[ 8 ];
|
||||
private AliasList mAliasList;
|
||||
|
||||
public FleetsyncMessage( BinaryMessage message, AliasList list )
|
||||
{
|
||||
mMessage = message;
|
||||
mAliasList = list;
|
||||
checkParity();
|
||||
}
|
||||
|
||||
private void checkParity()
|
||||
{
|
||||
//Check message block 1
|
||||
mCRC[ 0 ] = detectAndCorrect( 21, 85 );
|
||||
|
||||
//Only check subsequent blocks if we know block 1 is correct
|
||||
if( mCRC[ 0 ] == CRC.PASSED || mCRC[ 0 ] == CRC.CORRECTED )
|
||||
{
|
||||
if( hasFleetExtensionFlag() )
|
||||
{
|
||||
//Check message block 2
|
||||
mCRC[ 1 ] = detectAndCorrect( 85, 149 );
|
||||
}
|
||||
|
||||
if( hasGPSFlag() )
|
||||
{
|
||||
//Check message block 3
|
||||
mCRC[ 2 ] = detectAndCorrect( 149, 213 );
|
||||
//Check message block 4
|
||||
mCRC[ 3 ] = detectAndCorrect( 213, 277 );
|
||||
//Check message block 5
|
||||
mCRC[ 4 ] = detectAndCorrect( 277, 341 );
|
||||
//Check message block 6
|
||||
mCRC[ 5 ] = detectAndCorrect( 341, 405 );
|
||||
//Check message block 7
|
||||
mCRC[ 6 ] = detectAndCorrect( 405, 469 );
|
||||
//Check message block 8
|
||||
mCRC[ 7 ] = detectAndCorrect( 469, 533 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CRC detectAndCorrect( int start, int end )
|
||||
{
|
||||
BitSet original = mMessage.get( start, end );
|
||||
|
||||
CRC retVal = CRCFleetsync.check( original );
|
||||
|
||||
//Attempt to correct single-bit errors
|
||||
if( retVal == CRC.FAILED_PARITY )
|
||||
{
|
||||
int[] errorBitPositions = CRCFleetsync.findBitErrors( original );
|
||||
|
||||
if( errorBitPositions != null )
|
||||
{
|
||||
for( int errorBitPosition: errorBitPositions )
|
||||
{
|
||||
mMessage.flip( start + errorBitPosition );
|
||||
}
|
||||
|
||||
retVal = CRC.CORRECTED;
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public boolean isValid()
|
||||
{
|
||||
boolean valid = true;
|
||||
|
||||
for( int x = 0; x < mCRC.length; x++ )
|
||||
{
|
||||
CRC crc = mCRC[ x ];
|
||||
|
||||
if( crc != null )
|
||||
{
|
||||
if( crc == CRC.FAILED_CRC ||
|
||||
crc == CRC.FAILED_PARITY ||
|
||||
crc == CRC.UNKNOWN )
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( mSDF.format( new Date( System.currentTimeMillis() ) ) );
|
||||
|
||||
sb.append( " FSync2 " + getParity() );
|
||||
|
||||
sb.append( getMessage() );
|
||||
|
||||
sb.append( " [" + mMessage.toString() + "]" );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads spaces onto the end of the value to make it 'places' long
|
||||
*/
|
||||
public String pad( String value, int places, String padCharacter )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( value );
|
||||
|
||||
while( sb.length() < places )
|
||||
{
|
||||
sb.append( padCharacter );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 FleetsyncMessageType getMessageType()
|
||||
{
|
||||
if( hasStatusFlag() )
|
||||
{
|
||||
if( hasAcknowledgeFlag() )
|
||||
{
|
||||
return FleetsyncMessageType.ACKNOWLEDGE;
|
||||
}
|
||||
|
||||
if( hasGPSFlag() )
|
||||
{
|
||||
return FleetsyncMessageType.GPS;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FleetsyncMessageType.STATUS;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( hasAcknowledgeFlag() )
|
||||
{
|
||||
return FleetsyncMessageType.ACKNOWLEDGE;
|
||||
}
|
||||
|
||||
if( hasANIFlag() )
|
||||
{
|
||||
return FleetsyncMessageType.ANI;
|
||||
}
|
||||
|
||||
if( hasGPSFlag() )
|
||||
{
|
||||
return FleetsyncMessageType.GPS;
|
||||
}
|
||||
|
||||
if( hasPagingFlag() )
|
||||
{
|
||||
return FleetsyncMessageType.PAGING;
|
||||
}
|
||||
|
||||
if( hasEmergencyFlag() )
|
||||
{
|
||||
if( hasLoneWorkerFlag() )
|
||||
{
|
||||
return FleetsyncMessageType.LONE_WORKER_EMERGENCY;
|
||||
}
|
||||
else
|
||||
return FleetsyncMessageType.EMERGENCY;
|
||||
}
|
||||
}
|
||||
|
||||
return FleetsyncMessageType.UNKNOWN;
|
||||
}
|
||||
|
||||
public int getStatus()
|
||||
{
|
||||
return getStatusNumber() + 9;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RAW status number. The actual status number should be
|
||||
* accessed via the getStatus() method.
|
||||
* @return
|
||||
*/
|
||||
public int getStatusNumber()
|
||||
{
|
||||
return getInt( sStatusMessage );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public Alias getStatusAlias()
|
||||
{
|
||||
if( mAliasList != null )
|
||||
{
|
||||
return mAliasList.getStatus( getStatus() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getFleetFrom()
|
||||
{
|
||||
return getInt( sFleetFrom ) + 99;
|
||||
}
|
||||
|
||||
public int getIdentifierFrom()
|
||||
{
|
||||
return getInt( sIdentFrom ) + 999;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverted Flags - 0 = flag is true
|
||||
* @return
|
||||
*/
|
||||
public boolean hasEndOfTransmissionFlag()
|
||||
{
|
||||
return !mMessage.get( sEndOfTransmissionFlag );
|
||||
}
|
||||
|
||||
public boolean hasEmergencyFlag()
|
||||
{
|
||||
return !mMessage.get( sEmergencyFlag );
|
||||
}
|
||||
|
||||
public boolean hasLoneWorkerFlag()
|
||||
{
|
||||
return !mMessage.get( sLoneWorkerFlag );
|
||||
}
|
||||
|
||||
public boolean hasPagingFlag()
|
||||
{
|
||||
return !mMessage.get( sPagingFlag );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normal Flags - 1 = flag is true
|
||||
* @return
|
||||
*/
|
||||
public boolean hasANIFlag()
|
||||
{
|
||||
return mMessage.get( sANIFlag );
|
||||
}
|
||||
|
||||
public boolean hasAcknowledgeFlag()
|
||||
{
|
||||
return mMessage.get( sAcknowledgeFlag );
|
||||
}
|
||||
|
||||
public boolean hasFleetExtensionFlag()
|
||||
{
|
||||
return mMessage.get( sFleetExtensionFlag );
|
||||
}
|
||||
|
||||
public boolean hasGPSFlag()
|
||||
{
|
||||
return mMessage.get( sGPSExtensionFlag );
|
||||
}
|
||||
|
||||
public boolean hasStatusFlag()
|
||||
{
|
||||
return mMessage.get( sStatusFlag );
|
||||
}
|
||||
|
||||
|
||||
public int getFleetTo()
|
||||
{
|
||||
if( hasFleetExtensionFlag() )
|
||||
{
|
||||
return getInt( sFleetTo ) + 99;
|
||||
}
|
||||
else
|
||||
{
|
||||
return getInt( sFleetFrom ) + 99;
|
||||
}
|
||||
}
|
||||
|
||||
public int getIdentifierTo()
|
||||
{
|
||||
return getInt( sIdentTo ) + 999;
|
||||
}
|
||||
|
||||
/**
|
||||
* GPS Heading
|
||||
* @return
|
||||
*/
|
||||
public double getHeading()
|
||||
{
|
||||
double retVal = 0.0;
|
||||
|
||||
int heading = getInt( sGPSHeading );
|
||||
|
||||
if( heading != 4095 )
|
||||
{
|
||||
retVal = (double)(heading / 10.0 );
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public int getBlock7WhatIsIt()
|
||||
{
|
||||
int value = getInt( sBLK7WhatIsIt );
|
||||
|
||||
if( value == 65535 )
|
||||
{
|
||||
value = -1;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Speed in Kph - whole numbers and three decimal digits of precision
|
||||
* @return
|
||||
*/
|
||||
public double getSpeed()
|
||||
{
|
||||
double retVal = 0.0;
|
||||
|
||||
int temp = getInt( sSpeed );
|
||||
|
||||
if( temp != 0 )
|
||||
{
|
||||
retVal = (double)temp / 1000.0D;
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public double getLatitude()
|
||||
{
|
||||
//TODO: determine the correct hemisphere indicator and replace this
|
||||
//hardcoded "0" with the correct hemisphere value
|
||||
|
||||
return convertDDMToDD( 0,
|
||||
getInt( sLatitudeDegreesMinutes ),
|
||||
getInt( sLatitudeDecimalMinutes ) );
|
||||
}
|
||||
|
||||
public double getLongitude()
|
||||
{
|
||||
//TODO: determine the correct hemisphere indicator and replace this
|
||||
//hardcoded "1" with the correct hemisphere value
|
||||
|
||||
return convertDDMToDD( 1,
|
||||
getInt( sLongitudeDegreesMinutes ),
|
||||
getInt( sLongitudeDecimalMinutes ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Degrees Decimal Minutes to Decimal Degrees
|
||||
*
|
||||
* Latitude and Longitude values are represented by:
|
||||
*
|
||||
* @param hemisphere - 0=North & East, 1=South & West
|
||||
* @param degreesMinutes - an integer value with the first 2-3 digits representing
|
||||
* the degrees and the last two digits representing the minutes
|
||||
*
|
||||
* @param decimalDegrees - an integer value representing the fractional
|
||||
* minutes
|
||||
*
|
||||
* @return - decimal degrees formatted value
|
||||
*/
|
||||
public double convertDDMToDD( int hemisphere, int degreesMinutes, int decimalDegrees )
|
||||
{
|
||||
double retVal = 0.0;
|
||||
|
||||
if( degreesMinutes != 0 )
|
||||
{
|
||||
//Degrees - divide value by 100 and retain the whole number value (ie degrees)
|
||||
retVal += (double)( degreesMinutes / 100 );
|
||||
|
||||
//Minutes - modulus by 100 to get the whole minutes value
|
||||
int wholeMinutes = degreesMinutes % 100;
|
||||
|
||||
if( wholeMinutes != 0 )
|
||||
{
|
||||
retVal += (double)( wholeMinutes / 60.0D );
|
||||
}
|
||||
}
|
||||
|
||||
if( decimalDegrees != 0 )
|
||||
{
|
||||
//Fractional Minutes - divide by 10,000 to get the decimal place correct
|
||||
//then divide by 60 (minutes) to get the decimal value
|
||||
//10,000 * 60 = 600,000
|
||||
retVal += (double)( decimalDegrees / 600000.0D );
|
||||
}
|
||||
|
||||
//Adjust the value +/- for the hemisphere
|
||||
if( hemisphere == 1 ) //South and West values
|
||||
{
|
||||
retVal = -retVal;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public String getGPSTime()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( format( getInt( sGPSHours ), 2 ) );
|
||||
sb.append( ":" );
|
||||
sb.append( format( getInt( sGPSMinutes ), 2 ) );
|
||||
sb.append( ":" );
|
||||
sb.append( format( getInt( sGPSSeconds ), 2 ) );
|
||||
sb.append( "z" );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 7 bit checksum that is part of the GPGGA message, I think
|
||||
* @return
|
||||
*/
|
||||
public int getGPSChecksum()
|
||||
{
|
||||
return getInt( sGPSChecksum );
|
||||
}
|
||||
|
||||
public String getGPSChecksumString()
|
||||
{
|
||||
String sum = Integer.toHexString( getGPSChecksum() );
|
||||
|
||||
if( sum.length() == 1 )
|
||||
{
|
||||
return "*0" + sum;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "*" + sum;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 1-based calendar day of month
|
||||
* 1 = 1st day of month
|
||||
*/
|
||||
public int getGPSDay()
|
||||
{
|
||||
return getInt( sGPSDay ) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 0-based GPS Month
|
||||
* 0 = January
|
||||
*
|
||||
* Note: actual day of month value is 1-based, so we subtract 1 to get
|
||||
* the actual day of month value
|
||||
*/
|
||||
public int getGPSMonth()
|
||||
{
|
||||
return getInt( sGPSMonth );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns year, which is a combination of the century bit field + 1, times
|
||||
* 100, plus the year bit field
|
||||
*/
|
||||
public int getGPSYear()
|
||||
{
|
||||
// return ( ( getInt( sGPSCentury ) + 1 ) * 100 ) + getInt( sGPSYear );
|
||||
return ( 2000 ) + getInt( sGPSYear );
|
||||
}
|
||||
|
||||
public String getGPSLocation()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( pad( String.valueOf( mDecimalFormatter.format( getLatitude() ) ), 8, "0" ) + " " );
|
||||
sb.append( pad( String.valueOf( mDecimalFormatter.format( getLongitude() ) ), 10, "0" ) );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
public String getGPSDate()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( getGPSYear() + "-" );
|
||||
|
||||
int month = getGPSMonth();
|
||||
|
||||
if( month < 10 )
|
||||
{
|
||||
sb.append( "0" );
|
||||
}
|
||||
|
||||
sb.append( month + "-" );
|
||||
|
||||
int day = getGPSDay();
|
||||
|
||||
if( day < 10 )
|
||||
{
|
||||
sb.append( "0" );
|
||||
}
|
||||
|
||||
sb.append( day );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBinaryMessage()
|
||||
{
|
||||
return mMessage.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* String representing results of the parity check
|
||||
*
|
||||
* [P] = passes parity check
|
||||
* [f] = fails parity check
|
||||
* [C] = corrected message
|
||||
* [-] = message section not present
|
||||
*/
|
||||
public String getParity()
|
||||
{
|
||||
return "[" + CRC.format( mCRC ) + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol()
|
||||
{
|
||||
return "Fleetsync II";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType()
|
||||
{
|
||||
return getMessageType().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFromID()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( getFleetFrom() );
|
||||
sb.append( "-" );
|
||||
sb.append( getIdentifierFrom() );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
if( mAliasList != null )
|
||||
{
|
||||
return mAliasList.getFleetsyncAlias( getFromID() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToID()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( getFleetTo() );
|
||||
sb.append( "-" );
|
||||
sb.append( getIdentifierTo() );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getToIDAlias()
|
||||
{
|
||||
if( mAliasList != null )
|
||||
{
|
||||
return mAliasList.getFleetsyncAlias( getToID() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getFromTo( boolean includeFrom, boolean includeTo )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if( includeFrom )
|
||||
{
|
||||
sb.append( "FM:" );
|
||||
sb.append( getFromID() );
|
||||
|
||||
Alias from = getFromIDAlias();
|
||||
|
||||
if( from != null )
|
||||
{
|
||||
sb.append( " " );
|
||||
sb.append( from.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
if( includeFrom && includeTo )
|
||||
{
|
||||
sb.append( " " );
|
||||
}
|
||||
|
||||
if( includeTo )
|
||||
{
|
||||
sb.append( "TO:" );
|
||||
sb.append( getToID() );
|
||||
|
||||
Alias to = getToIDAlias();
|
||||
|
||||
if( to != null )
|
||||
{
|
||||
sb.append( " " );
|
||||
sb.append( to.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
FleetsyncMessageType type = getMessageType();
|
||||
|
||||
switch( type )
|
||||
{
|
||||
case ACKNOWLEDGE:
|
||||
sb.append( "ACKNOWLEDGE " );
|
||||
sb.append( getFromTo( true, true ) );
|
||||
break;
|
||||
case ANI:
|
||||
sb.append( "ANI " );
|
||||
sb.append( getFromTo( true, false ) );
|
||||
break;
|
||||
case EMERGENCY:
|
||||
sb.append( "**EMERGENCY** " );
|
||||
sb.append( getFromTo( true, true ) );
|
||||
break;
|
||||
case GPS:
|
||||
sb.append( "GPS [" );
|
||||
sb.append( getGPSLocation() );
|
||||
sb.append( "] " );
|
||||
|
||||
sb.append( getFromTo( true, true ) );
|
||||
|
||||
sb.append( " HDG:" );
|
||||
sb.append( pad( String.valueOf( getHeading() ), 5, " " ) );
|
||||
|
||||
sb.append( " GPSDate[" );
|
||||
sb.append( getGPSDate() );
|
||||
sb.append( " " );
|
||||
sb.append( getGPSTime() );
|
||||
sb.append( "]" );
|
||||
break;
|
||||
case LONE_WORKER_EMERGENCY:
|
||||
sb.append( "**LONE WORKER EMERGENCY** " );
|
||||
sb.append( getFromTo( true, true ) );
|
||||
break;
|
||||
case PAGING:
|
||||
sb.append( "PAGING " );
|
||||
sb.append( getFromTo( true, true ) );
|
||||
break;
|
||||
case STATUS:
|
||||
sb.append( "STATUS: " + format( getStatus(), 2 ) );
|
||||
|
||||
Alias status = getStatusAlias();
|
||||
if( status != null )
|
||||
{
|
||||
sb.append( "/" );
|
||||
sb.append( status.getName() );
|
||||
}
|
||||
|
||||
sb.append( getFromTo( true, true ) );
|
||||
break;
|
||||
case UNKNOWN:
|
||||
default:
|
||||
sb.append( "*** UNKNOWN *** " );
|
||||
sb.append( getFromTo( true, true ) );
|
||||
break;
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorStatus()
|
||||
{
|
||||
return CRC.format( mCRC );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plottable getPlottable()
|
||||
{
|
||||
if( isValid() && getMessageType() == FleetsyncMessageType.GPS )
|
||||
{
|
||||
GeoPosition position =
|
||||
new GeoPosition( getLatitude(), getLongitude() );
|
||||
|
||||
Alias alias = mAliasList.getFleetsyncAlias( getFromID() );
|
||||
|
||||
return new Plottable( mTimeReceived, position, getFromID(), alias );
|
||||
}
|
||||
else
|
||||
{
|
||||
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 );
|
||||
}
|
||||
|
||||
Alias status = getStatusAlias();
|
||||
|
||||
if( status != null )
|
||||
{
|
||||
aliases.add( status );
|
||||
}
|
||||
|
||||
return aliases;
|
||||
}
|
||||
}
|
||||
56
src/module/decode/fleetsync2/FleetsyncMessageFilter.java
Normal file
56
src/module/decode/fleetsync2/FleetsyncMessageFilter.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package module.decode.fleetsync2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import message.Message;
|
||||
import filter.Filter;
|
||||
import filter.FilterElement;
|
||||
|
||||
public class FleetsyncMessageFilter extends Filter<Message>
|
||||
{
|
||||
private HashMap<FleetsyncMessageType, FilterElement<FleetsyncMessageType>> mElements =
|
||||
new HashMap<FleetsyncMessageType, FilterElement<FleetsyncMessageType>>();
|
||||
|
||||
public FleetsyncMessageFilter()
|
||||
{
|
||||
super( "Fleetsync Message Filter" );
|
||||
|
||||
for( FleetsyncMessageType type: FleetsyncMessageType.values() )
|
||||
{
|
||||
if( type != FleetsyncMessageType.UNKNOWN )
|
||||
{
|
||||
mElements.put( type, new FilterElement<FleetsyncMessageType>( type ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes( Message message )
|
||||
{
|
||||
if( mEnabled && canProcess( message ) )
|
||||
{
|
||||
FleetsyncMessage fleet = (FleetsyncMessage)message;
|
||||
|
||||
if( mElements.containsKey( fleet.getMessageType() ) )
|
||||
{
|
||||
return mElements.get( fleet.getMessageType() ).isEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess( Message message )
|
||||
{
|
||||
return message instanceof FleetsyncMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FilterElement<?>> getFilterElements()
|
||||
{
|
||||
return new ArrayList<FilterElement<?>>( mElements.values() );
|
||||
}
|
||||
}
|
||||
42
src/module/decode/fleetsync2/FleetsyncMessageType.java
Normal file
42
src/module/decode/fleetsync2/FleetsyncMessageType.java
Normal file
@@ -0,0 +1,42 @@
|
||||
/*******************************************************************************
|
||||
* 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.fleetsync2;
|
||||
|
||||
public enum FleetsyncMessageType
|
||||
{
|
||||
ACKNOWLEDGE( "ACK" ),
|
||||
ANI( "ANI" ),
|
||||
EMERGENCY( "EMERG" ),
|
||||
GPS( "GPS" ),
|
||||
LONE_WORKER_EMERGENCY( "LONE WORKER" ),
|
||||
PAGING( "PAGE" ),
|
||||
STATUS( "STATUS" ),
|
||||
UNKNOWN( "UNK" );
|
||||
|
||||
private String mLabel;
|
||||
|
||||
private FleetsyncMessageType( String label )
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
138
src/module/decode/lj1200/LJ1200CallEvent.java
Normal file
138
src/module/decode/lj1200/LJ1200CallEvent.java
Normal file
@@ -0,0 +1,138 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.lj1200;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.event.CallEvent;
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
|
||||
public class LJ1200CallEvent extends CallEvent
|
||||
{
|
||||
public LJ1200CallEvent( CallEventType callEventType,
|
||||
AliasList aliasList,
|
||||
String fromID,
|
||||
String toID,
|
||||
String details )
|
||||
{
|
||||
super( DecoderType.LJ_1200, callEventType,
|
||||
aliasList, fromID, toID, details );
|
||||
}
|
||||
|
||||
private Alias getAlias( String ident )
|
||||
{
|
||||
if( hasAliasList() )
|
||||
{
|
||||
return getAliasList().getFleetsyncAlias( ident );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
return getAlias( getFromID() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getToIDAlias()
|
||||
{
|
||||
return getAlias( getToID() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannel()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrequency()
|
||||
{
|
||||
return 173075000;
|
||||
}
|
||||
|
||||
public static class Builder
|
||||
{
|
||||
/* Required parameters */
|
||||
private CallEventType mCallEventType;
|
||||
|
||||
/* Optional parameters */
|
||||
private AliasList mAliasList;
|
||||
private String mFromID;
|
||||
private String mToID;
|
||||
private String mDetails;
|
||||
|
||||
public Builder( CallEventType callEventType )
|
||||
{
|
||||
mCallEventType = callEventType;
|
||||
}
|
||||
|
||||
public Builder aliasList( AliasList aliasList )
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder from( String val )
|
||||
{
|
||||
mFromID = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder details( String details )
|
||||
{
|
||||
mDetails = details;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder to( String toID )
|
||||
{
|
||||
mToID = toID;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LJ1200CallEvent build()
|
||||
{
|
||||
return new LJ1200CallEvent( this );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor for the builder
|
||||
*/
|
||||
private LJ1200CallEvent( Builder builder )
|
||||
{
|
||||
this( builder.mCallEventType,
|
||||
builder.mAliasList,
|
||||
builder.mFromID,
|
||||
builder.mToID,
|
||||
builder.mDetails );
|
||||
}
|
||||
|
||||
public static LJ1200CallEvent getLJ1200Event( LJ1200Message message )
|
||||
{
|
||||
CallEventType type = CallEventType.COMMAND;
|
||||
|
||||
return new LJ1200CallEvent.Builder( type )
|
||||
.to( message.getAddress() )
|
||||
.details( message.getMessage() )
|
||||
.build();
|
||||
}
|
||||
}
|
||||
191
src/module/decode/lj1200/LJ1200Decoder.java
Normal file
191
src/module/decode/lj1200/LJ1200Decoder.java
Normal file
@@ -0,0 +1,191 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.lj1200;
|
||||
|
||||
import instrument.Instrumentable;
|
||||
import instrument.tap.Tap;
|
||||
import instrument.tap.stream.BinaryTap;
|
||||
import instrument.tap.stream.FloatBufferTap;
|
||||
import instrument.tap.stream.FloatTap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import module.decode.Decoder;
|
||||
import module.decode.DecoderType;
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import sample.real.IRealBufferListener;
|
||||
import sample.real.RealBuffer;
|
||||
import sample.real.RealSampleListener;
|
||||
import alias.AliasList;
|
||||
import bits.MessageFramer;
|
||||
import bits.SyncPattern;
|
||||
import dsp.filter.Filters;
|
||||
import dsp.filter.fir.real.RealFIRFilter_RB_RB;
|
||||
import dsp.filter.halfband.real.HalfBandFilter_RB_RB;
|
||||
import dsp.fsk.FSK2Decoder;
|
||||
import dsp.fsk.FSK2Decoder.Output;
|
||||
|
||||
/**
|
||||
* LJ1200 - 1200 baud 2FSK decoder
|
||||
*/
|
||||
public class LJ1200Decoder extends Decoder implements IRealBufferListener,
|
||||
Instrumentable
|
||||
{
|
||||
/* Decimated sample rate ( 48,000 / 2 = 24,000 ) feeding the decoder */
|
||||
private static final int DECIMATED_SAMPLE_RATE = 24000;
|
||||
|
||||
/* Baud or Symbol Rate */
|
||||
private static final int SYMBOL_RATE = 1200;
|
||||
|
||||
/* Message length - 16-bit sync plus 64 bit message */
|
||||
private static final int MESSAGE_LENGTH = 80;
|
||||
|
||||
/* Instrumentation Taps */
|
||||
private ArrayList<Tap> mAvailableTaps;
|
||||
private static final String INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD =
|
||||
"Tap Point: Bandpass Filter > < FSK2 Decoder";
|
||||
private static final String INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER =
|
||||
"Tap Point: FSK2 Decoder > < Message Framer";
|
||||
|
||||
private FSK2Decoder mFSKDecoder;
|
||||
private Broadcaster<Boolean> mFSKDecoderBroadcaster =
|
||||
new Broadcaster<Boolean>();
|
||||
|
||||
private HalfBandFilter_RB_RB mDecimationFilter;
|
||||
private RealFIRFilter_RB_RB mBandPassFilter;
|
||||
private MessageFramer mTowerMessageFramer;
|
||||
private MessageFramer mTransponderMessageFramer;
|
||||
private LJ1200MessageProcessor mMessageProcessor;
|
||||
|
||||
public LJ1200Decoder( AliasList aliasList )
|
||||
{
|
||||
super( new LJ1200DecoderState( aliasList ) );
|
||||
|
||||
mDecimationFilter = new HalfBandFilter_RB_RB(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0f, true );
|
||||
|
||||
mBandPassFilter = new RealFIRFilter_RB_RB(
|
||||
Filters.FIRBP_1200FSK_24000FS.getCoefficients(), 1.02f );
|
||||
mDecimationFilter.setListener( mBandPassFilter );
|
||||
|
||||
mFSKDecoder = new FSK2Decoder( DECIMATED_SAMPLE_RATE,
|
||||
SYMBOL_RATE, Output.INVERTED );
|
||||
mBandPassFilter.setListener( mFSKDecoder );
|
||||
|
||||
mFSKDecoder.setListener( mFSKDecoderBroadcaster );
|
||||
|
||||
mTowerMessageFramer = new MessageFramer(
|
||||
SyncPattern.LJ1200.getPattern(), MESSAGE_LENGTH );
|
||||
mTransponderMessageFramer = new MessageFramer(
|
||||
SyncPattern.LJ1200_TRANSPONDER.getPattern(), MESSAGE_LENGTH );
|
||||
mFSKDecoderBroadcaster.addListener( mTowerMessageFramer );
|
||||
mFSKDecoderBroadcaster.addListener( mTransponderMessageFramer );
|
||||
|
||||
mMessageProcessor = new LJ1200MessageProcessor( aliasList );
|
||||
mTowerMessageFramer.addMessageListener( mMessageProcessor );
|
||||
mTransponderMessageFramer.addMessageListener( mMessageProcessor );
|
||||
mMessageProcessor.setMessageListener( mMessageBroadcaster );
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.LJ_1200;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mDecimationFilter.dispose();
|
||||
mBandPassFilter.dispose();
|
||||
mFSKDecoder.dispose();
|
||||
mTowerMessageFramer.dispose();
|
||||
mMessageProcessor.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a float listener interface for connecting this decoder to a
|
||||
* float stream provider
|
||||
*/
|
||||
public RealSampleListener getRealReceiver()
|
||||
{
|
||||
return (RealSampleListener)mDecimationFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Tap> getTaps()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<Tap>();
|
||||
|
||||
mAvailableTaps.add( new FloatTap(
|
||||
INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD, 0, 0.5f ) );
|
||||
mAvailableTaps.addAll( mFSKDecoder.getTaps() );
|
||||
mAvailableTaps.add( new BinaryTap(
|
||||
INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER, 0, 0.025f ) );
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTap( Tap tap )
|
||||
{
|
||||
mFSKDecoder.addTap( tap );
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
|
||||
FloatBufferTap bpTap = (FloatBufferTap)tap;
|
||||
mBandPassFilter.setListener( bpTap );
|
||||
bpTap.setListener( mFSKDecoder );
|
||||
break;
|
||||
case INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER:
|
||||
BinaryTap decoderTap = (BinaryTap)tap;
|
||||
mFSKDecoder.setListener( decoderTap );
|
||||
decoderTap.setListener( mTowerMessageFramer );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTap( Tap tap )
|
||||
{
|
||||
mFSKDecoder.removeTap( tap );
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
|
||||
mBandPassFilter.setListener( mFSKDecoder );
|
||||
break;
|
||||
case INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER:
|
||||
mFSKDecoder.setListener( mTowerMessageFramer );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<RealBuffer> getRealBufferListener()
|
||||
{
|
||||
return mDecimationFilter;
|
||||
}
|
||||
}
|
||||
144
src/module/decode/lj1200/LJ1200DecoderState.java
Normal file
144
src/module/decode/lj1200/LJ1200DecoderState.java
Normal file
@@ -0,0 +1,144 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.lj1200;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import message.Message;
|
||||
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 alias.Alias;
|
||||
import alias.AliasList;
|
||||
|
||||
public class LJ1200DecoderState extends DecoderState
|
||||
{
|
||||
private Set<String> mAddresses = new TreeSet<String>();
|
||||
|
||||
private String mAddress;
|
||||
private Alias mAddressAlias;
|
||||
|
||||
public LJ1200DecoderState( AliasList aliasList )
|
||||
{
|
||||
super( aliasList );
|
||||
}
|
||||
|
||||
public String getAddress()
|
||||
{
|
||||
return mAddress;
|
||||
}
|
||||
|
||||
public Alias getAddressAlias()
|
||||
{
|
||||
return mAddressAlias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Message message )
|
||||
{
|
||||
if( message instanceof LJ1200Message )
|
||||
{
|
||||
LJ1200Message lj = (LJ1200Message)message;
|
||||
|
||||
if( lj.isValid() )
|
||||
{
|
||||
setAddress( lj.getAddress() );
|
||||
setAddressAlias( lj.getAddressAlias() );
|
||||
|
||||
mAddresses.add( mAddress );
|
||||
|
||||
broadcast( LJ1200CallEvent.getLJ1200Event( lj ) );
|
||||
|
||||
broadcast( new DecoderStateEvent( this, Event.DECODE, State.DATA ) );
|
||||
}
|
||||
}
|
||||
else if( message instanceof LJ1200TransponderMessage )
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void setAddress( String address )
|
||||
{
|
||||
mAddress = address;
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||
}
|
||||
|
||||
private void setAddressAlias( Alias alias )
|
||||
{
|
||||
mAddressAlias = alias;
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActivitySummary()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "=============================\n" );
|
||||
sb.append( "Decoder:\tLJ-1200I\n\n" );
|
||||
|
||||
if( !mAddresses.isEmpty() )
|
||||
{
|
||||
sb.append( "Transponder Addresses:\n" );
|
||||
|
||||
List<String> addresses = new ArrayList<String>( mAddresses );
|
||||
|
||||
Collections.sort( addresses );
|
||||
|
||||
for( String address: addresses )
|
||||
{
|
||||
sb.append( "\t" );
|
||||
sb.append( address );
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
/* No initialization steps */
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveDecoderStateEvent( DecoderStateEvent event )
|
||||
{
|
||||
if( event.getEvent() == Event.RESET )
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
mAddresses.clear();
|
||||
|
||||
setAddress( null );
|
||||
setAddressAlias( null );
|
||||
}
|
||||
}
|
||||
424
src/module/decode/lj1200/LJ1200Message.java
Normal file
424
src/module/decode/lj1200/LJ1200Message.java
Normal file
@@ -0,0 +1,424 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.lj1200;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import map.Plottable;
|
||||
import message.Message;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
import bits.BinaryMessage;
|
||||
import edac.CRC;
|
||||
import edac.CRCLJ;
|
||||
|
||||
public class LJ1200Message extends Message
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( LJ1200Message.class );
|
||||
|
||||
public static final String[] REPLY_CODE = { "0","1","2","3","4","5","6","7",
|
||||
"8","9","A","C","D","E","F","G","H","J","K","L","M","N","P","Q","R","S",
|
||||
"T","U","V","W","X","Y" };
|
||||
|
||||
public static int[] SYNC = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
|
||||
|
||||
public static int[] VRC = { 23,22,21,20,19,18,17,16 };
|
||||
|
||||
public static int[] LRC = { 31,30,29,28,27,26,25,24 };
|
||||
|
||||
public static int[] FUNCTION = { 35,34,33,32 };
|
||||
|
||||
public static int[] ADDRESS = { 63,62,61,60,59,58,57,56,55,54,53,52,51,50,
|
||||
49,48,47,46,45,44,43,42,41,40,39,38,37,36 };
|
||||
|
||||
public static int[] REPLY_1 = { 39,38,37,36,43 };
|
||||
public static int[] REPLY_2 = { 42,41,40,47,46 };
|
||||
public static int[] REPLY_3 = { 45,44,51,50,49 };
|
||||
public static int[] REPLY_4 = { 48,55,54,53,52 };
|
||||
public static int[] REPLY_5 = { 59,58,57,56,63 };
|
||||
|
||||
public static int[] MESSAGE_CRC = { 79,78,77,76,75,74,73,72,71,70,69,68,67,
|
||||
66,65,64 };
|
||||
|
||||
private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" );
|
||||
|
||||
private BinaryMessage mMessage;
|
||||
private AliasList mAliasList;
|
||||
private CRC mCRC;
|
||||
|
||||
public LJ1200Message( BinaryMessage message, AliasList list )
|
||||
{
|
||||
mMessage = message;
|
||||
mAliasList = list;
|
||||
|
||||
checkCRC();
|
||||
|
||||
switch( mCRC )
|
||||
{
|
||||
case CORRECTED:
|
||||
mLog.debug( "CORR:" + message.toString() );
|
||||
break;
|
||||
case FAILED_CRC:
|
||||
mLog.debug( "FAIL:" + message.toString() );
|
||||
break;
|
||||
case PASSED:
|
||||
mLog.debug( "PASS:" + message.toString() );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCRC()
|
||||
{
|
||||
mCRC = CRCLJ.checkAndCorrect( mMessage );
|
||||
}
|
||||
|
||||
public boolean isValid()
|
||||
{
|
||||
return mCRC == CRC.PASSED || mCRC == CRC.CORRECTED;
|
||||
}
|
||||
|
||||
public String getVRC()
|
||||
{
|
||||
return mMessage.getHex( VRC, 2 );
|
||||
}
|
||||
|
||||
public String getLRC()
|
||||
{
|
||||
return mMessage.getHex( LRC, 2 );
|
||||
}
|
||||
|
||||
public String getCRC()
|
||||
{
|
||||
return mMessage.getHex( MESSAGE_CRC, 4 );
|
||||
}
|
||||
|
||||
public Function getFunction()
|
||||
{
|
||||
return Function.fromValue( mMessage.getInt( FUNCTION ),
|
||||
mMessage.getInt( REPLY_3 ) );
|
||||
}
|
||||
|
||||
public String getAddress()
|
||||
{
|
||||
return mMessage.getHex( ADDRESS, 7 );
|
||||
}
|
||||
|
||||
public Alias getAddressAlias()
|
||||
{
|
||||
if( mAliasList != null )
|
||||
{
|
||||
return mAliasList.getESNAlias( getAddress() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Alias getSiteAndReplyCodeAlias()
|
||||
{
|
||||
if( mAliasList != null &&
|
||||
( getFunction() == Function.F1_SITE_ID ||
|
||||
getFunction() == Function.F1_SPEED_UP ) )
|
||||
{
|
||||
return mAliasList.getSiteID( getReplyCode() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 5 character reply code for function E and F, transponder activation.
|
||||
* @return - reply code
|
||||
*/
|
||||
public String getReplyCode()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( REPLY_CODE[ mMessage.getInt( REPLY_1 ) ] );
|
||||
sb.append( REPLY_CODE[ mMessage.getInt( REPLY_2 ) ] );
|
||||
sb.append( REPLY_CODE[ mMessage.getInt( REPLY_3 ) ] );
|
||||
sb.append( REPLY_CODE[ mMessage.getInt( REPLY_4 ) ] );
|
||||
sb.append( REPLY_CODE[ mMessage.getInt( REPLY_5 ) ] );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
Function function = getFunction();
|
||||
|
||||
sb.append( "FUNCTION: " );
|
||||
sb.append( function.toString() );
|
||||
|
||||
switch( function )
|
||||
{
|
||||
case F1_SITE_ID:
|
||||
sb.append( " SITE [" );
|
||||
break;
|
||||
case F1_SPEED_UP:
|
||||
case F2_TEST:
|
||||
case F3_DEACTIVATE:
|
||||
case F4_ACTIVATE:
|
||||
case FF_TRACK_PULSE:
|
||||
default:
|
||||
sb.append( " REPLY CODE [" );
|
||||
break;
|
||||
}
|
||||
|
||||
sb.append( getReplyCode() );
|
||||
|
||||
Alias site = getSiteAndReplyCodeAlias();
|
||||
|
||||
if( site != null )
|
||||
{
|
||||
sb.append( "/" );
|
||||
sb.append( site.getName() );
|
||||
}
|
||||
|
||||
sb.append( "] ADDRESS [" );
|
||||
sb.append( getAddress() );
|
||||
sb.append( "] VRC [" + getVRC() );
|
||||
sb.append( "] LRC [" + getLRC() );
|
||||
sb.append( "] CRC [" + getCRC() );
|
||||
sb.append( "]" );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads spaces onto the end of the value to make it 'places' long
|
||||
*/
|
||||
public String pad( String value, int places, String padCharacter )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( value );
|
||||
|
||||
while( sb.length() < places )
|
||||
{
|
||||
sb.append( padCharacter );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBinaryMessage()
|
||||
{
|
||||
return mMessage.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol()
|
||||
{
|
||||
return "LJ-1200";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType()
|
||||
{
|
||||
if( getFunction() == Function.F1_SPEED_UP )
|
||||
{
|
||||
return "TRANSPONDER";
|
||||
}
|
||||
|
||||
return "TOWER";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFromID()
|
||||
{
|
||||
return getReplyCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
return getSiteAndReplyCodeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToID()
|
||||
{
|
||||
return getAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getToIDAlias()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage()
|
||||
{
|
||||
return toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorStatus()
|
||||
{
|
||||
return mCRC.getDisplayText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plottable getPlottable()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public enum Function
|
||||
{
|
||||
/* Big Endian Format */
|
||||
F0_UNKNOWN( "0-UNKNOWN" ),
|
||||
F1_SITE_ID( "1Y-SITE ID" ),
|
||||
F1_SPEED_UP( "1-SPEED UP" ),
|
||||
F2_TEST( "2-TOWER TEST" ),
|
||||
F3_DEACTIVATE( "3-DEACTIVATE" ),
|
||||
F4_ACTIVATE( "4-ACTIVATE" ),
|
||||
F5_TRANSPONDER_TEST_REPLY( "5-TRANSPONDER TEST REPLY" ),
|
||||
F6_TRANSPONDER_TEST_COMMAND( "6-TRANSPONDER TEST COMMAND" ),
|
||||
F7_QUIET_COMMAND( "7-QUIET COMMAND" ),
|
||||
F8_UNKNOWN( "8-UNKNOWN" ),
|
||||
F9_UNKNOWN( "9-UNKNOWN" ),
|
||||
FA_UNKNOWN( "A-UNKNOWN" ),
|
||||
FB_UNKNOWN( "B-UNKNOWN" ),
|
||||
FC_ALT_DEACTIVATE( "C-ALT-DEACTIVATE" ),
|
||||
FD_UNKNOWN( "D-UNKNOWN" ),
|
||||
FE_UNKNOWN( "E-UNKNOWN" ),
|
||||
FF_TRACK_PULSE( "F-TRACK PULSE" ),
|
||||
|
||||
UNKNOWN( "UNKNOWN" );
|
||||
|
||||
private String mLabel;
|
||||
|
||||
private Function( String label )
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return getLabel();
|
||||
}
|
||||
|
||||
public static Function fromValue( int value, int replyCodeDigit3 )
|
||||
{
|
||||
switch( value )
|
||||
{
|
||||
case 0:
|
||||
return Function.F0_UNKNOWN;
|
||||
case 1:
|
||||
if( replyCodeDigit3 == 31 ) /* 'Y' middle character */
|
||||
{
|
||||
return Function.F1_SITE_ID;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Function.F1_SPEED_UP;
|
||||
}
|
||||
case 2:
|
||||
return Function.F2_TEST;
|
||||
case 3:
|
||||
return Function.F3_DEACTIVATE;
|
||||
case 4:
|
||||
return Function.F4_ACTIVATE;
|
||||
case 5:
|
||||
return Function.F5_TRANSPONDER_TEST_REPLY;
|
||||
case 6:
|
||||
return Function.F6_TRANSPONDER_TEST_COMMAND;
|
||||
case 7:
|
||||
return Function.F7_QUIET_COMMAND;
|
||||
case 8:
|
||||
return Function.F8_UNKNOWN;
|
||||
case 9:
|
||||
return Function.F9_UNKNOWN;
|
||||
case 10:
|
||||
return Function.FA_UNKNOWN;
|
||||
case 11:
|
||||
return Function.FB_UNKNOWN;
|
||||
case 12:
|
||||
return Function.FC_ALT_DEACTIVATE;
|
||||
case 13:
|
||||
return Function.FD_UNKNOWN;
|
||||
case 14:
|
||||
return Function.FE_UNKNOWN;
|
||||
case 15:
|
||||
return Function.FF_TRACK_PULSE;
|
||||
default:
|
||||
return Function.UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a listing of aliases contained in the message.
|
||||
*/
|
||||
public List<Alias> getAliases()
|
||||
{
|
||||
List<Alias> aliases = new ArrayList<Alias>();
|
||||
|
||||
Alias siteAndReply = getSiteAndReplyCodeAlias();
|
||||
|
||||
if( siteAndReply != null )
|
||||
{
|
||||
aliases.add( siteAndReply );
|
||||
}
|
||||
|
||||
Alias address = getAddressAlias();
|
||||
|
||||
if( address != null )
|
||||
{
|
||||
aliases.add( address );
|
||||
}
|
||||
|
||||
return aliases;
|
||||
}
|
||||
}
|
||||
53
src/module/decode/lj1200/LJ1200MessageFilter.java
Normal file
53
src/module/decode/lj1200/LJ1200MessageFilter.java
Normal file
@@ -0,0 +1,53 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.lj1200;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import message.Message;
|
||||
import filter.Filter;
|
||||
import filter.FilterElement;
|
||||
|
||||
public class LJ1200MessageFilter extends Filter<Message>
|
||||
{
|
||||
|
||||
public LJ1200MessageFilter()
|
||||
{
|
||||
super( "LJ-1200 Message Filter" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes( Message message )
|
||||
{
|
||||
return mEnabled && canProcess( message );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess( Message message )
|
||||
{
|
||||
return message instanceof LJ1200Message ||
|
||||
message instanceof LJ1200TransponderMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FilterElement<?>> getFilterElements()
|
||||
{
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
}
|
||||
84
src/module/decode/lj1200/LJ1200MessageProcessor.java
Normal file
84
src/module/decode/lj1200/LJ1200MessageProcessor.java
Normal file
@@ -0,0 +1,84 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.lj1200;
|
||||
|
||||
import message.Message;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
import alias.AliasList;
|
||||
import bits.BinaryMessage;
|
||||
|
||||
public class LJ1200MessageProcessor implements Listener<BinaryMessage>
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( LJ1200MessageProcessor.class );
|
||||
|
||||
public static int[] SYNC = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
|
||||
|
||||
public static int SYNC_TOWER = 0x550F;
|
||||
public static int SYNC_TRANSPONDER = 0x2AD5;
|
||||
|
||||
private Listener<Message> mMessageListener;
|
||||
|
||||
private AliasList mAliasList;
|
||||
|
||||
public LJ1200MessageProcessor( AliasList list )
|
||||
{
|
||||
mAliasList = list;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mAliasList = null;
|
||||
mMessageListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( BinaryMessage buffer )
|
||||
{
|
||||
int sync = buffer.getInt( SYNC );
|
||||
|
||||
if( sync == SYNC_TOWER )
|
||||
{
|
||||
if( mMessageListener != null )
|
||||
{
|
||||
mMessageListener.receive( new LJ1200Message( buffer, mAliasList ) );
|
||||
}
|
||||
}
|
||||
else if( sync == SYNC_TRANSPONDER )
|
||||
{
|
||||
if( mMessageListener != null )
|
||||
{
|
||||
mMessageListener.receive( new LJ1200TransponderMessage( buffer, mAliasList ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setMessageListener( Listener<Message> listener )
|
||||
{
|
||||
mMessageListener = listener;
|
||||
}
|
||||
|
||||
public void removeMessageListener()
|
||||
{
|
||||
mMessageListener = null;
|
||||
}
|
||||
}
|
||||
111
src/module/decode/lj1200/LJ1200Panel.java
Normal file
111
src/module/decode/lj1200/LJ1200Panel.java
Normal file
@@ -0,0 +1,111 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.lj1200;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import alias.Alias;
|
||||
import module.decode.state.ChangedAttribute;
|
||||
import module.decode.state.DecoderPanel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import settings.ColorSetting;
|
||||
import settings.ColorSetting.ColorSettingName;
|
||||
import settings.Setting;
|
||||
import settings.SettingsManager;
|
||||
|
||||
public class LJ1200Panel extends DecoderPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String PROTOCOL = "LJ-1200";
|
||||
|
||||
private JLabel mProtocol = new JLabel( PROTOCOL );
|
||||
private JLabel mTo = new JLabel();
|
||||
private JLabel mToAlias = new JLabel();
|
||||
|
||||
public LJ1200Panel( SettingsManager settingsManager, LJ1200Decoder decoder )
|
||||
{
|
||||
super( settingsManager, decoder );
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
protected void init()
|
||||
{
|
||||
/* Calling super init will get and broadcast color settings to properly
|
||||
* setup the jlabel colors */
|
||||
super.init();
|
||||
|
||||
setLayout( new MigLayout( "insets 1 0 0 0", "[grow,fill]", "" ) );
|
||||
|
||||
add( mProtocol );
|
||||
add( mTo );
|
||||
add( mToAlias, "wrap" );
|
||||
}
|
||||
|
||||
private LJ1200DecoderState getState()
|
||||
{
|
||||
return (LJ1200DecoderState)getDecoder().getDecoderState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( final ChangedAttribute changedAttribute )
|
||||
{
|
||||
switch( changedAttribute )
|
||||
{
|
||||
case TO_TALKGROUP:
|
||||
mTo.setText( getState().getAddress() );
|
||||
repaint();
|
||||
break;
|
||||
case FROM_TALKGROUP_ALIAS:
|
||||
Alias alias = getState().getAddressAlias();
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
mToAlias.setText( alias.getName() );
|
||||
repaint();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingChanged( Setting setting )
|
||||
{
|
||||
super.settingChanged( setting );
|
||||
|
||||
if( setting instanceof ColorSetting )
|
||||
{
|
||||
ColorSetting color = (ColorSetting)setting;
|
||||
|
||||
if( color.getColorSettingName() ==
|
||||
ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER )
|
||||
{
|
||||
mProtocol.setForeground( color.getColor() );
|
||||
mTo.setForeground( color.getColor() );
|
||||
mToAlias.setForeground( color.getColor() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
195
src/module/decode/lj1200/LJ1200TransponderMessage.java
Normal file
195
src/module/decode/lj1200/LJ1200TransponderMessage.java
Normal file
@@ -0,0 +1,195 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.lj1200;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
import map.Plottable;
|
||||
import message.Message;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
import bits.BinaryMessage;
|
||||
import edac.CRC;
|
||||
import edac.CRCLJ;
|
||||
|
||||
public class LJ1200TransponderMessage extends Message
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( LJ1200TransponderMessage.class );
|
||||
|
||||
public static int[] SYNC = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
|
||||
|
||||
public static int[] MESSAGE_CRC = { 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79 };
|
||||
|
||||
private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" );
|
||||
|
||||
private BinaryMessage mMessage;
|
||||
private AliasList mAliasList;
|
||||
private CRC mCRC;
|
||||
|
||||
public LJ1200TransponderMessage( BinaryMessage message, AliasList list )
|
||||
{
|
||||
mMessage = message;
|
||||
mAliasList = list;
|
||||
|
||||
checkCRC();
|
||||
|
||||
switch( mCRC )
|
||||
{
|
||||
case CORRECTED:
|
||||
mLog.debug( "CORR:" + message.toString() );
|
||||
break;
|
||||
case FAILED_CRC:
|
||||
mLog.debug( "FAIL:" + message.toString() );
|
||||
break;
|
||||
case PASSED:
|
||||
mLog.debug( "PASS:" + message.toString() );
|
||||
break;
|
||||
case UNKNOWN:
|
||||
mLog.debug( "UNKNOWN:" + message.toString() );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCRC()
|
||||
{
|
||||
mCRC = CRC.UNKNOWN;
|
||||
}
|
||||
|
||||
public boolean isValid()
|
||||
{
|
||||
// return mCRC == CRC.PASSED || mCRC == CRC.CORRECTED;
|
||||
|
||||
/* Override validity check until proper CRC is implemented */
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getCRC()
|
||||
{
|
||||
return mMessage.getHex( MESSAGE_CRC, 4 );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "TRANSPONDER TEST" );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads spaces onto the end of the value to make it 'places' long
|
||||
*/
|
||||
public String pad( String value, int places, String padCharacter )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( value );
|
||||
|
||||
while( sb.length() < places )
|
||||
{
|
||||
sb.append( padCharacter );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBinaryMessage()
|
||||
{
|
||||
return mMessage.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol()
|
||||
{
|
||||
return "LJ-1200";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType()
|
||||
{
|
||||
return "TRANSPONDER";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFromID()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToID()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getToIDAlias()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage()
|
||||
{
|
||||
return toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorStatus()
|
||||
{
|
||||
return mCRC.getDisplayText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plottable getPlottable()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
45
src/module/decode/ltrnet/DecodeConfigLTRNet.java
Normal file
45
src/module/decode/ltrnet/DecodeConfigLTRNet.java
Normal file
@@ -0,0 +1,45 @@
|
||||
/*******************************************************************************
|
||||
* 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.ltrnet;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
|
||||
import message.MessageDirection;
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
|
||||
public class DecodeConfigLTRNet extends DecodeConfiguration
|
||||
{
|
||||
private MessageDirection mMessageDirection = MessageDirection.OSW;
|
||||
|
||||
public DecodeConfigLTRNet()
|
||||
{
|
||||
super( DecoderType.LTR_NET );
|
||||
}
|
||||
|
||||
@XmlAttribute( name = "direction" )
|
||||
public MessageDirection getMessageDirection()
|
||||
{
|
||||
return mMessageDirection;
|
||||
}
|
||||
|
||||
public void setMessageDirection( MessageDirection direction )
|
||||
{
|
||||
mMessageDirection = direction;
|
||||
}
|
||||
}
|
||||
238
src/module/decode/ltrnet/LTRCallEvent.java
Normal file
238
src/module/decode/ltrnet/LTRCallEvent.java
Normal file
@@ -0,0 +1,238 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package module.decode.ltrnet;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.event.CallEvent;
|
||||
import module.decode.ltrstandard.LTRStandardMessage;
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
|
||||
public class LTRCallEvent extends CallEvent
|
||||
{
|
||||
private String mChannel;
|
||||
private long mFrequency;
|
||||
private int mValidCallMessages = 1;
|
||||
|
||||
public LTRCallEvent( CallEventType callEventType,
|
||||
AliasList aliasList,
|
||||
String fromID,
|
||||
String toID,
|
||||
String channel,
|
||||
long frequency,
|
||||
String details )
|
||||
{
|
||||
super( DecoderType.LTR_STANDARD, callEventType,
|
||||
aliasList, fromID, toID, details );
|
||||
|
||||
mChannel = channel;
|
||||
mFrequency = frequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* LTR sends multiple CALL messages during a call. In order to detect noise
|
||||
* bursts from valid calls, send every valid call message to this method
|
||||
* and the event will keep a tally of valid call messages in order to
|
||||
* determine if the call was a valid (and loggable) call event.
|
||||
*/
|
||||
public boolean addMessage( LTRStandardMessage message )
|
||||
{
|
||||
String talkgroup = message.getTalkgroupID();
|
||||
|
||||
if( talkgroup.contentEquals( getToID() ) )
|
||||
{
|
||||
mValidCallMessages++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the message's talkgroup matches this call event. Counts the
|
||||
* number of messages that match this call event to use in determining if
|
||||
* a call event is valid or an isolated (errant) message.
|
||||
*
|
||||
* See: isValid()
|
||||
*/
|
||||
public boolean isMatchingTalkgroup( LTRNetMessage message )
|
||||
{
|
||||
String talkgroup = message.getTalkgroupID();
|
||||
|
||||
if( talkgroup != null && getToID() != null &&
|
||||
talkgroup.contentEquals( getToID() ) )
|
||||
{
|
||||
mValidCallMessages++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isValid()
|
||||
{
|
||||
if( getCallEventType() == CallEventType.CALL )
|
||||
{
|
||||
return mValidCallMessages > 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Alias getAlias( String talkgroup )
|
||||
{
|
||||
if( hasAliasList() )
|
||||
{
|
||||
return getAliasList().getTalkgroupAlias( talkgroup );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
if( mCallEventType == CallEventType.CALL ||
|
||||
mCallEventType == CallEventType.REGISTER ||
|
||||
mCallEventType == CallEventType.ID_UNIQUE )
|
||||
{
|
||||
if( hasAliasList() )
|
||||
{
|
||||
try
|
||||
{
|
||||
int radioID = Integer.parseInt( getFromID() );
|
||||
|
||||
return mAliasList.getUniqueID( radioID );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
//Do nothing, we couldn't parse the int radio id value
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( mCallEventType == CallEventType.REGISTER_ESN )
|
||||
{
|
||||
if( hasAliasList() )
|
||||
{
|
||||
return mAliasList.getESNAlias( getFromID() );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return getAlias( getFromID() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getToIDAlias()
|
||||
{
|
||||
return getAlias( getToID() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannel()
|
||||
{
|
||||
return mChannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrequency()
|
||||
{
|
||||
return mFrequency;
|
||||
}
|
||||
|
||||
public static class Builder
|
||||
{
|
||||
/* Required parameters */
|
||||
private CallEventType mCallEventType;
|
||||
|
||||
/* Optional parameters */
|
||||
private AliasList mAliasList;
|
||||
private String mFromID;
|
||||
private String mToID;
|
||||
private String mDetails;
|
||||
private String mChannel;
|
||||
private long mFrequency;
|
||||
|
||||
public Builder( CallEventType callEventType )
|
||||
{
|
||||
mCallEventType = callEventType;
|
||||
}
|
||||
|
||||
public Builder aliasList( AliasList aliasList )
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder from( String val )
|
||||
{
|
||||
mFromID = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder channel( String channel )
|
||||
{
|
||||
mChannel = channel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder details( String details )
|
||||
{
|
||||
mDetails = details;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder frequency( long frequency )
|
||||
{
|
||||
mFrequency = frequency;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder to( String toID )
|
||||
{
|
||||
mToID = toID;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LTRCallEvent build()
|
||||
{
|
||||
return new LTRCallEvent( this );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor for the builder
|
||||
*/
|
||||
private LTRCallEvent( Builder builder )
|
||||
{
|
||||
this( builder.mCallEventType,
|
||||
builder.mAliasList,
|
||||
builder.mFromID,
|
||||
builder.mToID,
|
||||
builder.mChannel,
|
||||
builder.mFrequency,
|
||||
builder.mDetails );
|
||||
}
|
||||
|
||||
}
|
||||
113
src/module/decode/ltrnet/LTRNetDecoder.java
Normal file
113
src/module/decode/ltrnet/LTRNetDecoder.java
Normal file
@@ -0,0 +1,113 @@
|
||||
/*******************************************************************************
|
||||
* 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.ltrnet;
|
||||
|
||||
import instrument.Instrumentable;
|
||||
import instrument.tap.Tap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import message.MessageDirection;
|
||||
import module.decode.Decoder;
|
||||
import module.decode.DecoderType;
|
||||
import sample.Listener;
|
||||
import sample.real.IRealBufferListener;
|
||||
import sample.real.RealBuffer;
|
||||
import alias.AliasList;
|
||||
import bits.MessageFramer;
|
||||
import bits.SyncPattern;
|
||||
import dsp.fsk.LTRFSKDecoder;
|
||||
|
||||
public class LTRNetDecoder extends Decoder
|
||||
implements IRealBufferListener, Instrumentable
|
||||
{
|
||||
public static final int LTR_STANDARD_MESSAGE_LENGTH = 40;
|
||||
private LTRFSKDecoder mLTRFSKDecoder;
|
||||
private MessageFramer mLTRMessageFramer;
|
||||
private LTRNetMessageProcessor mLTRMessageProcessor;
|
||||
|
||||
private List<Tap> mAvailableTaps;
|
||||
|
||||
public LTRNetDecoder( DecodeConfigLTRNet config, AliasList aliasList )
|
||||
{
|
||||
super( new LTRNetDecoderState( aliasList ) );
|
||||
|
||||
mLTRFSKDecoder = new LTRFSKDecoder();
|
||||
|
||||
|
||||
if( config.getMessageDirection() == MessageDirection.OSW )
|
||||
{
|
||||
mLTRMessageFramer =
|
||||
new MessageFramer( SyncPattern.LTR_STANDARD_OSW.getPattern(),
|
||||
LTR_STANDARD_MESSAGE_LENGTH );
|
||||
}
|
||||
else
|
||||
{
|
||||
mLTRMessageFramer =
|
||||
new MessageFramer( SyncPattern.LTR_STANDARD_ISW.getPattern(),
|
||||
LTR_STANDARD_MESSAGE_LENGTH );
|
||||
}
|
||||
|
||||
mLTRFSKDecoder.addListener( mLTRMessageFramer );
|
||||
|
||||
mLTRMessageProcessor = new LTRNetMessageProcessor(
|
||||
config.getMessageDirection(), aliasList );
|
||||
|
||||
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
|
||||
|
||||
mLTRMessageProcessor.setMessageListener( mMessageBroadcaster );
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.LTR_NET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<RealBuffer> getRealBufferListener()
|
||||
{
|
||||
return mLTRFSKDecoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Tap> getTaps()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<Tap>();
|
||||
|
||||
mAvailableTaps.addAll( mLTRFSKDecoder.getTaps() );
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTap( Tap tap )
|
||||
{
|
||||
mLTRFSKDecoder.addTap( tap );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTap( Tap tap )
|
||||
{
|
||||
mLTRFSKDecoder.removeTap( tap );
|
||||
}
|
||||
}
|
||||
166
src/module/decode/ltrnet/LTRNetDecoderPanel.java
Normal file
166
src/module/decode/ltrnet/LTRNetDecoderPanel.java
Normal file
@@ -0,0 +1,166 @@
|
||||
/*******************************************************************************
|
||||
* 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.ltrnet;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import module.decode.state.ChangedAttribute;
|
||||
import module.decode.state.DecoderPanel;
|
||||
import settings.ColorSetting;
|
||||
import settings.Setting;
|
||||
import settings.SettingsManager;
|
||||
import alias.Alias;
|
||||
|
||||
public class LTRNetDecoderPanel extends DecoderPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JLabel mProtocol = new JLabel( "LTR-Net" );
|
||||
private JLabel mTalkgroup = new JLabel();
|
||||
private JLabel mTalkgroupAlias = new JLabel();
|
||||
|
||||
private JLabel mLCN = new JLabel( "LCN:" );
|
||||
private JLabel mDescription = new JLabel();
|
||||
|
||||
public LTRNetDecoderPanel( SettingsManager settingsManager, LTRNetDecoder decoder )
|
||||
{
|
||||
super( settingsManager, decoder );
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
super.init();
|
||||
|
||||
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]0[]") );
|
||||
|
||||
mProtocol.setFont( mFontDecoder );
|
||||
mProtocol.setForeground( mColorLabelDecoder );
|
||||
|
||||
mTalkgroup.setFont( mFontDecoder );
|
||||
mTalkgroup.setForeground( mColorLabelDecoder );
|
||||
|
||||
mTalkgroupAlias.setFont( mFontDecoder );
|
||||
mTalkgroupAlias.setForeground( mColorLabelDecoder );
|
||||
|
||||
mLCN.setFont( mFontDecoder );
|
||||
mLCN.setForeground( mColorLabelDecoder );
|
||||
|
||||
mDescription.setFont( mFontDecoder );
|
||||
mDescription.setForeground( mColorLabelDecoder );
|
||||
|
||||
add( mProtocol );
|
||||
add( mTalkgroup );
|
||||
add( mTalkgroupAlias, "wrap" );
|
||||
|
||||
add( mLCN );
|
||||
add( mDescription, "span,wrap" );
|
||||
}
|
||||
|
||||
private LTRNetDecoderState getDecoderState()
|
||||
{
|
||||
return (LTRNetDecoderState)getDecoder().getDecoderState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( final ChangedAttribute changedAttribute )
|
||||
{
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
LTRNetDecoderState state = getDecoderState();
|
||||
|
||||
switch( changedAttribute )
|
||||
{
|
||||
case TO_TALKGROUP:
|
||||
mTalkgroup.setText( state.getToTalkgroup() );
|
||||
break;
|
||||
case TO_TALKGROUP_ALIAS:
|
||||
Alias tgAlias = state.getToTalkgroupAlias();
|
||||
|
||||
if( tgAlias != null )
|
||||
{
|
||||
mTalkgroupAlias.setText( tgAlias.getName() );
|
||||
String iconNameString = tgAlias.getIconName();
|
||||
mTalkgroupAlias.setIcon( mSettingsManager
|
||||
.getImageIcon( iconNameString, 12 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
mTalkgroupAlias.setText( null );
|
||||
mTalkgroupAlias.setIcon( null );
|
||||
}
|
||||
break;
|
||||
case DESCRIPTION:
|
||||
mDescription.setText( state.getDescription() );
|
||||
break;
|
||||
case CHANNEL_NUMBER:
|
||||
mLCN.setText( "LCN: " + state.getChannelNumber() );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingChanged( Setting setting )
|
||||
{
|
||||
super.settingChanged( setting );
|
||||
|
||||
if( setting instanceof ColorSetting )
|
||||
{
|
||||
ColorSetting colorSetting = (ColorSetting)setting;
|
||||
|
||||
switch( colorSetting.getColorSettingName() )
|
||||
{
|
||||
case CHANNEL_STATE_LABEL_DECODER:
|
||||
if( mProtocol != null )
|
||||
{
|
||||
mProtocol.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
if( mTalkgroup != null )
|
||||
{
|
||||
mTalkgroup.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
if( mTalkgroupAlias != null )
|
||||
{
|
||||
mTalkgroupAlias.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
if( mLCN != null )
|
||||
{
|
||||
mLCN.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
if( mDescription != null )
|
||||
{
|
||||
mDescription.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
707
src/module/decode/ltrnet/LTRNetDecoderState.java
Normal file
707
src/module/decode/ltrnet/LTRNetDecoderState.java
Normal file
@@ -0,0 +1,707 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.ltrnet;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import message.Message;
|
||||
import module.decode.event.CallEvent;
|
||||
import module.decode.event.CallEvent.CallEventType;
|
||||
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 alias.Alias;
|
||||
import alias.AliasList;
|
||||
|
||||
public class LTRNetDecoderState extends DecoderState
|
||||
{
|
||||
private DecimalFormat mDecimalFormatter = new DecimalFormat( "0.00000" );
|
||||
|
||||
private TreeSet<String> mTalkgroups = new TreeSet<String>();
|
||||
private TreeSet<String> mTalkgroupsFirstHeard = new TreeSet<String>();
|
||||
private TreeSet<String> mESNs = new TreeSet<String>();
|
||||
private TreeSet<Integer> mUniqueIDs = new TreeSet<Integer>();
|
||||
private TreeSet<String> mNeighborIDs = new TreeSet<String>();
|
||||
private TreeSet<String> mSiteIDs = new TreeSet<String>();
|
||||
private HashMap<Integer,Long> mReceiveFrequencies =
|
||||
new HashMap<Integer,Long>();
|
||||
private HashMap<Integer,Long> mTransmitFrequencies =
|
||||
new HashMap<Integer,Long>();
|
||||
private HashMap<Integer,String> mActiveCalls = new HashMap<Integer,String>();
|
||||
|
||||
private String mTalkgroup;
|
||||
private Alias mTalkgroupAlias;
|
||||
private String mDescription;
|
||||
private int mChannelNumber;
|
||||
private long mFrequency = 0;
|
||||
|
||||
public LTRNetDecoderState( AliasList aliasList )
|
||||
{
|
||||
super( aliasList );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Message message )
|
||||
{
|
||||
if( message.isValid() )
|
||||
{
|
||||
if( message instanceof LTRNetOSWMessage )
|
||||
{
|
||||
LTRNetOSWMessage ltr = (LTRNetOSWMessage)message;
|
||||
|
||||
switch( ltr.getMessageType() )
|
||||
{
|
||||
case CA_STRT:
|
||||
case CA_ENDD:
|
||||
if( mChannelNumber == 0 )
|
||||
{
|
||||
mChannelNumber = ltr.getChannel();
|
||||
broadcast( ChangedAttribute.CHANNEL_NUMBER );
|
||||
}
|
||||
|
||||
if( ltr.getChannel() == 31 )
|
||||
{
|
||||
processCallEndMessage( ltr );
|
||||
}
|
||||
else if( ltr.getChannel() == mChannelNumber )
|
||||
{
|
||||
processCallMessage( ltr );
|
||||
}
|
||||
else
|
||||
{
|
||||
processCallDetectMessage( ltr );
|
||||
}
|
||||
break;
|
||||
case SY_IDLE:
|
||||
if( mChannelNumber != ltr.getChannel() )
|
||||
{
|
||||
mChannelNumber = ltr.getChannel();
|
||||
broadcast( ChangedAttribute.CHANNEL_NUMBER );
|
||||
}
|
||||
break;
|
||||
case MA_CHNH:
|
||||
break;
|
||||
case MA_CHNL:
|
||||
break;
|
||||
case FQ_RXHI:
|
||||
case FQ_RXLO:
|
||||
if( ltr.getFrequency() > 0 )
|
||||
{
|
||||
mReceiveFrequencies.put( ltr.getHomeRepeater(),
|
||||
ltr.getFrequency() );
|
||||
}
|
||||
break;
|
||||
case FQ_TXHI:
|
||||
case FQ_TXLO:
|
||||
if( ltr.getFrequency() > 0 )
|
||||
{
|
||||
mTransmitFrequencies.put( ltr.getHomeRepeater(),
|
||||
ltr.getFrequency() );
|
||||
}
|
||||
break;
|
||||
case ID_NBOR:
|
||||
String neighborID = ltr.getNeighborID();
|
||||
|
||||
if( neighborID != null )
|
||||
{
|
||||
mNeighborIDs.add( neighborID );
|
||||
}
|
||||
break;
|
||||
case ID_UNIQ:
|
||||
int uniqueID = ltr.getRadioUniqueID();
|
||||
|
||||
if( uniqueID != LTRNetOSWMessage.INT_NULL_VALUE )
|
||||
{
|
||||
mUniqueIDs.add( uniqueID );
|
||||
}
|
||||
|
||||
if( getCurrentLTRCallEvent() == null )
|
||||
{
|
||||
mCurrentCallEvent = new LTRCallEvent.Builder( CallEventType.REGISTER )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( String.valueOf( mChannelNumber ) )
|
||||
.frequency( mFrequency )
|
||||
.from( String.valueOf( uniqueID ) )
|
||||
.build();
|
||||
}
|
||||
else
|
||||
{
|
||||
mCurrentCallEvent.setFromID(
|
||||
String.valueOf( ltr.getRadioUniqueID() ) );
|
||||
mCurrentCallEvent.setDetails( "Unique ID" );
|
||||
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
|
||||
mTalkgroup = String.valueOf( uniqueID );
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||
|
||||
mTalkgroupAlias = ltr.getRadioUniqueIDAlias();
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
||||
break;
|
||||
case ID_SITE:
|
||||
String siteID = ltr.getSiteID();
|
||||
|
||||
if( siteID != null )
|
||||
{
|
||||
mSiteIDs.add( siteID );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if( message instanceof LTRNetISWMessage )
|
||||
{
|
||||
LTRNetISWMessage ltr = ((LTRNetISWMessage)message);
|
||||
|
||||
switch( ltr.getMessageType() )
|
||||
{
|
||||
case CA_STRT:
|
||||
processCallMessage( ltr );
|
||||
break;
|
||||
case CA_ENDD:
|
||||
processCallEndMessage( ltr );
|
||||
break;
|
||||
case ID_ESNH:
|
||||
case ID_ESNL:
|
||||
String esn = ltr.getESN();
|
||||
|
||||
if( !esn.contains( "xxxx" ) )
|
||||
{
|
||||
mESNs.add( ltr.getESN() );
|
||||
}
|
||||
|
||||
mDescription = "ESN";
|
||||
broadcast( ChangedAttribute.DESCRIPTION );
|
||||
|
||||
mTalkgroup = ltr.getESN();
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||
|
||||
mTalkgroupAlias = ltr.getESNAlias();
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
||||
|
||||
broadcast( new DecoderStateEvent( this, Event.DECODE, State.DATA ) );
|
||||
|
||||
if( mCurrentCallEvent == null )
|
||||
{
|
||||
mCurrentCallEvent = new LTRCallEvent.Builder( CallEventType.REGISTER_ESN )
|
||||
.aliasList( getAliasList() )
|
||||
.details( "ESN:" + ltr.getESN() )
|
||||
.frequency( mFrequency )
|
||||
.from( ltr.getESN() )
|
||||
.build();
|
||||
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
|
||||
break;
|
||||
case ID_UNIQ:
|
||||
int uniqueid = ltr.getRadioUniqueID();
|
||||
|
||||
if( uniqueid != LTRNetISWMessage.INT_NULL_VALUE )
|
||||
{
|
||||
mUniqueIDs.add( uniqueid );
|
||||
|
||||
mDescription = "REGISTER UID";
|
||||
broadcast( ChangedAttribute.DESCRIPTION );
|
||||
|
||||
mTalkgroup = String.valueOf( uniqueid );
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||
|
||||
mTalkgroupAlias = ltr.getRadioUniqueIDAlias();
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
||||
|
||||
if( getCurrentLTRCallEvent() == null )
|
||||
{
|
||||
mCurrentCallEvent = new LTRCallEvent.Builder( CallEventType.REGISTER )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( String.valueOf( mChannelNumber ) )
|
||||
.frequency( mFrequency )
|
||||
.from( String.valueOf( uniqueid ) )
|
||||
.build();
|
||||
}
|
||||
else
|
||||
{
|
||||
mCurrentCallEvent.setFromID(
|
||||
String.valueOf( ltr.getRadioUniqueID() ) );
|
||||
mCurrentCallEvent.setDetails( "Unique ID" );
|
||||
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public LTRCallEvent getCurrentLTRCallEvent()
|
||||
{
|
||||
if( mCurrentCallEvent != null )
|
||||
{
|
||||
return (LTRCallEvent)mCurrentCallEvent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActivitySummary()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "Activity Summary\n" );
|
||||
sb.append( "Decoder:\tLTR-Net\n\n" );
|
||||
|
||||
if( mSiteIDs.isEmpty() )
|
||||
{
|
||||
sb.append( "Site:\tUnknown\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Iterator<String> it = mSiteIDs.iterator();
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
sb.append( "Site:\t" );
|
||||
|
||||
String siteID = it.next();
|
||||
|
||||
sb.append( siteID );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias siteAlias = getAliasList().getSiteID( String.valueOf( siteID ) );
|
||||
|
||||
if( siteAlias != null )
|
||||
{
|
||||
sb.append( " " );
|
||||
sb.append( siteAlias.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\nLCNs (transmit | receive)\n" );
|
||||
|
||||
if( mReceiveFrequencies.isEmpty() && mTransmitFrequencies.isEmpty() )
|
||||
{
|
||||
sb.append( " None\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
for( int x = 1; x < 21; x++ )
|
||||
{
|
||||
long rcv = 0;
|
||||
|
||||
if( mReceiveFrequencies.containsKey( x ) )
|
||||
{
|
||||
rcv = mReceiveFrequencies.get( x );
|
||||
}
|
||||
|
||||
long xmt = 0;
|
||||
|
||||
if( mTransmitFrequencies.containsKey( x ) )
|
||||
{
|
||||
xmt = mTransmitFrequencies.get( x );
|
||||
}
|
||||
|
||||
if( rcv > 0 || xmt > 0 )
|
||||
{
|
||||
if( x < 10 )
|
||||
{
|
||||
sb.append( " " );
|
||||
}
|
||||
|
||||
sb.append( x );
|
||||
sb.append( ": " );
|
||||
|
||||
if( xmt == 0 )
|
||||
{
|
||||
sb.append( "---.-----" );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( mDecimalFormatter.format( (double)xmt/1E6d ) );
|
||||
}
|
||||
sb.append( " | " );
|
||||
|
||||
if( rcv == 0 )
|
||||
{
|
||||
sb.append( "---.-----" );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( mDecimalFormatter.format( (double)rcv/1E6d ) );
|
||||
}
|
||||
|
||||
if( x == mChannelNumber )
|
||||
{
|
||||
sb.append( " **" );
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\nTalkgroups\n" );
|
||||
|
||||
if( mTalkgroups.isEmpty() )
|
||||
{
|
||||
sb.append( " None\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Iterator<String> it = mTalkgroups.iterator();
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
String tgid = it.next();
|
||||
|
||||
sb.append( " " );
|
||||
sb.append( tgid );
|
||||
sb.append( " " );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias alias = getAliasList().getTalkgroupAlias( tgid );
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
sb.append( alias.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\nRadio Unique IDs\n" );
|
||||
|
||||
if( mUniqueIDs.isEmpty() )
|
||||
{
|
||||
sb.append( " None\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Iterator<Integer> it = mUniqueIDs.iterator();
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
int uid = it.next();
|
||||
|
||||
sb.append( " " );
|
||||
sb.append( uid );
|
||||
sb.append( " " );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias alias = getAliasList().getUniqueID( uid );
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
sb.append( alias.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\nESNs\n" );
|
||||
|
||||
if( mESNs.isEmpty() )
|
||||
{
|
||||
sb.append( " None\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Iterator<String> it = mESNs.iterator();
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
String esn = it.next();
|
||||
|
||||
sb.append( " " );
|
||||
sb.append( esn );
|
||||
sb.append( " " );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias alias = getAliasList().getESNAlias( esn );
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
sb.append( alias.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\nNeighbor Sites\n" );
|
||||
|
||||
if( mNeighborIDs.isEmpty() )
|
||||
{
|
||||
sb.append( " None\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Iterator<String> it = mNeighborIDs.iterator();
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
String neighbor = it.next();
|
||||
|
||||
sb.append( " " );
|
||||
sb.append( neighbor );
|
||||
sb.append( " " );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias alias = getAliasList().getSiteID( String.valueOf( neighbor ) );
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
sb.append( alias.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call Detect - current channel messages indicate a call on another channel
|
||||
*/
|
||||
private void processCallDetectMessage( LTRNetMessage message )
|
||||
{
|
||||
if( !mActiveCalls.containsKey( message.getChannel() ) ||
|
||||
!mActiveCalls.get( message.getChannel() ).contentEquals( message.getTalkgroupID() ) )
|
||||
{
|
||||
mActiveCalls.put( message.getChannel(), message.getTalkgroupID() );
|
||||
|
||||
int channel = message.getChannel();
|
||||
|
||||
long frequency = 0;
|
||||
|
||||
if( mTransmitFrequencies.containsKey( channel ) )
|
||||
{
|
||||
frequency = mTransmitFrequencies.get( channel );
|
||||
}
|
||||
|
||||
broadcast( new LTRCallEvent.Builder( CallEventType.CALL_DETECT )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( String.valueOf( message.getChannel() ) )
|
||||
.frequency( frequency )
|
||||
.to( message.getTalkgroupID() )
|
||||
.build() );
|
||||
}
|
||||
}
|
||||
|
||||
private void processCallMessage( LTRNetMessage message )
|
||||
{
|
||||
LTRCallEvent current = getCurrentLTRCallEvent();
|
||||
|
||||
int group = message.getGroup();
|
||||
|
||||
/* If this is a new call or the talkgroup is different from the current
|
||||
* call, create a new call event */
|
||||
if( current == null || !current.isMatchingTalkgroup( message ) )
|
||||
{
|
||||
/* Invalidate the current call */
|
||||
if( current != null )
|
||||
{
|
||||
current.setValid( false );
|
||||
broadcast( current );
|
||||
}
|
||||
|
||||
mTalkgroup = message.getTalkgroupID();
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||
|
||||
/* A talkgroup must be seen at least once before it will be added
|
||||
* to the mTalkgroups list that is used in the activity summary,
|
||||
* so that we don't pollute the summary with one-off error talkgroups */
|
||||
if( mTalkgroupsFirstHeard.contains( mTalkgroup ) )
|
||||
{
|
||||
mTalkgroups.add( mTalkgroup );
|
||||
}
|
||||
else
|
||||
{
|
||||
mTalkgroupsFirstHeard.add( mTalkgroup );
|
||||
}
|
||||
|
||||
mTalkgroupAlias = message.getTalkgroupIDAlias();
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
||||
|
||||
CallEventType type = CallEventType.CALL;
|
||||
|
||||
if( group == 253 )
|
||||
{
|
||||
type = CallEventType.REGISTER;
|
||||
|
||||
mDescription = "REGISTER";
|
||||
broadcast( ChangedAttribute.DESCRIPTION );
|
||||
}
|
||||
else if( group == 254 )
|
||||
{
|
||||
type = CallEventType.STATION_ID;
|
||||
|
||||
mDescription = "STATION ID";
|
||||
broadcast( ChangedAttribute.DESCRIPTION );
|
||||
}
|
||||
|
||||
mCurrentCallEvent = new LTRCallEvent.Builder( type )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( String.valueOf( message.getChannel() ) )
|
||||
.frequency( mFrequency )
|
||||
.to( message.getTalkgroupID() )
|
||||
.build();
|
||||
|
||||
broadcast( mCurrentCallEvent );
|
||||
|
||||
broadcast( new DecoderStateEvent( this, Event.START,
|
||||
( type == CallEventType.CALL ) ? State.CALL : State.DATA ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION,
|
||||
( group == 253 || group == 254 ) ? State.DATA : State.CALL ) );
|
||||
}
|
||||
}
|
||||
|
||||
private void processCallEndMessage( LTRNetMessage message )
|
||||
{
|
||||
if( mCurrentCallEvent != null &&
|
||||
mCurrentCallEvent.getCallEventType() == CallEventType.CALL )
|
||||
{
|
||||
mCurrentCallEvent.setEnd( System.currentTimeMillis() );
|
||||
broadcast( mCurrentCallEvent );
|
||||
|
||||
}
|
||||
|
||||
mCurrentCallEvent = null;
|
||||
|
||||
broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the decoder state for re-use on another channel
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
mChannelNumber = 0;
|
||||
|
||||
mActiveCalls.clear();
|
||||
mESNs.clear();
|
||||
mNeighborIDs.clear();
|
||||
mReceiveFrequencies.clear();
|
||||
mSiteIDs.clear();
|
||||
mTalkgroups.clear();
|
||||
mTransmitFrequencies.clear();
|
||||
mUniqueIDs.clear();
|
||||
|
||||
resetState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the decoder state after a call event
|
||||
*/
|
||||
private void resetState()
|
||||
{
|
||||
if( mCurrentCallEvent != null && mCurrentCallEvent
|
||||
.getCallEventType() == CallEventType.CALL )
|
||||
{
|
||||
mCurrentCallEvent.setEnd( System.currentTimeMillis() );
|
||||
broadcast( mCurrentCallEvent );
|
||||
}
|
||||
|
||||
mCurrentCallEvent = null;
|
||||
|
||||
mTalkgroup = null;
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||
|
||||
mTalkgroupAlias = null;
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
||||
|
||||
mDescription = null;
|
||||
broadcast( ChangedAttribute.DESCRIPTION );
|
||||
}
|
||||
|
||||
public String getToTalkgroup()
|
||||
{
|
||||
return mTalkgroup;
|
||||
}
|
||||
|
||||
public Alias getToTalkgroupAlias()
|
||||
{
|
||||
return mTalkgroupAlias;
|
||||
}
|
||||
|
||||
public String getDescription()
|
||||
{
|
||||
return mDescription;
|
||||
}
|
||||
|
||||
public boolean hasChannelNumber()
|
||||
{
|
||||
return mChannelNumber != 0;
|
||||
}
|
||||
|
||||
public int getChannelNumber()
|
||||
{
|
||||
return mChannelNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
/* No initialization steps required */
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveDecoderStateEvent( DecoderStateEvent event )
|
||||
{
|
||||
switch( event.getEvent() )
|
||||
{
|
||||
case RESET:
|
||||
resetState();
|
||||
break;
|
||||
case SOURCE_FREQUENCY:
|
||||
mFrequency = event.getFrequency();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/module/decode/ltrnet/LTRNetEditor.java
Normal file
72
src/module/decode/ltrnet/LTRNetEditor.java
Normal file
@@ -0,0 +1,72 @@
|
||||
/*******************************************************************************
|
||||
* 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.ltrnet;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import message.MessageDirection;
|
||||
import module.decode.DecodeEditor;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
|
||||
public class LTRNetEditor extends DecodeEditor
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JComboBox<MessageDirection> mComboDirection;
|
||||
|
||||
public LTRNetEditor( DecodeConfiguration config )
|
||||
{
|
||||
super( config );
|
||||
|
||||
initGUI();
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
mComboDirection = new JComboBox<MessageDirection>();
|
||||
|
||||
mComboDirection.setModel(
|
||||
new DefaultComboBoxModel<MessageDirection>( MessageDirection.values() ) );
|
||||
|
||||
mComboDirection.setSelectedItem( ((DecodeConfigLTRNet)mConfig).getMessageDirection() );
|
||||
|
||||
mComboDirection.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
MessageDirection selected = mComboDirection
|
||||
.getItemAt( mComboDirection.getSelectedIndex() );
|
||||
|
||||
if( selected != null )
|
||||
{
|
||||
((DecodeConfigLTRNet)mConfig).setMessageDirection( selected );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
add( new JLabel( "Format:" ) );
|
||||
add( mComboDirection, "wrap" );
|
||||
}
|
||||
}
|
||||
345
src/module/decode/ltrnet/LTRNetISWMessage.java
Normal file
345
src/module/decode/ltrnet/LTRNetISWMessage.java
Normal file
@@ -0,0 +1,345 @@
|
||||
/*******************************************************************************
|
||||
* 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.ltrnet;
|
||||
|
||||
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;
|
||||
import edac.CRC;
|
||||
import edac.CRCLTR;
|
||||
|
||||
public class LTRNetISWMessage extends LTRNetMessage
|
||||
{
|
||||
private static final int sCHIU_UNIQUE_ID = 24;
|
||||
private static final int sCHIU_ESN_LOW = 27;
|
||||
private static final int sCHIU_ESN_HIGH = 29;
|
||||
|
||||
private static final int sFREE_CALL = 21;
|
||||
private static final int sFREE_END_CALL = 23;
|
||||
private static final int sFREE_REQUEST_ACCESS = 31;
|
||||
|
||||
|
||||
private LTRNetISWMessage mAuxMessage;
|
||||
|
||||
public LTRNetISWMessage( BinaryMessage message, AliasList list )
|
||||
{
|
||||
super( message, MessageDirection.ISW, list );
|
||||
|
||||
/**
|
||||
* If the CRC fails, test for a transmitted CRC of 127 and then check
|
||||
* the Free field for special messages
|
||||
*/
|
||||
if( mCRC == CRC.FAILED_CRC && CRCLTR.getTransmittedChecksum( mMessage ) == 127 )
|
||||
{
|
||||
if( getFree() == sFREE_REQUEST_ACCESS ||
|
||||
getFree() == sFREE_END_CALL )
|
||||
{
|
||||
mCRC = CRC.PASSED;
|
||||
}
|
||||
}
|
||||
|
||||
mMessageType = getMessageType();
|
||||
}
|
||||
|
||||
public void setAuxiliaryMessage( LTRNetISWMessage message )
|
||||
{
|
||||
if( message != null )
|
||||
{
|
||||
mAuxMessage = message;
|
||||
}
|
||||
}
|
||||
|
||||
public String getESN()
|
||||
{
|
||||
if( mMessageType == MessageType.ID_ESNH )
|
||||
{
|
||||
if( mAuxMessage != null &&
|
||||
mAuxMessage.getMessageType() == MessageType.ID_ESNL )
|
||||
{
|
||||
return getESNHigh() + mAuxMessage.getESNLow();
|
||||
}
|
||||
else
|
||||
{
|
||||
return getESNHigh() + "xxxx";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( mAuxMessage != null &&
|
||||
mAuxMessage.getMessageType() == MessageType.ID_ESNH )
|
||||
{
|
||||
return mAuxMessage.getESNHigh() + getESNLow();
|
||||
}
|
||||
else
|
||||
{
|
||||
return "xxxx" + getESNLow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Alias getESNAlias()
|
||||
{
|
||||
if( mAliasList != null )
|
||||
{
|
||||
return mAliasList.getESNAlias( getESN() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getESNHigh()
|
||||
{
|
||||
int esnHigh = getInt( sSIXTEEN_BITS );
|
||||
|
||||
return String.format("%04X", esnHigh & 0xFFFF );
|
||||
}
|
||||
|
||||
public String getESNLow()
|
||||
{
|
||||
int esnLow = getInt( sSIXTEEN_BITS );
|
||||
|
||||
return String.format("%04X", esnLow & 0xFFFF );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( mDatestampFormatter.format( new Date( System.currentTimeMillis() ) ) );
|
||||
sb.append( " LTRNet ISW [" );
|
||||
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 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;
|
||||
case RQ_ACCE:
|
||||
sb.append( "REQUEST ACCESS 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 ID_ESNL:
|
||||
sb.append( "ESN LOW " );
|
||||
sb.append( getESN() );
|
||||
sb.append( "/" );
|
||||
sb.append( getESNAlias() );
|
||||
break;
|
||||
case ID_ESNH:
|
||||
sb.append( "ESN HIGH " );
|
||||
sb.append( getESN() );
|
||||
sb.append( "/" );
|
||||
sb.append( getESNAlias() );
|
||||
break;
|
||||
case ID_UNIQ:
|
||||
sb.append( "RADIO UNIQUE ID " );
|
||||
sb.append( getRadioUniqueID() );
|
||||
|
||||
if( getRadioUniqueIDAlias() != null )
|
||||
{
|
||||
sb.append( "/" );
|
||||
sb.append( getRadioUniqueIDAlias().getName() );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
sb.append( "UNKNOWN " );
|
||||
sb.append( getArea() );
|
||||
sb.append( " " );
|
||||
sb.append( format( getChannel(), 2 ) );
|
||||
sb.append( " " );
|
||||
sb.append( format( getHomeRepeater(), 2 ) );
|
||||
sb.append( " " );
|
||||
sb.append( format( getGroup(), 3 ) );
|
||||
sb.append( " " );
|
||||
sb.append( format( getFree(), 2 ) );
|
||||
sb.append( " CRC:" );
|
||||
sb.append( CRCLTR.getTransmittedChecksum( mMessage ) );
|
||||
sb.append( " CALC:" );
|
||||
sb.append( CRCLTR.getCalculatedChecksum( mMessage ) );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public int getRadioUniqueID()
|
||||
{
|
||||
int retVal = INT_NULL_VALUE;
|
||||
|
||||
if( mMessageType == MessageType.ID_UNIQ )
|
||||
{
|
||||
retVal = getInt( sSIXTEEN_BITS );
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public Alias getRadioUniqueIDAlias()
|
||||
{
|
||||
if( mAliasList != null )
|
||||
{
|
||||
return mAliasList.getUniqueID( getRadioUniqueID() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public MessageType getMessageType()
|
||||
{
|
||||
MessageType retVal = MessageType.UN_KNWN;
|
||||
|
||||
int channel = getChannel();
|
||||
|
||||
if( channel == 31 )
|
||||
{
|
||||
retVal = MessageType.CA_ENDD;
|
||||
}
|
||||
else if( channel > 20 )
|
||||
{
|
||||
switch( channel )
|
||||
{
|
||||
case sCHIU_UNIQUE_ID:
|
||||
retVal = MessageType.ID_UNIQ;
|
||||
break;
|
||||
case sCHIU_ESN_LOW:
|
||||
retVal = MessageType.ID_ESNH;
|
||||
break;
|
||||
case sCHIU_ESN_HIGH:
|
||||
retVal = MessageType.ID_ESNL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if( channel > 0 )
|
||||
{
|
||||
int free = getFree();
|
||||
|
||||
switch( free )
|
||||
{
|
||||
case sFREE_CALL:
|
||||
retVal = MessageType.CA_STRT;
|
||||
break;
|
||||
case sFREE_END_CALL:
|
||||
retVal = MessageType.CA_ENDD;
|
||||
break;
|
||||
case sFREE_REQUEST_ACCESS:
|
||||
retVal = MessageType.RQ_ACCE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
@Override
|
||||
public String getFromID()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuxiliaryMessage( LTRNetOSWMessage message )
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@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 );
|
||||
}
|
||||
|
||||
Alias esn = getESNAlias();
|
||||
|
||||
if( esn != null )
|
||||
{
|
||||
aliases.add( esn );
|
||||
}
|
||||
|
||||
return aliases;
|
||||
}
|
||||
}
|
||||
243
src/module/decode/ltrnet/LTRNetMessage.java
Normal file
243
src/module/decode/ltrnet/LTRNetMessage.java
Normal file
@@ -0,0 +1,243 @@
|
||||
/*******************************************************************************
|
||||
* 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.ltrnet;
|
||||
|
||||
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 LTRNetMessage extends Message
|
||||
{
|
||||
protected static final String sUNKNOWN = "**UNKNOWN**";
|
||||
protected static final int INT_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 static final int[] sSIXTEEN_BITS = { 32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17 };
|
||||
protected static final int[] sMANUFACTURER_ID = { 23,22,21,20,19,18,17 };
|
||||
|
||||
protected BinaryMessage mMessage;
|
||||
protected MessageType mMessageType;
|
||||
protected CRC mCRC;
|
||||
protected AliasList mAliasList;
|
||||
|
||||
public LTRNetMessage( BinaryMessage message,
|
||||
MessageDirection direction,
|
||||
AliasList list )
|
||||
{
|
||||
mMessage = message;
|
||||
mCRC = CRCLTR.check( message, direction );
|
||||
mAliasList = list;
|
||||
}
|
||||
|
||||
public MessageType getMessageType()
|
||||
{
|
||||
return mMessageType;
|
||||
}
|
||||
|
||||
public abstract void setAuxiliaryMessage( LTRNetOSWMessage message );
|
||||
|
||||
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()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( getArea() );
|
||||
sb.append( "-" );
|
||||
sb.append( format( getHomeRepeater(), 2 ) );
|
||||
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_NET.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();
|
||||
}
|
||||
}
|
||||
80
src/module/decode/ltrnet/LTRNetMessageFilter.java
Normal file
80
src/module/decode/ltrnet/LTRNetMessageFilter.java
Normal file
@@ -0,0 +1,80 @@
|
||||
package module.decode.ltrnet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import message.Message;
|
||||
import message.MessageType;
|
||||
import filter.Filter;
|
||||
import filter.FilterElement;
|
||||
|
||||
public class LTRNetMessageFilter extends Filter<Message>
|
||||
{
|
||||
private HashMap<MessageType, FilterElement<MessageType>> mElements =
|
||||
new HashMap<MessageType, FilterElement<MessageType>>();
|
||||
|
||||
public LTRNetMessageFilter()
|
||||
{
|
||||
super( "LTR-Net Message Filter" );
|
||||
|
||||
mElements.put( MessageType.CA_STRT,
|
||||
new FilterElement<MessageType>( MessageType.CA_STRT ) );
|
||||
mElements.put( MessageType.CA_ENDD,
|
||||
new FilterElement<MessageType>( MessageType.CA_ENDD ) );
|
||||
mElements.put( MessageType.SY_IDLE,
|
||||
new FilterElement<MessageType>( MessageType.SY_IDLE ) );
|
||||
mElements.put( MessageType.ID_UNIQ,
|
||||
new FilterElement<MessageType>( MessageType.ID_UNIQ ) );
|
||||
mElements.put( MessageType.ID_ESNH,
|
||||
new FilterElement<MessageType>( MessageType.ID_ESNH ) );
|
||||
mElements.put( MessageType.ID_ESNL,
|
||||
new FilterElement<MessageType>( MessageType.ID_ESNL ) );
|
||||
mElements.put( MessageType.RQ_ACCE,
|
||||
new FilterElement<MessageType>( MessageType.RQ_ACCE ) );
|
||||
mElements.put( MessageType.ID_SITE,
|
||||
new FilterElement<MessageType>( MessageType.ID_SITE ) );
|
||||
mElements.put( MessageType.FQ_RXHI,
|
||||
new FilterElement<MessageType>( MessageType.FQ_RXHI ) );
|
||||
mElements.put( MessageType.FQ_RXLO,
|
||||
new FilterElement<MessageType>( MessageType.FQ_RXLO ) );
|
||||
mElements.put( MessageType.FQ_TXHI,
|
||||
new FilterElement<MessageType>( MessageType.FQ_TXHI ) );
|
||||
mElements.put( MessageType.FQ_TXLO,
|
||||
new FilterElement<MessageType>( MessageType.FQ_TXLO ) );
|
||||
mElements.put( MessageType.ID_NBOR,
|
||||
new FilterElement<MessageType>( MessageType.ID_NBOR ) );
|
||||
mElements.put( MessageType.MA_CHNH,
|
||||
new FilterElement<MessageType>( MessageType.MA_CHNH ) );
|
||||
mElements.put( MessageType.MA_CHNL,
|
||||
new FilterElement<MessageType>( MessageType.MA_CHNL ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes( Message message )
|
||||
{
|
||||
if( mEnabled && canProcess( message ) )
|
||||
{
|
||||
LTRNetMessage ltr = (LTRNetMessage)message;
|
||||
|
||||
if( mElements.containsKey( ltr.getMessageType() ) )
|
||||
{
|
||||
return mElements.get( ltr.getMessageType() ).isEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess( Message message )
|
||||
{
|
||||
return message instanceof LTRNetMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FilterElement<?>> getFilterElements()
|
||||
{
|
||||
return new ArrayList<FilterElement<?>>( mElements.values() );
|
||||
}
|
||||
}
|
||||
167
src/module/decode/ltrnet/LTRNetMessageProcessor.java
Normal file
167
src/module/decode/ltrnet/LTRNetMessageProcessor.java
Normal file
@@ -0,0 +1,167 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.ltrnet;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import message.Message;
|
||||
import message.MessageDirection;
|
||||
import message.MessageType;
|
||||
import sample.Listener;
|
||||
import alias.AliasList;
|
||||
import bits.BinaryMessage;
|
||||
|
||||
public class LTRNetMessageProcessor implements Listener<BinaryMessage>
|
||||
{
|
||||
private Listener<Message> mMessageListener;
|
||||
|
||||
private HashMap<Integer,LTRNetOSWMessage> mReceiveHighAuxMessages =
|
||||
new HashMap<Integer,LTRNetOSWMessage>();
|
||||
private HashMap<Integer,LTRNetOSWMessage> mReceiveLowAuxMessages =
|
||||
new HashMap<Integer,LTRNetOSWMessage>();
|
||||
|
||||
private HashMap<Integer,LTRNetOSWMessage> mTransmitHighAuxMessages =
|
||||
new HashMap<Integer,LTRNetOSWMessage>();
|
||||
private HashMap<Integer,LTRNetOSWMessage> mTransmitLowAuxMessages =
|
||||
new HashMap<Integer,LTRNetOSWMessage>();
|
||||
|
||||
private LTRNetISWMessage mESNHighMessage;
|
||||
private LTRNetISWMessage mESNLowMessage;
|
||||
|
||||
private MessageDirection mDirection;
|
||||
private AliasList mAliasList;
|
||||
|
||||
public LTRNetMessageProcessor( MessageDirection direction,
|
||||
AliasList list )
|
||||
{
|
||||
mDirection = direction;
|
||||
mAliasList = list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( BinaryMessage buffer )
|
||||
{
|
||||
if( mMessageListener != null )
|
||||
{
|
||||
LTRNetMessage message;
|
||||
|
||||
if( mDirection == MessageDirection.OSW )
|
||||
{
|
||||
message = new LTRNetOSWMessage( buffer, mAliasList );
|
||||
|
||||
if( message.isValid() )
|
||||
{
|
||||
if( message.getMessageType() == MessageType.FQ_TXHI )
|
||||
{
|
||||
mTransmitHighAuxMessages.put( message.getHomeRepeater(),
|
||||
(LTRNetOSWMessage)message );
|
||||
|
||||
message.setAuxiliaryMessage( mTransmitLowAuxMessages.get(
|
||||
message.getHomeRepeater() ) );
|
||||
}
|
||||
else if( message.getMessageType() == MessageType.FQ_TXLO )
|
||||
{
|
||||
mTransmitLowAuxMessages.put( message.getHomeRepeater(),
|
||||
(LTRNetOSWMessage)message );
|
||||
|
||||
message.setAuxiliaryMessage( mTransmitHighAuxMessages.get(
|
||||
message.getHomeRepeater() ) );
|
||||
}
|
||||
else if( message.getMessageType() == MessageType.FQ_RXHI )
|
||||
{
|
||||
mReceiveHighAuxMessages.put( message.getHomeRepeater(),
|
||||
(LTRNetOSWMessage)message );
|
||||
|
||||
message.setAuxiliaryMessage( mReceiveLowAuxMessages.get(
|
||||
message.getHomeRepeater() ) );
|
||||
}
|
||||
else if( message.getMessageType() == MessageType.FQ_RXLO )
|
||||
{
|
||||
mReceiveLowAuxMessages.put( message.getHomeRepeater(),
|
||||
(LTRNetOSWMessage)message );
|
||||
message.setAuxiliaryMessage( mReceiveHighAuxMessages.get(
|
||||
message.getHomeRepeater() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.flip( 0, 40 );
|
||||
|
||||
message = new LTRNetISWMessage( buffer, mAliasList );
|
||||
|
||||
if( message.isValid() )
|
||||
{
|
||||
/**
|
||||
* Catch ESN High messages to marry up with ESN Low messages. Purge
|
||||
* the ESN high message after 2 messages, so that it doesn't get
|
||||
* paired with the wrong esn low message
|
||||
*/
|
||||
if( message.getMessageType() == MessageType.ID_ESNH )
|
||||
{
|
||||
mESNHighMessage = (LTRNetISWMessage)message;
|
||||
|
||||
if( mESNLowMessage != null )
|
||||
{
|
||||
/* Check that the stored message is not older than
|
||||
* 5 seconds */
|
||||
if( System.currentTimeMillis() -
|
||||
mESNLowMessage.getTimeReceived() < 5000 )
|
||||
{
|
||||
((LTRNetISWMessage)message).setAuxiliaryMessage( mESNLowMessage );
|
||||
}
|
||||
else
|
||||
{
|
||||
mESNLowMessage = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( message.getMessageType() == MessageType.ID_ESNL )
|
||||
{
|
||||
mESNLowMessage = (LTRNetISWMessage)message;
|
||||
|
||||
if( mESNHighMessage != null )
|
||||
{
|
||||
/* Check that the stored message is not older than 5 seconds */
|
||||
if( System.currentTimeMillis() - mESNHighMessage.getTimeReceived() < 5000 )
|
||||
{
|
||||
((LTRNetISWMessage)message).setAuxiliaryMessage( mESNHighMessage );
|
||||
}
|
||||
else
|
||||
{
|
||||
mESNHighMessage = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mMessageListener.receive( message );
|
||||
}
|
||||
}
|
||||
|
||||
public void setMessageListener( Listener<Message> listener )
|
||||
{
|
||||
mMessageListener = listener;
|
||||
}
|
||||
|
||||
public void removeMessageListener()
|
||||
{
|
||||
mMessageListener = null;
|
||||
}
|
||||
}
|
||||
644
src/module/decode/ltrnet/LTRNetOSWMessage.java
Normal file
644
src/module/decode/ltrnet/LTRNetOSWMessage.java
Normal file
@@ -0,0 +1,644 @@
|
||||
/*******************************************************************************
|
||||
* 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.ltrnet;
|
||||
|
||||
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 LTRNetOSWMessage extends LTRNetMessage
|
||||
{
|
||||
private static final int CHANNEL_FREQUENCY_MESSAGE_TYPE_BIT = 20;
|
||||
private static final int CHANNEL_MAP_MESSAGE_TYPE_BIT = 17;
|
||||
|
||||
private static final int CHIU_UNIQUE_ID = 17;
|
||||
private static final int CHIU_SITE_ID = 18;
|
||||
private static final int CHIU_TRANSMIT_FREQUENCY = 24;
|
||||
private static final int CHIU_RECEIVE_FREQUENCY = 25;
|
||||
private static final int CHIU_NEIGHBOR = 26;
|
||||
private static final int CHIU_MAP = 28;
|
||||
private static final int CHIU_CALL_END = 31;
|
||||
|
||||
private static final int HOME_DIRECTED_GROUP_CALL = 29;
|
||||
private static final int HOME_CHANNEL_IN_USE_MUTE_AUDIO = 30;
|
||||
|
||||
private static final int GROUP_DO_NOTHING = 253;
|
||||
private static final int GROUP_CWID = 254;
|
||||
private static final int GROUP_CHANNEL_IDLE = 255;
|
||||
|
||||
private static final int FREE_ALL_CHANNELS_BUSY = 0;
|
||||
private static final int FREE_LTR_GROUP_CALL = 30;
|
||||
private static final int FREE_LTRNET_GROUP_CALL = 31;
|
||||
|
||||
private LTRNetOSWMessage mAuxMessage;
|
||||
|
||||
public LTRNetOSWMessage( BinaryMessage message, AliasList list )
|
||||
{
|
||||
super( message, MessageDirection.OSW, list );
|
||||
mMessageType = getType();
|
||||
}
|
||||
|
||||
public void setAuxiliaryMessage( LTRNetOSWMessage message )
|
||||
{
|
||||
if( message != null )
|
||||
{
|
||||
mAuxMessage = message;
|
||||
}
|
||||
}
|
||||
|
||||
public String getRadioUniqueIDText()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
int radioId = getRadioUniqueID();
|
||||
|
||||
if( radioId != INT_NULL_VALUE )
|
||||
{
|
||||
sb.append( " RADIO UNIQUE ID: " );
|
||||
sb.append( radioId );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( mDatestampFormatter.format(
|
||||
new Date( System.currentTimeMillis() ) ) );
|
||||
sb.append( " LTRNet 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;
|
||||
case MA_CHNL:
|
||||
sb.append( "CHAN MAP LOW LCN [" );
|
||||
sb.append( getChannelMapAsString( getChannelMapLow() ) );
|
||||
sb.append( "]" );
|
||||
break;
|
||||
case MA_CHNH:
|
||||
sb.append( "CHAN MAP HIGH LCN [" );
|
||||
sb.append( getChannelMapAsString( getChannelMapHigh() ) );
|
||||
sb.append( "]" );
|
||||
break;
|
||||
case FQ_RXLO:
|
||||
sb.append( "CHAN RX LOW LCN:" );
|
||||
sb.append( format( getHomeRepeater(), 2 ) );
|
||||
sb.append( " = " );
|
||||
long rxFreqLo = getFrequency();
|
||||
if( rxFreqLo > 0 )
|
||||
{
|
||||
sb.append( mDecimalFormatter.format( (double)rxFreqLo / 1E6d ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( "000.00000" );
|
||||
}
|
||||
sb.append( " MHz" );
|
||||
break;
|
||||
case FQ_RXHI:
|
||||
sb.append( "CHAN RX HIGH LCN:" );
|
||||
sb.append( format( getHomeRepeater(), 2 ) );
|
||||
sb.append( " = " );
|
||||
long rxfreqhi = getFrequency();
|
||||
if( rxfreqhi > 0 )
|
||||
{
|
||||
sb.append( mDecimalFormatter.format( (double)rxfreqhi / 1E6d ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( "000.00000" );
|
||||
}
|
||||
sb.append( " MHz" );
|
||||
break;
|
||||
case FQ_TXHI:
|
||||
sb.append( "CHAN TX LOW LCN:" );
|
||||
sb.append( format( getHomeRepeater(), 2 ) );
|
||||
sb.append( " = " );
|
||||
long txfreq = getFrequency();
|
||||
if( txfreq > 0 )
|
||||
{
|
||||
sb.append( mDecimalFormatter.format( (double)txfreq / 1E6d ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( "000.00000" );
|
||||
}
|
||||
sb.append( " MHz" );
|
||||
break;
|
||||
case FQ_TXLO:
|
||||
sb.append( "CHAN TX HIGH LCN:" );
|
||||
sb.append( format( getHomeRepeater(), 2 ) );
|
||||
sb.append( " = " );
|
||||
long txFreqLo = getFrequency();
|
||||
if( txFreqLo > 0 )
|
||||
{
|
||||
sb.append( mDecimalFormatter.format( (double)txFreqLo / 1E6d ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( "000.00000" );
|
||||
}
|
||||
sb.append( " MHz" );
|
||||
break;
|
||||
case ID_SITE:
|
||||
String siteId = getSiteID();
|
||||
if( siteId != null )
|
||||
{
|
||||
sb.append( "SITE ID: " );
|
||||
sb.append( siteId );
|
||||
sb.append( " " );
|
||||
sb.append( getSiteIDAlias() );
|
||||
}
|
||||
break;
|
||||
case ID_NBOR:
|
||||
int neighborRank = getNeighborRank();
|
||||
String neighborId = getNeighborID();
|
||||
|
||||
if( neighborRank != INT_NULL_VALUE &&
|
||||
neighborId != null )
|
||||
{
|
||||
sb.append( "NEIGHBOR SITE " );
|
||||
sb.append( neighborRank );
|
||||
sb.append( ": " );
|
||||
sb.append( neighborId );
|
||||
sb.append( " " );
|
||||
sb.append( getNeighborIDAlias() );
|
||||
}
|
||||
break;
|
||||
case ID_UNIQ:
|
||||
sb.append( "REGISTER RADIO UNIQUE ID:" );
|
||||
sb.append( getRadioUniqueID() );
|
||||
break;
|
||||
default:
|
||||
sb.append( "UNKNOWN " );
|
||||
sb.append( getArea() );
|
||||
sb.append( " " + format( getChannel(), 2 ) );
|
||||
sb.append( " " + format( getHomeRepeater(), 2 ) );
|
||||
sb.append( " " + format( getGroup(), 3 ) );
|
||||
sb.append( " " + format( getFree(), 2 ) );
|
||||
sb.append( " 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()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( getArea() );
|
||||
sb.append( "-" );
|
||||
sb.append( format( getHomeRepeater(), 2 ) );
|
||||
sb.append( "-" );
|
||||
sb.append( format( getGroup(), 3 ) );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public Alias getTalkgroupIDAlias()
|
||||
{
|
||||
if( mAliasList != null )
|
||||
{
|
||||
return mAliasList.getTalkgroupAlias( getTalkgroupID() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ArrayList<Integer> getChannelMapLow()
|
||||
{
|
||||
ArrayList<Integer> retVal = new ArrayList<Integer>();
|
||||
|
||||
if( mMessageType == MessageType.MA_CHNL )
|
||||
{
|
||||
for( int x = 27; x >= 18; x-- )
|
||||
{
|
||||
if( mMessage.get( x ) )
|
||||
{
|
||||
retVal.add( 28 - x );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public ArrayList<Integer> getChannelMapHigh()
|
||||
{
|
||||
ArrayList<Integer> retVal = new ArrayList<Integer>();
|
||||
|
||||
if( mMessageType == MessageType.MA_CHNH )
|
||||
{
|
||||
for( int x = 27; x >= 18; x-- )
|
||||
{
|
||||
if( mMessage.get( x ) )
|
||||
{
|
||||
retVal.add( 38 - x );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public String getChannelMapAsString( ArrayList<Integer> channels )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for( Integer channel: channels )
|
||||
{
|
||||
sb.append( channel );
|
||||
sb.append( " " );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public int getRadioUniqueID()
|
||||
{
|
||||
int retVal = INT_NULL_VALUE;
|
||||
|
||||
if( mMessageType == MessageType.ID_UNIQ )
|
||||
{
|
||||
retVal = mMessage.getInt( 17, 32 );
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public Alias getRadioUniqueIDAlias()
|
||||
{
|
||||
if( mAliasList != null )
|
||||
{
|
||||
return mAliasList.getUniqueID( getRadioUniqueID() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getSiteID()
|
||||
{
|
||||
if( mMessageType == MessageType.ID_SITE )
|
||||
{
|
||||
return String.valueOf( mMessage.getInt( 23, 32 ) );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Alias getSiteIDAlias()
|
||||
{
|
||||
if( mAliasList != null )
|
||||
{
|
||||
return mAliasList.getSiteID( getSiteID() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getNeighborID()
|
||||
{
|
||||
if( mMessageType == MessageType.ID_NBOR )
|
||||
{
|
||||
int id = mMessage.getInt( 23, 32 );
|
||||
|
||||
if( id > 0 )
|
||||
{
|
||||
return String.valueOf( id );
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Alias getNeighborIDAlias()
|
||||
{
|
||||
if( mAliasList != null )
|
||||
{
|
||||
return mAliasList.getSiteID( getNeighborID() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getNeighborRank()
|
||||
{
|
||||
int retVal = INT_NULL_VALUE;
|
||||
|
||||
if( mMessageType == MessageType.ID_NBOR )
|
||||
{
|
||||
retVal = mMessage.getInt( 15, 18 ) + 1;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public long getFrequency()
|
||||
{
|
||||
long retVal = 0;
|
||||
|
||||
if( mAuxMessage != null )
|
||||
{
|
||||
int high;
|
||||
int low;
|
||||
|
||||
MessageType type = getMessageType();
|
||||
|
||||
if( type == MessageType.FQ_RXHI || type == MessageType.FQ_TXHI )
|
||||
{
|
||||
high = getHighChannelUnits();
|
||||
low = mAuxMessage.getLowChannelUnits();
|
||||
}
|
||||
else
|
||||
{
|
||||
high = mAuxMessage.getHighChannelUnits();
|
||||
low = getLowChannelUnits();
|
||||
}
|
||||
|
||||
if( high != INT_NULL_VALUE && low != INT_NULL_VALUE )
|
||||
{
|
||||
retVal = ( high + low ) * 1250 + 150000000;
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the high order (15 - 12) frequency bits which are
|
||||
* the lower 4 bits of the Free field, left shifted by 12, representing the
|
||||
* frequency value in units of .00125 MHz
|
||||
*/
|
||||
public int getHighChannelUnits()
|
||||
{
|
||||
int retVal = INT_NULL_VALUE;
|
||||
|
||||
if( mAuxMessage != null &&
|
||||
( mMessageType == MessageType.FQ_RXHI ||
|
||||
mMessageType == MessageType.FQ_TXHI ) )
|
||||
{
|
||||
retVal = mMessage.getInt( 29, 32 );
|
||||
|
||||
retVal = Integer.rotateLeft( retVal, 12 );
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the low order (11 - 0) frequency bits which are
|
||||
* contained in the Group and Free fields representing the frequency value
|
||||
* units of .00125 MHz
|
||||
*/
|
||||
public int getLowChannelUnits()
|
||||
{
|
||||
int retVal = INT_NULL_VALUE;
|
||||
|
||||
if( mMessageType == MessageType.FQ_RXLO ||
|
||||
mMessageType == MessageType.FQ_TXLO )
|
||||
{
|
||||
retVal = mMessage.getInt( 21, 32 );
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public MessageType getType()
|
||||
{
|
||||
MessageType retVal = MessageType.UN_KNWN;
|
||||
|
||||
int channel = getChannel();
|
||||
|
||||
int home = getHomeRepeater();
|
||||
|
||||
//LTR-Net messages
|
||||
if( home != 31 && ( channel > 20 || home > 20 ) )
|
||||
{
|
||||
switch( channel )
|
||||
{
|
||||
case CHIU_UNIQUE_ID:
|
||||
retVal = MessageType.ID_UNIQ;
|
||||
break;
|
||||
case CHIU_SITE_ID:
|
||||
retVal = MessageType.ID_SITE;
|
||||
break;
|
||||
case CHIU_TRANSMIT_FREQUENCY:
|
||||
if( getHomeRepeater() == 24 )
|
||||
{
|
||||
retVal = MessageType.ID_UNIQ;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( mMessage.get( CHANNEL_FREQUENCY_MESSAGE_TYPE_BIT ) )
|
||||
{
|
||||
retVal = MessageType.FQ_TXHI;
|
||||
}
|
||||
else
|
||||
{
|
||||
retVal = MessageType.FQ_TXLO;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CHIU_RECEIVE_FREQUENCY:
|
||||
if( mMessage.get( CHANNEL_FREQUENCY_MESSAGE_TYPE_BIT ) )
|
||||
{
|
||||
retVal = MessageType.FQ_RXHI;
|
||||
}
|
||||
else
|
||||
{
|
||||
retVal = MessageType.FQ_RXLO;
|
||||
}
|
||||
break;
|
||||
case CHIU_NEIGHBOR:
|
||||
retVal = MessageType.ID_NBOR;
|
||||
break;
|
||||
case CHIU_MAP:
|
||||
if( mMessage.get( CHANNEL_MAP_MESSAGE_TYPE_BIT ) )
|
||||
{
|
||||
retVal = MessageType.MA_CHNH;
|
||||
}
|
||||
else
|
||||
{
|
||||
retVal = MessageType.MA_CHNL;
|
||||
}
|
||||
break;
|
||||
case CHIU_CALL_END:
|
||||
retVal = MessageType.CA_ENDD;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int group = getGroup();
|
||||
|
||||
if( group == GROUP_CHANNEL_IDLE )
|
||||
{
|
||||
retVal = MessageType.SY_IDLE;
|
||||
}
|
||||
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 );
|
||||
}
|
||||
|
||||
Alias neighbor = getNeighborIDAlias();
|
||||
|
||||
if( neighbor != null )
|
||||
{
|
||||
aliases.add( neighbor );
|
||||
}
|
||||
|
||||
Alias uid = getRadioUniqueIDAlias();
|
||||
|
||||
if( uid != null )
|
||||
{
|
||||
aliases.add( uid );
|
||||
}
|
||||
|
||||
Alias site = getSiteIDAlias();
|
||||
|
||||
if( site != null )
|
||||
{
|
||||
aliases.add( site );
|
||||
}
|
||||
|
||||
return aliases;
|
||||
}
|
||||
}
|
||||
45
src/module/decode/ltrstandard/DecodeConfigLTRStandard.java
Normal file
45
src/module/decode/ltrstandard/DecodeConfigLTRStandard.java
Normal file
@@ -0,0 +1,45 @@
|
||||
/*******************************************************************************
|
||||
* 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 javax.xml.bind.annotation.XmlAttribute;
|
||||
|
||||
import message.MessageDirection;
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
|
||||
public class DecodeConfigLTRStandard extends DecodeConfiguration
|
||||
{
|
||||
private MessageDirection mMessageDirection = MessageDirection.OSW;
|
||||
|
||||
public DecodeConfigLTRStandard()
|
||||
{
|
||||
super( DecoderType.LTR_STANDARD );
|
||||
}
|
||||
|
||||
@XmlAttribute( name = "direction" )
|
||||
public MessageDirection getMessageDirection()
|
||||
{
|
||||
return mMessageDirection;
|
||||
}
|
||||
|
||||
public void setMessageDirection( MessageDirection direction )
|
||||
{
|
||||
mMessageDirection = direction;
|
||||
}
|
||||
}
|
||||
78
src/module/decode/ltrstandard/LTRStandardDecoder.java
Normal file
78
src/module/decode/ltrstandard/LTRStandardDecoder.java
Normal file
@@ -0,0 +1,78 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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 sample.Listener;
|
||||
import sample.real.IRealBufferListener;
|
||||
import sample.real.RealBuffer;
|
||||
import message.MessageDirection;
|
||||
import module.decode.Decoder;
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
import alias.AliasList;
|
||||
import bits.MessageFramer;
|
||||
import bits.SyncPattern;
|
||||
import dsp.fsk.LTRFSKDecoder;
|
||||
|
||||
public class LTRStandardDecoder extends Decoder implements IRealBufferListener
|
||||
{
|
||||
public static final int LTR_STANDARD_MESSAGE_LENGTH = 40;
|
||||
|
||||
private LTRFSKDecoder mLTRFSKDecoder;
|
||||
private MessageFramer mLTRMessageFramer;
|
||||
private LTRStandardMessageProcessor mLTRMessageProcessor;
|
||||
|
||||
public LTRStandardDecoder( AliasList aliasList,
|
||||
MessageDirection direction )
|
||||
{
|
||||
super( new LTRStandardDecoderState( aliasList ) );
|
||||
|
||||
mLTRFSKDecoder = new LTRFSKDecoder();
|
||||
|
||||
if( direction == MessageDirection.OSW )
|
||||
{
|
||||
mLTRMessageFramer =
|
||||
new MessageFramer( SyncPattern.LTR_STANDARD_OSW.getPattern(),
|
||||
LTR_STANDARD_MESSAGE_LENGTH );
|
||||
}
|
||||
else
|
||||
{
|
||||
mLTRMessageFramer =
|
||||
new MessageFramer( SyncPattern.LTR_STANDARD_ISW.getPattern(),
|
||||
LTR_STANDARD_MESSAGE_LENGTH );
|
||||
}
|
||||
|
||||
mLTRFSKDecoder.addListener( mLTRMessageFramer );
|
||||
|
||||
mLTRMessageProcessor = new LTRStandardMessageProcessor( direction, aliasList );
|
||||
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
|
||||
mLTRMessageProcessor.setMessageListener( mMessageBroadcaster );
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.LTR_STANDARD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<RealBuffer> getRealBufferListener()
|
||||
{
|
||||
return mLTRFSKDecoder;
|
||||
}
|
||||
}
|
||||
157
src/module/decode/ltrstandard/LTRStandardDecoderPanel.java
Normal file
157
src/module/decode/ltrstandard/LTRStandardDecoderPanel.java
Normal file
@@ -0,0 +1,157 @@
|
||||
/*******************************************************************************
|
||||
* 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.awt.EventQueue;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import module.decode.state.ChangedAttribute;
|
||||
import module.decode.state.DecoderPanel;
|
||||
import settings.ColorSetting;
|
||||
import settings.Setting;
|
||||
import settings.SettingsManager;
|
||||
import alias.Alias;
|
||||
|
||||
public class LTRStandardDecoderPanel extends DecoderPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JLabel mProtocol = new JLabel( "LTR Standard" );
|
||||
private JLabel mTalkgroup = new JLabel();
|
||||
private JLabel mTalkgroupAlias = new JLabel();
|
||||
|
||||
private JLabel mLCN = new JLabel( "LCN:" );
|
||||
|
||||
public LTRStandardDecoderPanel( SettingsManager settingsManager, LTRStandardDecoder decoder )
|
||||
{
|
||||
super( settingsManager, decoder );
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
mProtocol.setFont( mFontDecoder );
|
||||
mProtocol.setForeground( mColorLabelDecoder );
|
||||
|
||||
mTalkgroup.setFont( mFontDecoder );
|
||||
mTalkgroup.setForeground( mColorLabelDecoder );
|
||||
|
||||
mTalkgroupAlias.setFont( mFontDecoder );
|
||||
mTalkgroupAlias.setForeground( mColorLabelDecoder );
|
||||
|
||||
mLCN.setFont( mFontDecoder );
|
||||
mLCN.setForeground( mColorLabelDecoder );
|
||||
|
||||
add( mProtocol );
|
||||
add( mTalkgroup );
|
||||
add( mTalkgroupAlias, "wrap" );
|
||||
|
||||
add( mLCN, "wrap" );
|
||||
}
|
||||
|
||||
private LTRStandardDecoderState getDecoderState()
|
||||
{
|
||||
return (LTRStandardDecoderState)getDecoder().getDecoderState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( final ChangedAttribute attribute )
|
||||
{
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
LTRStandardDecoderState state = getDecoderState();
|
||||
|
||||
switch( attribute )
|
||||
{
|
||||
case CHANNEL_NUMBER:
|
||||
if( state.getChannelNumber() != 0 )
|
||||
{
|
||||
mLCN.setText( "LCN: " + state.getChannelNumber() );
|
||||
}
|
||||
break;
|
||||
case TO_TALKGROUP:
|
||||
mTalkgroup.setText( state.getTalkgroup() );
|
||||
/* fall-through */
|
||||
case TO_TALKGROUP_ALIAS:
|
||||
Alias alias = state.getTalkgroupAlias();
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
mTalkgroupAlias.setText( alias.getName() );
|
||||
mTalkgroupAlias.setIcon( mSettingsManager
|
||||
.getImageIcon( alias.getIconName(), 12 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
mTalkgroupAlias.setText( null );
|
||||
mTalkgroupAlias.setIcon( null );
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingChanged( Setting setting )
|
||||
{
|
||||
super.settingChanged( setting );
|
||||
|
||||
if( setting instanceof ColorSetting )
|
||||
{
|
||||
ColorSetting colorSetting = (ColorSetting)setting;
|
||||
|
||||
switch( colorSetting.getColorSettingName() )
|
||||
{
|
||||
case CHANNEL_STATE_LABEL_DECODER:
|
||||
if( mProtocol != null )
|
||||
{
|
||||
mProtocol.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
if( mTalkgroup != null )
|
||||
{
|
||||
mTalkgroup.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
if( mTalkgroupAlias != null )
|
||||
{
|
||||
mTalkgroupAlias.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
if( mLCN != null )
|
||||
{
|
||||
mLCN.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
398
src/module/decode/ltrstandard/LTRStandardDecoderState.java
Normal file
398
src/module/decode/ltrstandard/LTRStandardDecoderState.java
Normal file
@@ -0,0 +1,398 @@
|
||||
/*******************************************************************************
|
||||
* 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.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import message.Message;
|
||||
import module.decode.event.CallEvent.CallEventType;
|
||||
import module.decode.ltrnet.LTRCallEvent;
|
||||
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 alias.Alias;
|
||||
import alias.AliasList;
|
||||
|
||||
public class LTRStandardDecoderState extends DecoderState
|
||||
{
|
||||
private static final int sINT_NULL_VALUE = -1;
|
||||
private DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000");
|
||||
|
||||
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 int mMonitoredLCN;
|
||||
|
||||
private String mTalkgroup;
|
||||
private Alias mTalkgroupAlias;
|
||||
private int mChannelNumber;
|
||||
|
||||
private long mFrequency;
|
||||
|
||||
public LTRStandardDecoderState( AliasList aliasList )
|
||||
{
|
||||
super( aliasList );
|
||||
}
|
||||
|
||||
private LTRCallEvent getCurrentCallEvent()
|
||||
{
|
||||
return (LTRCallEvent)mCurrentCallEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Message message )
|
||||
{
|
||||
if( message.isValid() )
|
||||
{
|
||||
if( message instanceof LTRStandardOSWMessage )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
|
||||
/* 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();
|
||||
|
||||
/* If we have an ongoing call, then add this message
|
||||
* to the current event - if addMessage returns
|
||||
* false, then we have a different talkgroup and a
|
||||
* new call event, so close out the old one. */
|
||||
if( current != null && !current.addMessage( ltr ) )
|
||||
{
|
||||
mCurrentCallEvent.setEnd( System.currentTimeMillis() );
|
||||
broadcast( mCurrentCallEvent );
|
||||
mCurrentCallEvent = null;
|
||||
}
|
||||
|
||||
if( current == null )
|
||||
{
|
||||
mCurrentCallEvent =
|
||||
new LTRCallEvent.Builder( CallEventType.CALL )
|
||||
.to( ltr.getTalkgroupID() )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( String.valueOf( ltr.getChannel() ) )
|
||||
.frequency( mFrequency )
|
||||
.build();
|
||||
|
||||
broadcast( mCurrentCallEvent );
|
||||
|
||||
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.FROM_TALKGROUP );
|
||||
|
||||
mTalkgroupAlias = ltr.getTalkgroupIDAlias();
|
||||
broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS );
|
||||
|
||||
if( mCurrentCallEvent != null )
|
||||
{
|
||||
mCurrentCallEvent.setEnd( System.currentTimeMillis() );
|
||||
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 channel = ltr.getChannel();
|
||||
|
||||
if( 0 < channel && channel <= 20 )
|
||||
{
|
||||
if( channel == home )
|
||||
{
|
||||
if( mMonitoredLCN != channel )
|
||||
{
|
||||
mMonitoredLCN = 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( mMonitoredLCN != idleChannel )
|
||||
{
|
||||
mMonitoredLCN = idleChannel;
|
||||
broadcast( ChangedAttribute.CHANNEL_NUMBER );
|
||||
}
|
||||
|
||||
mActiveLCNs.add( idleChannel );
|
||||
}
|
||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.IDLE ) );
|
||||
break;
|
||||
case UN_KNWN:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Intercept the fade event so that we can generate a call end event
|
||||
// */
|
||||
// @Override
|
||||
// public void fade( final CallEventType type )
|
||||
// {
|
||||
// /* We can receive multiple call tear-down messages -- only respond to
|
||||
// * the message that can change the state to fade */
|
||||
// if( getState().canChangeTo( State.FADE ) )
|
||||
// {
|
||||
// mActiveCalls.clear();
|
||||
//
|
||||
// LTRCallEvent current = getCurrentLTRCallEvent();
|
||||
//
|
||||
// if( current != null )
|
||||
// {
|
||||
// /* Close out the call event. If we only received 1 call
|
||||
// * message, then flag it as noise and remove the call event */
|
||||
// if( current.isValid() )
|
||||
// {
|
||||
// mCallEventModel.setEnd( current );
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// mCallEventModel.remove( current );
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// setCurrentCallEvent( null );
|
||||
// }
|
||||
//
|
||||
// super.fade( type );
|
||||
// }
|
||||
|
||||
public void reset()
|
||||
{
|
||||
resetState();
|
||||
|
||||
if( mCurrentCallEvent != null )
|
||||
{
|
||||
mCurrentCallEvent.setEnd( System.currentTimeMillis() );
|
||||
broadcast( mCurrentCallEvent );
|
||||
mCurrentCallEvent = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void resetState()
|
||||
{
|
||||
mTalkgroup = null;
|
||||
broadcast( ChangedAttribute.FROM_TALKGROUP );
|
||||
|
||||
mTalkgroupAlias = null;
|
||||
broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS );
|
||||
}
|
||||
|
||||
public String getTalkgroup()
|
||||
{
|
||||
return mTalkgroup;
|
||||
}
|
||||
|
||||
public Alias getTalkgroupAlias()
|
||||
{
|
||||
return mTalkgroupAlias;
|
||||
}
|
||||
|
||||
public boolean hasChannelNumber()
|
||||
{
|
||||
return mChannelNumber != 0;
|
||||
}
|
||||
|
||||
public int getChannelNumber()
|
||||
{
|
||||
return mChannelNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveDecoderStateEvent( DecoderStateEvent event )
|
||||
{
|
||||
switch( event.getEvent() )
|
||||
{
|
||||
case RESET:
|
||||
resetState();
|
||||
break;
|
||||
case SOURCE_FREQUENCY:
|
||||
mFrequency = event.getFrequency();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActivitySummary()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "Activity Summary\n\n" );
|
||||
|
||||
sb.append( "Decoder:\tLTR-Standard\n" );
|
||||
|
||||
sb.append( "Monitored LCN: " );
|
||||
|
||||
if( mMonitoredLCN > 0 )
|
||||
{
|
||||
sb.append( mMonitoredLCN );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( "unknown" );
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
|
||||
Integer[] lcns = mActiveLCNs.toArray( new Integer[0] );
|
||||
|
||||
sb.append( "Active LCNs:\t" );
|
||||
|
||||
if( lcns.length > 0 )
|
||||
{
|
||||
sb.append( Arrays.toString( lcns ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( "none" );
|
||||
}
|
||||
sb.append( "\n\n" );
|
||||
|
||||
sb.append( "Talkgroups\n" );
|
||||
|
||||
if( mTalkgroups.isEmpty() )
|
||||
{
|
||||
sb.append( " None\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Iterator<String> it = mTalkgroups.iterator();
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
String tgid = it.next();
|
||||
|
||||
sb.append( " " );
|
||||
sb.append( formatTalkgroup( tgid ) );
|
||||
sb.append( " " );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias alias = getAliasList().getTalkgroupAlias( tgid );
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
sb.append( alias.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String formatTalkgroup( String talkgroup )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if( talkgroup.length() == 6 )
|
||||
{
|
||||
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();
|
||||
}
|
||||
else
|
||||
{
|
||||
return talkgroup;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
71
src/module/decode/ltrstandard/LTRStandardEditor.java
Normal file
71
src/module/decode/ltrstandard/LTRStandardEditor.java
Normal file
@@ -0,0 +1,71 @@
|
||||
/*******************************************************************************
|
||||
* 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.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import message.MessageDirection;
|
||||
import module.decode.DecodeEditor;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
|
||||
public class LTRStandardEditor extends DecodeEditor
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JComboBox<MessageDirection> mComboDirection;
|
||||
|
||||
public LTRStandardEditor( DecodeConfiguration config )
|
||||
{
|
||||
super( config );
|
||||
|
||||
initGUI();
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
mComboDirection = new JComboBox<MessageDirection>();
|
||||
|
||||
mComboDirection.setModel(
|
||||
new DefaultComboBoxModel<MessageDirection>( MessageDirection.values() ) );
|
||||
|
||||
mComboDirection.setSelectedItem( ((DecodeConfigLTRStandard)mConfig).getMessageDirection() );
|
||||
|
||||
mComboDirection.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
MessageDirection selected = mComboDirection
|
||||
.getItemAt( mComboDirection.getSelectedIndex() );
|
||||
|
||||
if( selected != null )
|
||||
{
|
||||
((DecodeConfigLTRStandard)mConfig).setMessageDirection( selected );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
add( new JLabel( "Format:" ) );
|
||||
add( mComboDirection, "wrap" );
|
||||
}
|
||||
}
|
||||
238
src/module/decode/ltrstandard/LTRStandardISWMessage.java
Normal file
238
src/module/decode/ltrstandard/LTRStandardISWMessage.java
Normal file
@@ -0,0 +1,238 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <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;
|
||||
}
|
||||
}
|
||||
254
src/module/decode/ltrstandard/LTRStandardMessage.java
Normal file
254
src/module/decode/ltrstandard/LTRStandardMessage.java
Normal file
@@ -0,0 +1,254 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <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();
|
||||
}
|
||||
}
|
||||
56
src/module/decode/ltrstandard/LTRStandardMessageFilter.java
Normal file
56
src/module/decode/ltrstandard/LTRStandardMessageFilter.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package module.decode.ltrstandard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import message.Message;
|
||||
import message.MessageType;
|
||||
import filter.Filter;
|
||||
import filter.FilterElement;
|
||||
|
||||
public class LTRStandardMessageFilter extends Filter<Message>
|
||||
{
|
||||
private HashMap<MessageType, FilterElement<MessageType>> mElements =
|
||||
new HashMap<MessageType, FilterElement<MessageType>>();
|
||||
|
||||
public LTRStandardMessageFilter()
|
||||
{
|
||||
super( "LTR Message Filter" );
|
||||
|
||||
mElements.put( MessageType.CA_STRT,
|
||||
new FilterElement<MessageType>( MessageType.CA_STRT ) );
|
||||
mElements.put( MessageType.CA_ENDD,
|
||||
new FilterElement<MessageType>( MessageType.CA_ENDD ) );
|
||||
mElements.put( MessageType.SY_IDLE,
|
||||
new FilterElement<MessageType>( MessageType.SY_IDLE ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes( Message message )
|
||||
{
|
||||
if( mEnabled && canProcess( message ) )
|
||||
{
|
||||
LTRStandardMessage ltr = (LTRStandardMessage)message;
|
||||
|
||||
if( mElements.containsKey( ltr.getMessageType() ) )
|
||||
{
|
||||
return mElements.get( ltr.getMessageType() ).isEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess( Message message )
|
||||
{
|
||||
return message instanceof LTRStandardMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FilterElement<?>> getFilterElements()
|
||||
{
|
||||
return new ArrayList<FilterElement<?>>( mElements.values() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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 message.Message;
|
||||
import message.MessageDirection;
|
||||
import sample.Listener;
|
||||
import alias.AliasList;
|
||||
import bits.BinaryMessage;
|
||||
|
||||
public class LTRStandardMessageProcessor implements Listener<BinaryMessage>
|
||||
{
|
||||
private MessageDirection mDirection;
|
||||
private AliasList mAliasList;
|
||||
private Listener<Message> mMessageListener;
|
||||
|
||||
public LTRStandardMessageProcessor( MessageDirection direction, AliasList list )
|
||||
{
|
||||
mDirection = direction;
|
||||
mAliasList = list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( BinaryMessage buffer )
|
||||
{
|
||||
if( mMessageListener != null )
|
||||
{
|
||||
LTRStandardMessage message;
|
||||
|
||||
if( mDirection == MessageDirection.OSW )
|
||||
{
|
||||
message = new LTRStandardOSWMessage( buffer, mAliasList );
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.flip( 0, 40 );
|
||||
|
||||
message = new LTRStandardISWMessage( buffer, mAliasList );
|
||||
}
|
||||
|
||||
mMessageListener.receive( message );
|
||||
}
|
||||
}
|
||||
|
||||
public void setMessageListener( Listener<Message> listener )
|
||||
{
|
||||
mMessageListener = listener;
|
||||
}
|
||||
|
||||
public void removeMessageListener()
|
||||
{
|
||||
mMessageListener = null;
|
||||
}
|
||||
}
|
||||
248
src/module/decode/ltrstandard/LTRStandardOSWMessage.java
Normal file
248
src/module/decode/ltrstandard/LTRStandardOSWMessage.java
Normal file
@@ -0,0 +1,248 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <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;
|
||||
}
|
||||
}
|
||||
159
src/module/decode/mdc1200/MDCCallEvent.java
Normal file
159
src/module/decode/mdc1200/MDCCallEvent.java
Normal file
@@ -0,0 +1,159 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.mdc1200;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.event.CallEvent;
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
|
||||
public class MDCCallEvent extends CallEvent
|
||||
{
|
||||
public MDCCallEvent( CallEventType callEventType,
|
||||
AliasList aliasList,
|
||||
String fromID,
|
||||
String toID,
|
||||
String details )
|
||||
{
|
||||
super( DecoderType.MDC1200, callEventType,
|
||||
aliasList, fromID, toID, details );
|
||||
}
|
||||
|
||||
private Alias getAlias( String ident )
|
||||
{
|
||||
if( hasAliasList() )
|
||||
{
|
||||
return getAliasList().getMDC1200Alias( ident );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
return getAlias( getFromID() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getToIDAlias()
|
||||
{
|
||||
return getAlias( getToID() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannel()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrequency()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static class Builder
|
||||
{
|
||||
/* Required parameters */
|
||||
private CallEventType mCallEventType;
|
||||
|
||||
/* Optional parameters */
|
||||
private AliasList mAliasList;
|
||||
private String mFromID;
|
||||
private String mToID;
|
||||
private String mDetails;
|
||||
|
||||
public Builder( CallEventType callEventType )
|
||||
{
|
||||
mCallEventType = callEventType;
|
||||
}
|
||||
|
||||
public Builder aliasList( AliasList aliasList )
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder from( String val )
|
||||
{
|
||||
mFromID = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder details( String details )
|
||||
{
|
||||
mDetails = details;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder to( String toID )
|
||||
{
|
||||
mToID = toID;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MDCCallEvent build()
|
||||
{
|
||||
return new MDCCallEvent( this );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor for the builder
|
||||
*/
|
||||
private MDCCallEvent( Builder builder )
|
||||
{
|
||||
this( builder.mCallEventType,
|
||||
builder.mAliasList,
|
||||
builder.mFromID,
|
||||
builder.mToID,
|
||||
builder.mDetails );
|
||||
}
|
||||
|
||||
public static MDCCallEvent
|
||||
getMDCCallEvent( MDCMessage message )
|
||||
{
|
||||
CallEventType type = CallEventType.UNKNOWN;
|
||||
StringBuilder sbDetails = new StringBuilder();
|
||||
sbDetails.append( "MDC-1200 OPCODE:" );
|
||||
sbDetails.append( String.valueOf( message.getOpcode() ) );
|
||||
|
||||
switch( message.getMessageType() )
|
||||
{
|
||||
case ACKNOWLEDGE:
|
||||
type = CallEventType.RESPONSE;
|
||||
break;
|
||||
case ANI:
|
||||
type = CallEventType.ID_ANI;
|
||||
break;
|
||||
case EMERGENCY:
|
||||
type = CallEventType.EMERGENCY;
|
||||
break;
|
||||
case PAGING:
|
||||
type = CallEventType.PAGE;
|
||||
break;
|
||||
}
|
||||
|
||||
return new MDCCallEvent.Builder( type )
|
||||
.details( sbDetails.toString() )
|
||||
.from( message.getFromID() )
|
||||
.to( message.getToID() )
|
||||
.build();
|
||||
}
|
||||
}
|
||||
201
src/module/decode/mdc1200/MDCDecoder.java
Normal file
201
src/module/decode/mdc1200/MDCDecoder.java
Normal file
@@ -0,0 +1,201 @@
|
||||
/*******************************************************************************
|
||||
* 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.mdc1200;
|
||||
|
||||
import instrument.Instrumentable;
|
||||
import instrument.tap.Tap;
|
||||
import instrument.tap.stream.BinaryTap;
|
||||
import instrument.tap.stream.FloatBufferTap;
|
||||
import instrument.tap.stream.FloatTap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import module.decode.Decoder;
|
||||
import module.decode.DecoderType;
|
||||
import sample.Listener;
|
||||
import sample.real.IRealBufferListener;
|
||||
import sample.real.RealBuffer;
|
||||
import alias.AliasList;
|
||||
import bits.MessageFramer;
|
||||
import bits.SyncPattern;
|
||||
import dsp.NRZDecoder;
|
||||
import dsp.filter.Filters;
|
||||
import dsp.filter.fir.real.RealFIRFilter_RB_RB;
|
||||
import dsp.filter.halfband.real.HalfBandFilter_RB_RB;
|
||||
import dsp.fsk.FSK2Decoder;
|
||||
import dsp.fsk.FSK2Decoder.Output;
|
||||
|
||||
/**
|
||||
* MDC1200 Decoder - 1200 baud 2FSK decoder that can process 48k sample rate
|
||||
* complex or floating point samples and output fully framed MDC1200 messages
|
||||
*/
|
||||
public class MDCDecoder extends Decoder implements IRealBufferListener,
|
||||
Instrumentable
|
||||
{
|
||||
/* Decimated sample rate ( 48,000 / 2 = 24,000 ) feeding the decoder */
|
||||
private static final int sDECIMATED_SAMPLE_RATE = 24000;
|
||||
|
||||
/* Baud or Symbol Rate */
|
||||
private static final int sSYMBOL_RATE = 1200;
|
||||
|
||||
/* Message length */
|
||||
private static final int sMESSAGE_LENGTH = 304;
|
||||
|
||||
/* Instrumentation Taps */
|
||||
private ArrayList<Tap> mAvailableTaps;
|
||||
private static final String INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD =
|
||||
"Tap Point: Bandpass Filter > < FSK2 Decoder";
|
||||
private static final String INSTRUMENT_FSK2_DECODER_TO_NRZ_DECODER =
|
||||
"Tap Point: FSK2 Decoder > < NRZI Decoder";
|
||||
private static final String INSTRUMENT_NRZI_DECODER_TO_MESSAGE_FRAMER =
|
||||
"Tap Point: NRZ Decoder > < Message Framer";
|
||||
|
||||
private FSK2Decoder mFSKDecoder;
|
||||
private HalfBandFilter_RB_RB mDecimationFilter;
|
||||
private RealFIRFilter_RB_RB mBandPassFilter;
|
||||
private NRZDecoder mNRZDecoder;
|
||||
private MessageFramer mMessageFramer;
|
||||
private MDCMessageProcessor mMessageProcessor;
|
||||
|
||||
public MDCDecoder( AliasList aliasList )
|
||||
{
|
||||
super( new MDCDecoderState( aliasList ) );
|
||||
|
||||
/* Decimation filter: 48000 / 2 = 24000 */
|
||||
mDecimationFilter = new HalfBandFilter_RB_RB(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0f, true );
|
||||
|
||||
/* Bandpass filter - predefined */
|
||||
mBandPassFilter = new RealFIRFilter_RB_RB(
|
||||
Filters.FIRBP_1200FSK_24000FS.getCoefficients(), 1.02f );
|
||||
mDecimationFilter.setListener( mBandPassFilter );
|
||||
|
||||
/* 2FSK Decoder */
|
||||
mFSKDecoder = new FSK2Decoder( sDECIMATED_SAMPLE_RATE,
|
||||
sSYMBOL_RATE, Output.NORMAL );
|
||||
mBandPassFilter.setListener( mFSKDecoder );
|
||||
|
||||
/* NRZ Decoder */
|
||||
mNRZDecoder = new NRZDecoder( NRZDecoder.MODE_INVERTED );
|
||||
mFSKDecoder.setListener( mNRZDecoder );
|
||||
|
||||
/* Message Framer */
|
||||
mMessageFramer = new MessageFramer( SyncPattern.MDC1200.getPattern(),
|
||||
sMESSAGE_LENGTH );
|
||||
mNRZDecoder.setListener( mMessageFramer );
|
||||
|
||||
/* Message Processor */
|
||||
mMessageProcessor = new MDCMessageProcessor( aliasList );
|
||||
mMessageFramer.addMessageListener( mMessageProcessor );
|
||||
mMessageProcessor.addMessageListener( mMessageBroadcaster );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mDecimationFilter.dispose();
|
||||
mBandPassFilter.dispose();
|
||||
mFSKDecoder.dispose();
|
||||
mNRZDecoder.dispose();
|
||||
mMessageFramer.dispose();
|
||||
mMessageProcessor.dispose();
|
||||
}
|
||||
|
||||
/* Instrumentation */
|
||||
@Override
|
||||
public List<Tap> getTaps()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<Tap>();
|
||||
|
||||
mAvailableTaps.add(
|
||||
new FloatTap( INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD, 0, 0.5f ) );
|
||||
mAvailableTaps.addAll( mFSKDecoder.getTaps() );
|
||||
mAvailableTaps.add(
|
||||
new BinaryTap( INSTRUMENT_FSK2_DECODER_TO_NRZ_DECODER, 0, 0.025f ) );
|
||||
mAvailableTaps.add(
|
||||
new BinaryTap( INSTRUMENT_NRZI_DECODER_TO_MESSAGE_FRAMER, 0, 0.025f ) );
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTap( Tap tap )
|
||||
{
|
||||
mFSKDecoder.addTap( tap );
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
|
||||
/* wire the tap between the bandpass filter and the demod */
|
||||
FloatBufferTap floatTap = (FloatBufferTap)tap;
|
||||
mBandPassFilter.setListener( floatTap );
|
||||
floatTap.setListener( mFSKDecoder );
|
||||
break;
|
||||
case INSTRUMENT_FSK2_DECODER_TO_NRZ_DECODER:
|
||||
/* wire between FSK2 decoder and NRZ decoder */
|
||||
BinaryTap binTap = (BinaryTap)tap;
|
||||
mFSKDecoder.setListener( binTap );
|
||||
binTap.setListener( mNRZDecoder );
|
||||
break;
|
||||
case INSTRUMENT_NRZI_DECODER_TO_MESSAGE_FRAMER:
|
||||
/* wire between demod and framer */
|
||||
BinaryTap binInvTap = (BinaryTap)tap;
|
||||
mNRZDecoder.setListener( binInvTap );
|
||||
binInvTap.setListener( mMessageFramer );
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTap( Tap tap )
|
||||
{
|
||||
mFSKDecoder.removeTap( tap );
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
|
||||
mBandPassFilter.setListener( mFSKDecoder );
|
||||
break;
|
||||
case INSTRUMENT_FSK2_DECODER_TO_NRZ_DECODER:
|
||||
mFSKDecoder.setListener( mNRZDecoder );
|
||||
break;
|
||||
case INSTRUMENT_NRZI_DECODER_TO_MESSAGE_FRAMER:
|
||||
mNRZDecoder.setListener( mMessageFramer );
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.MDC1200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<RealBuffer> getRealBufferListener()
|
||||
{
|
||||
return mDecimationFilter;
|
||||
}
|
||||
}
|
||||
288
src/module/decode/mdc1200/MDCDecoderState.java
Normal file
288
src/module/decode/mdc1200/MDCDecoderState.java
Normal file
@@ -0,0 +1,288 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.mdc1200;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import message.Message;
|
||||
import module.decode.state.ChangedAttribute;
|
||||
import module.decode.state.DecoderState;
|
||||
import module.decode.state.DecoderStateEvent;
|
||||
import module.decode.state.State;
|
||||
import module.decode.state.DecoderStateEvent.Event;
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
|
||||
public class MDCDecoderState extends DecoderState
|
||||
{
|
||||
private TreeSet<String> mIdents = new TreeSet<String>();
|
||||
private TreeSet<String> mEmergencyIdents = new TreeSet<String>();
|
||||
|
||||
private String mFrom;
|
||||
private Alias mFromAlias;
|
||||
private String mTo;
|
||||
private Alias mToAlias;
|
||||
private String mMessage;
|
||||
private String mMessageType;
|
||||
|
||||
public MDCDecoderState( AliasList aliasList )
|
||||
{
|
||||
super( aliasList );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
mIdents.clear();
|
||||
mEmergencyIdents.clear();
|
||||
|
||||
resetState();
|
||||
}
|
||||
|
||||
private void resetState()
|
||||
{
|
||||
setFrom( null );
|
||||
setFromAlias( null );
|
||||
setTo( null);
|
||||
setToAlias( null );
|
||||
setMessage( null );
|
||||
setMessageType( null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Message message )
|
||||
{
|
||||
if( message instanceof MDCMessage )
|
||||
{
|
||||
MDCMessage mdc = (MDCMessage)message;
|
||||
|
||||
mIdents.add( mdc.getUnitID() );
|
||||
|
||||
if( mdc.isEmergency() )
|
||||
{
|
||||
mEmergencyIdents.add( mdc.getUnitID() );
|
||||
}
|
||||
|
||||
setFrom( mdc.getFromID() );
|
||||
setFromAlias( mdc.getFromIDAlias() );
|
||||
|
||||
MDCMessageType type = mdc.getMessageType();
|
||||
|
||||
setMessageType( type.getLabel() );
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "OPCODE " );
|
||||
sb.append( String.valueOf( mdc.getOpcode() ) );
|
||||
|
||||
if( mdc.isBOT() )
|
||||
{
|
||||
sb.append( " TYPE:BOT" );
|
||||
}
|
||||
|
||||
if( mdc.isEOT() )
|
||||
{
|
||||
sb.append( " TYPE:EOT" );
|
||||
}
|
||||
|
||||
setMessage( sb.toString() );
|
||||
|
||||
MDCCallEvent event = MDCCallEvent.getMDCCallEvent( mdc );
|
||||
event.setAliasList( getAliasList() );
|
||||
broadcast( event );
|
||||
|
||||
switch( type )
|
||||
{
|
||||
case ANI:
|
||||
broadcast( new DecoderStateEvent( this, Event.DECODE, State.CALL ) );
|
||||
break;
|
||||
case ACKNOWLEDGE:
|
||||
case EMERGENCY:
|
||||
case PAGING:
|
||||
case STATUS:
|
||||
default:
|
||||
broadcast( new DecoderStateEvent( this, Event.DECODE, State.DATA ) );
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public String getFrom()
|
||||
{
|
||||
return mFrom;
|
||||
}
|
||||
|
||||
public void setFrom( String from )
|
||||
{
|
||||
mFrom = from;
|
||||
broadcast( ChangedAttribute.FROM_TALKGROUP );
|
||||
}
|
||||
|
||||
public Alias getFromAlias()
|
||||
{
|
||||
return mFromAlias;
|
||||
}
|
||||
|
||||
public void setFromAlias( Alias alias )
|
||||
{
|
||||
mFromAlias = alias;
|
||||
broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS );
|
||||
}
|
||||
|
||||
public String getTo()
|
||||
{
|
||||
return mTo;
|
||||
}
|
||||
|
||||
public void setTo( String to )
|
||||
{
|
||||
mTo = to;
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||
}
|
||||
|
||||
public Alias getToAlias()
|
||||
{
|
||||
return mToAlias;
|
||||
}
|
||||
|
||||
public void setToAlias( Alias alias )
|
||||
{
|
||||
mToAlias = alias;
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
||||
}
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
public void setMessage( String message )
|
||||
{
|
||||
mMessage = message;
|
||||
broadcast( ChangedAttribute.MESSAGE );
|
||||
}
|
||||
|
||||
public String getMessageType()
|
||||
{
|
||||
return mMessageType;
|
||||
}
|
||||
|
||||
public void setMessageType( String type )
|
||||
{
|
||||
mMessageType = type;
|
||||
broadcast( ChangedAttribute.MESSAGE_TYPE );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActivitySummary()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "=============================\n" );
|
||||
sb.append( "Decoder:\tMDC-1200\n\n" );
|
||||
sb.append( "MDC-1200 Idents\n" );
|
||||
|
||||
if( mIdents.isEmpty() )
|
||||
{
|
||||
sb.append( " None\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Iterator<String> it = mIdents.iterator();
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
String ident = it.next();
|
||||
|
||||
sb.append( " " );
|
||||
sb.append( ident );
|
||||
sb.append( " " );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias alias = getAliasList().getMDC1200Alias( ident );
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
sb.append( alias.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "MDC-1200 Emergency Idents\n" );
|
||||
|
||||
if( mEmergencyIdents.isEmpty() )
|
||||
{
|
||||
sb.append( " None\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Iterator<String> it = mEmergencyIdents.iterator();
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
String ident = it.next();
|
||||
|
||||
sb.append( " " );
|
||||
sb.append( ident );
|
||||
sb.append( " " );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias alias = getAliasList().getMDC1200Alias( ident );
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
sb.append( alias.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
/* No initialization required */
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to reset events issued by the channel state
|
||||
*/
|
||||
@Override
|
||||
public void receiveDecoderStateEvent( DecoderStateEvent event )
|
||||
{
|
||||
switch( event.getEvent() )
|
||||
{
|
||||
case RESET:
|
||||
resetState();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
345
src/module/decode/mdc1200/MDCMessage.java
Normal file
345
src/module/decode/mdc1200/MDCMessage.java
Normal file
@@ -0,0 +1,345 @@
|
||||
/*******************************************************************************
|
||||
* 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.mdc1200;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import map.Plottable;
|
||||
import message.Message;
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
import bits.BinaryMessage;
|
||||
|
||||
public class MDCMessage extends Message
|
||||
{
|
||||
private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" );
|
||||
|
||||
private static int[] sSYNC1 = { 0,1,2,3,4,5,6,7,8,9,
|
||||
10,11,12,13,14,15,16,17,18,19,
|
||||
20,21,22,23,24,25,26,27,28,29,
|
||||
30,31,32,33,34,35,36,37,38,39 };
|
||||
|
||||
private static int[] sOPCODE = { 47,46,45,44,43,42,41,40 };
|
||||
private static int sANI_FLAG = 40;
|
||||
private static int sDIRECTION_FLAG = 45;
|
||||
private static int sACKNOWLEDGE_REQUIRED_FLAG = 46;
|
||||
private static int sPACKET_TYPE_FLAG = 47;
|
||||
private static int sEMERGENCY_FLAG = 48;
|
||||
private static int[] sARGUMENT = { 49,50,51,52,53,54 };
|
||||
private static int sBOT_EOT_FLAG = 55;
|
||||
private static int[] sDIGIT_2 = { 59,58,57,56 };
|
||||
private static int[] sDIGIT_1 = { 63,62,61,60 };
|
||||
private static int[] sDIGIT_4 = { 67,66,65,64 };
|
||||
private static int[] sDIGIT_3 = { 71,70,69,68 };
|
||||
|
||||
private BinaryMessage mMessage;
|
||||
private AliasList mAliasList;
|
||||
|
||||
public MDCMessage( BinaryMessage message, AliasList list )
|
||||
{
|
||||
mMessage = message;
|
||||
mAliasList = list;
|
||||
}
|
||||
|
||||
public boolean isValid()
|
||||
{
|
||||
//TODO: add CRC and/or convolution decoding/repair
|
||||
return true;
|
||||
}
|
||||
|
||||
public PacketType getPacketType()
|
||||
{
|
||||
if( mMessage.get( sPACKET_TYPE_FLAG ) )
|
||||
{
|
||||
return PacketType.DATA;
|
||||
}
|
||||
else
|
||||
{
|
||||
return PacketType.CMND;
|
||||
}
|
||||
}
|
||||
|
||||
public Acknowledge getResponse()
|
||||
{
|
||||
if( mMessage.get( sACKNOWLEDGE_REQUIRED_FLAG ) )
|
||||
{
|
||||
return Acknowledge.YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Acknowledge.NO;
|
||||
}
|
||||
}
|
||||
|
||||
public Direction getDirection()
|
||||
{
|
||||
if( mMessage.get( sDIRECTION_FLAG ) )
|
||||
{
|
||||
return Direction.OUT;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Direction.IN;
|
||||
}
|
||||
}
|
||||
|
||||
public int getOpcode()
|
||||
{
|
||||
return mMessage.getInt( sOPCODE );
|
||||
}
|
||||
|
||||
public int getArgument()
|
||||
{
|
||||
return mMessage.getInt( sARGUMENT );
|
||||
}
|
||||
|
||||
public boolean isEmergency()
|
||||
{
|
||||
return mMessage.get( sEMERGENCY_FLAG );
|
||||
}
|
||||
|
||||
|
||||
public boolean isANI()
|
||||
{
|
||||
return mMessage.get( sANI_FLAG );
|
||||
}
|
||||
|
||||
public boolean isBOT()
|
||||
{
|
||||
return mMessage.get( sBOT_EOT_FLAG );
|
||||
}
|
||||
|
||||
public boolean isEOT()
|
||||
{
|
||||
return !mMessage.get( sBOT_EOT_FLAG );
|
||||
}
|
||||
|
||||
public String getUnitID()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( Integer.toHexString( mMessage.getInt( sDIGIT_1 ) ).toUpperCase() );
|
||||
sb.append( Integer.toHexString( mMessage.getInt( sDIGIT_2 ) ).toUpperCase() );
|
||||
sb.append( Integer.toHexString( mMessage.getInt( sDIGIT_3 ) ).toUpperCase() );
|
||||
sb.append( Integer.toHexString( mMessage.getInt( sDIGIT_4 ) ).toUpperCase() );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public MDCMessageType getMessageType()
|
||||
{
|
||||
switch( getOpcode() )
|
||||
{
|
||||
case 0:
|
||||
if( isEmergency() )
|
||||
{
|
||||
return MDCMessageType.EMERGENCY;
|
||||
}
|
||||
case 1:
|
||||
return MDCMessageType.ANI;
|
||||
default:
|
||||
return MDCMessageType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( mSDF.format( new Date( System.currentTimeMillis() ) ) );
|
||||
|
||||
sb.append( getMessage() );
|
||||
|
||||
sb.append( " [" + mMessage.toString() + "]" );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads spaces onto the end of the value to make it 'places' long
|
||||
*/
|
||||
public String pad( String value, int places, String padCharacter )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( value );
|
||||
|
||||
while( sb.length() < places )
|
||||
{
|
||||
sb.append( padCharacter );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
@SuppressWarnings( "unused" )
|
||||
private 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBinaryMessage()
|
||||
{
|
||||
return mMessage.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol()
|
||||
{
|
||||
return "MDC-1200";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFromID()
|
||||
{
|
||||
return getUnitID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
return mAliasList.getMDC1200Alias( getFromID() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToID()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getToIDAlias()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "MDC1200 UNIT:" + getUnitID() );
|
||||
if( isEmergency() )
|
||||
{
|
||||
sb.append( " **EMERGENCY**" );
|
||||
}
|
||||
|
||||
if( isBOT() )
|
||||
{
|
||||
sb.append( " BOT" );
|
||||
}
|
||||
|
||||
if( isEOT() )
|
||||
{
|
||||
sb.append( " EOT" );
|
||||
}
|
||||
|
||||
sb.append( " OPCODE:" + format( getOpcode(), 2 ) );
|
||||
sb.append( " ARG:" + format( getArgument(), 3 ) );
|
||||
sb.append( " TYPE:" + getPacketType().toString() );
|
||||
sb.append( " ACK:" + getResponse().toString() );
|
||||
sb.append( " DIR:" + pad( getDirection().toString(), 3, " " ) );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorStatus()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
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;
|
||||
}
|
||||
|
||||
private enum PacketType { CMND, DATA };
|
||||
private enum Acknowledge { YES, NO };
|
||||
private enum Direction { IN, OUT };
|
||||
}
|
||||
56
src/module/decode/mdc1200/MDCMessageFilter.java
Normal file
56
src/module/decode/mdc1200/MDCMessageFilter.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package module.decode.mdc1200;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import message.Message;
|
||||
import filter.Filter;
|
||||
import filter.FilterElement;
|
||||
|
||||
public class MDCMessageFilter extends Filter<Message>
|
||||
{
|
||||
private HashMap<MDCMessageType, FilterElement<MDCMessageType>> mElements =
|
||||
new HashMap<MDCMessageType, FilterElement<MDCMessageType>>();
|
||||
|
||||
public MDCMessageFilter()
|
||||
{
|
||||
super( "MDC-1200 Message Filter" );
|
||||
|
||||
for( MDCMessageType type: MDCMessageType.values() )
|
||||
{
|
||||
if( type != MDCMessageType.UNKNOWN )
|
||||
{
|
||||
mElements.put( type, new FilterElement<MDCMessageType>( type ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes( Message message )
|
||||
{
|
||||
if( mEnabled && canProcess( message ) )
|
||||
{
|
||||
MDCMessage mdc = (MDCMessage)message;
|
||||
|
||||
if( mElements.containsKey( mdc.getMessageType() ) )
|
||||
{
|
||||
return mElements.get( mdc.getMessageType() ).isEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess( Message message )
|
||||
{
|
||||
return message instanceof MDCMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FilterElement<?>> getFilterElements()
|
||||
{
|
||||
return new ArrayList<FilterElement<?>>( mElements.values() );
|
||||
}
|
||||
}
|
||||
155
src/module/decode/mdc1200/MDCMessageProcessor.java
Normal file
155
src/module/decode/mdc1200/MDCMessageProcessor.java
Normal file
@@ -0,0 +1,155 @@
|
||||
/*******************************************************************************
|
||||
* 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.mdc1200;
|
||||
|
||||
import message.Message;
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import alias.AliasList;
|
||||
import bits.BinaryMessage;
|
||||
|
||||
public class MDCMessageProcessor implements Listener<BinaryMessage>
|
||||
{
|
||||
private static int sMESSAGE_LENGTH = 112;
|
||||
|
||||
private Broadcaster<Message> mBroadcaster = new Broadcaster<Message>();
|
||||
private AliasList mAliasList;
|
||||
|
||||
public MDCMessageProcessor( AliasList aliasList )
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mBroadcaster.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( BinaryMessage buffer )
|
||||
{
|
||||
/**
|
||||
* De-interleave the 112 bits of message one, starting after the sync
|
||||
* pattern
|
||||
*/
|
||||
deinterleave( buffer, 40 );
|
||||
|
||||
/**
|
||||
* For simplicity, we also deinterleave message 2, which may or may not
|
||||
* be there, starting after the initial sync ( 0 - 39 ) and the initial
|
||||
* message ( 40 - 151 ) and the second sync ( 152 - 191 ).
|
||||
*/
|
||||
deinterleave( buffer, 192 );
|
||||
|
||||
/**
|
||||
* Wrap the buffer in a message along with the designated alias list
|
||||
* and send it on its merry way
|
||||
*/
|
||||
MDCMessage message = new MDCMessage( buffer, mAliasList );
|
||||
|
||||
mBroadcaster.receive( message );
|
||||
}
|
||||
|
||||
public void addMessageListener( Listener<Message> listener )
|
||||
{
|
||||
mBroadcaster.addListener( listener );
|
||||
}
|
||||
|
||||
public void removeMessageListener( Listener<Message> listener )
|
||||
{
|
||||
mBroadcaster.removeListener( listener );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes NRZ-I encoding from the entire buffer. Assumes that the initial
|
||||
* bit will be a 0 after decode, despite what is currently in the buffer,
|
||||
* in order to maintain correct polarity of the decoded message.
|
||||
*/
|
||||
private void removeNRZIEncoding( BinaryMessage buffer )
|
||||
{
|
||||
/**
|
||||
* Clear bit position 0, the first bit of the sync pattern, to start
|
||||
* the decode with the correct polarity
|
||||
*/
|
||||
buffer.clear( 0 );
|
||||
|
||||
/**
|
||||
* Set each bit according to the state of the previous bit using XOR
|
||||
* as follows:
|
||||
*
|
||||
* 0 = same as previous state
|
||||
* 1 = inverse of previous state
|
||||
*/
|
||||
for( int x = 1; x < buffer.size(); x++ )
|
||||
{
|
||||
if( buffer.get( x - 1 ) ^ buffer.get( x ) )
|
||||
{
|
||||
buffer.set( x );
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.clear( x );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deinterleaves a 112-bit packet, starting at the offset into the buffer
|
||||
*/
|
||||
private void deinterleave( BinaryMessage buffer, int offset )
|
||||
{
|
||||
if( buffer.size() < sMESSAGE_LENGTH + offset )
|
||||
{
|
||||
throw new IllegalArgumentException( "MDCMessageProcessor - "
|
||||
+ "cannot deinterleave message - message buffer too short" );
|
||||
}
|
||||
|
||||
BinaryMessage deinterleaved = new BinaryMessage( 112 );
|
||||
|
||||
int deinterleavedPointer = 0;
|
||||
|
||||
for( int column = 0; column < 16; column++ )
|
||||
{
|
||||
for( int row = 0; row < 7; row++ )
|
||||
{
|
||||
if( buffer.get( ( row * 16 ) + column + offset ) )
|
||||
{
|
||||
deinterleaved.set( deinterleavedPointer );
|
||||
}
|
||||
|
||||
deinterleavedPointer++;
|
||||
}
|
||||
}
|
||||
|
||||
//Overlay the deinterleaved bits back onto the source message and
|
||||
//return it
|
||||
buffer.clear( offset, offset + sMESSAGE_LENGTH );
|
||||
|
||||
for( int x = 0; x < sMESSAGE_LENGTH; x++ )
|
||||
{
|
||||
if( deinterleaved.get( x ) )
|
||||
{
|
||||
buffer.set( x + offset );
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.clear( x + offset );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/module/decode/mdc1200/MDCMessageType.java
Normal file
40
src/module/decode/mdc1200/MDCMessageType.java
Normal file
@@ -0,0 +1,40 @@
|
||||
/*******************************************************************************
|
||||
* 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.mdc1200;
|
||||
|
||||
public enum MDCMessageType
|
||||
{
|
||||
ACKNOWLEDGE( "Acknowledge" ),
|
||||
ANI( "ANI" ),
|
||||
EMERGENCY( "Emergency" ),
|
||||
PAGING( "Paging" ),
|
||||
STATUS( "Status" ),
|
||||
UNKNOWN( "Unk" );
|
||||
|
||||
private String mLabel;
|
||||
|
||||
private MDCMessageType( String label )
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
182
src/module/decode/mdc1200/MDCPanel.java
Normal file
182
src/module/decode/mdc1200/MDCPanel.java
Normal file
@@ -0,0 +1,182 @@
|
||||
/*******************************************************************************
|
||||
* 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.mdc1200;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import alias.Alias;
|
||||
import module.decode.state.ChangedAttribute;
|
||||
import module.decode.state.DecoderPanel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import settings.ColorSetting;
|
||||
import settings.ColorSetting.ColorSettingName;
|
||||
import settings.Setting;
|
||||
import settings.SettingsManager;
|
||||
|
||||
public class MDCPanel extends DecoderPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String sPROTOCOL = "MDC-1200";
|
||||
|
||||
private JLabel mFromLabel = new JLabel( " " );
|
||||
private JLabel mFrom = new JLabel();
|
||||
private JLabel mFromAlias = new JLabel();
|
||||
|
||||
private JLabel mToLabel = new JLabel( " " );
|
||||
private JLabel mTo = new JLabel();
|
||||
private JLabel mToAlias = new JLabel();
|
||||
|
||||
private JLabel mProtocol = new JLabel( sPROTOCOL );
|
||||
private JLabel mMessage = new JLabel();
|
||||
private JLabel mMessageType = new JLabel();
|
||||
|
||||
public MDCPanel( SettingsManager settingsManager, MDCDecoder decoder )
|
||||
{
|
||||
super( settingsManager, decoder );
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
public MDCDecoderState getState()
|
||||
{
|
||||
return (MDCDecoderState)getDecoder().getDecoderState();
|
||||
}
|
||||
|
||||
protected void init()
|
||||
{
|
||||
/* Calling super init will get and broadcast color settings to properly
|
||||
* setup the jlabel colors */
|
||||
super.init();
|
||||
|
||||
setLayout( new MigLayout( "insets 1 0 0 0", "[grow,fill]", "[]0[]0[]") );
|
||||
|
||||
add( mFromLabel );
|
||||
add( mFrom );
|
||||
add( mFromAlias, "wrap" );
|
||||
|
||||
add( mToLabel );
|
||||
add( mTo );
|
||||
add( mToAlias, "wrap" );
|
||||
|
||||
add( mProtocol );
|
||||
add( mMessageType );
|
||||
add( mMessage, "wrap" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( final ChangedAttribute changedAttribute )
|
||||
{
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
switch( changedAttribute )
|
||||
{
|
||||
case FROM_TALKGROUP:
|
||||
String from = getState().getFrom();
|
||||
|
||||
if( from != null )
|
||||
{
|
||||
mFromLabel.setText( "FM:" );
|
||||
}
|
||||
else
|
||||
{
|
||||
mFromLabel.setText( " " );
|
||||
}
|
||||
mFrom.setText( from );
|
||||
break;
|
||||
case FROM_TALKGROUP_ALIAS:
|
||||
Alias fromAlias = getState().getFromAlias();
|
||||
|
||||
if( fromAlias != null )
|
||||
{
|
||||
mFromAlias.setText( fromAlias.getName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
mFromAlias.setText( null );
|
||||
}
|
||||
break;
|
||||
case TO_TALKGROUP:
|
||||
String to = getState().getTo();
|
||||
|
||||
if( to != null )
|
||||
{
|
||||
mToLabel.setText( "TO:" );
|
||||
}
|
||||
else
|
||||
{
|
||||
mToLabel.setText( " " );
|
||||
}
|
||||
mTo.setText( to );
|
||||
break;
|
||||
case TO_TALKGROUP_ALIAS:
|
||||
Alias toAlias = getState().getToAlias();
|
||||
|
||||
if( toAlias != null )
|
||||
{
|
||||
mToAlias.setText( toAlias.getName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
mToAlias.setText( null );
|
||||
}
|
||||
break;
|
||||
case MESSAGE:
|
||||
mMessage.setText( getState().getMessage() );
|
||||
break;
|
||||
case MESSAGE_TYPE:
|
||||
mMessageType.setText( getState().getMessageType() );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingChanged( Setting setting )
|
||||
{
|
||||
super.settingChanged( setting );
|
||||
|
||||
if( setting instanceof ColorSetting )
|
||||
{
|
||||
ColorSetting color = (ColorSetting)setting;
|
||||
|
||||
if( color.getColorSettingName() ==
|
||||
ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER )
|
||||
{
|
||||
mFromLabel.setForeground( color.getColor() );
|
||||
mFrom.setForeground( color.getColor() );
|
||||
mFromAlias.setForeground( color.getColor() );
|
||||
mToLabel.setForeground( color.getColor() );
|
||||
mTo.setForeground( color.getColor() );
|
||||
mToAlias.setForeground( color.getColor() );
|
||||
mProtocol.setForeground( color.getColor() );
|
||||
mMessageType.setForeground( color.getColor() );
|
||||
mMessage.setForeground( color.getColor() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
102
src/module/decode/mpt1327/DecodeConfigMPT1327.java
Normal file
102
src/module/decode/mpt1327/DecodeConfigMPT1327.java
Normal file
@@ -0,0 +1,102 @@
|
||||
/*******************************************************************************
|
||||
* 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.mpt1327;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
import module.decode.mpt1327.MPT1327Decoder.Sync;
|
||||
|
||||
public class DecodeConfigMPT1327 extends DecodeConfiguration
|
||||
{
|
||||
private String mChannelMapName;
|
||||
private Sync mSync = Sync.NORMAL;
|
||||
|
||||
private int mCallTimeout = DEFAULT_CALL_TIMEOUT_SECONDS;
|
||||
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
|
||||
|
||||
public DecodeConfigMPT1327()
|
||||
{
|
||||
super( DecoderType.MPT1327 );
|
||||
}
|
||||
|
||||
@XmlElement
|
||||
public String getChannelMapName()
|
||||
{
|
||||
return mChannelMapName;
|
||||
}
|
||||
|
||||
public void setChannelMapName( String name )
|
||||
{
|
||||
mChannelMapName = name;
|
||||
}
|
||||
|
||||
@XmlAttribute( name="sync" )
|
||||
public Sync getSync()
|
||||
{
|
||||
return mSync;
|
||||
}
|
||||
|
||||
public void setSync( Sync sync )
|
||||
{
|
||||
mSync = sync;
|
||||
}
|
||||
|
||||
@XmlAttribute( name="call_timeout" )
|
||||
public int getCallTimeout()
|
||||
{
|
||||
return mCallTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the call timeout value in seconds ( 10 - 600 );
|
||||
* @param timeout
|
||||
*/
|
||||
public void setCallTimeout( int timeout )
|
||||
{
|
||||
if( CALL_TIMEOUT_MINIMUM <= timeout && timeout <= CALL_TIMEOUT_MAXIMUM )
|
||||
{
|
||||
mCallTimeout = timeout;
|
||||
}
|
||||
else
|
||||
{
|
||||
mCallTimeout = DEFAULT_CALL_TIMEOUT_SECONDS;
|
||||
}
|
||||
}
|
||||
|
||||
@XmlAttribute( name="traffic_channel_pool_size" )
|
||||
public int getTrafficChannelPoolSize()
|
||||
{
|
||||
return mTrafficChannelPoolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the traffic channel pool size which is the maximum number of
|
||||
* simultaneous traffic channels that can be allocated.
|
||||
*
|
||||
* This limits the maximum calls so that busy systems won't cause more
|
||||
* traffic channels to be allocated than the decoder/software/host computer
|
||||
* can support.
|
||||
*/
|
||||
public void setTrafficChannelPoolSize( int size )
|
||||
{
|
||||
mTrafficChannelPoolSize = size;
|
||||
}
|
||||
}
|
||||
154
src/module/decode/mpt1327/MPT1327CallEvent.java
Normal file
154
src/module/decode/mpt1327/MPT1327CallEvent.java
Normal file
@@ -0,0 +1,154 @@
|
||||
/*******************************************************************************
|
||||
* 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.mpt1327;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.event.CallEvent;
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
|
||||
public class MPT1327CallEvent extends CallEvent
|
||||
{
|
||||
private String mChannel;
|
||||
private long mFrequency;
|
||||
|
||||
public MPT1327CallEvent( CallEventType callEventType,
|
||||
AliasList aliasList,
|
||||
String fromID,
|
||||
String toID,
|
||||
String channel,
|
||||
long frequency,
|
||||
String details )
|
||||
{
|
||||
super( DecoderType.MPT1327, callEventType,
|
||||
aliasList, fromID, toID, details );
|
||||
|
||||
mChannel = channel;
|
||||
mFrequency = frequency;
|
||||
}
|
||||
|
||||
private Alias getAlias( String mpt1327Talkgroup )
|
||||
{
|
||||
if( hasAliasList() )
|
||||
{
|
||||
return getAliasList().getMPT1327Alias( mpt1327Talkgroup );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
return getAlias( getFromID() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getToIDAlias()
|
||||
{
|
||||
return getAlias( getToID() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannel()
|
||||
{
|
||||
return mChannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrequency()
|
||||
{
|
||||
return mFrequency;
|
||||
}
|
||||
|
||||
public static class Builder
|
||||
{
|
||||
/* Required parameters */
|
||||
private CallEventType mCallEventType;
|
||||
|
||||
/* Optional parameters */
|
||||
private AliasList mAliasList;
|
||||
private String mFromID;
|
||||
private String mToID;
|
||||
private String mDetails;
|
||||
private String mChannel;
|
||||
private long mFrequency;
|
||||
|
||||
public Builder( CallEventType callEventType )
|
||||
{
|
||||
mCallEventType = callEventType;
|
||||
}
|
||||
|
||||
public Builder aliasList( AliasList aliasList )
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder from( String val )
|
||||
{
|
||||
mFromID = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder channel( String channel )
|
||||
{
|
||||
mChannel = channel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder details( String details )
|
||||
{
|
||||
mDetails = details;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder frequency( long frequency )
|
||||
{
|
||||
mFrequency = frequency;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder to( String toID )
|
||||
{
|
||||
mToID = toID;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MPT1327CallEvent build()
|
||||
{
|
||||
return new MPT1327CallEvent( this );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor for the builder
|
||||
*/
|
||||
private MPT1327CallEvent( Builder builder )
|
||||
{
|
||||
this( builder.mCallEventType,
|
||||
builder.mAliasList,
|
||||
builder.mFromID,
|
||||
builder.mToID,
|
||||
builder.mChannel,
|
||||
builder.mFrequency,
|
||||
builder.mDetails );
|
||||
}
|
||||
|
||||
}
|
||||
195
src/module/decode/mpt1327/MPT1327ConfigEditor.java
Normal file
195
src/module/decode/mpt1327/MPT1327ConfigEditor.java
Normal file
@@ -0,0 +1,195 @@
|
||||
/*******************************************************************************
|
||||
* 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.mpt1327;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import module.decode.DecodeEditor;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
import module.decode.mpt1327.MPT1327Decoder.Sync;
|
||||
import controller.channel.ChannelNode;
|
||||
import controller.channel.map.ChannelMap;
|
||||
|
||||
public class MPT1327ConfigEditor extends DecodeEditor
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JComboBox<ChannelMap> mComboChannelMaps;
|
||||
private JComboBox<Sync> mComboSync;
|
||||
private JLabel mCallTimeoutLabel;
|
||||
private JSlider mCallTimeout;
|
||||
private JLabel mTrafficChannelPoolSizeLabel;
|
||||
private JSlider mTrafficChannelPoolSize;
|
||||
private ChannelNode mChannelNode;
|
||||
|
||||
public MPT1327ConfigEditor( DecodeConfiguration config, ChannelNode channelNode )
|
||||
{
|
||||
super( config );
|
||||
|
||||
mChannelNode = channelNode;
|
||||
|
||||
initGUI();
|
||||
}
|
||||
|
||||
private DecodeConfigMPT1327 getMPTConfig()
|
||||
{
|
||||
return (DecodeConfigMPT1327)mConfig;
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
/**
|
||||
* ComboBox: Alias Lists
|
||||
*/
|
||||
ArrayList<ChannelMap> maps = mChannelNode.getModel().getResourceManager()
|
||||
.getPlaylistManager().getPlayist().getChannelMapList().getChannelMap();
|
||||
|
||||
ChannelMap[] mapArray = maps.toArray( new ChannelMap[ maps.size() ] );
|
||||
|
||||
mComboChannelMaps = new JComboBox<ChannelMap>();
|
||||
mComboChannelMaps.setModel(
|
||||
new DefaultComboBoxModel<ChannelMap>( mapArray ) );
|
||||
|
||||
add( new JLabel( "Channel Map:" ) );
|
||||
add( mComboChannelMaps, "wrap" );
|
||||
|
||||
mComboSync = new JComboBox<Sync>();
|
||||
mComboSync.setModel( new DefaultComboBoxModel<Sync>( Sync.values() ) );
|
||||
|
||||
add( new JLabel( "Sync:" ) );
|
||||
add( mComboSync, "wrap" );
|
||||
|
||||
mCallTimeout = new JSlider( JSlider.HORIZONTAL,
|
||||
DecodeConfiguration.CALL_TIMEOUT_MINIMUM,
|
||||
DecodeConfiguration.CALL_TIMEOUT_MAXIMUM,
|
||||
DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS );
|
||||
|
||||
mCallTimeout.setMajorTickSpacing( 90 );
|
||||
mCallTimeout.setMinorTickSpacing( 10 );
|
||||
mCallTimeout.setPaintTicks( true );
|
||||
|
||||
mCallTimeout.setLabelTable( mCallTimeout.createStandardLabels( 100, 100 ) );
|
||||
mCallTimeout.setPaintLabels( true );
|
||||
|
||||
mCallTimeoutLabel = new JLabel( "Call Timeout: " + mCallTimeout.getValue() + " " );
|
||||
|
||||
mCallTimeout.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent e )
|
||||
{
|
||||
mCallTimeoutLabel.setText( "Call Timeout: " + mCallTimeout.getValue() );
|
||||
}
|
||||
} );
|
||||
|
||||
add( mCallTimeoutLabel );
|
||||
add( mCallTimeout, "wrap,grow" );
|
||||
|
||||
mTrafficChannelPoolSize = new JSlider( JSlider.HORIZONTAL,
|
||||
DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_MINIMUM,
|
||||
DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_MAXIMUM,
|
||||
DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT );
|
||||
|
||||
mTrafficChannelPoolSize.setMajorTickSpacing( 10 );
|
||||
mTrafficChannelPoolSize.setMinorTickSpacing( 1 );
|
||||
mTrafficChannelPoolSize.setPaintTicks( true );
|
||||
|
||||
mTrafficChannelPoolSize.setLabelTable( mTrafficChannelPoolSize.createStandardLabels( 10, 10 ) );
|
||||
mTrafficChannelPoolSize.setPaintLabels( true );
|
||||
|
||||
mTrafficChannelPoolSizeLabel = new JLabel( "Traffic Channel Pool Size: " + mTrafficChannelPoolSize.getValue() + " " );
|
||||
|
||||
mTrafficChannelPoolSize.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent e )
|
||||
{
|
||||
mTrafficChannelPoolSizeLabel.setText( "Traffic Channel Pool Size: " + mTrafficChannelPoolSize.getValue() );
|
||||
}
|
||||
} );
|
||||
|
||||
add( mTrafficChannelPoolSizeLabel );
|
||||
add( mTrafficChannelPoolSize, "wrap,grow" );
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
DecodeConfigMPT1327 config = getMPTConfig();
|
||||
|
||||
ChannelMap selected = (ChannelMap)mComboChannelMaps.getSelectedItem();
|
||||
|
||||
if( selected != null )
|
||||
{
|
||||
config.setChannelMapName( selected.getName() );
|
||||
}
|
||||
|
||||
Sync selectedSync = (Sync)mComboSync.getSelectedItem();
|
||||
|
||||
if( selectedSync != null )
|
||||
{
|
||||
config.setSync( selectedSync );
|
||||
}
|
||||
|
||||
config.setCallTimeout( mCallTimeout.getValue() );
|
||||
|
||||
config.setTrafficChannelPoolSize( mTrafficChannelPoolSize.getValue() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
DecodeConfigMPT1327 config = (DecodeConfigMPT1327)mConfig;
|
||||
|
||||
String name = config.getChannelMapName();
|
||||
|
||||
ChannelMap selected = null;
|
||||
|
||||
if( name != null )
|
||||
{
|
||||
for( ChannelMap map: mChannelNode.getModel().getResourceManager()
|
||||
.getPlaylistManager().getPlayist().getChannelMapList()
|
||||
.getChannelMap() )
|
||||
{
|
||||
if( map.getName().equalsIgnoreCase( name ) )
|
||||
{
|
||||
selected = map;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mComboChannelMaps.setSelectedItem( selected );
|
||||
mComboChannelMaps.requestFocus();
|
||||
mComboChannelMaps.requestFocusInWindow();
|
||||
|
||||
mComboSync.setSelectedItem( config.getSync() );
|
||||
|
||||
mCallTimeout.setValue( config.getCallTimeout() );
|
||||
|
||||
mTrafficChannelPoolSize.setValue( config.getTrafficChannelPoolSize() );
|
||||
}
|
||||
}
|
||||
295
src/module/decode/mpt1327/MPT1327Decoder.java
Normal file
295
src/module/decode/mpt1327/MPT1327Decoder.java
Normal file
@@ -0,0 +1,295 @@
|
||||
/*******************************************************************************
|
||||
* 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.mpt1327;
|
||||
|
||||
import instrument.Instrumentable;
|
||||
import instrument.tap.Tap;
|
||||
import instrument.tap.stream.BinaryTap;
|
||||
import instrument.tap.stream.FloatBufferTap;
|
||||
import instrument.tap.stream.FloatTap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import module.decode.Decoder;
|
||||
import module.decode.DecoderType;
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import sample.real.IRealBufferListener;
|
||||
import sample.real.RealBuffer;
|
||||
import alias.AliasList;
|
||||
import bits.MessageFramer;
|
||||
import bits.SyncPattern;
|
||||
import controller.channel.Channel.ChannelType;
|
||||
import controller.channel.map.ChannelMap;
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.Filters;
|
||||
import dsp.filter.Window.WindowType;
|
||||
import dsp.filter.dc.DCRemovalFilterLyons;
|
||||
import dsp.filter.fir.real.RealFIRFilter_RB_RB;
|
||||
import dsp.filter.halfband.real.HalfBandFilter_RB_RB;
|
||||
import dsp.fsk.FSK2Decoder;
|
||||
import dsp.fsk.FSK2Decoder.Output;
|
||||
|
||||
/**
|
||||
* MPT1327 Decoder - 1200 baud 2FSK decoder that can process 48k sample rate
|
||||
* complex or floating point samples and output fully framed MPT1327 control and
|
||||
* traffic messages.
|
||||
*/
|
||||
public class MPT1327Decoder extends Decoder
|
||||
implements IRealBufferListener, Instrumentable
|
||||
{
|
||||
/* Decimated sample rate ( 48,000 / 2 = 24,000 ) feeding the decoder */
|
||||
private static final int sDECIMATED_SAMPLE_RATE = 24000;
|
||||
|
||||
/* Baud or Symbol Rate */
|
||||
private static final int sSYMBOL_RATE = 1200;
|
||||
|
||||
/* Message length -- longest possible message is:
|
||||
* 4xREVS + 16xSYNC + 64xADD1 + 64xDCW1 + 64xDCW2 + 64xDCW3 + 64xDCW4 */
|
||||
private static final int sMESSAGE_LENGTH = 350;
|
||||
|
||||
/* Instrumentation Taps */
|
||||
private static final String INSTRUMENT_HB1_FILTER_TO_LOW_PASS =
|
||||
"Tap Point: Half-band Decimation Filter > < Low Pass Filter";
|
||||
private static final String INSTRUMENT_LOW_PASS_TO_DECODER =
|
||||
"Tap Point: Low Pass Filter > < Decoder";
|
||||
private static final String INSTRUMENT_DECODER_TO_FRAMER =
|
||||
"Tap Point: Decoder > < Sync Detect/Message Framer";
|
||||
private List<Tap> mAvailableTaps;
|
||||
|
||||
private HalfBandFilter_RB_RB mDecimationFilter;
|
||||
private RealFIRFilter_RB_RB mLowPassFilter;
|
||||
private DCRemovalFilterLyons mDCRemovalFilter;
|
||||
private FSK2Decoder mFSKDecoder;
|
||||
private Broadcaster<Boolean> mSymbolBroadcaster;
|
||||
private MessageFramer mControlMessageFramer;
|
||||
private MessageFramer mTrafficMessageFramer;
|
||||
private MPT1327MessageProcessor mMessageProcessor;
|
||||
|
||||
public MPT1327Decoder( ChannelMap channelMap,
|
||||
ChannelType channelType,
|
||||
AliasList aliasList,
|
||||
Sync sync )
|
||||
{
|
||||
super( new MPT1327DecoderState( aliasList, channelMap, channelType ) );
|
||||
|
||||
/* Decimation filter - 48000 / 2 = 24000 output */
|
||||
mDecimationFilter = new HalfBandFilter_RB_RB(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0002f, true );
|
||||
|
||||
/* Low pass filter: 2kHz to pass the 1200 & 1800 Hz FSK */
|
||||
mLowPassFilter = new RealFIRFilter_RB_RB(
|
||||
FilterFactory.getLowPass( 24000, //sampleRate,
|
||||
2000, //passFrequency,
|
||||
4000, //stopFrequency,
|
||||
60, //attenuation,
|
||||
WindowType.HANNING, //windowType,
|
||||
true ) //forceOddLength
|
||||
, 2.0f ); //Gain
|
||||
mDecimationFilter.setListener( mLowPassFilter );
|
||||
|
||||
/**
|
||||
* Normal: 2FSK Decoder with inverted output
|
||||
* French: 2FSK Decoder with normal output
|
||||
*/
|
||||
if( sync == Sync.NORMAL )
|
||||
{
|
||||
mFSKDecoder = new FSK2Decoder( 24000, 1200, Output.INVERTED );
|
||||
}
|
||||
else if( sync == Sync.FRENCH )
|
||||
{
|
||||
mFSKDecoder = new FSK2Decoder( 24000, 1200, Output.NORMAL );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException( "MPT1327 Decoder - unrecognized Sync type" );
|
||||
}
|
||||
mLowPassFilter.setListener( mFSKDecoder );
|
||||
|
||||
mSymbolBroadcaster = new Broadcaster<Boolean>();
|
||||
mFSKDecoder.setListener( mSymbolBroadcaster );
|
||||
|
||||
/* Message framer for control channel messages */
|
||||
mControlMessageFramer =
|
||||
new MessageFramer( sync.getControlSyncPattern().getPattern(),
|
||||
sMESSAGE_LENGTH );
|
||||
mSymbolBroadcaster.addListener( mControlMessageFramer );
|
||||
|
||||
/* Message framer for traffic channel massages */
|
||||
mTrafficMessageFramer =
|
||||
new MessageFramer( sync.getTrafficSyncPattern().getPattern(),
|
||||
sMESSAGE_LENGTH );
|
||||
mSymbolBroadcaster.addListener( mTrafficMessageFramer );
|
||||
|
||||
/* Fully decoded and framed messages processor */
|
||||
mMessageProcessor = new MPT1327MessageProcessor( aliasList );
|
||||
mMessageProcessor.setMessageListener( mMessageBroadcaster );
|
||||
mControlMessageFramer.addMessageListener( mMessageProcessor );
|
||||
mTrafficMessageFramer.addMessageListener( mMessageProcessor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<RealBuffer> getRealBufferListener()
|
||||
{
|
||||
return mDecimationFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.MPT1327;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup method
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mSymbolBroadcaster.dispose();
|
||||
|
||||
mControlMessageFramer.dispose();
|
||||
|
||||
if( mDCRemovalFilter != null )
|
||||
{
|
||||
mDCRemovalFilter.dispose();
|
||||
}
|
||||
|
||||
mFSKDecoder.dispose();
|
||||
|
||||
mDecimationFilter.dispose();
|
||||
|
||||
mLowPassFilter.dispose();
|
||||
|
||||
mMessageProcessor.dispose();
|
||||
|
||||
mTrafficMessageFramer.dispose();
|
||||
}
|
||||
|
||||
/* Instrumentation Taps */
|
||||
@Override
|
||||
public List<Tap> getTaps()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<Tap>();
|
||||
|
||||
mAvailableTaps.add(
|
||||
new FloatTap( INSTRUMENT_HB1_FILTER_TO_LOW_PASS, 0, 0.5f ) );
|
||||
mAvailableTaps.add(
|
||||
new FloatTap( INSTRUMENT_LOW_PASS_TO_DECODER, 0, 0.5f ) );
|
||||
mAvailableTaps.add(
|
||||
new BinaryTap( INSTRUMENT_DECODER_TO_FRAMER, 0, 0.025f ) );
|
||||
|
||||
/* Add the taps from the FSK decoder */
|
||||
mAvailableTaps.addAll( mFSKDecoder.getTaps() );
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTap( Tap tap )
|
||||
{
|
||||
/* Send request to decoder */
|
||||
mFSKDecoder.addTap( tap );
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_HB1_FILTER_TO_LOW_PASS:
|
||||
FloatBufferTap hb1Tap = (FloatBufferTap)tap;
|
||||
mDecimationFilter.setListener( hb1Tap );
|
||||
hb1Tap.setListener( mLowPassFilter );
|
||||
break;
|
||||
case INSTRUMENT_LOW_PASS_TO_DECODER:
|
||||
FloatBufferTap lowTap = (FloatBufferTap)tap;
|
||||
mLowPassFilter.setListener( lowTap );
|
||||
lowTap.setListener( mFSKDecoder );
|
||||
break;
|
||||
case INSTRUMENT_DECODER_TO_FRAMER:
|
||||
BinaryTap decoderTap = (BinaryTap)tap;
|
||||
mFSKDecoder.setListener( decoderTap );
|
||||
decoderTap.setListener( mSymbolBroadcaster );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTap( Tap tap )
|
||||
{
|
||||
mFSKDecoder.removeTap( tap );
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_HB1_FILTER_TO_LOW_PASS:
|
||||
mDecimationFilter.setListener( mLowPassFilter );
|
||||
break;
|
||||
case INSTRUMENT_LOW_PASS_TO_DECODER:
|
||||
mLowPassFilter.setListener( mFSKDecoder );
|
||||
break;
|
||||
case INSTRUMENT_DECODER_TO_FRAMER:
|
||||
mFSKDecoder.setListener( mSymbolBroadcaster );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Sync
|
||||
{
|
||||
NORMAL( "Normal",
|
||||
SyncPattern.MPT1327_CONTROL,
|
||||
SyncPattern.MPT1327_TRAFFIC ),
|
||||
FRENCH( "French",
|
||||
SyncPattern.MPT1327_CONTROL_FRENCH,
|
||||
SyncPattern.MPT1327_TRAFFIC_FRENCH );
|
||||
|
||||
private String mLabel;
|
||||
private SyncPattern mControlSyncPattern;
|
||||
private SyncPattern mTrafficSyncPattern;
|
||||
|
||||
private Sync( String label,
|
||||
SyncPattern controlPattern,
|
||||
SyncPattern trafficPattern )
|
||||
{
|
||||
mLabel = label;
|
||||
mControlSyncPattern = controlPattern;
|
||||
mTrafficSyncPattern = trafficPattern;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public SyncPattern getControlSyncPattern()
|
||||
{
|
||||
return mControlSyncPattern;
|
||||
}
|
||||
|
||||
public SyncPattern getTrafficSyncPattern()
|
||||
{
|
||||
return mTrafficSyncPattern;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return getLabel();
|
||||
}
|
||||
}
|
||||
}
|
||||
222
src/module/decode/mpt1327/MPT1327DecoderPanel.java
Normal file
222
src/module/decode/mpt1327/MPT1327DecoderPanel.java
Normal file
@@ -0,0 +1,222 @@
|
||||
/*******************************************************************************
|
||||
* 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.mpt1327;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import module.decode.state.ChangedAttribute;
|
||||
import module.decode.state.DecoderPanel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import settings.ColorSetting;
|
||||
import settings.Setting;
|
||||
import settings.SettingsManager;
|
||||
import alias.Alias;
|
||||
import controller.channel.Channel.ChannelType;
|
||||
|
||||
public class MPT1327DecoderPanel extends DecoderPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JLabel mProtocol = new JLabel( "" );
|
||||
private JLabel mSiteOrFromTalkgroup = new JLabel( "" );
|
||||
private JLabel mSiteOrFromTalkgroupAlias = new JLabel( "" );
|
||||
|
||||
private JLabel mToTalkgroupLabel = new JLabel( "" );
|
||||
private JLabel mToTalkgroup = new JLabel( "" );
|
||||
private JLabel mToTalkgroupAliasLabel = new JLabel( "" );
|
||||
|
||||
public MPT1327DecoderPanel( SettingsManager settingsManager, MPT1327Decoder decoder )
|
||||
{
|
||||
super( settingsManager, decoder );
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private MPT1327DecoderState getDecoderState()
|
||||
{
|
||||
return (MPT1327DecoderState)getDecoder().getDecoderState();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
super.init();
|
||||
|
||||
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]0[]") );
|
||||
|
||||
mProtocol.setText( "MPT1327" );
|
||||
mProtocol.setFont( mFontDecoder );
|
||||
mProtocol.setForeground( mColorLabelDecoder );
|
||||
|
||||
MPT1327DecoderState mptState = getDecoderState();
|
||||
|
||||
mSiteOrFromTalkgroup.setFont( mFontDecoder );
|
||||
mSiteOrFromTalkgroup.setForeground( mColorLabelDecoder );
|
||||
|
||||
mSiteOrFromTalkgroupAlias.setFont( mFontDecoder );
|
||||
mSiteOrFromTalkgroupAlias.setForeground( mColorLabelDecoder );
|
||||
|
||||
mToTalkgroupLabel.setFont( mFontDetails );
|
||||
mToTalkgroupLabel.setForeground( mColorLabelDetails );
|
||||
|
||||
mToTalkgroup.setFont( mFontDecoder );
|
||||
mToTalkgroup.setForeground( mColorLabelDecoder );
|
||||
|
||||
mToTalkgroupAliasLabel.setFont( mFontDecoder );
|
||||
mToTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
|
||||
|
||||
|
||||
switch( mptState.getChannelType() )
|
||||
{
|
||||
case STANDARD:
|
||||
mSiteOrFromTalkgroup.setText( mptState.getSite() );
|
||||
setAliasLabel( mSiteOrFromTalkgroupAlias, mptState.getSiteAlias() );
|
||||
break;
|
||||
case TRAFFIC:
|
||||
mSiteOrFromTalkgroup.setText( mptState.getFromTalkgroup() );
|
||||
setAliasLabel( mSiteOrFromTalkgroupAlias, mptState.getFromTalkgroupAlias() );
|
||||
|
||||
mToTalkgroupLabel.setText( "To:" );
|
||||
mToTalkgroup.setText( getDecoderState().getToTalkgroup() );
|
||||
setAliasLabel( mToTalkgroupAliasLabel, mptState.getToTalkgroupAlias() );
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException( "Unrecognized channel "
|
||||
+ "type in MPT1327 Decoder [" + getDecoderState()
|
||||
.getChannelType().name() + "]" );
|
||||
}
|
||||
|
||||
add( mProtocol );
|
||||
add( mSiteOrFromTalkgroup );
|
||||
add( mSiteOrFromTalkgroupAlias, "wrap" );
|
||||
|
||||
if( mptState.getChannelType() == ChannelType.TRAFFIC )
|
||||
{
|
||||
add( mToTalkgroupLabel );
|
||||
add( mToTalkgroup );
|
||||
add( mToTalkgroupAliasLabel, "wrap" );
|
||||
}
|
||||
}
|
||||
|
||||
private void setAliasLabel( final JLabel label, final Alias alias )
|
||||
{
|
||||
if( alias != null )
|
||||
{
|
||||
label.setText( alias.getName() );
|
||||
|
||||
String iconName = alias.getIconName();
|
||||
|
||||
if( iconName != null )
|
||||
{
|
||||
label.setIcon( mSettingsManager.getImageIcon( iconName, 12 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
label.setIcon( null );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
label.setText( "" );
|
||||
label.setIcon( null );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( final ChangedAttribute changedAttribute )
|
||||
{
|
||||
final MPT1327DecoderState state = getDecoderState();
|
||||
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
switch( changedAttribute )
|
||||
{
|
||||
case CHANNEL_SITE_NUMBER:
|
||||
mSiteOrFromTalkgroup.setText( String.valueOf( state.getSite() ) );
|
||||
setAliasLabel( mSiteOrFromTalkgroupAlias, state.getSiteAlias() );
|
||||
repaint();
|
||||
break;
|
||||
case FROM_TALKGROUP:
|
||||
mSiteOrFromTalkgroup.setText( state.getFromTalkgroup() );
|
||||
setAliasLabel( mSiteOrFromTalkgroupAlias, state.getFromTalkgroupAlias() );
|
||||
repaint();
|
||||
break;
|
||||
case TO_TALKGROUP:
|
||||
mToTalkgroup.setText( state.getToTalkgroup() );
|
||||
setAliasLabel( mToTalkgroupAliasLabel, state.getToTalkgroupAlias() );
|
||||
repaint();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingChanged( Setting setting )
|
||||
{
|
||||
super.settingChanged( setting );
|
||||
|
||||
if( setting instanceof ColorSetting )
|
||||
{
|
||||
ColorSetting colorSetting = (ColorSetting)setting;
|
||||
|
||||
switch( colorSetting.getColorSettingName() )
|
||||
{
|
||||
case CHANNEL_STATE_LABEL_DECODER:
|
||||
if( mProtocol != null )
|
||||
{
|
||||
mProtocol.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
if( mSiteOrFromTalkgroup != null )
|
||||
{
|
||||
mSiteOrFromTalkgroup.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
if( mSiteOrFromTalkgroupAlias != null )
|
||||
{
|
||||
mSiteOrFromTalkgroupAlias.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
if( mToTalkgroup != null )
|
||||
{
|
||||
mToTalkgroup.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
if( mToTalkgroupAliasLabel != null )
|
||||
{
|
||||
mToTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
break;
|
||||
case CHANNEL_STATE_LABEL_DETAILS:
|
||||
if( mToTalkgroupLabel != null )
|
||||
{
|
||||
mToTalkgroupLabel.setForeground( mColorLabelDetails );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
572
src/module/decode/mpt1327/MPT1327DecoderState.java
Normal file
572
src/module/decode/mpt1327/MPT1327DecoderState.java
Normal file
@@ -0,0 +1,572 @@
|
||||
/*******************************************************************************
|
||||
* 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.mpt1327;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import message.Message;
|
||||
import module.decode.event.CallEvent;
|
||||
import module.decode.event.CallEvent.CallEventType;
|
||||
import module.decode.mpt1327.MPT1327Message.IdentType;
|
||||
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 module.decode.state.TrafficChannelAllocationEvent;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
import controller.channel.Channel.ChannelType;
|
||||
import controller.channel.map.ChannelMap;
|
||||
|
||||
public class MPT1327DecoderState extends DecoderState
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( MPT1327DecoderState.class );
|
||||
|
||||
private TreeSet<String> mIdents = new TreeSet<String>();
|
||||
private HashMap<String,ArrayList<String>> mGroups = new HashMap<String,ArrayList<String>>();
|
||||
|
||||
private String mSite;
|
||||
private String mFromTalkgroup;
|
||||
private String mToTalkgroup;
|
||||
|
||||
private int mChannelNumber;
|
||||
private ChannelType mChannelType;
|
||||
private ChannelMap mChannelMap;
|
||||
|
||||
private long mFrequency = 0;
|
||||
|
||||
public MPT1327DecoderState( AliasList aliasList, ChannelMap channelMap,
|
||||
ChannelType channelType )
|
||||
{
|
||||
super( aliasList );
|
||||
|
||||
mChannelMap = channelMap;
|
||||
|
||||
mChannelType = channelType;
|
||||
}
|
||||
|
||||
public ChannelType getChannelType()
|
||||
{
|
||||
return mChannelType;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Message message )
|
||||
{
|
||||
if( message.isValid() )
|
||||
{
|
||||
if( message instanceof MPT1327Message )
|
||||
{
|
||||
MPT1327Message mpt = ((MPT1327Message)message);
|
||||
|
||||
switch( mpt.getMessageType() )
|
||||
{
|
||||
case ACK:
|
||||
mIdents.add( mpt.getFromID() );
|
||||
|
||||
IdentType identType = mpt.getIdent1Type();
|
||||
|
||||
if( identType == IdentType.REGI )
|
||||
{
|
||||
broadcast( new MPT1327CallEvent.Builder( CallEventType.REGISTER )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( String.valueOf( mChannelNumber ) )
|
||||
.details( "REGISTERED ON NETWORK" )
|
||||
.frequency( mFrequency )
|
||||
.from( mpt.getToID() )
|
||||
.to( mpt.getFromID() )
|
||||
.build() );
|
||||
}
|
||||
else
|
||||
{
|
||||
broadcast( new MPT1327CallEvent.Builder( CallEventType.RESPONSE )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( String.valueOf( mChannelNumber ) )
|
||||
.details( "ACK " + identType.getLabel() )
|
||||
.frequency( mFrequency )
|
||||
.from( mpt.getFromID() )
|
||||
.to( mpt.getToID() )
|
||||
.build() );
|
||||
}
|
||||
|
||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
|
||||
break;
|
||||
case ACKI:
|
||||
mIdents.add( mpt.getFromID() );
|
||||
mIdents.add( mpt.getToID() );
|
||||
|
||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
|
||||
break;
|
||||
case AHYC:
|
||||
mIdents.add( mpt.getToID() );
|
||||
|
||||
broadcast( new MPT1327CallEvent.Builder( CallEventType.COMMAND )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( String.valueOf( mChannelNumber ) )
|
||||
.details( ( (MPT1327Message) message ).getRequestString() )
|
||||
.frequency( mFrequency )
|
||||
.from( mpt.getFromID() )
|
||||
.to( mpt.getToID() )
|
||||
.build() );
|
||||
|
||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
|
||||
break;
|
||||
case AHYQ:
|
||||
broadcast( new MPT1327CallEvent.Builder( CallEventType.STATUS )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( String.valueOf( mChannelNumber ) )
|
||||
.details( mpt.getStatusMessage() )
|
||||
.frequency( mFrequency )
|
||||
.from( mpt.getFromID() )
|
||||
.to( mpt.getToID() )
|
||||
.build() );
|
||||
|
||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
|
||||
break;
|
||||
case ALH:
|
||||
String site = mpt.getSiteID();
|
||||
|
||||
if( site != null && ( mSite == null || !site.contentEquals( mSite ) ) )
|
||||
{
|
||||
mSite = site;
|
||||
broadcast( ChangedAttribute.CHANNEL_SITE_NUMBER );
|
||||
}
|
||||
mSite = mpt.getSiteID();
|
||||
|
||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
|
||||
break;
|
||||
case GTC:
|
||||
String from = mpt.getFromID();
|
||||
String to = mpt.getToID();
|
||||
|
||||
if( from != null )
|
||||
{
|
||||
mIdents.add( from );
|
||||
}
|
||||
|
||||
if( to != null )
|
||||
{
|
||||
mIdents.add( to );
|
||||
}
|
||||
|
||||
/* Capture the idents that talk to each group */
|
||||
if( from != null && to != null )
|
||||
{
|
||||
if( mGroups.containsKey( to ) )
|
||||
{
|
||||
ArrayList<String> talkgroups = mGroups.get( to );
|
||||
|
||||
if( !talkgroups.contains( from ) )
|
||||
{
|
||||
talkgroups.add( from );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ArrayList<String> talkgroups = new ArrayList<String>();
|
||||
talkgroups.add( from );
|
||||
|
||||
mGroups.put( to, talkgroups );
|
||||
}
|
||||
}
|
||||
|
||||
int channel = mpt.getChannel();
|
||||
|
||||
long frequency = 0;
|
||||
|
||||
if( getChannelMap() != null )
|
||||
{
|
||||
frequency = getChannelMap().getFrequency( channel );
|
||||
}
|
||||
|
||||
CallEvent event = new MPT1327CallEvent.Builder( CallEventType.CALL )
|
||||
.aliasList( getAliasList() )
|
||||
.channel( String.valueOf( channel ) )
|
||||
.details( "GTC" )
|
||||
.frequency( frequency )
|
||||
.from( mpt.getFromID() )
|
||||
.to( mpt.getToID() )
|
||||
.build();
|
||||
|
||||
broadcast( new TrafficChannelAllocationEvent( this, event ) );
|
||||
break;
|
||||
case HEAD_PLUS1:
|
||||
case HEAD_PLUS2:
|
||||
case HEAD_PLUS3:
|
||||
case HEAD_PLUS4:
|
||||
broadcast( new MPT1327CallEvent.Builder( CallEventType.SDM )
|
||||
.aliasList( getAliasList() )
|
||||
.details( mpt.getMessage() )
|
||||
.from( mpt.getFromID() )
|
||||
.to( mpt.getToID() )
|
||||
.build() );
|
||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
|
||||
break;
|
||||
|
||||
/* Traffic Channel Events */
|
||||
case CLEAR:
|
||||
case MAINT:
|
||||
broadcast( new DecoderStateEvent( this, Event.END, State.END ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Intercept the fade event so that we can generate a call end event
|
||||
// */
|
||||
// @Override
|
||||
// public void fade( final CallEventType type )
|
||||
// {
|
||||
// /*
|
||||
// * We can receive multiple call tear-down messages -- only post a call
|
||||
// * end event for the one that can change the state to fade
|
||||
// */
|
||||
// if( getState().canChangeTo( State.FADE ) )
|
||||
// {
|
||||
// CallEvent current = getCurrentCallEvent();
|
||||
//
|
||||
// if( current != null )
|
||||
// {
|
||||
// mCallEventModel.setEnd( current );
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// mCallEventModel.add(
|
||||
// new MPT1327CallEvent.Builder( type )
|
||||
// .aliasList( getAliasList() )
|
||||
// .channel( String.valueOf( mChannelNumber ) )
|
||||
// .frequency( mChannelMap.getFrequency( mChannelNumber ) )
|
||||
// .from( mFromTalkgroup )
|
||||
// .to( mToTalkgroup )
|
||||
// .build() );
|
||||
// }
|
||||
//
|
||||
// setCurrentCall( null );
|
||||
// }
|
||||
//
|
||||
// super.fade( type );
|
||||
// }
|
||||
|
||||
public void reset()
|
||||
{
|
||||
mIdents.clear();
|
||||
|
||||
resetState();
|
||||
}
|
||||
|
||||
private void resetState()
|
||||
{
|
||||
mFromTalkgroup = null;
|
||||
broadcast( ChangedAttribute.FROM_TALKGROUP );
|
||||
|
||||
mToTalkgroup = null;
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||
}
|
||||
|
||||
public String getSite()
|
||||
{
|
||||
return mSite;
|
||||
}
|
||||
|
||||
public Alias getSiteAlias()
|
||||
{
|
||||
if( hasAliasList() )
|
||||
{
|
||||
return getAliasList().getSiteID( mSite );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ChannelMap getChannelMap()
|
||||
{
|
||||
return mChannelMap;
|
||||
}
|
||||
|
||||
public void setChannelMap( ChannelMap channelMap )
|
||||
{
|
||||
mChannelMap = channelMap;
|
||||
}
|
||||
|
||||
public String getFromTalkgroup()
|
||||
{
|
||||
return mFromTalkgroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the talkgroup. This is used primarily for traffic channels since
|
||||
* the talkgroup will already have been identified prior to the traffic
|
||||
* channel being created.
|
||||
*/
|
||||
public void setFromTalkgroup( String fromTalkgroup )
|
||||
{
|
||||
mFromTalkgroup = fromTalkgroup;
|
||||
|
||||
broadcast( ChangedAttribute.FROM_TALKGROUP );
|
||||
}
|
||||
|
||||
public Alias getFromTalkgroupAlias()
|
||||
{
|
||||
if( hasAliasList() )
|
||||
{
|
||||
return getAliasList().getTalkgroupAlias( mFromTalkgroup );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getToTalkgroup()
|
||||
{
|
||||
return mToTalkgroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the talkgroup. This is used primarily for traffic channels since
|
||||
* the talkgroup will already have been identified prior to the traffic
|
||||
* channel being created.
|
||||
*/
|
||||
public void setToTalkgroup( String toTalkgroup )
|
||||
{
|
||||
mToTalkgroup = toTalkgroup;
|
||||
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||
}
|
||||
|
||||
public Alias getToTalkgroupAlias()
|
||||
{
|
||||
if( hasAliasList() )
|
||||
{
|
||||
return getAliasList().getTalkgroupAlias( mToTalkgroup );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public int getChannelNumber()
|
||||
{
|
||||
return mChannelNumber;
|
||||
}
|
||||
/**
|
||||
* Set the channel number. This is used primarily for traffic channels since
|
||||
* the channel will already have been identified prior to the traffic
|
||||
* channel being created.
|
||||
*/
|
||||
public void setChannelNumber( int channel )
|
||||
{
|
||||
mChannelNumber = channel;
|
||||
|
||||
broadcast( ChangedAttribute.CHANNEL_NUMBER );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActivitySummary()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "Activity Summary\n" );
|
||||
sb.append( "Decoder:\tMPT-1327\n" );
|
||||
sb.append( "Site:\t" );
|
||||
|
||||
if( mSite != null )
|
||||
{
|
||||
sb.append( mSite );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias siteAlias = getAliasList().getSiteID( mSite );
|
||||
|
||||
if( siteAlias != null )
|
||||
{
|
||||
sb.append( " " );
|
||||
sb.append( siteAlias.getName() );
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( "Unknown\n" );
|
||||
}
|
||||
|
||||
sb.append( "\n\n" );
|
||||
|
||||
sb.append( "Talkgroups\n\n" );
|
||||
|
||||
if( mGroups.isEmpty() )
|
||||
{
|
||||
sb.append( " None\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
List<String> talkgroups = new ArrayList<String>( mGroups.keySet() );
|
||||
Collections.sort( talkgroups );
|
||||
|
||||
for( String talkgroup: talkgroups )
|
||||
{
|
||||
sb.append( " " );
|
||||
sb.append( talkgroup );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias alias = getAliasList().getMPT1327Alias( talkgroup );
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
sb.append( " " );
|
||||
sb.append( alias.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
|
||||
ArrayList<String> members = mGroups.get( talkgroup );
|
||||
Collections.sort( members );
|
||||
|
||||
for( String member: members )
|
||||
{
|
||||
sb.append( " >" );
|
||||
sb.append( member );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias alias = getAliasList().getMPT1327Alias( member );
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
sb.append( " " );
|
||||
sb.append( alias.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
|
||||
sb.append( "Individual Idents\n" );
|
||||
if( mIdents.isEmpty() )
|
||||
{
|
||||
sb.append( " None\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Iterator<String> it = mIdents.iterator();
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
String ident = it.next();
|
||||
|
||||
sb.append( " " );
|
||||
sb.append( ident );
|
||||
sb.append( " " );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias alias = getAliasList().getMPT1327Alias( ident );
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
sb.append( alias.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
/* No initialization required */
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveDecoderStateEvent( DecoderStateEvent event )
|
||||
{
|
||||
switch( event.getEvent() )
|
||||
{
|
||||
case RESET:
|
||||
resetState();
|
||||
break;
|
||||
case SOURCE_FREQUENCY:
|
||||
mFrequency = event.getFrequency();
|
||||
break;
|
||||
case TRAFFIC_CHANNEL_ALLOCATION:
|
||||
if( event.getSource() != MPT1327DecoderState.this )
|
||||
{
|
||||
if( event instanceof TrafficChannelAllocationEvent )
|
||||
{
|
||||
TrafficChannelAllocationEvent allocationEvent =
|
||||
(TrafficChannelAllocationEvent)event;
|
||||
|
||||
String channel = allocationEvent.getCallEvent().getChannel();
|
||||
|
||||
if( channel != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
mChannelNumber = Integer.valueOf( channel );
|
||||
broadcast( ChangedAttribute.CHANNEL_NUMBER );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
//Do nothing, we couldn't parse the channel number
|
||||
}
|
||||
}
|
||||
|
||||
mFrequency = allocationEvent.getCallEvent().getFrequency();
|
||||
broadcast( ChangedAttribute.SOURCE );
|
||||
|
||||
mFromTalkgroup = allocationEvent.getCallEvent().getFromID();
|
||||
broadcast( ChangedAttribute.FROM_TALKGROUP );
|
||||
|
||||
mToTalkgroup = allocationEvent.getCallEvent().getToID();
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
2108
src/module/decode/mpt1327/MPT1327Message.java
Normal file
2108
src/module/decode/mpt1327/MPT1327Message.java
Normal file
File diff suppressed because it is too large
Load Diff
62
src/module/decode/mpt1327/MPT1327MessageFilter.java
Normal file
62
src/module/decode/mpt1327/MPT1327MessageFilter.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package module.decode.mpt1327;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import message.Message;
|
||||
import module.decode.mpt1327.MPT1327Message.MPTMessageType;
|
||||
import filter.Filter;
|
||||
import filter.FilterElement;
|
||||
|
||||
public class MPT1327MessageFilter extends Filter<Message>
|
||||
{
|
||||
private HashMap<MPTMessageType,FilterElement<MPTMessageType>> mFilterElements =
|
||||
new HashMap<MPTMessageType,FilterElement<MPTMessageType>>();
|
||||
|
||||
public MPT1327MessageFilter()
|
||||
{
|
||||
super( "MPT1327 Message Type Filter" );
|
||||
|
||||
for( MPTMessageType type: MPT1327Message.MPTMessageType.values() )
|
||||
{
|
||||
if( type != MPTMessageType.UNKN )
|
||||
{
|
||||
mFilterElements.put( type,
|
||||
new FilterElement<MPTMessageType>( type ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes( Message message )
|
||||
{
|
||||
if( mEnabled && canProcess( message ) )
|
||||
{
|
||||
MPT1327Message mpt = (MPT1327Message)message;
|
||||
|
||||
FilterElement<MPTMessageType> element =
|
||||
mFilterElements.get( mpt.getMessageType() );
|
||||
|
||||
if( element != null )
|
||||
{
|
||||
return element.isEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess( Message message )
|
||||
{
|
||||
return message instanceof MPT1327Message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FilterElement<?>> getFilterElements()
|
||||
{
|
||||
return new ArrayList<FilterElement<?>>( mFilterElements.values() );
|
||||
}
|
||||
}
|
||||
|
||||
67
src/module/decode/mpt1327/MPT1327MessageProcessor.java
Normal file
67
src/module/decode/mpt1327/MPT1327MessageProcessor.java
Normal file
@@ -0,0 +1,67 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.mpt1327;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import message.Message;
|
||||
import module.decode.DecoderFactory;
|
||||
import sample.Listener;
|
||||
import alias.AliasList;
|
||||
import bits.BinaryMessage;
|
||||
|
||||
public class MPT1327MessageProcessor implements Listener<BinaryMessage>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( MPT1327MessageProcessor.class );
|
||||
|
||||
private Listener<Message> mMessageListener;
|
||||
|
||||
private AliasList mAliasList;
|
||||
|
||||
public MPT1327MessageProcessor( AliasList list )
|
||||
{
|
||||
mAliasList = list;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mMessageListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( BinaryMessage buffer )
|
||||
{
|
||||
if( mMessageListener != null )
|
||||
{
|
||||
MPT1327Message message = new MPT1327Message( buffer, mAliasList );
|
||||
|
||||
mMessageListener.receive( message );
|
||||
}
|
||||
}
|
||||
|
||||
public void setMessageListener( Listener<Message> listener )
|
||||
{
|
||||
mMessageListener = listener;
|
||||
}
|
||||
|
||||
public void removeMessageListener()
|
||||
{
|
||||
mMessageListener = null;
|
||||
}
|
||||
}
|
||||
177
src/module/decode/mpt1327/MPT1327TrafficPanel.java
Normal file
177
src/module/decode/mpt1327/MPT1327TrafficPanel.java
Normal file
@@ -0,0 +1,177 @@
|
||||
/*******************************************************************************
|
||||
* 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.mpt1327;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import module.decode.state.ChangedAttribute;
|
||||
import module.decode.state.DecoderPanel;
|
||||
import settings.ColorSetting;
|
||||
import settings.Setting;
|
||||
import settings.SettingsManager;
|
||||
import alias.Alias;
|
||||
|
||||
public class MPT1327TrafficPanel extends DecoderPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JLabel mProtocolLabel;
|
||||
private JLabel mFromTalkgroupLabel;
|
||||
private JLabel mFromTalkgroupAliasLabel;
|
||||
|
||||
private JLabel mToLabel;
|
||||
private JLabel mToTalkgroupLabel;
|
||||
private JLabel mToTalkgroupAliasLabel;
|
||||
|
||||
public MPT1327TrafficPanel( SettingsManager settingsManager, MPT1327Decoder decoder )
|
||||
{
|
||||
super( settingsManager, decoder );
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private MPT1327DecoderState getDecoderState()
|
||||
{
|
||||
return (MPT1327DecoderState)getDecoder().getDecoderState();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
mProtocolLabel = new JLabel( "MPT1327" );
|
||||
mProtocolLabel.setFont( mFontDecoder );
|
||||
mProtocolLabel.setForeground( mColorLabelDecoder );
|
||||
|
||||
mFromTalkgroupLabel = new JLabel();
|
||||
mFromTalkgroupLabel.setFont( mFontDecoder );
|
||||
mFromTalkgroupLabel.setForeground( mColorLabelDecoder );
|
||||
|
||||
mFromTalkgroupAliasLabel = new JLabel();
|
||||
mFromTalkgroupAliasLabel.setFont( mFontDecoder );
|
||||
mFromTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
|
||||
|
||||
mToLabel = new JLabel( "TO:" );
|
||||
mToLabel.setFont( mFontDetails );
|
||||
mToLabel.setForeground( mColorLabelDetails );
|
||||
|
||||
mToTalkgroupLabel = new JLabel( getDecoderState().getFromTalkgroup() );
|
||||
mToTalkgroupLabel.setFont( mFontDecoder );
|
||||
mToTalkgroupLabel.setForeground( mColorLabelDecoder );
|
||||
|
||||
mToTalkgroupAliasLabel = new JLabel();
|
||||
mToTalkgroupAliasLabel.setFont( mFontDecoder );
|
||||
mToTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
|
||||
|
||||
add( mProtocolLabel );
|
||||
add( mFromTalkgroupLabel );
|
||||
add( mFromTalkgroupAliasLabel, "wrap" );
|
||||
|
||||
add( mToLabel );
|
||||
add( mToTalkgroupLabel );
|
||||
add( mToTalkgroupAliasLabel, "wrap" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( final ChangedAttribute changedAttribute )
|
||||
{
|
||||
final MPT1327DecoderState state = getDecoderState();
|
||||
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
switch( changedAttribute )
|
||||
{
|
||||
case FROM_TALKGROUP:
|
||||
String talkgroup = state.getFromTalkgroup();
|
||||
|
||||
mFromTalkgroupLabel.setText( talkgroup );
|
||||
|
||||
Alias alias = state.getFromTalkgroupAlias();
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
mFromTalkgroupAliasLabel.setText( alias.getName() );
|
||||
|
||||
String icon = alias.getIconName();
|
||||
|
||||
mFromTalkgroupAliasLabel.setIcon( mSettingsManager
|
||||
.getImageIcon( icon, 12 ) );
|
||||
}
|
||||
break;
|
||||
case TO_TALKGROUP:
|
||||
String toTalkgroup = state.getToTalkgroup();
|
||||
|
||||
mToTalkgroupLabel.setText( toTalkgroup );
|
||||
|
||||
Alias toAlias = state.getToTalkgroupAlias();
|
||||
|
||||
if( toAlias != null )
|
||||
{
|
||||
mToTalkgroupAliasLabel.setText( toAlias.getName() );
|
||||
|
||||
String icon = toAlias.getIconName();
|
||||
|
||||
if( icon != null )
|
||||
{
|
||||
mToTalkgroupAliasLabel.setIcon( mSettingsManager
|
||||
.getImageIcon( icon, 12 ) );
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingChanged( Setting setting )
|
||||
{
|
||||
super.settingChanged( setting );
|
||||
|
||||
if( setting instanceof ColorSetting )
|
||||
{
|
||||
ColorSetting colorSetting = (ColorSetting)setting;
|
||||
|
||||
switch( colorSetting.getColorSettingName() )
|
||||
{
|
||||
case CHANNEL_STATE_LABEL_DECODER:
|
||||
mProtocolLabel.setForeground( mColorLabelDecoder );
|
||||
mFromTalkgroupLabel.setForeground( mColorLabelDecoder );
|
||||
mFromTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
|
||||
mToTalkgroupLabel.setForeground( mColorLabelDecoder );
|
||||
mToTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
|
||||
break;
|
||||
case CHANNEL_STATE_LABEL_DETAILS:
|
||||
mToLabel.setForeground( mColorLabelDetails );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/module/decode/nbfm/DecodeConfigNBFM.java
Normal file
29
src/module/decode/nbfm/DecodeConfigNBFM.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*******************************************************************************
|
||||
* 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.nbfm;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
|
||||
public class DecodeConfigNBFM extends DecodeConfiguration
|
||||
{
|
||||
public DecodeConfigNBFM()
|
||||
{
|
||||
super( DecoderType.NBFM );
|
||||
}
|
||||
}
|
||||
37
src/module/decode/nbfm/NBFMDecoder.java
Normal file
37
src/module/decode/nbfm/NBFMDecoder.java
Normal file
@@ -0,0 +1,37 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.nbfm;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.PrimaryDecoder;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
import module.decode.state.AlwaysUnsquelchedDecoderState;
|
||||
|
||||
public class NBFMDecoder extends PrimaryDecoder
|
||||
{
|
||||
public NBFMDecoder( DecodeConfiguration config )
|
||||
{
|
||||
super( config, new AlwaysUnsquelchedDecoderState( "NBFM" ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.NBFM;
|
||||
}
|
||||
}
|
||||
78
src/module/decode/nbfm/NBFMDecoderPanel.java
Normal file
78
src/module/decode/nbfm/NBFMDecoderPanel.java
Normal file
@@ -0,0 +1,78 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.nbfm;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import module.decode.Decoder;
|
||||
import module.decode.state.ChangedAttribute;
|
||||
import module.decode.state.DecoderPanel;
|
||||
import settings.ColorSetting;
|
||||
import settings.Setting;
|
||||
import settings.SettingsManager;
|
||||
import settings.ColorSetting.ColorSettingName;
|
||||
|
||||
public class NBFMDecoderPanel extends DecoderPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JLabel mProtocol = new JLabel( "NBFM" );
|
||||
|
||||
public NBFMDecoderPanel( SettingsManager settingsManager, Decoder decoder )
|
||||
{
|
||||
super( settingsManager, decoder );
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
protected void init()
|
||||
{
|
||||
super.init();
|
||||
|
||||
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]0[]") );
|
||||
|
||||
mProtocol.setFont( mFontDecoder );
|
||||
mProtocol.setForeground( mColorLabelDecoder );
|
||||
|
||||
add( mProtocol, "wrap" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( ChangedAttribute t )
|
||||
{
|
||||
/* Not implemented */
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingChanged( Setting setting )
|
||||
{
|
||||
super.settingChanged( setting );
|
||||
|
||||
if( setting instanceof ColorSetting )
|
||||
{
|
||||
ColorSetting color = (ColorSetting)setting;
|
||||
|
||||
if( color.getColorSettingName() ==
|
||||
ColorSettingName.CHANNEL_STATE_LABEL_DECODER )
|
||||
{
|
||||
mProtocol.setForeground( color.getColor() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/module/decode/nbfm/NBFMEditor.java
Normal file
35
src/module/decode/nbfm/NBFMEditor.java
Normal file
@@ -0,0 +1,35 @@
|
||||
/*******************************************************************************
|
||||
* 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.nbfm;
|
||||
|
||||
import javax.swing.JCheckBox;
|
||||
|
||||
import module.decode.DecodeEditor;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
|
||||
public class NBFMEditor extends DecodeEditor
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JCheckBox mAFC;
|
||||
|
||||
public NBFMEditor( DecodeConfiguration config )
|
||||
{
|
||||
super( config );
|
||||
}
|
||||
}
|
||||
15
src/module/decode/p25/C4FMFrameListener.java
Normal file
15
src/module/decode/p25/C4FMFrameListener.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package module.decode.p25;
|
||||
|
||||
import bits.BinaryMessage;
|
||||
|
||||
public interface C4FMFrameListener
|
||||
{
|
||||
/**
|
||||
* Listener interface to receive the output of the C4FMMessageFramer.
|
||||
*
|
||||
* @param buffer - framed message without the sync pattern
|
||||
* @param inverted - flag indicating if the message was received inverted
|
||||
*/
|
||||
public void receive( BinaryMessage buffer, boolean inverted );
|
||||
|
||||
}
|
||||
70
src/module/decode/p25/C4FMSlicer.java
Normal file
70
src/module/decode/p25/C4FMSlicer.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package module.decode.p25;
|
||||
|
||||
import dsp.symbol.Dibit;
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import sample.real.RealSampleListener;
|
||||
|
||||
/**
|
||||
* C4FM slicer to convert the output stream of the C4FMSymbolFilter into a
|
||||
* stream of C4FM symbols.
|
||||
*
|
||||
* Supports registering listener(s) to receive normal and/or inverted symbol
|
||||
* output streams.
|
||||
*/
|
||||
public class C4FMSlicer implements RealSampleListener
|
||||
{
|
||||
private static final float THRESHOLD = 2.0f;
|
||||
|
||||
private Broadcaster<Dibit> mBroadcaster = new Broadcaster<Dibit>();
|
||||
|
||||
/**
|
||||
* Primary method for receiving output from the C4FMSymbolFilter. Slices
|
||||
* (converts) the filtered sample value into a C4FMSymbol decision.
|
||||
*/
|
||||
@Override
|
||||
public void receive( float sample )
|
||||
{
|
||||
if( sample > THRESHOLD )
|
||||
{
|
||||
dispatch( Dibit.D01_PLUS_3 );
|
||||
}
|
||||
else if( sample > 0 )
|
||||
{
|
||||
dispatch( Dibit.D00_PLUS_1 );
|
||||
}
|
||||
else if( sample > -THRESHOLD )
|
||||
{
|
||||
dispatch( Dibit.D10_MINUS_1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatch( Dibit.D11_MINUS_3 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the symbol decision to any registered listeners
|
||||
*/
|
||||
private void dispatch( Dibit symbol )
|
||||
{
|
||||
mBroadcaster.receive( symbol );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive the normal (non-inverted) C4FM symbol
|
||||
* stream.
|
||||
*/
|
||||
public void addListener( Listener<Dibit> listener )
|
||||
{
|
||||
mBroadcaster.addListener( listener );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener
|
||||
*/
|
||||
public void removeListener( Listener<Dibit> listener )
|
||||
{
|
||||
mBroadcaster.removeListener( listener );
|
||||
}
|
||||
}
|
||||
431
src/module/decode/p25/C4FMSymbolFilter.java
Normal file
431
src/module/decode/p25/C4FMSymbolFilter.java
Normal file
@@ -0,0 +1,431 @@
|
||||
package module.decode.p25;
|
||||
|
||||
import instrument.Instrumentable;
|
||||
import instrument.tap.Tap;
|
||||
import instrument.tap.stream.AdditiveFloatTap;
|
||||
import instrument.tap.stream.FloatTap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.real.RealSampleListener;
|
||||
import sample.real.RealSampleProvider;
|
||||
import source.tuner.frequency.FrequencyCorrectionControl;
|
||||
import source.tuner.frequency.FrequencyCorrectionControl.FrequencyCorrectionResetListener;
|
||||
import dsp.gain.GainController;
|
||||
|
||||
public class C4FMSymbolFilter implements FrequencyCorrectionResetListener,
|
||||
RealSampleListener,
|
||||
RealSampleProvider,
|
||||
Instrumentable
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( C4FMSymbolFilter.class );
|
||||
|
||||
private static final float TAPS[][] =
|
||||
{
|
||||
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 0/128
|
||||
{ -1.54700e-04f, 8.53777e-04f, -2.76968e-03f, 7.89295e-03f, 9.98534e-01f, -5.41054e-03f, 1.24642e-03f, -1.98993e-04f }, // 1/128
|
||||
{ -3.09412e-04f, 1.70888e-03f, -5.55134e-03f, 1.58840e-02f, 9.96891e-01f, -1.07209e-02f, 2.47942e-03f, -3.96391e-04f }, // 2/128
|
||||
{ -4.64053e-04f, 2.56486e-03f, -8.34364e-03f, 2.39714e-02f, 9.95074e-01f, -1.59305e-02f, 3.69852e-03f, -5.92100e-04f }, // 3/128
|
||||
{ -6.18544e-04f, 3.42130e-03f, -1.11453e-02f, 3.21531e-02f, 9.93082e-01f, -2.10389e-02f, 4.90322e-03f, -7.86031e-04f }, // 4/128
|
||||
{ -7.72802e-04f, 4.27773e-03f, -1.39548e-02f, 4.04274e-02f, 9.90917e-01f, -2.60456e-02f, 6.09305e-03f, -9.78093e-04f }, // 5/128
|
||||
{ -9.26747e-04f, 5.13372e-03f, -1.67710e-02f, 4.87921e-02f, 9.88580e-01f, -3.09503e-02f, 7.26755e-03f, -1.16820e-03f }, // 6/128
|
||||
{ -1.08030e-03f, 5.98883e-03f, -1.95925e-02f, 5.72454e-02f, 9.86071e-01f, -3.57525e-02f, 8.42626e-03f, -1.35627e-03f }, // 7/128
|
||||
{ -1.23337e-03f, 6.84261e-03f, -2.24178e-02f, 6.57852e-02f, 9.83392e-01f, -4.04519e-02f, 9.56876e-03f, -1.54221e-03f }, // 8/128
|
||||
{ -1.38589e-03f, 7.69462e-03f, -2.52457e-02f, 7.44095e-02f, 9.80543e-01f, -4.50483e-02f, 1.06946e-02f, -1.72594e-03f }, // 9/128
|
||||
{ -1.53777e-03f, 8.54441e-03f, -2.80746e-02f, 8.31162e-02f, 9.77526e-01f, -4.95412e-02f, 1.18034e-02f, -1.90738e-03f }, // 10/128
|
||||
{ -1.68894e-03f, 9.39154e-03f, -3.09033e-02f, 9.19033e-02f, 9.74342e-01f, -5.39305e-02f, 1.28947e-02f, -2.08645e-03f }, // 11/128
|
||||
{ -1.83931e-03f, 1.02356e-02f, -3.37303e-02f, 1.00769e-01f, 9.70992e-01f, -5.82159e-02f, 1.39681e-02f, -2.26307e-03f }, // 12/128
|
||||
{ -1.98880e-03f, 1.10760e-02f, -3.65541e-02f, 1.09710e-01f, 9.67477e-01f, -6.23972e-02f, 1.50233e-02f, -2.43718e-03f }, // 13/128
|
||||
{ -2.13733e-03f, 1.19125e-02f, -3.93735e-02f, 1.18725e-01f, 9.63798e-01f, -6.64743e-02f, 1.60599e-02f, -2.60868e-03f }, // 14/128
|
||||
{ -2.28483e-03f, 1.27445e-02f, -4.21869e-02f, 1.27812e-01f, 9.59958e-01f, -7.04471e-02f, 1.70776e-02f, -2.77751e-03f }, // 15/128
|
||||
{ -2.43121e-03f, 1.35716e-02f, -4.49929e-02f, 1.36968e-01f, 9.55956e-01f, -7.43154e-02f, 1.80759e-02f, -2.94361e-03f }, // 16/128
|
||||
{ -2.57640e-03f, 1.43934e-02f, -4.77900e-02f, 1.46192e-01f, 9.51795e-01f, -7.80792e-02f, 1.90545e-02f, -3.10689e-03f }, // 17/128
|
||||
{ -2.72032e-03f, 1.52095e-02f, -5.05770e-02f, 1.55480e-01f, 9.47477e-01f, -8.17385e-02f, 2.00132e-02f, -3.26730e-03f }, // 18/128
|
||||
{ -2.86289e-03f, 1.60193e-02f, -5.33522e-02f, 1.64831e-01f, 9.43001e-01f, -8.52933e-02f, 2.09516e-02f, -3.42477e-03f }, // 19/128
|
||||
{ -3.00403e-03f, 1.68225e-02f, -5.61142e-02f, 1.74242e-01f, 9.38371e-01f, -8.87435e-02f, 2.18695e-02f, -3.57923e-03f }, // 20/128
|
||||
{ -3.14367e-03f, 1.76185e-02f, -5.88617e-02f, 1.83711e-01f, 9.33586e-01f, -9.20893e-02f, 2.27664e-02f, -3.73062e-03f }, // 21/128
|
||||
{ -3.28174e-03f, 1.84071e-02f, -6.15931e-02f, 1.93236e-01f, 9.28650e-01f, -9.53307e-02f, 2.36423e-02f, -3.87888e-03f }, // 22/128
|
||||
{ -3.41815e-03f, 1.91877e-02f, -6.43069e-02f, 2.02814e-01f, 9.23564e-01f, -9.84679e-02f, 2.44967e-02f, -4.02397e-03f }, // 23/128
|
||||
{ -3.55283e-03f, 1.99599e-02f, -6.70018e-02f, 2.12443e-01f, 9.18329e-01f, -1.01501e-01f, 2.53295e-02f, -4.16581e-03f }, // 24/128
|
||||
{ -3.68570e-03f, 2.07233e-02f, -6.96762e-02f, 2.22120e-01f, 9.12947e-01f, -1.04430e-01f, 2.61404e-02f, -4.30435e-03f }, // 25/128
|
||||
{ -3.81671e-03f, 2.14774e-02f, -7.23286e-02f, 2.31843e-01f, 9.07420e-01f, -1.07256e-01f, 2.69293e-02f, -4.43955e-03f }, // 26/128
|
||||
{ -3.94576e-03f, 2.22218e-02f, -7.49577e-02f, 2.41609e-01f, 9.01749e-01f, -1.09978e-01f, 2.76957e-02f, -4.57135e-03f }, // 27/128
|
||||
{ -4.07279e-03f, 2.29562e-02f, -7.75620e-02f, 2.51417e-01f, 8.95936e-01f, -1.12597e-01f, 2.84397e-02f, -4.69970e-03f }, // 28/128
|
||||
{ -4.19774e-03f, 2.36801e-02f, -8.01399e-02f, 2.61263e-01f, 8.89984e-01f, -1.15113e-01f, 2.91609e-02f, -4.82456e-03f }, // 29/128
|
||||
{ -4.32052e-03f, 2.43930e-02f, -8.26900e-02f, 2.71144e-01f, 8.83893e-01f, -1.17526e-01f, 2.98593e-02f, -4.94589e-03f }, // 30/128
|
||||
{ -4.44107e-03f, 2.50946e-02f, -8.52109e-02f, 2.81060e-01f, 8.77666e-01f, -1.19837e-01f, 3.05345e-02f, -5.06363e-03f }, // 31/128
|
||||
{ -4.55932e-03f, 2.57844e-02f, -8.77011e-02f, 2.91006e-01f, 8.71305e-01f, -1.22047e-01f, 3.11866e-02f, -5.17776e-03f }, // 32/128
|
||||
{ -4.67520e-03f, 2.64621e-02f, -9.01591e-02f, 3.00980e-01f, 8.64812e-01f, -1.24154e-01f, 3.18153e-02f, -5.28823e-03f }, // 33/128
|
||||
{ -4.78866e-03f, 2.71272e-02f, -9.25834e-02f, 3.10980e-01f, 8.58189e-01f, -1.26161e-01f, 3.24205e-02f, -5.39500e-03f }, // 34/128
|
||||
{ -4.89961e-03f, 2.77794e-02f, -9.49727e-02f, 3.21004e-01f, 8.51437e-01f, -1.28068e-01f, 3.30021e-02f, -5.49804e-03f }, // 35/128
|
||||
{ -5.00800e-03f, 2.84182e-02f, -9.73254e-02f, 3.31048e-01f, 8.44559e-01f, -1.29874e-01f, 3.35600e-02f, -5.59731e-03f }, // 36/128
|
||||
{ -5.11376e-03f, 2.90433e-02f, -9.96402e-02f, 3.41109e-01f, 8.37557e-01f, -1.31581e-01f, 3.40940e-02f, -5.69280e-03f }, // 37/128
|
||||
{ -5.21683e-03f, 2.96543e-02f, -1.01915e-01f, 3.51186e-01f, 8.30432e-01f, -1.33189e-01f, 3.46042e-02f, -5.78446e-03f }, // 38/128
|
||||
{ -5.31716e-03f, 3.02507e-02f, -1.04150e-01f, 3.61276e-01f, 8.23188e-01f, -1.34699e-01f, 3.50903e-02f, -5.87227e-03f }, // 39/128
|
||||
{ -5.41467e-03f, 3.08323e-02f, -1.06342e-01f, 3.71376e-01f, 8.15826e-01f, -1.36111e-01f, 3.55525e-02f, -5.95620e-03f }, // 40/128
|
||||
{ -5.50931e-03f, 3.13987e-02f, -1.08490e-01f, 3.81484e-01f, 8.08348e-01f, -1.37426e-01f, 3.59905e-02f, -6.03624e-03f }, // 41/128
|
||||
{ -5.60103e-03f, 3.19495e-02f, -1.10593e-01f, 3.91596e-01f, 8.00757e-01f, -1.38644e-01f, 3.64044e-02f, -6.11236e-03f }, // 42/128
|
||||
{ -5.68976e-03f, 3.24843e-02f, -1.12650e-01f, 4.01710e-01f, 7.93055e-01f, -1.39767e-01f, 3.67941e-02f, -6.18454e-03f }, // 43/128
|
||||
{ -5.77544e-03f, 3.30027e-02f, -1.14659e-01f, 4.11823e-01f, 7.85244e-01f, -1.40794e-01f, 3.71596e-02f, -6.25277e-03f }, // 44/128
|
||||
{ -5.85804e-03f, 3.35046e-02f, -1.16618e-01f, 4.21934e-01f, 7.77327e-01f, -1.41727e-01f, 3.75010e-02f, -6.31703e-03f }, // 45/128
|
||||
{ -5.93749e-03f, 3.39894e-02f, -1.18526e-01f, 4.32038e-01f, 7.69305e-01f, -1.42566e-01f, 3.78182e-02f, -6.37730e-03f }, // 46/128
|
||||
{ -6.01374e-03f, 3.44568e-02f, -1.20382e-01f, 4.42134e-01f, 7.61181e-01f, -1.43313e-01f, 3.81111e-02f, -6.43358e-03f }, // 47/128
|
||||
{ -6.08674e-03f, 3.49066e-02f, -1.22185e-01f, 4.52218e-01f, 7.52958e-01f, -1.43968e-01f, 3.83800e-02f, -6.48585e-03f }, // 48/128
|
||||
{ -6.15644e-03f, 3.53384e-02f, -1.23933e-01f, 4.62289e-01f, 7.44637e-01f, -1.44531e-01f, 3.86247e-02f, -6.53412e-03f }, // 49/128
|
||||
{ -6.22280e-03f, 3.57519e-02f, -1.25624e-01f, 4.72342e-01f, 7.36222e-01f, -1.45004e-01f, 3.88454e-02f, -6.57836e-03f }, // 50/128
|
||||
{ -6.28577e-03f, 3.61468e-02f, -1.27258e-01f, 4.82377e-01f, 7.27714e-01f, -1.45387e-01f, 3.90420e-02f, -6.61859e-03f }, // 51/128
|
||||
{ -6.34530e-03f, 3.65227e-02f, -1.28832e-01f, 4.92389e-01f, 7.19116e-01f, -1.45682e-01f, 3.92147e-02f, -6.65479e-03f }, // 52/128
|
||||
{ -6.40135e-03f, 3.68795e-02f, -1.30347e-01f, 5.02377e-01f, 7.10431e-01f, -1.45889e-01f, 3.93636e-02f, -6.68698e-03f }, // 53/128
|
||||
{ -6.45388e-03f, 3.72167e-02f, -1.31800e-01f, 5.12337e-01f, 7.01661e-01f, -1.46009e-01f, 3.94886e-02f, -6.71514e-03f }, // 54/128
|
||||
{ -6.50285e-03f, 3.75341e-02f, -1.33190e-01f, 5.22267e-01f, 6.92808e-01f, -1.46043e-01f, 3.95900e-02f, -6.73929e-03f }, // 55/128
|
||||
{ -6.54823e-03f, 3.78315e-02f, -1.34515e-01f, 5.32164e-01f, 6.83875e-01f, -1.45993e-01f, 3.96678e-02f, -6.75943e-03f }, // 56/128
|
||||
{ -6.58996e-03f, 3.81085e-02f, -1.35775e-01f, 5.42025e-01f, 6.74865e-01f, -1.45859e-01f, 3.97222e-02f, -6.77557e-03f }, // 57/128
|
||||
{ -6.62802e-03f, 3.83650e-02f, -1.36969e-01f, 5.51849e-01f, 6.65779e-01f, -1.45641e-01f, 3.97532e-02f, -6.78771e-03f }, // 58/128
|
||||
{ -6.66238e-03f, 3.86006e-02f, -1.38094e-01f, 5.61631e-01f, 6.56621e-01f, -1.45343e-01f, 3.97610e-02f, -6.79588e-03f }, // 59/128
|
||||
{ -6.69300e-03f, 3.88151e-02f, -1.39150e-01f, 5.71370e-01f, 6.47394e-01f, -1.44963e-01f, 3.97458e-02f, -6.80007e-03f }, // 60/128
|
||||
{ -6.71985e-03f, 3.90083e-02f, -1.40136e-01f, 5.81063e-01f, 6.38099e-01f, -1.44503e-01f, 3.97077e-02f, -6.80032e-03f }, // 61/128
|
||||
{ -6.74291e-03f, 3.91800e-02f, -1.41050e-01f, 5.90706e-01f, 6.28739e-01f, -1.43965e-01f, 3.96469e-02f, -6.79662e-03f }, // 62/128
|
||||
{ -6.76214e-03f, 3.93299e-02f, -1.41891e-01f, 6.00298e-01f, 6.19318e-01f, -1.43350e-01f, 3.95635e-02f, -6.78902e-03f }, // 63/128
|
||||
{ -6.77751e-03f, 3.94578e-02f, -1.42658e-01f, 6.09836e-01f, 6.09836e-01f, -1.42658e-01f, 3.94578e-02f, -6.77751e-03f }, // 64/128
|
||||
{ -6.78902e-03f, 3.95635e-02f, -1.43350e-01f, 6.19318e-01f, 6.00298e-01f, -1.41891e-01f, 3.93299e-02f, -6.76214e-03f }, // 65/128
|
||||
{ -6.79662e-03f, 3.96469e-02f, -1.43965e-01f, 6.28739e-01f, 5.90706e-01f, -1.41050e-01f, 3.91800e-02f, -6.74291e-03f }, // 66/128
|
||||
{ -6.80032e-03f, 3.97077e-02f, -1.44503e-01f, 6.38099e-01f, 5.81063e-01f, -1.40136e-01f, 3.90083e-02f, -6.71985e-03f }, // 67/128
|
||||
{ -6.80007e-03f, 3.97458e-02f, -1.44963e-01f, 6.47394e-01f, 5.71370e-01f, -1.39150e-01f, 3.88151e-02f, -6.69300e-03f }, // 68/128
|
||||
{ -6.79588e-03f, 3.97610e-02f, -1.45343e-01f, 6.56621e-01f, 5.61631e-01f, -1.38094e-01f, 3.86006e-02f, -6.66238e-03f }, // 69/128
|
||||
{ -6.78771e-03f, 3.97532e-02f, -1.45641e-01f, 6.65779e-01f, 5.51849e-01f, -1.36969e-01f, 3.83650e-02f, -6.62802e-03f }, // 70/128
|
||||
{ -6.77557e-03f, 3.97222e-02f, -1.45859e-01f, 6.74865e-01f, 5.42025e-01f, -1.35775e-01f, 3.81085e-02f, -6.58996e-03f }, // 71/128
|
||||
{ -6.75943e-03f, 3.96678e-02f, -1.45993e-01f, 6.83875e-01f, 5.32164e-01f, -1.34515e-01f, 3.78315e-02f, -6.54823e-03f }, // 72/128
|
||||
{ -6.73929e-03f, 3.95900e-02f, -1.46043e-01f, 6.92808e-01f, 5.22267e-01f, -1.33190e-01f, 3.75341e-02f, -6.50285e-03f }, // 73/128
|
||||
{ -6.71514e-03f, 3.94886e-02f, -1.46009e-01f, 7.01661e-01f, 5.12337e-01f, -1.31800e-01f, 3.72167e-02f, -6.45388e-03f }, // 74/128
|
||||
{ -6.68698e-03f, 3.93636e-02f, -1.45889e-01f, 7.10431e-01f, 5.02377e-01f, -1.30347e-01f, 3.68795e-02f, -6.40135e-03f }, // 75/128
|
||||
{ -6.65479e-03f, 3.92147e-02f, -1.45682e-01f, 7.19116e-01f, 4.92389e-01f, -1.28832e-01f, 3.65227e-02f, -6.34530e-03f }, // 76/128
|
||||
{ -6.61859e-03f, 3.90420e-02f, -1.45387e-01f, 7.27714e-01f, 4.82377e-01f, -1.27258e-01f, 3.61468e-02f, -6.28577e-03f }, // 77/128
|
||||
{ -6.57836e-03f, 3.88454e-02f, -1.45004e-01f, 7.36222e-01f, 4.72342e-01f, -1.25624e-01f, 3.57519e-02f, -6.22280e-03f }, // 78/128
|
||||
{ -6.53412e-03f, 3.86247e-02f, -1.44531e-01f, 7.44637e-01f, 4.62289e-01f, -1.23933e-01f, 3.53384e-02f, -6.15644e-03f }, // 79/128
|
||||
{ -6.48585e-03f, 3.83800e-02f, -1.43968e-01f, 7.52958e-01f, 4.52218e-01f, -1.22185e-01f, 3.49066e-02f, -6.08674e-03f }, // 80/128
|
||||
{ -6.43358e-03f, 3.81111e-02f, -1.43313e-01f, 7.61181e-01f, 4.42134e-01f, -1.20382e-01f, 3.44568e-02f, -6.01374e-03f }, // 81/128
|
||||
{ -6.37730e-03f, 3.78182e-02f, -1.42566e-01f, 7.69305e-01f, 4.32038e-01f, -1.18526e-01f, 3.39894e-02f, -5.93749e-03f }, // 82/128
|
||||
{ -6.31703e-03f, 3.75010e-02f, -1.41727e-01f, 7.77327e-01f, 4.21934e-01f, -1.16618e-01f, 3.35046e-02f, -5.85804e-03f }, // 83/128
|
||||
{ -6.25277e-03f, 3.71596e-02f, -1.40794e-01f, 7.85244e-01f, 4.11823e-01f, -1.14659e-01f, 3.30027e-02f, -5.77544e-03f }, // 84/128
|
||||
{ -6.18454e-03f, 3.67941e-02f, -1.39767e-01f, 7.93055e-01f, 4.01710e-01f, -1.12650e-01f, 3.24843e-02f, -5.68976e-03f }, // 85/128
|
||||
{ -6.11236e-03f, 3.64044e-02f, -1.38644e-01f, 8.00757e-01f, 3.91596e-01f, -1.10593e-01f, 3.19495e-02f, -5.60103e-03f }, // 86/128
|
||||
{ -6.03624e-03f, 3.59905e-02f, -1.37426e-01f, 8.08348e-01f, 3.81484e-01f, -1.08490e-01f, 3.13987e-02f, -5.50931e-03f }, // 87/128
|
||||
{ -5.95620e-03f, 3.55525e-02f, -1.36111e-01f, 8.15826e-01f, 3.71376e-01f, -1.06342e-01f, 3.08323e-02f, -5.41467e-03f }, // 88/128
|
||||
{ -5.87227e-03f, 3.50903e-02f, -1.34699e-01f, 8.23188e-01f, 3.61276e-01f, -1.04150e-01f, 3.02507e-02f, -5.31716e-03f }, // 89/128
|
||||
{ -5.78446e-03f, 3.46042e-02f, -1.33189e-01f, 8.30432e-01f, 3.51186e-01f, -1.01915e-01f, 2.96543e-02f, -5.21683e-03f }, // 90/128
|
||||
{ -5.69280e-03f, 3.40940e-02f, -1.31581e-01f, 8.37557e-01f, 3.41109e-01f, -9.96402e-02f, 2.90433e-02f, -5.11376e-03f }, // 91/128
|
||||
{ -5.59731e-03f, 3.35600e-02f, -1.29874e-01f, 8.44559e-01f, 3.31048e-01f, -9.73254e-02f, 2.84182e-02f, -5.00800e-03f }, // 92/128
|
||||
{ -5.49804e-03f, 3.30021e-02f, -1.28068e-01f, 8.51437e-01f, 3.21004e-01f, -9.49727e-02f, 2.77794e-02f, -4.89961e-03f }, // 93/128
|
||||
{ -5.39500e-03f, 3.24205e-02f, -1.26161e-01f, 8.58189e-01f, 3.10980e-01f, -9.25834e-02f, 2.71272e-02f, -4.78866e-03f }, // 94/128
|
||||
{ -5.28823e-03f, 3.18153e-02f, -1.24154e-01f, 8.64812e-01f, 3.00980e-01f, -9.01591e-02f, 2.64621e-02f, -4.67520e-03f }, // 95/128
|
||||
{ -5.17776e-03f, 3.11866e-02f, -1.22047e-01f, 8.71305e-01f, 2.91006e-01f, -8.77011e-02f, 2.57844e-02f, -4.55932e-03f }, // 96/128
|
||||
{ -5.06363e-03f, 3.05345e-02f, -1.19837e-01f, 8.77666e-01f, 2.81060e-01f, -8.52109e-02f, 2.50946e-02f, -4.44107e-03f }, // 97/128
|
||||
{ -4.94589e-03f, 2.98593e-02f, -1.17526e-01f, 8.83893e-01f, 2.71144e-01f, -8.26900e-02f, 2.43930e-02f, -4.32052e-03f }, // 98/128
|
||||
{ -4.82456e-03f, 2.91609e-02f, -1.15113e-01f, 8.89984e-01f, 2.61263e-01f, -8.01399e-02f, 2.36801e-02f, -4.19774e-03f }, // 99/128
|
||||
{ -4.69970e-03f, 2.84397e-02f, -1.12597e-01f, 8.95936e-01f, 2.51417e-01f, -7.75620e-02f, 2.29562e-02f, -4.07279e-03f }, // 100/128
|
||||
{ -4.57135e-03f, 2.76957e-02f, -1.09978e-01f, 9.01749e-01f, 2.41609e-01f, -7.49577e-02f, 2.22218e-02f, -3.94576e-03f }, // 101/128
|
||||
{ -4.43955e-03f, 2.69293e-02f, -1.07256e-01f, 9.07420e-01f, 2.31843e-01f, -7.23286e-02f, 2.14774e-02f, -3.81671e-03f }, // 102/128
|
||||
{ -4.30435e-03f, 2.61404e-02f, -1.04430e-01f, 9.12947e-01f, 2.22120e-01f, -6.96762e-02f, 2.07233e-02f, -3.68570e-03f }, // 103/128
|
||||
{ -4.16581e-03f, 2.53295e-02f, -1.01501e-01f, 9.18329e-01f, 2.12443e-01f, -6.70018e-02f, 1.99599e-02f, -3.55283e-03f }, // 104/128
|
||||
{ -4.02397e-03f, 2.44967e-02f, -9.84679e-02f, 9.23564e-01f, 2.02814e-01f, -6.43069e-02f, 1.91877e-02f, -3.41815e-03f }, // 105/128
|
||||
{ -3.87888e-03f, 2.36423e-02f, -9.53307e-02f, 9.28650e-01f, 1.93236e-01f, -6.15931e-02f, 1.84071e-02f, -3.28174e-03f }, // 106/128
|
||||
{ -3.73062e-03f, 2.27664e-02f, -9.20893e-02f, 9.33586e-01f, 1.83711e-01f, -5.88617e-02f, 1.76185e-02f, -3.14367e-03f }, // 107/128
|
||||
{ -3.57923e-03f, 2.18695e-02f, -8.87435e-02f, 9.38371e-01f, 1.74242e-01f, -5.61142e-02f, 1.68225e-02f, -3.00403e-03f }, // 108/128
|
||||
{ -3.42477e-03f, 2.09516e-02f, -8.52933e-02f, 9.43001e-01f, 1.64831e-01f, -5.33522e-02f, 1.60193e-02f, -2.86289e-03f }, // 109/128
|
||||
{ -3.26730e-03f, 2.00132e-02f, -8.17385e-02f, 9.47477e-01f, 1.55480e-01f, -5.05770e-02f, 1.52095e-02f, -2.72032e-03f }, // 110/128
|
||||
{ -3.10689e-03f, 1.90545e-02f, -7.80792e-02f, 9.51795e-01f, 1.46192e-01f, -4.77900e-02f, 1.43934e-02f, -2.57640e-03f }, // 111/128
|
||||
{ -2.94361e-03f, 1.80759e-02f, -7.43154e-02f, 9.55956e-01f, 1.36968e-01f, -4.49929e-02f, 1.35716e-02f, -2.43121e-03f }, // 112/128
|
||||
{ -2.77751e-03f, 1.70776e-02f, -7.04471e-02f, 9.59958e-01f, 1.27812e-01f, -4.21869e-02f, 1.27445e-02f, -2.28483e-03f }, // 113/128
|
||||
{ -2.60868e-03f, 1.60599e-02f, -6.64743e-02f, 9.63798e-01f, 1.18725e-01f, -3.93735e-02f, 1.19125e-02f, -2.13733e-03f }, // 114/128
|
||||
{ -2.43718e-03f, 1.50233e-02f, -6.23972e-02f, 9.67477e-01f, 1.09710e-01f, -3.65541e-02f, 1.10760e-02f, -1.98880e-03f }, // 115/128
|
||||
{ -2.26307e-03f, 1.39681e-02f, -5.82159e-02f, 9.70992e-01f, 1.00769e-01f, -3.37303e-02f, 1.02356e-02f, -1.83931e-03f }, // 116/128
|
||||
{ -2.08645e-03f, 1.28947e-02f, -5.39305e-02f, 9.74342e-01f, 9.19033e-02f, -3.09033e-02f, 9.39154e-03f, -1.68894e-03f }, // 117/128
|
||||
{ -1.90738e-03f, 1.18034e-02f, -4.95412e-02f, 9.77526e-01f, 8.31162e-02f, -2.80746e-02f, 8.54441e-03f, -1.53777e-03f }, // 118/128
|
||||
{ -1.72594e-03f, 1.06946e-02f, -4.50483e-02f, 9.80543e-01f, 7.44095e-02f, -2.52457e-02f, 7.69462e-03f, -1.38589e-03f }, // 119/128
|
||||
{ -1.54221e-03f, 9.56876e-03f, -4.04519e-02f, 9.83392e-01f, 6.57852e-02f, -2.24178e-02f, 6.84261e-03f, -1.23337e-03f }, // 120/128
|
||||
{ -1.35627e-03f, 8.42626e-03f, -3.57525e-02f, 9.86071e-01f, 5.72454e-02f, -1.95925e-02f, 5.98883e-03f, -1.08030e-03f }, // 121/128
|
||||
{ -1.16820e-03f, 7.26755e-03f, -3.09503e-02f, 9.88580e-01f, 4.87921e-02f, -1.67710e-02f, 5.13372e-03f, -9.26747e-04f }, // 122/128
|
||||
{ -9.78093e-04f, 6.09305e-03f, -2.60456e-02f, 9.90917e-01f, 4.04274e-02f, -1.39548e-02f, 4.27773e-03f, -7.72802e-04f }, // 123/128
|
||||
{ -7.86031e-04f, 4.90322e-03f, -2.10389e-02f, 9.93082e-01f, 3.21531e-02f, -1.11453e-02f, 3.42130e-03f, -6.18544e-04f }, // 124/128
|
||||
{ -5.92100e-04f, 3.69852e-03f, -1.59305e-02f, 9.95074e-01f, 2.39714e-02f, -8.34364e-03f, 2.56486e-03f, -4.64053e-04f }, // 125/128
|
||||
{ -3.96391e-04f, 2.47942e-03f, -1.07209e-02f, 9.96891e-01f, 1.58840e-02f, -5.55134e-03f, 1.70888e-03f, -3.09412e-04f }, // 126/128
|
||||
{ -1.98993e-04f, 1.24642e-03f, -5.41054e-03f, 9.98534e-01f, 7.89295e-03f, -2.76968e-03f, 8.53777e-04f, -1.54700e-04f }, // 127/128
|
||||
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 128/128
|
||||
};
|
||||
|
||||
/* Instrumentation Taps */
|
||||
private static final String INSTRUMENT_SYMBOL_SPREAD = "Tap Point: Symbol Spread (Goal=2.0)";
|
||||
private FloatTap mSymbolSpreadTap;
|
||||
private List<Tap> mAvailableTaps;
|
||||
|
||||
private static final int NUMBER_FILTER_TAPS = 8;
|
||||
private static final int NUMBER_FILTER_STEPS = 128;
|
||||
|
||||
private static final int SAMPLE_RATE = 48000;
|
||||
private static final int SYMBOL_RATE = 4800;
|
||||
|
||||
/* Tracking loop gain constant */
|
||||
private static final double K_SYMBOL_SPREAD = 0.0100;
|
||||
|
||||
/* Constraints on symbol spreading */
|
||||
private static final float SYMBOL_SPREAD_MAX = 2.4f; // upper range limit: +20%
|
||||
private static final float SYMBOL_SPREAD_MIN = 1.6f; // lower range limit: -20%
|
||||
|
||||
/* Symbol clock tracking loop gain */
|
||||
private static final double K_SYMBOL_TIMING = 0.025;
|
||||
|
||||
/* Coarse and fine frequency tracking constants */
|
||||
private static final double K_COARSE_FREQUENCY = 0.00125;
|
||||
private static final double K_FINE_FREQUENCY = 0.125;
|
||||
|
||||
/* Frequency correction broadcast threshold */
|
||||
// private static final double COARSE_FREQUENCY_DEADBAND = 1.66;
|
||||
private static final double COARSE_FREQUENCY_DEADBAND = 1.20;
|
||||
|
||||
/* 2.0 symbol spread gives -3, -1, 1, 3 */
|
||||
private float mSymbolSpread = 2.0f;
|
||||
private float mSymbolClock = 0.0f;
|
||||
private float mSymbolTime = (float)SYMBOL_RATE / (float)SAMPLE_RATE;
|
||||
|
||||
private float mFineFrequencyCorrection = 0.0f;
|
||||
private float mCoarseFrequencyCorrection = 0.0f;
|
||||
|
||||
private float mHistory[] = new float[ NUMBER_FILTER_TAPS ];
|
||||
private int mHistoryLast = 0;
|
||||
|
||||
private boolean mReset = false;
|
||||
|
||||
private RealSampleListener mListener;
|
||||
|
||||
private GainController mGainController;
|
||||
private FrequencyCorrectionControl mFrequencyCorrectionControl;
|
||||
|
||||
/**
|
||||
* C4FM Symbol Filter
|
||||
*
|
||||
* Sample Gain values - the gain of incoming sample values will critically
|
||||
* impact the performance of this filter. Gain should be adjusted to
|
||||
* optimize the value of mSymbolSpread toward a value of 2.0. If the value
|
||||
* is less than 2.0, then increase gain. If the value is over 2.0, then
|
||||
* decrease gain.
|
||||
*
|
||||
* When preceded by the RealAutomaticGainFilter class, an optimal gain
|
||||
* setting for this filter is 15.4 and that yields a symbol spread that
|
||||
* is centered on 2.0, ranging 1.92 to 2.08.
|
||||
*/
|
||||
public C4FMSymbolFilter( GainController gainController,
|
||||
FrequencyCorrectionControl frequencyControl )
|
||||
{
|
||||
mGainController = gainController;
|
||||
|
||||
mFrequencyCorrectionControl = frequencyControl;
|
||||
|
||||
/* Register for reset events, so we can reset internal frequency tracking */
|
||||
mFrequencyCorrectionControl.setListener( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( float sample )
|
||||
{
|
||||
//TODO: limit frequency corrections to once per buffer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!11
|
||||
if( mReset )
|
||||
{
|
||||
mGainController.reset();
|
||||
|
||||
mCoarseFrequencyCorrection = 0.0f;
|
||||
mFineFrequencyCorrection = 0.0f;
|
||||
mSymbolSpread = 2.0f;
|
||||
mSymbolClock = 0.0f;
|
||||
mSymbolTime = (float)SYMBOL_RATE / (float)SAMPLE_RATE;
|
||||
|
||||
mReset = false;
|
||||
}
|
||||
|
||||
mSymbolClock += mSymbolTime;
|
||||
|
||||
mHistory[ mHistoryLast++ ] = sample;
|
||||
|
||||
mHistoryLast %= NUMBER_FILTER_TAPS;
|
||||
|
||||
if( mSymbolClock > 1.0f )
|
||||
{
|
||||
mSymbolClock -= 1.0f;
|
||||
|
||||
int imu = (int)Math.floor( 0.5 +
|
||||
( (float)NUMBER_FILTER_STEPS * ( mSymbolClock / mSymbolTime ) ) );
|
||||
|
||||
if( imu >= NUMBER_FILTER_STEPS )
|
||||
{
|
||||
imu = NUMBER_FILTER_STEPS - 1;
|
||||
}
|
||||
|
||||
int imu_p1 = imu + 1;
|
||||
|
||||
int j = mHistoryLast;
|
||||
|
||||
double interp = 0.0;
|
||||
double interp_p1 = 0.0;
|
||||
|
||||
for( int i = 0; i < NUMBER_FILTER_TAPS; i++ )
|
||||
{
|
||||
interp += TAPS[ imu][ i ] * mHistory[ j ];
|
||||
interp_p1 += TAPS[ imu_p1 ][ i ] * mHistory[ j ];
|
||||
|
||||
j = ( j + 1 ) % NUMBER_FILTER_TAPS;
|
||||
}
|
||||
|
||||
/* Output symbol will be interpolated value corrected for symbol
|
||||
* spread and frequency offset */
|
||||
interp -= mFineFrequencyCorrection;
|
||||
interp_p1 -= mFineFrequencyCorrection;
|
||||
|
||||
/* Correct output for symbol deviation (spread) */
|
||||
float output = (float)( 2.0 * interp / mSymbolSpread );
|
||||
|
||||
/* Detect received symbol error: basically use a hard decision and
|
||||
* subtract off expected position nominal symbol level which will be
|
||||
* +/- 0.5 * symbol spread and +/- 1.5 symbol spread. Remember that
|
||||
* nominal symbol spread will be 2.0 */
|
||||
double symbolError;
|
||||
|
||||
if( interp < -mSymbolSpread )
|
||||
{
|
||||
/* symbol is -3: Expected at -1.5 * symbol spread */
|
||||
symbolError = interp + ( 1.5 * mSymbolSpread );
|
||||
mSymbolSpread -= ( symbolError * 0.5 * K_SYMBOL_SPREAD );
|
||||
}
|
||||
else if( interp < 0.0 )
|
||||
{
|
||||
/* symbol is -1: Expected at -0.5 * symbol_spread */
|
||||
symbolError = interp + (0.5 * mSymbolSpread );
|
||||
mSymbolSpread -= ( symbolError * K_SYMBOL_SPREAD );
|
||||
}
|
||||
else if( interp < mSymbolSpread )
|
||||
{
|
||||
/* symbol is +1: Expected at +0.5 * symbol_spread */
|
||||
symbolError = interp - ( 0.5 * mSymbolSpread );
|
||||
mSymbolSpread += ( symbolError * K_SYMBOL_SPREAD );
|
||||
}
|
||||
else
|
||||
{
|
||||
/* symbol is +3: Expected at +1.5 * symbol_spread */
|
||||
symbolError = interp - ( 1.5 * mSymbolSpread );
|
||||
mSymbolSpread += ( symbolError * 0.5 * K_SYMBOL_SPREAD );
|
||||
}
|
||||
|
||||
/* Symbol clock tracking loop adjustment */
|
||||
if( interp_p1 < interp )
|
||||
{
|
||||
mSymbolClock += symbolError * K_SYMBOL_TIMING;
|
||||
}
|
||||
else
|
||||
{
|
||||
mSymbolClock -= symbolError * K_SYMBOL_TIMING;
|
||||
}
|
||||
|
||||
if( mSymbolSpread < SYMBOL_SPREAD_MIN )
|
||||
{
|
||||
mGainController.increase();
|
||||
|
||||
mSymbolSpread = SYMBOL_SPREAD_MIN;
|
||||
}
|
||||
else if( mSymbolSpread > SYMBOL_SPREAD_MAX )
|
||||
{
|
||||
mGainController.decrease();
|
||||
|
||||
mSymbolSpread = SYMBOL_SPREAD_MAX;
|
||||
}
|
||||
|
||||
mCoarseFrequencyCorrection += ( ( mFineFrequencyCorrection -
|
||||
mCoarseFrequencyCorrection ) * K_COARSE_FREQUENCY );
|
||||
|
||||
mFineFrequencyCorrection += ( symbolError * K_FINE_FREQUENCY );
|
||||
|
||||
/* send frequency correction */
|
||||
if( Math.abs( mCoarseFrequencyCorrection ) > COARSE_FREQUENCY_DEADBAND )
|
||||
{
|
||||
int adjustment = 500 * ( mCoarseFrequencyCorrection > 0 ? 1 : -1 );
|
||||
|
||||
mFrequencyCorrectionControl.adjust( adjustment );
|
||||
|
||||
/* reset internal frequency error tracker */
|
||||
mCoarseFrequencyCorrection = 0;
|
||||
}
|
||||
|
||||
if( mSymbolSpreadTap != null )
|
||||
{
|
||||
mSymbolSpreadTap.receive( mSymbolSpread );
|
||||
}
|
||||
|
||||
/* dispatch the interpolated value to the listener */
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( output );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener interface for frequency correction reset events. We reset the
|
||||
* coarse and fine frequency control values when the tuner channel source
|
||||
* has changed frequency or frequency offset.
|
||||
*/
|
||||
@Override
|
||||
public void resetFrequencyCorrection()
|
||||
{
|
||||
mReset = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setListener( RealSampleListener listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener( RealSampleListener listener )
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Tap> getTaps()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<Tap>();
|
||||
|
||||
/*
|
||||
* Since the target is 2.0, we subtract 2.0 from the value so that
|
||||
* a good value is plotted right on the zero center line
|
||||
*/
|
||||
mAvailableTaps.add(
|
||||
new AdditiveFloatTap( INSTRUMENT_SYMBOL_SPREAD, 0, 0.1f, -2.0f ) );
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTap( Tap tap )
|
||||
{
|
||||
if( tap.getName().contentEquals( INSTRUMENT_SYMBOL_SPREAD ) )
|
||||
{
|
||||
mSymbolSpreadTap = (FloatTap)tap;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTap( Tap tap )
|
||||
{
|
||||
if( tap.getName().contentEquals( INSTRUMENT_SYMBOL_SPREAD ) )
|
||||
{
|
||||
mSymbolSpreadTap = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
97
src/module/decode/p25/DecodeConfigP25Phase1.java
Normal file
97
src/module/decode/p25/DecodeConfigP25Phase1.java
Normal file
@@ -0,0 +1,97 @@
|
||||
/*******************************************************************************
|
||||
* 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.p25;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
import module.decode.p25.P25Decoder.Modulation;
|
||||
|
||||
public class DecodeConfigP25Phase1 extends DecodeConfiguration
|
||||
{
|
||||
private P25_LSMDecoder.Modulation mModulation = Modulation.C4FM;
|
||||
|
||||
private int mCallTimeout = DEFAULT_CALL_TIMEOUT_SECONDS;
|
||||
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
|
||||
|
||||
public DecodeConfigP25Phase1()
|
||||
{
|
||||
super( DecoderType.P25_PHASE1 );
|
||||
|
||||
setAFC( false );
|
||||
}
|
||||
|
||||
public boolean supportsAFC()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@XmlAttribute( name = "modulation" )
|
||||
public P25_LSMDecoder.Modulation getModulation()
|
||||
{
|
||||
return mModulation;
|
||||
}
|
||||
|
||||
public void setModulation( P25_LSMDecoder.Modulation modulation )
|
||||
{
|
||||
mModulation = modulation;
|
||||
}
|
||||
|
||||
@XmlAttribute( name="call_timeout" )
|
||||
public int getCallTimeout()
|
||||
{
|
||||
return mCallTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the call timeout value in seconds ( 10 - 600 );
|
||||
* @param timeout
|
||||
*/
|
||||
public void setCallTimeout( int timeout )
|
||||
{
|
||||
if( CALL_TIMEOUT_MINIMUM <= timeout && timeout <= CALL_TIMEOUT_MAXIMUM )
|
||||
{
|
||||
mCallTimeout = timeout;
|
||||
}
|
||||
else
|
||||
{
|
||||
mCallTimeout = DEFAULT_CALL_TIMEOUT_SECONDS;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@XmlAttribute( name="traffic_channel_pool_size" )
|
||||
public int getTrafficChannelPoolSize()
|
||||
{
|
||||
return mTrafficChannelPoolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the traffic channel pool size which is the maximum number of
|
||||
* simultaneous traffic channels that can be allocated.
|
||||
*
|
||||
* This limits the maximum calls so that busy systems won't cause more
|
||||
* traffic channels to be allocated than the decoder/software/host computer
|
||||
* can support.
|
||||
*/
|
||||
public void setTrafficChannelPoolSize( int size )
|
||||
{
|
||||
mTrafficChannelPoolSize = size;
|
||||
}
|
||||
}
|
||||
154
src/module/decode/p25/P25CallEvent.java
Normal file
154
src/module/decode/p25/P25CallEvent.java
Normal file
@@ -0,0 +1,154 @@
|
||||
/*******************************************************************************
|
||||
* 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.p25;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.event.CallEvent;
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
|
||||
public class P25CallEvent extends CallEvent
|
||||
{
|
||||
private String mChannel;
|
||||
private long mFrequency;
|
||||
|
||||
public P25CallEvent( CallEventType callEventType,
|
||||
AliasList aliasList,
|
||||
String fromID,
|
||||
String toID,
|
||||
String channel,
|
||||
long frequency,
|
||||
String details )
|
||||
{
|
||||
super( DecoderType.P25_PHASE1, callEventType,
|
||||
aliasList, fromID, toID, details );
|
||||
|
||||
mChannel = channel;
|
||||
mFrequency = frequency;
|
||||
}
|
||||
|
||||
private Alias getAlias( String talkgroup )
|
||||
{
|
||||
if( hasAliasList() )
|
||||
{
|
||||
return getAliasList().getTalkgroupAlias( talkgroup );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
return getAlias( getFromID() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getToIDAlias()
|
||||
{
|
||||
return getAlias( getToID() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannel()
|
||||
{
|
||||
return mChannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrequency()
|
||||
{
|
||||
return mFrequency;
|
||||
}
|
||||
|
||||
public static class Builder
|
||||
{
|
||||
/* Required parameters */
|
||||
private CallEventType mCallEventType;
|
||||
|
||||
/* Optional parameters */
|
||||
private AliasList mAliasList;
|
||||
private String mFromID;
|
||||
private String mToID;
|
||||
private String mDetails;
|
||||
private String mChannel;
|
||||
private long mFrequency;
|
||||
|
||||
public Builder( CallEventType callEventType )
|
||||
{
|
||||
mCallEventType = callEventType;
|
||||
}
|
||||
|
||||
public Builder aliasList( AliasList aliasList )
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder from( String val )
|
||||
{
|
||||
mFromID = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder channel( String channel )
|
||||
{
|
||||
mChannel = channel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder details( String details )
|
||||
{
|
||||
mDetails = details;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder frequency( long frequency )
|
||||
{
|
||||
mFrequency = frequency;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder to( String toID )
|
||||
{
|
||||
mToID = toID;
|
||||
return this;
|
||||
}
|
||||
|
||||
public P25CallEvent build()
|
||||
{
|
||||
return new P25CallEvent( this );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor for the builder
|
||||
*/
|
||||
private P25CallEvent( Builder builder )
|
||||
{
|
||||
this( builder.mCallEventType,
|
||||
builder.mAliasList,
|
||||
builder.mFromID,
|
||||
builder.mToID,
|
||||
builder.mChannel,
|
||||
builder.mFrequency,
|
||||
builder.mDetails );
|
||||
}
|
||||
|
||||
}
|
||||
65
src/module/decode/p25/P25Decoder.java
Normal file
65
src/module/decode/p25/P25Decoder.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package module.decode.p25;
|
||||
|
||||
import instrument.Instrumentable;
|
||||
import module.decode.Decoder;
|
||||
import module.decode.DecoderType;
|
||||
import source.tuner.frequency.IFrequencyCorrectionController;
|
||||
import alias.AliasList;
|
||||
import controller.channel.Channel.ChannelType;
|
||||
|
||||
public abstract class P25Decoder extends Decoder
|
||||
implements IFrequencyCorrectionController, Instrumentable
|
||||
{
|
||||
private P25MessageProcessor mMessageProcessor;
|
||||
|
||||
public P25Decoder( AliasList aliasList, ChannelType channelType )
|
||||
{
|
||||
super( new P25DecoderState( aliasList, channelType ) );
|
||||
|
||||
mMessageProcessor = new P25MessageProcessor( aliasList );
|
||||
mMessageProcessor.setMessageListener( mMessageBroadcaster );
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.P25_PHASE1;
|
||||
}
|
||||
|
||||
protected P25MessageProcessor getMessageProcessor()
|
||||
{
|
||||
return mMessageProcessor;
|
||||
}
|
||||
|
||||
public abstract Modulation getModulation();
|
||||
|
||||
public enum Modulation
|
||||
{
|
||||
CQPSK( "Simulcast (LSM)", "LSM" ),
|
||||
C4FM( "Normal (C4FM)", "C4FM" );
|
||||
|
||||
private String mLabel;
|
||||
private String mShortLabel;
|
||||
|
||||
private Modulation( String label, String shortLabel )
|
||||
{
|
||||
mLabel = label;
|
||||
mShortLabel = shortLabel;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public String getShortLabel()
|
||||
{
|
||||
return mShortLabel;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return getLabel();
|
||||
}
|
||||
}
|
||||
}
|
||||
200
src/module/decode/p25/P25DecoderPanel.java
Normal file
200
src/module/decode/p25/P25DecoderPanel.java
Normal file
@@ -0,0 +1,200 @@
|
||||
/*******************************************************************************
|
||||
* 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.p25;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import module.decode.state.ChangedAttribute;
|
||||
import module.decode.state.DecoderPanel;
|
||||
import module.decode.state.State;
|
||||
import settings.ColorSetting;
|
||||
import settings.Setting;
|
||||
import settings.SettingsManager;
|
||||
import alias.Alias;
|
||||
|
||||
public class P25DecoderPanel extends DecoderPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JLabel mProtocol;
|
||||
private JLabel mFrom = new JLabel( " " );
|
||||
private JLabel mFromAlias = new JLabel( " " );
|
||||
|
||||
private JLabel mNAC = new JLabel( "NAC:" );
|
||||
private JLabel mTo = new JLabel( " " );
|
||||
private JLabel mToAlias = new JLabel( " " );
|
||||
|
||||
private JLabel mSystem = new JLabel( "SYS:" );
|
||||
private JLabel mSite = new JLabel( "Site:" );
|
||||
private JLabel mSiteAlias = new JLabel( "" );
|
||||
|
||||
|
||||
public P25DecoderPanel( SettingsManager settingsManager, P25Decoder decoder )
|
||||
{
|
||||
super( settingsManager, decoder );
|
||||
|
||||
mProtocol = new JLabel( "P25-1 " + decoder.getModulation().getShortLabel() );
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
super.init();
|
||||
|
||||
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]0[]") );
|
||||
|
||||
mProtocol.setFont( mFontDecoder );
|
||||
mProtocol.setForeground( mColorLabelDecoder );
|
||||
|
||||
mFrom.setFont( mFontDecoder );
|
||||
mFrom.setForeground( mColorLabelDecoder );
|
||||
|
||||
mFromAlias.setFont( mFontDecoder );
|
||||
mFromAlias.setForeground( mColorLabelDecoder );
|
||||
|
||||
mNAC.setFont( mFontDetails );
|
||||
mNAC.setForeground( mColorLabelDecoder );
|
||||
|
||||
mTo.setFont( mFontDecoder );
|
||||
mTo.setForeground( mColorLabelDecoder );
|
||||
|
||||
mToAlias.setFont( mFontDecoder );
|
||||
mToAlias.setForeground( mColorLabelDecoder );
|
||||
|
||||
mSystem.setFont( mFontDetails );
|
||||
mSystem.setForeground( mColorLabelDecoder );
|
||||
|
||||
mSiteAlias.setFont( mFontDecoder );
|
||||
mSiteAlias.setForeground( mColorLabelDecoder );
|
||||
|
||||
mSite.setFont( mFontDetails );
|
||||
mSite.setForeground( mColorLabelDecoder );
|
||||
|
||||
add( mProtocol );
|
||||
add( mFrom );
|
||||
add( mFromAlias, "wrap" );
|
||||
|
||||
add( mNAC );
|
||||
add( mTo );
|
||||
add( mToAlias, "wrap" );
|
||||
|
||||
add( mSystem );
|
||||
add( mSite );
|
||||
add( mSiteAlias, "wrap" );
|
||||
}
|
||||
|
||||
private P25DecoderState getDecoderState()
|
||||
{
|
||||
return (P25DecoderState)getDecoder().getDecoderState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( final ChangedAttribute changedAttribute )
|
||||
{
|
||||
final P25DecoderState state = getDecoderState();
|
||||
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
|
||||
switch( changedAttribute )
|
||||
{
|
||||
case NAC:
|
||||
mNAC.setText( "NAC:" + state.getNAC() );
|
||||
break;
|
||||
case SYSTEM:
|
||||
mSystem.setText( "SYS:" + state.getSystem() );
|
||||
break;
|
||||
case SITE:
|
||||
mSite.setText( "SITE:" + state.getSite() );
|
||||
break;
|
||||
case SITE_ALIAS:
|
||||
mSiteAlias.setText( state.getSiteAlias() );
|
||||
break;
|
||||
case FROM_TALKGROUP:
|
||||
mFrom.setText( state.getFromTalkgroup() );
|
||||
break;
|
||||
case FROM_TALKGROUP_ALIAS:
|
||||
Alias from = state.getFromAlias();
|
||||
|
||||
if( from != null )
|
||||
{
|
||||
mFromAlias.setText( from.getName() );
|
||||
mFromAlias.setIcon( mSettingsManager
|
||||
.getImageIcon( from.getIconName(), 12 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
mFromAlias.setText( null );
|
||||
mFromAlias.setIcon( null );
|
||||
}
|
||||
break;
|
||||
case TO_TALKGROUP:
|
||||
mTo.setText( state.getToTalkgroup() );
|
||||
break;
|
||||
case TO_TALKGROUP_ALIAS:
|
||||
Alias to = state.getToAlias();
|
||||
|
||||
if( to != null )
|
||||
{
|
||||
mToAlias.setText( to.getName() );
|
||||
mToAlias.setIcon( mSettingsManager
|
||||
.getImageIcon( to.getIconName(), 12 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
mToAlias.setText( null );
|
||||
mToAlias.setIcon( null );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingChanged( Setting setting )
|
||||
{
|
||||
super.settingChanged( setting );
|
||||
|
||||
if( setting instanceof ColorSetting )
|
||||
{
|
||||
ColorSetting colorSetting = (ColorSetting)setting;
|
||||
|
||||
switch( colorSetting.getColorSettingName() )
|
||||
{
|
||||
case CHANNEL_STATE_LABEL_DECODER:
|
||||
if( mProtocol != null )
|
||||
{
|
||||
mProtocol.setForeground( mColorLabelDecoder );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2932
src/module/decode/p25/P25DecoderState.java
Normal file
2932
src/module/decode/p25/P25DecoderState.java
Normal file
File diff suppressed because it is too large
Load Diff
102
src/module/decode/p25/P25Editor.java
Normal file
102
src/module/decode/p25/P25Editor.java
Normal file
@@ -0,0 +1,102 @@
|
||||
/*******************************************************************************
|
||||
* 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.p25;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import module.decode.DecodeEditor;
|
||||
import module.decode.config.DecodeConfiguration;
|
||||
import module.decode.p25.P25Decoder.Modulation;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import source.SourceEditor;
|
||||
import source.tuner.TunerEditor;
|
||||
import controller.Editor;
|
||||
import controller.channel.ChannelValidationException;
|
||||
|
||||
public class P25Editor extends DecodeEditor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( P25Editor.class );
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JComboBox<P25_LSMDecoder.Modulation> mComboModulation;
|
||||
|
||||
public P25Editor( DecodeConfiguration config )
|
||||
{
|
||||
super( config );
|
||||
|
||||
initGUI();
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
mComboModulation = new JComboBox<P25_LSMDecoder.Modulation>();
|
||||
|
||||
mComboModulation.setModel( new DefaultComboBoxModel<P25_LSMDecoder.Modulation>(
|
||||
P25_LSMDecoder.Modulation.values() ) );
|
||||
|
||||
mComboModulation.setSelectedItem( ((DecodeConfigP25Phase1)mConfig).getModulation() );
|
||||
|
||||
mComboModulation.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
P25_LSMDecoder.Modulation selected = mComboModulation
|
||||
.getItemAt( mComboModulation.getSelectedIndex() );
|
||||
|
||||
if( selected != null )
|
||||
{
|
||||
((DecodeConfigP25Phase1)mConfig).setModulation( selected );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
add( new JLabel( "Modulation:" ) );
|
||||
add( mComboModulation, "wrap" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce source=tuner for CQPSK modulation
|
||||
*/
|
||||
@Override
|
||||
public void validate( Editor editor ) throws ChannelValidationException
|
||||
{
|
||||
if( editor instanceof SourceEditor &&
|
||||
((DecodeConfigP25Phase1)mConfig).getModulation() == Modulation.CQPSK )
|
||||
{
|
||||
if( !( editor instanceof TunerEditor ) )
|
||||
{
|
||||
throw new ChannelValidationException(
|
||||
"<html><body width='175'><h1>LSM Simulcast</h1>"
|
||||
+ "<p>P25 LSM Simulcast decoder can only be used with "
|
||||
+ "a tuner source. Please change the Source to use a tuner"
|
||||
+ " or change the P25 Decoder to C4FM modulation" );
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
161
src/module/decode/p25/P25Interleave.java
Normal file
161
src/module/decode/p25/P25Interleave.java
Normal file
@@ -0,0 +1,161 @@
|
||||
package module.decode.p25;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import bits.BinaryMessage;
|
||||
|
||||
/**
|
||||
* Utility class to process interleave of P25 Voice and Data messages.
|
||||
*/
|
||||
public class P25Interleave
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( P25Interleave.class );
|
||||
|
||||
private static int[] DATA_INTERLEAVE = new int[] { 0,1,2,3,52,53,54,55,100,
|
||||
101,102,103,148,149,150,151,4,5,6,7,56,57,58,59,104,105,106,107,152,153,
|
||||
154,155,8,9,10,11,60,61,62,63,108,109,110,111,156,157,158,159,12,13,14,
|
||||
15,64,65,66,67,112,113,114,115,160,161,162,163,16,17,18,19,68,69,70,71,
|
||||
116,117,118,119,164,165,166,167,20,21,22,23,72,73,74,75,120,121,122,123,
|
||||
168,169,170,171,24,25,26,27,76,77,78,79,124,125,126,127,172,173,174,175,
|
||||
28,29,30,31,80,81,82,83,128,129,130,131,176,177,178,179,32,33,34,35,84,
|
||||
85,86,87,132,133,134,135,180,181,182,183,36,37,38,39,88,89,90,91,136,
|
||||
137,138,139,184,185,186,187,40,41,42,43,92,93,94,95,140,141,142,143,188,
|
||||
189,190,191,44,45,46,47,96,97,98,99,144,145,146,147,192,193,194,195,48,
|
||||
49,50,51 };
|
||||
|
||||
private static int[] DATA_DEINTERLEAVE = new int[] { 0,1,2,3,16,17,18,19,32,
|
||||
33,34,35,48,49,50,51,64,65,66,67,80,81,82,83,96,97,98,99,112,113,114,115,
|
||||
128,129,130,131,144,145,146,147,160,161,162,163,176,177,178,179,192,193,
|
||||
194,195,4,5,6,7,20,21,22,23,36,37,38,39,52,53,54,55,68,69,70,71,84,85,
|
||||
86,87,100,101,102,103,116,117,118,119,132,133,134,135,148,149,150,151,
|
||||
164,165,166,167,180,181,182,183,8,9,10,11,24,25,26,27,40,41,42,43,56,57,
|
||||
58,59,72,73,74,75,88,89,90,91,104,105,106,107,120,121,122,123,136,137,
|
||||
138,139,152,153,154,155,168,169,170,171,184,185,186,187,12,13,14,15,28,
|
||||
29,30,31,44,45,46,47,60,61,62,63,76,77,78,79,92,93,94,95,108,109,110,
|
||||
111,124,125,126,127,140,141,142,143,156,157,158,159,172,173,174,175,188,
|
||||
189,190,191 };
|
||||
|
||||
private static int[] VOICE_INTERLEAVE = new int[] { 0,24,48,72,96,120,25,
|
||||
1,73,49,121,97,2,26,50,74,98,122,27,3,75,51,123,99,4,28,52,76,100,124,
|
||||
29,5,77,53,125,101,6,30,54,78,102,126,31,7,79,55,127,103,8,32,56,80,104,
|
||||
128,33,9,81,57,129,105,10,34,58,82,106,130,35,11,83,59,131,107,12,36,60,
|
||||
84,108,132,37,13,85,61,133,109,14,38,62,86,110,134,39,87,15,63,135,111,
|
||||
16,40,64,88,112,136,41,17,89,65,137,113,18,42,66,90,114,138,43,19,91,
|
||||
67,139,115,20,44,68,92,116,140,45,21,93,69,141,117,22,46,70,94,119,142,
|
||||
47,23,95,71,143,118 };
|
||||
|
||||
private static int[] VOICE_DEINTERLEAVE = new int[] { 0,7,12,19,24,31,36,43,
|
||||
48,55,60,67,72,79,84,91,96,103,108,115,120,127,132,139,1,6,13,18,25,30,
|
||||
37,42,49,54,61,66,73,78,85,90,97,102,109,114,121,126,133,138,2,9,14,21,
|
||||
26,33,38,45,50,57,62,69,74,81,86,93,98,105,110,117,122,129,134,141,3,8,
|
||||
15,20,27,32,39,44,51,56,63,68,75,80,87,92,99,104,111,116,123,128,135,
|
||||
140,4,11,16,23,28,35,40,47,52,59,64,71,76,83,88,95,100,107,112,119,124,
|
||||
131,136,143,5,10,17,22,29,34,41,46,53,58,65,70,77,82,89,94,101,106,113,
|
||||
118,125,130,137,142 };
|
||||
/**
|
||||
* Deinterleaves the 196-bit block in message, identified by start and end
|
||||
* bit positions. Note: end index (exclusive) should be one more than the
|
||||
* last bit in the block.
|
||||
*
|
||||
* @param message - source message to deinterleave
|
||||
* @param start - starting bit index for the block
|
||||
* @param end - ending bit index for the block, plus 1
|
||||
*/
|
||||
public static BinaryMessage deinterleaveData( BinaryMessage message,
|
||||
int start, int end )
|
||||
{
|
||||
return deinterleave( DATA_DEINTERLEAVE, message, start, end );
|
||||
}
|
||||
|
||||
public static BinaryMessage deinterleaveVoice( BinaryMessage message,
|
||||
int start, int end )
|
||||
{
|
||||
return deinterleave( VOICE_DEINTERLEAVE, message, start, end );
|
||||
}
|
||||
|
||||
public static BinaryMessage deinterleave( int[] pattern, BinaryMessage message,
|
||||
int start, int end )
|
||||
{
|
||||
BitSet original = message.get( start, end );
|
||||
|
||||
/* Clear block bits in source message */
|
||||
message.clear( start, end );
|
||||
|
||||
/* Iterate only the set bits in the original message and apply
|
||||
* the deinterleave -- we don't have to evaluate the 0 bits */
|
||||
for (int i = original.nextSetBit( 0 );
|
||||
i >= 0 && i < pattern.length;
|
||||
i = original.nextSetBit( i + 1 ) )
|
||||
{
|
||||
message.set( start + pattern[ i ] );
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interleaves the 196-bit block in message, identified by start and end
|
||||
* bit positions. Note: end index (exclusive) should be one more than the
|
||||
* last bit in the block.
|
||||
*
|
||||
* @param message - source message to interleave
|
||||
* @param start - starting bit index for the block
|
||||
* @param end - ending bit index for the block, plus 1
|
||||
*/
|
||||
public static BinaryMessage interleaveData( BinaryMessage message,
|
||||
int start, int end )
|
||||
{
|
||||
return interleave( DATA_INTERLEAVE, message, start, end );
|
||||
}
|
||||
|
||||
public static BinaryMessage interleaveVoice( BinaryMessage message,
|
||||
int start, int end )
|
||||
{
|
||||
return interleave( VOICE_INTERLEAVE, message, start, end );
|
||||
}
|
||||
|
||||
public static BinaryMessage interleave( int[] pattern, BinaryMessage message,
|
||||
int start, int end )
|
||||
{
|
||||
BitSet original = message.get( start, end );
|
||||
|
||||
/* Clear block bits in source message */
|
||||
message.clear( start, end );
|
||||
|
||||
/* Iterate only the set bits in the original message and apply
|
||||
* the deinterleave -- we don't have to evaluate the 0 bits */
|
||||
for (int i = original.nextSetBit( 0 );
|
||||
i >= 0 && i < pattern.length;
|
||||
i = original.nextSetBit( i + 1 ) )
|
||||
{
|
||||
message.set( start + pattern[ i ] );
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public static void main( String[] args )
|
||||
{
|
||||
int start = 0;
|
||||
int end = 144;
|
||||
int set = 50;
|
||||
|
||||
String interleaved = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
|
||||
|
||||
BinaryMessage b = BinaryMessage.load( interleaved );
|
||||
|
||||
b.set( set );
|
||||
|
||||
mLog.debug( "INT:" + b.toString() );
|
||||
|
||||
P25Interleave.deinterleaveVoice( b, start, end );
|
||||
|
||||
mLog.debug( "DEI:" + b.toString() );
|
||||
|
||||
mLog.debug( "In: " + set + " Out:" + b.nextSetBit( 0 ) );
|
||||
}
|
||||
}
|
||||
692
src/module/decode/p25/P25MessageFramer.java
Normal file
692
src/module/decode/p25/P25MessageFramer.java
Normal file
@@ -0,0 +1,692 @@
|
||||
package module.decode.p25;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import message.Message;
|
||||
import module.decode.p25.message.P25Message;
|
||||
import module.decode.p25.message.hdu.HDUMessage;
|
||||
import module.decode.p25.message.ldu.LDU1Message;
|
||||
import module.decode.p25.message.ldu.LDU2Message;
|
||||
import module.decode.p25.message.ldu.lc.LDULCMessageFactory;
|
||||
import module.decode.p25.message.pdu.PDUMessage;
|
||||
import module.decode.p25.message.pdu.PDUMessageFactory;
|
||||
import module.decode.p25.message.pdu.confirmed.PDUConfirmedMessage;
|
||||
import module.decode.p25.message.pdu.confirmed.PDUConfirmedMessageFactory;
|
||||
import module.decode.p25.message.tdu.TDUMessage;
|
||||
import module.decode.p25.message.tdu.lc.TDULCMessageFactory;
|
||||
import module.decode.p25.message.tdu.lc.TDULinkControlMessage;
|
||||
import module.decode.p25.message.tsbk.TSBKMessage;
|
||||
import module.decode.p25.message.tsbk.TSBKMessageFactory;
|
||||
import module.decode.p25.reference.DataUnitID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
import alias.AliasList;
|
||||
import bits.BinaryMessage;
|
||||
import bits.BitSetFullException;
|
||||
import bits.ISyncDetectListener;
|
||||
import bits.MultiSyncPatternMatcher;
|
||||
import bits.SoftSyncDetector;
|
||||
import bits.SyncDetector;
|
||||
import dsp.psk.CQPSKDemodulator;
|
||||
import dsp.psk.CQPSKDemodulator2;
|
||||
import dsp.symbol.Dibit;
|
||||
import dsp.symbol.FrameSync;
|
||||
import edac.BCH_63_16_11;
|
||||
import edac.CRC;
|
||||
import edac.CRCP25;
|
||||
|
||||
public class P25MessageFramer implements Listener<Dibit>
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( P25MessageFramer.class );
|
||||
|
||||
/* Determines the threshold for sync pattern soft matching */
|
||||
private static final int SYNC_MATCH_THRESHOLD = 2;
|
||||
private static final int SYNC_IN_CALL_THRESHOLD = 4;
|
||||
|
||||
private SoftSyncDetector mPrimarySyncDetector = new SoftSyncDetector(
|
||||
FrameSync.P25_PHASE1_NORMAL.getSync(), SYNC_MATCH_THRESHOLD );
|
||||
|
||||
/* Costas Loop phase lock error correction values. A phase lock error of
|
||||
* 90 degrees requires a correction of 1/4 of the symbol rate (1200Hz). An
|
||||
* error of 180 degrees requires a correction of 1/2 of the symbol rate */
|
||||
public static final double SYMBOL_FREQUENCY = 2.0d * Math.PI * 4800.0d / 48000.0d;
|
||||
public static final double PHASE_CORRECTION_90_DEGREES = SYMBOL_FREQUENCY / 4.0d;
|
||||
public static final double PHASE_CORRECTION_180_DEGREES = SYMBOL_FREQUENCY / 2.0d;
|
||||
|
||||
private MultiSyncPatternMatcher mMatcher = new MultiSyncPatternMatcher( 48 );
|
||||
private ArrayList<P25MessageAssembler> mAssemblers =
|
||||
new ArrayList<P25MessageAssembler>();
|
||||
|
||||
public static final int TSBK_BEGIN = 64;
|
||||
public static final int TSBK_CRC_START = 144;
|
||||
public static final int TSBK_END = 260;
|
||||
public static final int TSBK_DECODED_END = 160;
|
||||
|
||||
public static final int PDU0_BEGIN = 64;
|
||||
public static final int PDU0_CRC_BEGIN = 144;
|
||||
public static final int PDU0_END = 260;
|
||||
public static final int PDU1_BEGIN = 160;
|
||||
public static final int PDU1_END = 356;
|
||||
public static final int PDU2_BEGIN = 256;
|
||||
public static final int PDU2_END = 452;
|
||||
public static final int PDU3_BEGIN = 352;
|
||||
public static final int PDU3_END = 548;
|
||||
public static final int PDU3_DECODED_END = 448;
|
||||
|
||||
private Listener<Message> mListener;
|
||||
private AliasList mAliasList;
|
||||
|
||||
private Trellis_1_2_Rate mHalfRate = new Trellis_1_2_Rate();
|
||||
private Trellis_3_4_Rate mThreeQuarterRate = new Trellis_3_4_Rate();
|
||||
private BCH_63_16_11 mNIDDecoder = new BCH_63_16_11();
|
||||
|
||||
/**
|
||||
* Constructs a P25 message framer to receive a stream of symbols and
|
||||
* detect the sync pattern then capture the following stream of symbols up
|
||||
* to the message length, and then broadcast that bit buffer to the registered
|
||||
* listener.
|
||||
*/
|
||||
public P25MessageFramer( AliasList aliasList )
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
|
||||
mPrimarySyncDetector.setListener( new ISyncDetectListener()
|
||||
{
|
||||
@Override
|
||||
public void syncDetected()
|
||||
{
|
||||
for( P25MessageAssembler assembler: mAssemblers )
|
||||
{
|
||||
if( !assembler.isActive() )
|
||||
{
|
||||
assembler.setActive( true );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
mMatcher.add( mPrimarySyncDetector );
|
||||
|
||||
/**
|
||||
* We use two message assemblers to catch any sync detections, so that
|
||||
* if we have a false trigger then we still have a chance to catch the
|
||||
* valid message. If we have two false triggers before the arrival of
|
||||
* the actual message sync, then the third or subsequent real or false
|
||||
* sync pattern will produce a debug message indicating no assemblers
|
||||
* are available..
|
||||
*/
|
||||
mAssemblers.add( new P25MessageAssembler() );
|
||||
mAssemblers.add( new P25MessageAssembler() );
|
||||
}
|
||||
|
||||
public P25MessageFramer( AliasList aliasList, CQPSKDemodulator demodulator )
|
||||
{
|
||||
this( aliasList );
|
||||
|
||||
if( demodulator != null )
|
||||
{
|
||||
/* For CQPSK, we include 3 additional sync detectors to watch for and
|
||||
* correction +/-90 and 180 degree costas loop phase lock errors */
|
||||
mMatcher.add( new CostasPhaseErrorDetector( FrameSync.P25_PHASE1_ERROR_90_CCW,
|
||||
demodulator, PHASE_CORRECTION_90_DEGREES ) );
|
||||
|
||||
mMatcher.add( new CostasPhaseErrorDetector( FrameSync.P25_PHASE1_ERROR_90_CW,
|
||||
demodulator, -PHASE_CORRECTION_90_DEGREES ) );
|
||||
|
||||
mMatcher.add( new CostasPhaseErrorDetector( FrameSync.P25_PHASE1_ERROR_180,
|
||||
demodulator, PHASE_CORRECTION_180_DEGREES ) );
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatch( Message message )
|
||||
{
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( message );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Dibit symbol )
|
||||
{
|
||||
for( P25MessageAssembler assembler: mAssemblers )
|
||||
{
|
||||
if( assembler.isActive() )
|
||||
{
|
||||
assembler.receive( symbol );
|
||||
|
||||
if( assembler.complete() )
|
||||
{
|
||||
assembler.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mMatcher.receive( symbol.getBit1(), symbol.getBit2() );
|
||||
}
|
||||
|
||||
public void setListener( Listener<Message> listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void removeListener( Listener<Message> listener )
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
private class P25MessageAssembler
|
||||
{
|
||||
/* Starting position of the status symbol counter is 24 symbols to
|
||||
* account for the 48-bit sync pattern which is not included in message */
|
||||
private int mStatusSymbolPointer = 24;
|
||||
private BinaryMessage mMessage;
|
||||
private int mMessageLength;
|
||||
private boolean mComplete = false;
|
||||
private boolean mActive = false;
|
||||
private DataUnitID mDUID = DataUnitID.NID;
|
||||
|
||||
public P25MessageAssembler()
|
||||
{
|
||||
mMessageLength = mDUID.getMessageLength();
|
||||
mMessage = new BinaryMessage( mMessageLength );
|
||||
reset();
|
||||
}
|
||||
|
||||
public void receive( Dibit dibit )
|
||||
{
|
||||
if( mActive )
|
||||
{
|
||||
if( mStatusSymbolPointer == 35 )
|
||||
{
|
||||
mStatusSymbolPointer = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
mStatusSymbolPointer++;
|
||||
|
||||
try
|
||||
{
|
||||
mMessage.add( dibit.getBit1() );
|
||||
mMessage.add( dibit.getBit2() );
|
||||
}
|
||||
catch( BitSetFullException e )
|
||||
{
|
||||
mComplete = true;
|
||||
}
|
||||
|
||||
/* Check the message for complete */
|
||||
if( mMessage.isFull() )
|
||||
{
|
||||
checkComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
mDUID = DataUnitID.NID;
|
||||
mMessage.setSize( mDUID.getMessageLength() );
|
||||
mMessage.clear();
|
||||
mStatusSymbolPointer = 24;
|
||||
mComplete = false;
|
||||
mActive = false;
|
||||
}
|
||||
|
||||
public void setActive( boolean active )
|
||||
{
|
||||
mActive = active;
|
||||
}
|
||||
|
||||
private void setDUID( DataUnitID id )
|
||||
{
|
||||
mDUID = id;
|
||||
mMessageLength = id.getMessageLength();
|
||||
mMessage.setSize( mMessageLength );
|
||||
}
|
||||
|
||||
private void checkComplete()
|
||||
{
|
||||
switch( mDUID )
|
||||
{
|
||||
case NID:
|
||||
mMessage = mNIDDecoder.correctNID( mMessage );
|
||||
|
||||
if( mMessage.getCRC() != CRC.FAILED_CRC )
|
||||
{
|
||||
int value = mMessage.getInt( P25Message.DUID );
|
||||
|
||||
DataUnitID duid = DataUnitID.fromValue( value );
|
||||
|
||||
if( duid != DataUnitID.UNKN )
|
||||
{
|
||||
setDUID( duid );
|
||||
}
|
||||
else
|
||||
{
|
||||
mComplete = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mComplete = true;
|
||||
}
|
||||
break;
|
||||
case HDU:
|
||||
mComplete = true;
|
||||
dispatch( new HDUMessage( mMessage.copy(), mDUID, mAliasList ) );
|
||||
|
||||
/* We're in a call now, lower the sync match threshold */
|
||||
mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD );
|
||||
break;
|
||||
case LDU1:
|
||||
mComplete = true;
|
||||
|
||||
LDU1Message ldu1 = new LDU1Message( mMessage.copy(),
|
||||
mDUID, mAliasList );
|
||||
|
||||
/* Convert the LDU1 message into a link control LDU1 message */
|
||||
dispatch( LDULCMessageFactory.getMessage( ldu1 ) );
|
||||
|
||||
/* We're in a call now, lower the sync match threshold */
|
||||
mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD );
|
||||
break;
|
||||
case LDU2:
|
||||
mComplete = true;
|
||||
dispatch( new LDU2Message( mMessage.copy(), mDUID, mAliasList ) );
|
||||
|
||||
/* We're in a call now, lower the sync match threshold */
|
||||
mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD );
|
||||
break;
|
||||
case PDU0:
|
||||
|
||||
/* Remove interleaving */
|
||||
P25Interleave.deinterleaveData( mMessage, PDU0_BEGIN, PDU0_END );
|
||||
|
||||
/* Remove trellis encoding - abort processing if we have an
|
||||
* unsuccessful decode due to excessive errors */
|
||||
if( mHalfRate.decode( mMessage, PDU0_BEGIN, PDU0_END ) )
|
||||
{
|
||||
mMessage = CRCP25.correctCCITT80( mMessage,
|
||||
PDU0_BEGIN, PDU0_CRC_BEGIN );
|
||||
|
||||
|
||||
if( mMessage.getCRC() != CRC.FAILED_CRC )
|
||||
{
|
||||
boolean confirmed = mMessage.get(
|
||||
PDUMessage.CONFIRMATION_REQUIRED_INDICATOR );
|
||||
|
||||
if( confirmed )
|
||||
{
|
||||
setDUID( DataUnitID.PDUC );
|
||||
}
|
||||
else
|
||||
{
|
||||
setDUID( DataUnitID.PDU1 );
|
||||
}
|
||||
|
||||
mMessage.setPointer( PDU1_BEGIN );
|
||||
}
|
||||
else
|
||||
{
|
||||
mComplete = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mComplete = true;
|
||||
}
|
||||
|
||||
/* Set sync match threshold to normal */
|
||||
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
|
||||
|
||||
break;
|
||||
case PDU1:
|
||||
/* Remove interleaving */
|
||||
P25Interleave.deinterleaveData( mMessage, PDU1_BEGIN, PDU1_END );
|
||||
|
||||
/* Remove trellis encoding - abort processing if we have an
|
||||
* unsuccessful decode due to excessive errors */
|
||||
if( mHalfRate.decode( mMessage, PDU1_BEGIN, PDU1_END ) )
|
||||
{
|
||||
if( mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW ) == 1 )
|
||||
{
|
||||
mMessage.setSize( PDU2_BEGIN );
|
||||
|
||||
PDUMessage pduMessage1 = PDUMessageFactory.getMessage(
|
||||
mMessage.copy(), DataUnitID.PDU1, mAliasList );
|
||||
|
||||
dispatch( pduMessage1 );
|
||||
|
||||
mComplete = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
setDUID( DataUnitID.PDU2 );
|
||||
|
||||
mMessage.setPointer( PDU2_BEGIN );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mComplete = true;
|
||||
}
|
||||
break;
|
||||
case PDU2:
|
||||
/* Remove interleaving */
|
||||
P25Interleave.deinterleaveData( mMessage, PDU2_BEGIN, PDU2_END );
|
||||
|
||||
/* Remove trellis encoding - abort processing if we have an
|
||||
* unsuccessful decode due to excessive errors */
|
||||
if( mHalfRate.decode( mMessage, PDU2_BEGIN, PDU2_END ) )
|
||||
{
|
||||
if( mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW ) == 2 )
|
||||
{
|
||||
mMessage.setSize( PDU3_BEGIN );
|
||||
|
||||
PDUMessage pduMessage2 = PDUMessageFactory.getMessage(
|
||||
mMessage.copy(), DataUnitID.PDU2, mAliasList );
|
||||
|
||||
dispatch( pduMessage2 );
|
||||
|
||||
mComplete = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
setDUID( DataUnitID.PDU3 );
|
||||
|
||||
mMessage.setPointer( PDU3_BEGIN );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mComplete = true;
|
||||
}
|
||||
|
||||
/* Set sync match threshold to normal */
|
||||
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
|
||||
break;
|
||||
case PDU3:
|
||||
/* Remove interleaving */
|
||||
P25Interleave.deinterleaveData( mMessage, PDU3_BEGIN, PDU3_END );
|
||||
|
||||
/* Remove trellis encoding - abort processing if we have an
|
||||
* unsuccessful decode due to excessive errors */
|
||||
if( mHalfRate.decode( mMessage, PDU3_BEGIN, PDU3_END ) )
|
||||
{
|
||||
mMessage.setSize( PDU3_DECODED_END );
|
||||
|
||||
PDUMessage pduMessage3 = PDUMessageFactory.getMessage(
|
||||
mMessage.copy(), DataUnitID.PDU3, mAliasList );
|
||||
|
||||
dispatch( pduMessage3 );
|
||||
}
|
||||
|
||||
mComplete = true;
|
||||
break;
|
||||
case PDUC:
|
||||
/* De-interleave the latest block*/
|
||||
P25Interleave.deinterleaveData( mMessage,
|
||||
mMessage.size() - 196, mMessage.size() );
|
||||
|
||||
/* Decode 3/4 rate convolutional encoding from latest block */
|
||||
if( mThreeQuarterRate.decode( mMessage,
|
||||
mMessage.size() - 196, mMessage.size() ) )
|
||||
{
|
||||
/* Resize the message and adjust the message pointer
|
||||
* to account for removing 48 + 4 parity bits */
|
||||
mMessage.setSize( mMessage.size() - 52 );
|
||||
mMessage.adjustPointer( -52 );
|
||||
|
||||
int blocks = mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW );
|
||||
|
||||
int current = ( mMessage.size() - 160 ) / 144;
|
||||
|
||||
if( current < blocks )
|
||||
{
|
||||
mMessage.setSize( mMessage.size() + 196 );
|
||||
mMessageLength = mMessage.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
PDUConfirmedMessage pducm =
|
||||
new PDUConfirmedMessage( mMessage.copy(),
|
||||
mAliasList );
|
||||
|
||||
/* Translate into correct subclass */
|
||||
dispatch( PDUConfirmedMessageFactory.getMessage( pducm ) );
|
||||
|
||||
mComplete = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
PDUConfirmedMessage pducm =
|
||||
new PDUConfirmedMessage( mMessage.copy(),
|
||||
mAliasList );
|
||||
|
||||
/* Translate into correct subclass */
|
||||
dispatch( PDUConfirmedMessageFactory.getMessage( pducm ) );
|
||||
|
||||
mComplete = true;
|
||||
}
|
||||
|
||||
/* Set sync match threshold to normal */
|
||||
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
|
||||
break;
|
||||
case TDU:
|
||||
dispatch( new TDUMessage( mMessage.copy(), mDUID, mAliasList ) );
|
||||
mComplete = true;
|
||||
|
||||
/* Set sync match threshold to normal */
|
||||
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
|
||||
break;
|
||||
case TDULC:
|
||||
TDULinkControlMessage tdulc = new TDULinkControlMessage(
|
||||
mMessage.copy(), mDUID, mAliasList );
|
||||
|
||||
/* Convert to an appropriate link control message */
|
||||
tdulc = TDULCMessageFactory.getMessage( tdulc );
|
||||
|
||||
dispatch( tdulc );
|
||||
mComplete = true;
|
||||
|
||||
/* Set sync match threshold to normal */
|
||||
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
|
||||
break;
|
||||
case TSBK1:
|
||||
/* Remove interleaving */
|
||||
P25Interleave.deinterleaveData( mMessage, TSBK_BEGIN, TSBK_END );
|
||||
|
||||
/* Remove trellis encoding - abort processing if we have an
|
||||
* unsuccessful decode due to excessive errors */
|
||||
|
||||
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
|
||||
{
|
||||
mMessage = CRCP25.correctCCITT80( mMessage,
|
||||
TSBK_BEGIN, TSBK_CRC_START );
|
||||
|
||||
if( mMessage.getCRC() != CRC.FAILED_CRC )
|
||||
{
|
||||
BinaryMessage tsbkBuffer1 = mMessage.copy();
|
||||
tsbkBuffer1.setSize( TSBK_DECODED_END );
|
||||
|
||||
TSBKMessage tsbkMessage1 = TSBKMessageFactory.getMessage(
|
||||
tsbkBuffer1, DataUnitID.TSBK1, mAliasList );
|
||||
|
||||
if( tsbkMessage1.isLastBlock() )
|
||||
{
|
||||
mComplete = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
setDUID( DataUnitID.TSBK2 );
|
||||
mMessage.setPointer( TSBK_BEGIN );
|
||||
}
|
||||
|
||||
dispatch( tsbkMessage1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
mComplete = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mComplete = true;
|
||||
}
|
||||
|
||||
/* Set sync match threshold to normal */
|
||||
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
|
||||
break;
|
||||
case TSBK2:
|
||||
/* Remove interleaving */
|
||||
P25Interleave.deinterleaveData( mMessage, TSBK_BEGIN, TSBK_END );
|
||||
|
||||
/* Remove trellis encoding - abort processing if we have an
|
||||
* unsuccessful decode due to excessive errors */
|
||||
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
|
||||
{
|
||||
mMessage = CRCP25.correctCCITT80( mMessage,
|
||||
TSBK_BEGIN, TSBK_CRC_START );
|
||||
|
||||
if( mMessage.getCRC() != CRC.FAILED_CRC )
|
||||
{
|
||||
BinaryMessage tsbkBuffer2 = mMessage.copy();
|
||||
tsbkBuffer2.setSize( TSBK_DECODED_END );
|
||||
|
||||
TSBKMessage tsbkMessage2 = TSBKMessageFactory.getMessage(
|
||||
tsbkBuffer2, DataUnitID.TSBK2, mAliasList );
|
||||
|
||||
if( tsbkMessage2.isLastBlock() )
|
||||
{
|
||||
mComplete = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
setDUID( DataUnitID.TSBK3 );
|
||||
mMessage.setPointer( TSBK_BEGIN );
|
||||
}
|
||||
|
||||
dispatch( tsbkMessage2 );
|
||||
}
|
||||
else
|
||||
{
|
||||
mComplete = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mComplete = true;
|
||||
}
|
||||
break;
|
||||
case TSBK3:
|
||||
/* Remove interleaving */
|
||||
P25Interleave.deinterleaveData( mMessage, TSBK_BEGIN, TSBK_END );
|
||||
|
||||
/* Remove trellis encoding - abort processing if we have an
|
||||
* unsuccessful decode due to excessive errors */
|
||||
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
|
||||
{
|
||||
mMessage = CRCP25.correctCCITT80( mMessage,
|
||||
TSBK_BEGIN, TSBK_CRC_START );
|
||||
|
||||
if( mMessage.getCRC() != CRC.FAILED_CRC )
|
||||
{
|
||||
BinaryMessage tsbkBuffer3 = mMessage.copy();
|
||||
tsbkBuffer3.setSize( TSBK_DECODED_END );
|
||||
|
||||
TSBKMessage tsbkMessage3 = TSBKMessageFactory.getMessage(
|
||||
tsbkBuffer3, DataUnitID.TSBK3, mAliasList );
|
||||
|
||||
dispatch( tsbkMessage3 );
|
||||
}
|
||||
}
|
||||
|
||||
mComplete = true;
|
||||
break;
|
||||
case UNKN:
|
||||
mComplete = true;
|
||||
dispatch( new P25Message( mMessage.copy(), mDUID, mAliasList ) );
|
||||
break;
|
||||
default:
|
||||
mComplete = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mMessage = null;
|
||||
mHalfRate.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag to indicate when this assembler has received all of the bits it
|
||||
* is looking for (ie message length), and should then be removed from
|
||||
* receiving any more bits
|
||||
*/
|
||||
public boolean complete()
|
||||
{
|
||||
return mComplete;
|
||||
}
|
||||
|
||||
public boolean isActive()
|
||||
{
|
||||
return mActive;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync pattern detector to listen for costas loop phase lock errors and
|
||||
* apply a phase correction to the costas loop so that we don't miss any
|
||||
* messages.
|
||||
*
|
||||
* When the costas loop locks with a +/- 90 degree or 180 degree phase
|
||||
* error, the slicer will incorrectly apply the symbol pattern rotated left
|
||||
* or right by the phase error. However, we can detect these rotated sync
|
||||
* patterns and apply immediate phase correction so that message processing
|
||||
* can continue.
|
||||
*/
|
||||
public class CostasPhaseErrorDetector extends SyncDetector
|
||||
{
|
||||
private CQPSKDemodulator mDemodulator;
|
||||
private double mCorrection;
|
||||
|
||||
public CostasPhaseErrorDetector( FrameSync frameSync,
|
||||
CQPSKDemodulator demodulator, double correction )
|
||||
{
|
||||
super( frameSync.getSync() );
|
||||
|
||||
mDemodulator = demodulator;
|
||||
|
||||
mCorrection = correction;
|
||||
|
||||
setListener( new ISyncDetectListener()
|
||||
{
|
||||
@Override
|
||||
public void syncDetected()
|
||||
{
|
||||
mDemodulator.correctPhaseError( mCorrection );
|
||||
|
||||
/* Since we detected a sync pattern, start a message assembler */
|
||||
for( P25MessageAssembler assembler: mAssemblers )
|
||||
{
|
||||
if( !assembler.isActive() )
|
||||
{
|
||||
assembler.setActive( true );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
98
src/module/decode/p25/P25MessageProcessor.java
Normal file
98
src/module/decode/p25/P25MessageProcessor.java
Normal file
@@ -0,0 +1,98 @@
|
||||
package module.decode.p25;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import message.Message;
|
||||
import module.decode.p25.message.IBandIdentifier;
|
||||
import module.decode.p25.message.IdentifierReceiver;
|
||||
import module.decode.p25.message.ldu.LDUMessage;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import alias.AliasList;
|
||||
|
||||
public class P25MessageProcessor implements Listener<Message>
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( P25MessageProcessor.class );
|
||||
|
||||
private Listener<Message> mMessageListener;
|
||||
|
||||
/* Map of up to 16 band identifiers per RFSS. These identifier update
|
||||
* messages are inserted into any message that conveys channel information
|
||||
* so that the uplink/downlink frequencies can be calculated */
|
||||
private HashMap<Integer,IBandIdentifier> mBandIdentifierMap =
|
||||
new HashMap<Integer,IBandIdentifier>();
|
||||
|
||||
private AliasList mAliasList;
|
||||
|
||||
public P25MessageProcessor( AliasList aliasList )
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Message message )
|
||||
{
|
||||
/**
|
||||
* Capture frequency band identifier messages and inject them into any
|
||||
* messages that require band information in order to calculate the
|
||||
* up-link and down-link frequencies for any numeric channel references
|
||||
* contained within the message.
|
||||
*/
|
||||
if( message.isValid() )
|
||||
{
|
||||
/* Insert band identifier update messages into channel-type messages */
|
||||
if( message instanceof IdentifierReceiver )
|
||||
{
|
||||
IdentifierReceiver receiver = (IdentifierReceiver)message;
|
||||
|
||||
int[] identifiers = receiver.getIdentifiers();
|
||||
|
||||
for( int identifier: identifiers )
|
||||
{
|
||||
receiver.setIdentifierMessage( identifier,
|
||||
mBandIdentifierMap.get( identifier ) );
|
||||
}
|
||||
}
|
||||
|
||||
/* Store band identifiers so that they can be injected into channel
|
||||
* type messages */
|
||||
if( message instanceof IBandIdentifier )
|
||||
{
|
||||
IBandIdentifier bandIdentifier = (IBandIdentifier)message;
|
||||
|
||||
mBandIdentifierMap.put( bandIdentifier.getIdentifier(),
|
||||
bandIdentifier );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast all valid messages and any LDU voice messages regardless if
|
||||
* they are valid or not, so that we don't miss any voice frames
|
||||
*/
|
||||
if( mMessageListener != null && message.isValid() || message instanceof LDUMessage )
|
||||
{
|
||||
mMessageListener.receive( message );
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mBandIdentifierMap.clear();
|
||||
mMessageListener = null;
|
||||
}
|
||||
|
||||
public void setMessageListener( Listener<Message> listener )
|
||||
{
|
||||
mMessageListener = listener;
|
||||
}
|
||||
|
||||
public void removeMessageListener()
|
||||
{
|
||||
mMessageListener = null;
|
||||
}
|
||||
}
|
||||
239
src/module/decode/p25/P25_C4FMDecoder.java
Normal file
239
src/module/decode/p25/P25_C4FMDecoder.java
Normal file
@@ -0,0 +1,239 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.p25;
|
||||
|
||||
import instrument.tap.Tap;
|
||||
import instrument.tap.stream.DibitTap;
|
||||
import instrument.tap.stream.FloatTap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.real.IRealBufferListener;
|
||||
import sample.real.RealBuffer;
|
||||
import sample.real.RealBufferToStreamConverter;
|
||||
import source.tuner.frequency.FrequencyCorrectionControl;
|
||||
import alias.AliasList;
|
||||
import controller.channel.Channel.ChannelType;
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.Window.WindowType;
|
||||
import dsp.filter.fir.real.RealFIRFilter_RB_RB;
|
||||
import dsp.gain.DirectGainControl;
|
||||
|
||||
public class P25_C4FMDecoder extends P25Decoder implements IRealBufferListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class );
|
||||
|
||||
/* Instrumentation Taps */
|
||||
private static final String INSTRUMENT_FILTER_OUTPUT = "Tap Point: Pre-Filter Output";
|
||||
private static final String INSTRUMENT_DGC_OUTPUT = "Tap Point: DGC Output";
|
||||
private static final String INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT = "Tap Point: Symbol Filter Output";
|
||||
private static final String INSTRUMENT_C4FM_SLICER_OUTPUT = "Tap Point: C4FM Slicer Output";
|
||||
|
||||
private final static int MAXIMUM_FREQUENCY_CORRECTION = 3000; //Hertz, +/-
|
||||
|
||||
private List<Tap> mAvailableTaps;
|
||||
private RealFIRFilter_RB_RB mC4FMPreFilter;
|
||||
private DirectGainControl mDGC;
|
||||
private RealBufferToStreamConverter mStreamConverter =
|
||||
new RealBufferToStreamConverter();
|
||||
private FrequencyCorrectionControl mFrequencyCorrectionControl;
|
||||
private C4FMSymbolFilter mSymbolFilter;
|
||||
private C4FMSlicer mC4FMSlicer;
|
||||
private P25MessageFramer mMessageFramer;
|
||||
|
||||
/**
|
||||
* P25 Phase 1 C4FM Decoder processes real buffers of un-filtered,
|
||||
* demodulated audio and produces decoded P25 Phase 1 Messages.
|
||||
*
|
||||
* Provides Frequency Control to steer external tuner source and incorporates
|
||||
* internal gain control.
|
||||
*
|
||||
* Note: use the P25AudioModule to convert the decoded messages into audio.
|
||||
*
|
||||
* @param aliasList - optional (can be null) list of alias values for network
|
||||
* infrastructure and subscriber identities that will be included in each
|
||||
* decoded message
|
||||
*/
|
||||
public P25_C4FMDecoder( AliasList aliasList, ChannelType channelType )
|
||||
{
|
||||
super( aliasList, channelType );
|
||||
|
||||
/* Filter demodulated sample buffers */
|
||||
mC4FMPreFilter = new RealFIRFilter_RB_RB( FilterFactory.getLowPass(
|
||||
48000, 6750, 7500, 60, WindowType.HANNING, true ), 1.0f );
|
||||
|
||||
/* Convert buffers to a sample stream so that the symbol filter can
|
||||
* provide fine-grained gain control over individual samples */
|
||||
mC4FMPreFilter.setListener( mStreamConverter );
|
||||
|
||||
/* Apply gain remotely controlled by the downstream symbol filter */
|
||||
mDGC = new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
|
||||
mStreamConverter.setListener( mDGC );
|
||||
|
||||
/* Issue tuned frequency correction commands, remotely controlled by the
|
||||
* downstream symbol filter */
|
||||
mFrequencyCorrectionControl = new FrequencyCorrectionControl(
|
||||
MAXIMUM_FREQUENCY_CORRECTION );
|
||||
|
||||
/* Shape gain and frequency offsets to optimize sample stream */
|
||||
mSymbolFilter = new C4FMSymbolFilter( mDGC, mFrequencyCorrectionControl );
|
||||
mDGC.setListener( mSymbolFilter );
|
||||
|
||||
/* Convert samples to symbols */
|
||||
mC4FMSlicer = new C4FMSlicer();
|
||||
mSymbolFilter.setListener( mC4FMSlicer );
|
||||
|
||||
/* Sync pattern detection and message construction */
|
||||
mMessageFramer = new P25MessageFramer( aliasList );
|
||||
mC4FMSlicer.addListener( mMessageFramer );
|
||||
|
||||
/* Process and broadcast messages */
|
||||
mMessageFramer.setListener( getMessageProcessor() );
|
||||
}
|
||||
|
||||
public Modulation getModulation()
|
||||
{
|
||||
return Modulation.C4FM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFrequencyCorrectionControl()
|
||||
{
|
||||
return mFrequencyCorrectionControl != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrequencyCorrectionControl getFrequencyCorrectionControl()
|
||||
{
|
||||
return mFrequencyCorrectionControl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<RealBuffer> getRealBufferListener()
|
||||
{
|
||||
return mC4FMPreFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instrumentation taps for monitoring internal processing
|
||||
*/
|
||||
@Override
|
||||
public List<Tap> getTaps()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<Tap>();
|
||||
|
||||
mAvailableTaps.add( new FloatTap( INSTRUMENT_FILTER_OUTPUT, 0, 1.0f ) );
|
||||
mAvailableTaps.add( new FloatTap( INSTRUMENT_DGC_OUTPUT, 0, 1.0f ) );
|
||||
|
||||
if( mSymbolFilter != null )
|
||||
{
|
||||
mAvailableTaps.addAll( mSymbolFilter.getTaps() );
|
||||
}
|
||||
|
||||
mAvailableTaps.add( new FloatTap( INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT, 0, 0.1f ) );
|
||||
mAvailableTaps.add( new DibitTap( INSTRUMENT_C4FM_SLICER_OUTPUT, 0, 0.1f ) );
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an instrumentation tap to monitor internal processing.
|
||||
*/
|
||||
@Override
|
||||
public void addTap( Tap tap )
|
||||
{
|
||||
if( mSymbolFilter != null )
|
||||
{
|
||||
mSymbolFilter.addTap( tap );
|
||||
}
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_FILTER_OUTPUT:
|
||||
FloatTap filterOutput = (FloatTap)tap;
|
||||
mStreamConverter.setListener( filterOutput );
|
||||
filterOutput.setListener( mDGC );
|
||||
break;
|
||||
case INSTRUMENT_DGC_OUTPUT:
|
||||
FloatTap dgcTap = (FloatTap)tap;
|
||||
mDGC.setListener( dgcTap );
|
||||
dgcTap.setListener( mSymbolFilter );
|
||||
break;
|
||||
case INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT:
|
||||
FloatTap symbolTap = (FloatTap)tap;
|
||||
mSymbolFilter.setListener( symbolTap );
|
||||
symbolTap.setListener( mC4FMSlicer );
|
||||
break;
|
||||
case INSTRUMENT_C4FM_SLICER_OUTPUT:
|
||||
DibitTap slicerTap = (DibitTap)tap;
|
||||
|
||||
if( mC4FMSlicer != null )
|
||||
{
|
||||
mC4FMSlicer.addListener( slicerTap );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException( "Unrecognized tap: " +
|
||||
tap.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the instrumentation tap.
|
||||
*/
|
||||
@Override
|
||||
public void removeTap( Tap tap )
|
||||
{
|
||||
if( mSymbolFilter != null )
|
||||
{
|
||||
mSymbolFilter.removeTap( tap );
|
||||
}
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_FILTER_OUTPUT:
|
||||
mStreamConverter.setListener( mDGC );
|
||||
break;
|
||||
case INSTRUMENT_DGC_OUTPUT:
|
||||
mDGC.setListener( mSymbolFilter );
|
||||
break;
|
||||
case INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT:
|
||||
mSymbolFilter.setListener( mC4FMSlicer );
|
||||
break;
|
||||
case INSTRUMENT_C4FM_SLICER_OUTPUT:
|
||||
DibitTap slicerTap = (DibitTap)tap;
|
||||
|
||||
if( mC4FMSlicer != null )
|
||||
{
|
||||
mC4FMSlicer.removeListener( slicerTap );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException( "Unrecognized tap: " +
|
||||
tap.getName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
205
src/module/decode/p25/P25_LSMDecoder.java
Normal file
205
src/module/decode/p25/P25_LSMDecoder.java
Normal file
@@ -0,0 +1,205 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.p25;
|
||||
|
||||
import instrument.tap.Tap;
|
||||
import instrument.tap.stream.ComplexSampleTap;
|
||||
import instrument.tap.stream.ComplexTap;
|
||||
import instrument.tap.stream.DibitTap;
|
||||
import instrument.tap.stream.FloatTap;
|
||||
import instrument.tap.stream.QPSKTap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import controller.channel.Channel.ChannelType;
|
||||
import sample.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import sample.complex.ComplexBufferToStreamConverter;
|
||||
import sample.complex.IComplexBufferListener;
|
||||
import source.tuner.frequency.FrequencyCorrectionControl;
|
||||
import alias.AliasList;
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.Window.WindowType;
|
||||
import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
|
||||
import dsp.gain.ComplexFeedForwardGainControl;
|
||||
import dsp.psk.CQPSKDemodulator;
|
||||
import dsp.psk.QPSKPolarSlicer;
|
||||
|
||||
public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( P25_LSMDecoder.class );
|
||||
|
||||
/* Instrumentation Taps */
|
||||
private static final String INSTRUMENT_BASEBAND_FILTER_OUTPUT = "Tap Point: Baseband Filter Output";
|
||||
private static final String INSTRUMENT_AGC_OUTPUT = "Tap Point: AGC Output";
|
||||
private static final String INSTRUMENT_QPSK_DEMODULATOR_OUTPUT = "Tap Point: CQPSK Demodulator Output";
|
||||
private static final String INSTRUMENT_CQPSK_SLICER_OUTPUT = "Tap Point: CQPSK Slicer Output";
|
||||
|
||||
private List<Tap> mAvailableTaps;
|
||||
|
||||
private ComplexFIRFilter_CB_CB mBasebandFilter;
|
||||
private ComplexBufferToStreamConverter mStreamConverter =
|
||||
new ComplexBufferToStreamConverter();
|
||||
private ComplexFeedForwardGainControl mAGC =
|
||||
new ComplexFeedForwardGainControl( 32 );
|
||||
private CQPSKDemodulator mCQPSKDemodulator = new CQPSKDemodulator();
|
||||
private QPSKPolarSlicer mCQPSKSlicer = new QPSKPolarSlicer();
|
||||
private P25MessageFramer mMessageFramer;
|
||||
|
||||
public P25_LSMDecoder( AliasList aliasList, ChannelType channelType )
|
||||
{
|
||||
super( aliasList, channelType );
|
||||
|
||||
mBasebandFilter = new ComplexFIRFilter_CB_CB( FilterFactory.getLowPass(
|
||||
48000, 7250, 8000, 60, WindowType.HANNING, true ), 1.0f );
|
||||
|
||||
mBasebandFilter.setListener( mStreamConverter );
|
||||
|
||||
mStreamConverter.setListener( mAGC );
|
||||
|
||||
mAGC.setListener( mCQPSKDemodulator );
|
||||
|
||||
mCQPSKDemodulator.setSymbolListener( mCQPSKSlicer );
|
||||
|
||||
mCQPSKSlicer.addListener( mMessageFramer );
|
||||
|
||||
mMessageFramer = new P25MessageFramer( aliasList, mCQPSKDemodulator );
|
||||
mCQPSKSlicer.addListener( mMessageFramer );
|
||||
|
||||
mMessageFramer.setListener( getMessageProcessor() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ComplexBuffer> getComplexBufferListener()
|
||||
{
|
||||
return mBasebandFilter;
|
||||
}
|
||||
|
||||
public Modulation getModulation()
|
||||
{
|
||||
return Modulation.CQPSK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFrequencyCorrectionControl()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrequencyCorrectionControl getFrequencyCorrectionControl()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of instrumentation taps for monitoring internal processing
|
||||
*/
|
||||
@Override
|
||||
public List<Tap> getTaps()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<Tap>();
|
||||
|
||||
mAvailableTaps.add( new ComplexTap( INSTRUMENT_BASEBAND_FILTER_OUTPUT, 0, 1.0f ) );
|
||||
mAvailableTaps.add( new ComplexTap( INSTRUMENT_AGC_OUTPUT, 0, 1.0f ) );
|
||||
mAvailableTaps.add( new QPSKTap( INSTRUMENT_QPSK_DEMODULATOR_OUTPUT, 0, 1.0f ) );
|
||||
mAvailableTaps.add( new DibitTap( INSTRUMENT_CQPSK_SLICER_OUTPUT, 0, 0.1f ) );
|
||||
|
||||
if( mCQPSKDemodulator != null )
|
||||
{
|
||||
mAvailableTaps.addAll( mCQPSKDemodulator.getTaps() );
|
||||
}
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the instrumentation tap
|
||||
*/
|
||||
@Override
|
||||
public void addTap( Tap tap )
|
||||
{
|
||||
if( mCQPSKDemodulator != null )
|
||||
{
|
||||
mCQPSKDemodulator.addTap( tap );
|
||||
}
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_BASEBAND_FILTER_OUTPUT:
|
||||
ComplexSampleTap baseband = (ComplexSampleTap)tap;
|
||||
mStreamConverter.setListener( baseband );
|
||||
baseband.setListener( mAGC );
|
||||
break;
|
||||
case INSTRUMENT_AGC_OUTPUT:
|
||||
ComplexSampleTap agcSymbol = (ComplexSampleTap)tap;
|
||||
mAGC.setListener( agcSymbol );
|
||||
agcSymbol.setListener( mCQPSKDemodulator );
|
||||
break;
|
||||
case INSTRUMENT_QPSK_DEMODULATOR_OUTPUT:
|
||||
QPSKTap qpsk = (QPSKTap)tap;
|
||||
mCQPSKDemodulator.setSymbolListener( qpsk );
|
||||
qpsk.setListener( mCQPSKSlicer );
|
||||
break;
|
||||
case INSTRUMENT_CQPSK_SLICER_OUTPUT:
|
||||
mCQPSKSlicer.addListener( (DibitTap)tap );
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException( "Unrecognized tap: " +
|
||||
tap.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the instrumentation tap
|
||||
*/
|
||||
@Override
|
||||
public void removeTap( Tap tap )
|
||||
{
|
||||
if( mCQPSKDemodulator != null )
|
||||
{
|
||||
mCQPSKDemodulator.removeTap( tap );
|
||||
}
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_BASEBAND_FILTER_OUTPUT:
|
||||
mStreamConverter.setListener( mAGC );
|
||||
break;
|
||||
case INSTRUMENT_AGC_OUTPUT:
|
||||
mAGC.setListener( mCQPSKDemodulator );
|
||||
break;
|
||||
case INSTRUMENT_QPSK_DEMODULATOR_OUTPUT:
|
||||
mCQPSKDemodulator.setSymbolListener( mCQPSKSlicer );
|
||||
break;
|
||||
case INSTRUMENT_CQPSK_SLICER_OUTPUT:
|
||||
mCQPSKSlicer.removeListener( (DibitTap)tap );
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException( "Unrecognized tap: " +
|
||||
tap.getName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
534
src/module/decode/p25/Trellis_1_2_Rate.java
Normal file
534
src/module/decode/p25/Trellis_1_2_Rate.java
Normal file
@@ -0,0 +1,534 @@
|
||||
package module.decode.p25;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import bits.BinaryMessage;
|
||||
import dsp.symbol.Dibit;
|
||||
|
||||
public class Trellis_1_2_Rate
|
||||
{
|
||||
public final static int MAX_ERROR_THRESHOLD = 7;
|
||||
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( Trellis_1_2_Rate.class );
|
||||
|
||||
private ArrayList<ConstellationNode> mConstellationNodes =
|
||||
new ArrayList<ConstellationNode>();
|
||||
|
||||
private static final int[][] CONSTELLATION_COSTS =
|
||||
{ { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 },
|
||||
{ 1,0,2,1,2,1,3,2,2,1,3,2,3,2,4,3 },
|
||||
{ 1,2,0,1,2,3,1,2,2,3,1,2,3,4,2,3 },
|
||||
{ 2,1,1,0,3,2,2,1,3,2,2,1,4,3,3,2 },
|
||||
{ 1,2,2,3,0,1,1,2,2,3,3,4,1,2,2,3 },
|
||||
{ 2,1,3,2,1,0,2,1,3,2,4,3,2,1,3,2 },
|
||||
{ 2,3,1,2,1,2,0,1,3,4,2,3,2,3,1,2 },
|
||||
{ 3,2,2,1,2,1,1,0,4,3,3,2,3,2,2,1 },
|
||||
{ 1,2,2,3,2,3,3,4,0,1,1,2,1,2,2,3 },
|
||||
{ 2,1,3,2,3,2,4,3,1,0,2,1,2,1,3,2 },
|
||||
{ 2,3,1,2,3,4,2,3,1,2,0,1,2,3,1,2 },
|
||||
{ 3,2,2,1,4,3,3,2,2,1,1,0,3,2,2,1 },
|
||||
{ 2,3,3,4,1,2,2,3,1,2,2,3,0,1,1,2 },
|
||||
{ 3,2,4,3,2,1,3,2,2,1,3,2,1,0,2,1 },
|
||||
{ 3,4,2,3,2,3,1,2,2,3,1,2,1,2,0,1 },
|
||||
{ 4,3,3,2,3,2,2,1,3,2,2,1,2,1,1,0 } };
|
||||
|
||||
public Trellis_1_2_Rate()
|
||||
{
|
||||
/* Create 49 x 4-bit constellation nodes to handle 196-bit blocks */
|
||||
ConstellationNode previous = null;
|
||||
|
||||
for( int x = 0; x < 49; x++ )
|
||||
{
|
||||
ConstellationNode node = new ConstellationNode();
|
||||
|
||||
if( previous != null )
|
||||
{
|
||||
previous.connect( node );
|
||||
}
|
||||
|
||||
previous = node;
|
||||
|
||||
mConstellationNodes.add( node );
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
for( ConstellationNode node: mConstellationNodes )
|
||||
{
|
||||
node.dispose();
|
||||
}
|
||||
|
||||
mConstellationNodes.clear();
|
||||
}
|
||||
|
||||
private String constellationsToString( String label )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( label );
|
||||
|
||||
for( ConstellationNode node: mConstellationNodes )
|
||||
{
|
||||
sb.append( " " + node.getConstellation().name() );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String inputsToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( " INPUTS: " );
|
||||
|
||||
for( ConstellationNode node: mConstellationNodes )
|
||||
{
|
||||
sb.append( " " );
|
||||
sb.append( ( node.getConstellation().getInput().getBit1() ? "1" : "0" ) );
|
||||
sb.append( ( node.getConstellation().getInput().getBit2() ? "1" : "0" ) );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public boolean decode( BinaryMessage message, int start, int end )
|
||||
{
|
||||
/* load each of the nodes with de-interleaved constellations */
|
||||
for( int index = 0; index < 49; index++ )
|
||||
{
|
||||
Constellation c = getConstellation( message, start + index * 4 );
|
||||
|
||||
mConstellationNodes.get( index ).setConstellation( c );
|
||||
}
|
||||
|
||||
/* test to see if constellations are correct - otherwise correct them */
|
||||
ConstellationNode firstNode = mConstellationNodes.get( 0 );
|
||||
|
||||
int errorCount = firstNode.getErrorCount();
|
||||
|
||||
if( errorCount > 0 )
|
||||
{
|
||||
if( errorCount < MAX_ERROR_THRESHOLD )
|
||||
{
|
||||
/* repair the errors */
|
||||
firstNode.correctTo( Dibit.D00_PLUS_1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* clear constellations from original message */
|
||||
message.clear( start, end );
|
||||
|
||||
/* replace with decoded values from the constellation nodes */
|
||||
for( int index = 0; index < 49; index++ )
|
||||
{
|
||||
ConstellationNode node = mConstellationNodes.get( index );
|
||||
|
||||
Dibit input = node.getInputDibit();
|
||||
|
||||
if( input.getBit1() )
|
||||
{
|
||||
message.set( start + ( index * 2 ) );
|
||||
}
|
||||
if( input.getBit2() )
|
||||
{
|
||||
message.set( start + ( index * 2 ) + 1 );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Constellation getConstellation( BinaryMessage message, int index )
|
||||
{
|
||||
int transmittedValue = 0;
|
||||
|
||||
for( int x = 0; x < 4; x++ )
|
||||
{
|
||||
if( message.get( index + x ) )
|
||||
{
|
||||
transmittedValue += ( 1 << ( 3 - x ) );
|
||||
}
|
||||
}
|
||||
|
||||
return Constellation.fromTransmittedValue( transmittedValue );
|
||||
}
|
||||
|
||||
public class ConstellationNode
|
||||
{
|
||||
private ConstellationNode mConnectedNode;
|
||||
private Constellation mConstellation;
|
||||
private boolean mCorrect;
|
||||
|
||||
public ConstellationNode()
|
||||
{
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mConnectedNode = null;
|
||||
}
|
||||
|
||||
public Constellation getConstellation()
|
||||
{
|
||||
return mConstellation;
|
||||
}
|
||||
|
||||
public Dibit getInputDibit()
|
||||
{
|
||||
return mConstellation.getInput();
|
||||
}
|
||||
|
||||
public boolean hasStateDibit( Dibit dibit )
|
||||
{
|
||||
return mConstellation.getState() == dibit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a correction down the line of connected nodes. Only nodes
|
||||
* with the mCorrect flag set to false will be corrected.
|
||||
*
|
||||
* Note: Assumes that the starting node's value is 0. Initiate the
|
||||
* corrective sequence by invoking this method with Dibit.D0 on the
|
||||
* first node.
|
||||
*
|
||||
* @param stateDibit to use for the left side.
|
||||
*/
|
||||
public void correctTo( Dibit stateDibit )
|
||||
{
|
||||
if( mCorrect && mConstellation.getState() == stateDibit )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if( isCurrentConnectionCorrect() )
|
||||
{
|
||||
mConstellation = Constellation.fromStateAndInputDibits( stateDibit,
|
||||
mConstellation.getInput() );
|
||||
|
||||
mCorrect = true;
|
||||
|
||||
if( mConnectedNode != null )
|
||||
{
|
||||
/* the next node's state was this node's input */
|
||||
mConnectedNode.correctTo( mConstellation.getInput() );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Constellation cheapestConstellation = mConstellation;
|
||||
|
||||
int cheapestCost = 100; //arbitrary
|
||||
|
||||
for( Dibit testInput: Dibit.values() )
|
||||
{
|
||||
Constellation testConstellation = Constellation
|
||||
.fromStateAndInputDibits( stateDibit, testInput );
|
||||
|
||||
int testCost = mConstellation.costTo( testConstellation ) +
|
||||
mConnectedNode.costTo( testInput );
|
||||
|
||||
if( testCost < cheapestCost )
|
||||
{
|
||||
cheapestCost = testCost;
|
||||
cheapestConstellation = testConstellation;
|
||||
}
|
||||
}
|
||||
|
||||
mConstellation = cheapestConstellation;
|
||||
|
||||
mConnectedNode.correctTo( mConstellation.getInput() );
|
||||
|
||||
mCorrect = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the cost (hamming distance) of using the argument as the
|
||||
* state dibit for the current node, and recursively finding the
|
||||
* cheapest corresponding input dibit.
|
||||
*
|
||||
* @param stateTest
|
||||
* @return
|
||||
*/
|
||||
public int costTo( Dibit stateTest )
|
||||
{
|
||||
if( isCurrentConnectionCorrect() )
|
||||
{
|
||||
Constellation c = Constellation.fromStateAndInputDibits(
|
||||
stateTest, mConstellation.getInput() );
|
||||
|
||||
return mConstellation.costTo( c );
|
||||
}
|
||||
else
|
||||
{
|
||||
int cheapestCost = 100; //arbitrary
|
||||
|
||||
for( Dibit inputTest: Dibit.values() )
|
||||
{
|
||||
Constellation constellationTest =
|
||||
Constellation.fromStateAndInputDibits( stateTest, inputTest );
|
||||
|
||||
int cost = mConnectedNode.costTo( inputTest ) +
|
||||
mConstellation.costTo( constellationTest );
|
||||
|
||||
if( cost < cheapestCost )
|
||||
{
|
||||
cheapestCost = cost;
|
||||
}
|
||||
}
|
||||
|
||||
return cheapestCost;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the immediate connection to the right is correct
|
||||
*/
|
||||
public boolean isCurrentConnectionCorrect()
|
||||
{
|
||||
return ( mConnectedNode == null ||
|
||||
mConstellation.getInput() == mConnectedNode.getStateDibit() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a recursive call to all nodes to the right, setting the
|
||||
* mCorrect flag on all nodes to true, if the node's connection to the
|
||||
* right node is correct and all nodes to the right are correct. Or,
|
||||
* false if this node's connection, or any node's connection to the
|
||||
* right is incorrect.
|
||||
*
|
||||
* @return - true - all node connections to the right are correct
|
||||
* false - one or more nodes to the right are incorrect
|
||||
*/
|
||||
public int getErrorCount()
|
||||
{
|
||||
if( mConnectedNode == null )
|
||||
{
|
||||
mCorrect = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
mCorrect = mConstellation.getInput() == mConnectedNode.getStateDibit();
|
||||
|
||||
return mConnectedNode.getErrorCount() + ( mCorrect ? 0 : 1 );
|
||||
}
|
||||
|
||||
public Dibit getStateDibit()
|
||||
{
|
||||
return mConstellation.getState();
|
||||
}
|
||||
|
||||
public void setConstellation( Constellation constellation )
|
||||
{
|
||||
mConstellation = constellation;
|
||||
mCorrect = false;
|
||||
}
|
||||
|
||||
public void connect( ConstellationNode node )
|
||||
{
|
||||
mConnectedNode = node;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Constellation
|
||||
{
|
||||
/* State (previous), Input (next), Transmit 1, Transmit 2 */
|
||||
CB( Dibit.D01_PLUS_3, Dibit.D01_PLUS_3, Dibit.D00_PLUS_1, Dibit.D00_PLUS_1, 0 ),
|
||||
CC( Dibit.D00_PLUS_1, Dibit.D10_MINUS_1, Dibit.D00_PLUS_1, Dibit.D01_PLUS_3, 1 ),
|
||||
C0( Dibit.D00_PLUS_1, Dibit.D00_PLUS_1, Dibit.D00_PLUS_1, Dibit.D10_MINUS_1, 2 ),
|
||||
C7( Dibit.D01_PLUS_3, Dibit.D11_MINUS_3, Dibit.D00_PLUS_1, Dibit.D11_MINUS_3, 3 ),
|
||||
CE( Dibit.D10_MINUS_1, Dibit.D11_MINUS_3, Dibit.D01_PLUS_3, Dibit.D00_PLUS_1, 4 ),
|
||||
C9( Dibit.D11_MINUS_3, Dibit.D00_PLUS_1, Dibit.D01_PLUS_3, Dibit.D01_PLUS_3, 5 ),
|
||||
C5( Dibit.D11_MINUS_3, Dibit.D10_MINUS_1, Dibit.D01_PLUS_3, Dibit.D10_MINUS_1, 6 ),
|
||||
C2( Dibit.D10_MINUS_1, Dibit.D01_PLUS_3, Dibit.D01_PLUS_3, Dibit.D11_MINUS_3, 7 ),
|
||||
CA( Dibit.D11_MINUS_3, Dibit.D11_MINUS_3, Dibit.D10_MINUS_1, Dibit.D00_PLUS_1, 8 ),
|
||||
CD( Dibit.D10_MINUS_1, Dibit.D00_PLUS_1, Dibit.D10_MINUS_1, Dibit.D01_PLUS_3, 9 ),
|
||||
C1( Dibit.D10_MINUS_1, Dibit.D10_MINUS_1, Dibit.D10_MINUS_1, Dibit.D10_MINUS_1, 10 ),
|
||||
C6( Dibit.D11_MINUS_3, Dibit.D01_PLUS_3, Dibit.D10_MINUS_1, Dibit.D11_MINUS_3, 11 ),
|
||||
CF( Dibit.D00_PLUS_1, Dibit.D01_PLUS_3, Dibit.D11_MINUS_3, Dibit.D00_PLUS_1, 12 ),
|
||||
C8( Dibit.D01_PLUS_3, Dibit.D10_MINUS_1, Dibit.D11_MINUS_3, Dibit.D01_PLUS_3, 13 ),
|
||||
C4( Dibit.D01_PLUS_3, Dibit.D00_PLUS_1, Dibit.D11_MINUS_3, Dibit.D10_MINUS_1, 14 ),
|
||||
C3( Dibit.D00_PLUS_1, Dibit.D11_MINUS_3, Dibit.D11_MINUS_3, Dibit.D11_MINUS_3, 15 );
|
||||
|
||||
private Dibit mStateDibit;
|
||||
private Dibit mInputDibit;
|
||||
private Dibit mTransmitDibit1;
|
||||
private Dibit mTransmitDibit2;
|
||||
private int mTransmittedValue;
|
||||
|
||||
private Constellation( Dibit stateDibit,
|
||||
Dibit inputDibit,
|
||||
Dibit transmitDibit1,
|
||||
Dibit transmitDibit2,
|
||||
int transmittedValue )
|
||||
{
|
||||
mStateDibit = stateDibit;
|
||||
mInputDibit = inputDibit;
|
||||
mTransmitDibit1 = transmitDibit1;
|
||||
mTransmitDibit2 = transmitDibit2;
|
||||
mTransmittedValue = transmittedValue;
|
||||
}
|
||||
|
||||
public Dibit getState()
|
||||
{
|
||||
return mStateDibit;
|
||||
}
|
||||
|
||||
public Dibit getInput()
|
||||
{
|
||||
return mInputDibit;
|
||||
}
|
||||
|
||||
public Dibit getTransmitDibit1()
|
||||
{
|
||||
return mTransmitDibit1;
|
||||
}
|
||||
|
||||
public Dibit getTransmitDibit2()
|
||||
{
|
||||
return mTransmitDibit2;
|
||||
}
|
||||
|
||||
public int getTransmittedValue()
|
||||
{
|
||||
return mTransmittedValue;
|
||||
}
|
||||
|
||||
public static Constellation fromTransmittedValue( int value )
|
||||
{
|
||||
if( 0 <= value && value <= 15 )
|
||||
{
|
||||
return values()[ value ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Constellation fromTransmittedDibits( Dibit left, Dibit right )
|
||||
{
|
||||
return fromTransmittedValue( left.getHighValue() + right.getLowValue() );
|
||||
}
|
||||
|
||||
public static Constellation fromStateAndInputDibits( Dibit state, Dibit input )
|
||||
{
|
||||
switch( state )
|
||||
{
|
||||
case D00_PLUS_1:
|
||||
switch( input )
|
||||
{
|
||||
case D00_PLUS_1:
|
||||
return C0;
|
||||
case D01_PLUS_3:
|
||||
return CF;
|
||||
case D10_MINUS_1:
|
||||
return CC;
|
||||
case D11_MINUS_3:
|
||||
return C3;
|
||||
}
|
||||
break;
|
||||
case D01_PLUS_3:
|
||||
switch( input )
|
||||
{
|
||||
case D00_PLUS_1:
|
||||
return C4;
|
||||
case D01_PLUS_3:
|
||||
return CB;
|
||||
case D10_MINUS_1:
|
||||
return C8;
|
||||
case D11_MINUS_3:
|
||||
return C7;
|
||||
}
|
||||
break;
|
||||
case D10_MINUS_1:
|
||||
switch( input )
|
||||
{
|
||||
case D00_PLUS_1:
|
||||
return CD;
|
||||
case D01_PLUS_3:
|
||||
return C2;
|
||||
case D10_MINUS_1:
|
||||
return C1;
|
||||
case D11_MINUS_3:
|
||||
return CE;
|
||||
}
|
||||
break;
|
||||
case D11_MINUS_3:
|
||||
switch( input )
|
||||
{
|
||||
case D00_PLUS_1:
|
||||
return C9;
|
||||
case D01_PLUS_3:
|
||||
return C6;
|
||||
case D10_MINUS_1:
|
||||
return C5;
|
||||
case D11_MINUS_3:
|
||||
return CA;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Should never get to here */
|
||||
return C0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cost or hamming distance to the other constellation using
|
||||
* the values from the constellation costs table
|
||||
*/
|
||||
public int costTo( Constellation other )
|
||||
{
|
||||
return CONSTELLATION_COSTS[ getTransmittedValue() ][ other.getTransmittedValue() ];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test harness
|
||||
*/
|
||||
public static void main( String[] args )
|
||||
{
|
||||
Random random = new Random();
|
||||
|
||||
BinaryMessage buffer = new BinaryMessage( 196 );
|
||||
|
||||
try
|
||||
{
|
||||
for( int x = 0; x < 196; x++ )
|
||||
{
|
||||
int number = random.nextInt( 2 );
|
||||
|
||||
if( number == 1 )
|
||||
{
|
||||
buffer.add( false );
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.add( true );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
mLog.debug( " BUFFER: " + buffer.toString() );
|
||||
|
||||
Trellis_1_2_Rate t = new Trellis_1_2_Rate();
|
||||
|
||||
t.decode( buffer, 0, 196 );
|
||||
mLog.debug( " DECODED: " + buffer.toString() );
|
||||
mLog.debug( "Finished!" );
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user