Initial code check in continued

This commit is contained in:
dennis.sheirer
2014-04-26 19:48:24 +00:00
parent 960a6f9952
commit 3dfedcf8d1
474 changed files with 76613 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
/*******************************************************************************
* 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 decode;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.SwingUtilities;
import controller.channel.AbstractChannelEditor;
import controller.channel.ChannelNode;
import decode.config.AuxDecodeConfiguration;
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 DecoderType mDecoderType;
public AuxDecoderCheckBox( DecoderType decoder )
{
super( decoder.getDisplayString() );
mDecoderType = decoder;
}
}
}

View File

@@ -0,0 +1,134 @@
/*******************************************************************************
* 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 decode;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.SwingUtilities;
import controller.channel.AbstractChannelEditor;
import controller.channel.ChannelNode;
import decode.config.DecodeConfigFactory;
import decode.config.DecodeConfiguration;
public class DecodeComponentEditor extends AbstractChannelEditor
{
private static final long serialVersionUID = 1L;
private JComboBox<DecoderType> mComboDecoders;
private DecodeEditor mEditor;
public DecodeComponentEditor( ChannelNode channelNode )
{
super( channelNode );
/**
* ComboBox: Decoders
*/
mComboDecoders = new JComboBox<DecoderType>();
DefaultComboBoxModel<DecoderType> model =
new DefaultComboBoxModel<DecoderType>();
for( DecoderType type: DecoderType.getAvailableDecoders() )
{
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 = DecodeEditorFactory.getPanel( config, mChannelNode );
add( mEditor, "span 2" );
revalidate();
repaint();
}
}
});
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()
{
mEditor.save();
//The component that calls save will invoke the change broadcast
mChannelNode.getChannel()
.setDecodeConfiguration( mEditor.getConfig(), false );
}
}

View File

@@ -0,0 +1,52 @@
/*******************************************************************************
* 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 decode;
import controller.Editor;
import decode.config.DecodeConfiguration;
public class DecodeEditor extends Editor
{
private static final long serialVersionUID = 1L;
protected DecodeConfiguration mConfig;
public DecodeEditor( DecodeConfiguration config )
{
mConfig = config;
}
public DecodeConfiguration getConfig()
{
return mConfig;
}
@Override
public void save()
{
// TODO Auto-generated method stub
}
@Override
public void reset()
{
// TODO Auto-generated method stub
}
}

View File

@@ -0,0 +1,59 @@
/*******************************************************************************
* 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 decode;
import controller.channel.ChannelNode;
import decode.config.DecodeConfiguration;
import decode.ltrnet.LTRNetEditor;
import decode.ltrstandard.LTRStandardEditor;
import decode.mpt1327.MPT1327Editor;
import decode.nbfm.NBFMEditor;
import decode.passport.PassportEditor;
public class DecodeEditorFactory
{
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 MPT1327Editor( config, channelNode );
break;
case PASSPORT:
configuredPanel = new PassportEditor( config );
break;
default:
configuredPanel = new DecodeEditor( config );
break;
}
return configuredPanel;
}
}

148
src/decode/Decoder.java Normal file
View File

@@ -0,0 +1,148 @@
/*******************************************************************************
* 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 decode;
import java.util.ArrayList;
import java.util.List;
import message.Message;
import sample.Broadcaster;
import sample.Listener;
import sample.complex.ComplexSample;
import source.Source.SampleType;
import eventlog.MessageEventLogger;
public abstract class Decoder implements Listener<Message>
{
private MessageEventLogger mEventLogger;
private Broadcaster<Message> mMessageBroadcaster = new Broadcaster<Message>();
private Broadcaster<Float> mFloatBroadcaster = new Broadcaster<Float>();
private Broadcaster<ComplexSample> mComplexBroadcaster =
new Broadcaster<ComplexSample>();
protected SampleType mSourceSampleType;
protected ArrayList<Decoder> mAuxiliaryDecoders = new ArrayList<Decoder>();
public Decoder( SampleType sampleType )
{
mSourceSampleType = sampleType;
}
/**
* Returns a float listener interface for connecting this decoder to a
* float stream provider
*/
public Listener<Float> getFloatReceiver()
{
return (Listener<Float>)mFloatBroadcaster;
}
/**
* Returns a complex listener interface for connecting this decoder to a
* float stream provider
*/
public Listener<ComplexSample> getComplexReceiver()
{
return (Listener<ComplexSample>)mComplexBroadcaster;
}
public abstract DecoderType getType();
public void addAuxiliaryDecoder( Decoder decoder )
{
mAuxiliaryDecoders.add( decoder );
mFloatBroadcaster.addListener( decoder.getFloatReceiver() );
decoder.addMessageListener( this );
}
/**
* Cleanup method. Invoke this method after stop and before delete.
*/
public void dispose()
{
for( Decoder auxiliaryDecoder: mAuxiliaryDecoders )
{
auxiliaryDecoder.dispose();
}
mAuxiliaryDecoders.clear();
mMessageBroadcaster.clear();
mComplexBroadcaster.clear();
mFloatBroadcaster.clear();
}
public List<Decoder> getAuxiliaryDecoders()
{
return mAuxiliaryDecoders;
}
public void setEventLogger( MessageEventLogger eventLogger )
{
mEventLogger = eventLogger;
}
public MessageEventLogger getEventLogger()
{
return mEventLogger;
}
@Override
public void receive( Message message )
{
send( message );
}
public void send( Message message )
{
if( mMessageBroadcaster != null )
{
mMessageBroadcaster.receive( message );
}
}
public void addMessageListener( Listener<Message> listener )
{
mMessageBroadcaster.addListener( listener );
}
public void removeMessageListener( Listener<Message> listener )
{
mMessageBroadcaster.removeListener( listener );
}
public void addFloatListener( Listener<Float> listener )
{
mFloatBroadcaster.addListener( listener );
}
public void removeFloatListener( Listener<Float> listener )
{
mFloatBroadcaster.removeListener( listener );
}
public void addComplexListener( Listener<ComplexSample> listener )
{
mComplexBroadcaster.addListener( listener );
}
public void removeComplexListener( Listener<ComplexSample> listener )
{
mComplexBroadcaster.removeListener( listener );
}
}

View File

@@ -0,0 +1,181 @@
/*******************************************************************************
* 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 decode;
import source.Source.SampleType;
import alias.AliasList;
import controller.channel.ChannelMap;
import controller.channel.ProcessingChain;
import controller.state.ChannelState;
import decode.config.AuxDecodeConfiguration;
import decode.config.DecodeConfigLTRNet;
import decode.config.DecodeConfigLTRStandard;
import decode.config.DecodeConfigMPT1327;
import decode.config.DecodeConfigNBFM;
import decode.config.DecodeConfiguration;
import decode.fleetsync2.Fleetsync2Decoder;
import decode.fleetsync2.FleetsyncChannelState;
import decode.ltrnet.LTRNetChannelState;
import decode.ltrnet.LTRNetDecoder;
import decode.ltrstandard.LTRChannelState;
import decode.ltrstandard.LTRStandardDecoder;
import decode.mdc1200.MDCChannelState;
import decode.mdc1200.MDCDecoder;
import decode.mpt1327.MPT1327ChannelState;
import decode.mpt1327.MPT1327Decoder;
import decode.nbfm.NBFMChannelState;
import decode.nbfm.NBFMConventionalDecoder;
import decode.passport.PassportChannelState;
import decode.passport.PassportDecoder;
public class DecoderFactory
{
/**
* Returns a fully configured decoder with embedded auxiliary decoders
* @param channelConfig
* @return
*/
public static Decoder getDecoder( ProcessingChain chain,
SampleType sampleType,
AliasList aliasList )
{
Decoder retVal = null;
DecodeConfiguration config =
chain.getChannel().getDecodeConfiguration();
switch( config.getDecoderType() )
{
case NBFM:
retVal = new NBFMConventionalDecoder( sampleType );
break;
case LTR_STANDARD:
DecodeConfigLTRStandard ltrStandardConfig =
(DecodeConfigLTRStandard)config;
retVal = new LTRStandardDecoder( sampleType, aliasList,
ltrStandardConfig.getMessageDirection() );
break;
case LTR_NET:
DecodeConfigLTRNet ltrNetConfig = (DecodeConfigLTRNet)config;
retVal = new LTRNetDecoder( sampleType, aliasList,
ltrNetConfig.getMessageDirection() );
break;
case MPT1327:
retVal = new MPT1327Decoder( sampleType, aliasList );
break;
case PASSPORT:
retVal = new PassportDecoder( sampleType, aliasList );
break;
default:
throw new IllegalArgumentException(
"Unknown decoder type [" +
config.getDecoderType().toString() + "]" );
}
if( retVal != null )
{
AuxDecodeConfiguration auxConfig =
chain.getChannel().getAuxDecodeConfiguration();
for( DecoderType auxDecoder: auxConfig.getAuxDecoders() )
{
switch( auxDecoder )
{
case FLEETSYNC2:
retVal.addAuxiliaryDecoder(
new Fleetsync2Decoder( aliasList ) );
break;
case MDC1200:
retVal.addAuxiliaryDecoder(
new MDCDecoder( aliasList ) );
break;
}
}
}
return retVal;
}
public static ChannelState getChannelStateNew( ProcessingChain chain,
AliasList aliasList )
{
ChannelState retVal = null;
DecodeConfiguration decodeConfig =
chain.getChannel().getDecodeConfiguration();
switch( decodeConfig.getDecoderType() )
{
case NBFM:
retVal = new NBFMChannelState( chain, aliasList );
DecodeConfigNBFM conv =
(DecodeConfigNBFM)decodeConfig;
break;
case LTR_STANDARD:
retVal = new LTRChannelState( chain, aliasList );
break;
case LTR_NET:
retVal = new LTRNetChannelState( chain, aliasList );
break;
case MPT1327:
DecodeConfigMPT1327 mpt = (DecodeConfigMPT1327)decodeConfig;
String channelMapName = mpt.getChannelMapName();
ChannelMap map = chain.getResourceManager().getPlaylistManager()
.getPlayist().getChannelMapList()
.getChannelMap( channelMapName );
retVal = new MPT1327ChannelState( chain, aliasList, map );
break;
case PASSPORT:
retVal = new PassportChannelState( chain, aliasList );
break;
default:
break;
}
if( retVal != null )
{
AuxDecodeConfiguration auxConfig =
chain.getChannel().getAuxDecodeConfiguration();
for( DecoderType auxDecoder: auxConfig.getAuxDecoders() )
{
switch( auxDecoder )
{
case FLEETSYNC2:
retVal.addAuxChannelState(
new FleetsyncChannelState( retVal ) );
break;
case MDC1200:
retVal.addAuxChannelState(
new MDCChannelState( retVal ) );
break;
}
}
}
return retVal;
}
}

110
src/decode/DecoderType.java Normal file
View File

@@ -0,0 +1,110 @@
/*******************************************************************************
* 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 decode;
import java.util.ArrayList;
import java.util.EnumSet;
/**
* Enumeration of decoder types
*/
public enum DecoderType
{
FLEETSYNC2( "Fleetsync II", "Fsync2", "images/fm", 12500 ),
LTR_STANDARD( "LTR-Standard", "LTR", "images/ltr_standard", 10000 ),
LTR_NET( "LTR-Net", "LTR-Net", "images/ltr_net", 10000 ),
MDC1200( "MDC1200", "MDC1200", "images/fm", 12500 ),
MPT1327( "MPT1327", "MPT1327", "images/mpt1327", 12500 ),
NBFM( "NBFM", "NBFM", "images/fm", 12500 ),
PASSPORT( "Passport", "Passport", "images/passport", 12500 );
private String mDisplayString;
private String mShortDisplayString;
private String mIconPrefix;
private int mChannelBandwidth;
DecoderType( String displayString, String shortDisplayString,
String iconPrefix, int bandwidth )
{
mDisplayString = displayString;
mShortDisplayString = shortDisplayString;
mIconPrefix = iconPrefix;
mChannelBandwidth = bandwidth;
}
/**
* Primary decoders
*/
public static EnumSet<DecoderType> getAvailableDecoders()
{
return EnumSet.of( DecoderType.LTR_NET,
DecoderType.LTR_STANDARD,
DecoderType.MPT1327,
DecoderType.NBFM,
DecoderType.PASSPORT );
}
/**
* Decoders that can be used in the viewer application
*/
public static EnumSet<DecoderType> getInstrumentableDecoders()
{
return EnumSet.of( DecoderType.FLEETSYNC2,
DecoderType.MDC1200,
DecoderType.MPT1327 );
}
/**
* Returns listing of auxiliary decoders.
*/
public static ArrayList<DecoderType> getAuxDecoders()
{
ArrayList<DecoderType> decoders = new ArrayList<DecoderType>();
decoders.add( DecoderType.FLEETSYNC2 );
decoders.add( DecoderType.MDC1200 );
return decoders;
}
public String getDisplayString()
{
return mDisplayString;
}
public String getShortDisplayString()
{
return mShortDisplayString;
}
public String getIconPrefix()
{
return mIconPrefix;
}
public int getChannelBandwidth()
{
return mChannelBandwidth;
}
@Override
public String toString()
{
return mDisplayString;
}
}

View 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 decode.config;
import java.util.ArrayList;
import javax.xml.bind.annotation.XmlElement;
import controller.config.Configuration;
import decode.DecoderType;
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();
}
}

View File

@@ -0,0 +1,58 @@
/*******************************************************************************
* 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 decode.config;
import decode.DecoderType;
public class DecodeConfigFactory
{
public static DecodeConfiguration getDefaultDecodeConfiguration()
{
return getDecodeConfiguration( DecoderType.NBFM );
}
public static DecodeConfiguration
getDecodeConfiguration( DecoderType decoder )
{
DecodeConfiguration retVal;
switch( decoder )
{
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;
default:
throw new IllegalArgumentException( "DecodeConfigFactory - "
+ "unknown decoder type [" + decoder.toString() + "]" );
}
return retVal;
}
}

View File

@@ -0,0 +1,44 @@
/*******************************************************************************
* 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 decode.config;
import javax.xml.bind.annotation.XmlAttribute;
import message.MessageDirection;
import decode.DecoderType;
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;
}
}

View File

@@ -0,0 +1,44 @@
/*******************************************************************************
* 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 decode.config;
import javax.xml.bind.annotation.XmlAttribute;
import message.MessageDirection;
import decode.DecoderType;
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;
}
}

View File

@@ -0,0 +1,43 @@
/*******************************************************************************
* 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 decode.config;
import javax.xml.bind.annotation.XmlElement;
import decode.DecoderType;
public class DecodeConfigMPT1327 extends DecodeConfiguration
{
private String mChannelMapName;
public DecodeConfigMPT1327()
{
super( DecoderType.MPT1327 );
}
@XmlElement
public String getChannelMapName()
{
return mChannelMapName;
}
public void setChannelMapName( String name )
{
mChannelMapName = name;
}
}

View File

@@ -0,0 +1,28 @@
/*******************************************************************************
* 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 decode.config;
import decode.DecoderType;
public class DecodeConfigNBFM extends DecodeConfiguration
{
public DecodeConfigNBFM()
{
super( DecoderType.NBFM );
}
}

View File

@@ -0,0 +1,28 @@
/*******************************************************************************
* 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 decode.config;
import decode.DecoderType;
public class DecodeConfigPassport extends DecodeConfiguration
{
public DecodeConfigPassport()
{
super( DecoderType.PASSPORT );
}
}

View File

@@ -0,0 +1,50 @@
/*******************************************************************************
* 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 decode.config;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import controller.config.Configuration;
import decode.DecoderType;
@XmlSeeAlso( { DecodeConfigNBFM.class,
DecodeConfigLTRNet.class,
DecodeConfigLTRStandard.class,
DecodeConfigMPT1327.class,
DecodeConfigPassport.class } )
@XmlRootElement( name = "decode_configuration" )
public abstract class DecodeConfiguration extends Configuration
{
private DecoderType mDecoderType = DecoderType.NBFM;
public DecodeConfiguration()
{
this( DecoderType.NBFM );
}
public DecodeConfiguration( DecoderType type )
{
mDecoderType = type;
}
public DecoderType getDecoderType()
{
return mDecoderType;
}
}

View File

@@ -0,0 +1,175 @@
/*******************************************************************************
* 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 decode.fleetsync1;
import java.text.SimpleDateFormat;
import java.util.Date;
import map.Plottable;
import message.Message;
import alias.Alias;
import bits.BitSetBuffer;
public class Fleetsync1Message extends Message
{
private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
private BitSetBuffer mMessage;
public Fleetsync1Message( BitSetBuffer 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;
}
}

View File

@@ -0,0 +1,49 @@
/*******************************************************************************
* 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 decode.fleetsync1;
import log.Log;
import message.Message;
import sample.Broadcaster;
import sample.Listener;
import bits.BitSetBuffer;
public class Fleetsync1MessageProcessor implements Listener<BitSetBuffer>
{
private Broadcaster<Message> mBroadcaster = new Broadcaster<Message>();
@Override
public void receive( BitSetBuffer buffer )
{
Fleetsync1Message message = new Fleetsync1Message( buffer );
Log.info( message.toString() );
mBroadcaster.receive( message );
}
public void addMessageListener( Listener<Message> listener )
{
mBroadcaster.addListener( listener );
}
public void removeMessageListener( Listener<Message> listener )
{
mBroadcaster.removeListener( listener );
}
}

View File

@@ -0,0 +1,184 @@
/*******************************************************************************
* 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 decode.fleetsync2;
import instrument.Instrumentable;
import instrument.tap.Tap;
import instrument.tap.stream.BinaryTap;
import instrument.tap.stream.FloatTap;
import java.util.ArrayList;
import java.util.List;
import sample.Listener;
import source.Source.SampleType;
import alias.AliasList;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
import decode.DecoderType;
import dsp.filter.Filters;
import dsp.filter.FloatFIRFilter;
import dsp.filter.FloatHalfBandFilter;
import dsp.fsk.FSK2Decoder;
import dsp.fsk.FSK2Decoder.Output;
/**
* Fleetsync II Decoder - 1200 baud 2FSK decoder that can process 48k sample rate
* complex or floating point samples and output fully framed Fleetsync II
* messages
*/
public class Fleetsync2Decoder extends Decoder implements 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_INPUT =
"Tap Point: Float Input";
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 FloatHalfBandFilter mDecimationFilter;
private FloatFIRFilter mBandPassFilter;
private MessageFramer mMessageFramer;
private Fleetsync2MessageProcessor mMessageProcessor;
public Fleetsync2Decoder( AliasList aliasList )
{
super( SampleType.FLOAT );
mDecimationFilter = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
addFloatListener( mDecimationFilter );
mBandPassFilter = new FloatFIRFilter(
Filters.FIRBP_1200FSK_24000FS.getCoefficients(), 1.02 );
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.addMessageListener( this );
}
public void dispose()
{
super.dispose();
mDecimationFilter.dispose();
mBandPassFilter.dispose();
mFSKDecoder.dispose();
mMessageFramer.dispose();
mMessageProcessor.dispose();
}
@Override
public DecoderType getType()
{
return DecoderType.FLEETSYNC2;
}
/**
* Returns a float listener interface for connecting this decoder to a
* float stream provider
*/
public Listener<Float> getFloatReceiver()
{
return (Listener<Float>)mDecimationFilter;
}
@Override
public List<Tap> getTaps()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
mAvailableTaps.add( new FloatTap( INSTRUMENT_INPUT, 0, 1.0f ) );
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_INPUT:
FloatTap inputTap = (FloatTap)tap;
addFloatListener( inputTap );
break;
case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
FloatTap bpTap = (FloatTap)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_INPUT:
FloatTap inputTap = (FloatTap)tap;
removeFloatListener( inputTap );
break;
case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
mBandPassFilter.setListener( mFSKDecoder );
break;
case INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER:
mFSKDecoder.setListener( mMessageFramer );
break;
}
}
}

View File

@@ -0,0 +1,59 @@
/*******************************************************************************
* 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 decode.fleetsync2;
import message.Message;
import sample.Broadcaster;
import sample.Listener;
import alias.AliasList;
import bits.BitSetBuffer;
public class Fleetsync2MessageProcessor implements Listener<BitSetBuffer>
{
private Broadcaster<Message> mBroadcaster = new Broadcaster<Message>();
private AliasList mAliasList;
public Fleetsync2MessageProcessor( AliasList list )
{
mAliasList = list;
}
public void dispose()
{
mBroadcaster.dispose();
}
@Override
public void receive( BitSetBuffer buffer )
{
FleetsyncMessage message = new FleetsyncMessage( buffer, mAliasList );
mBroadcaster.receive( message );
}
public void addMessageListener( Listener<Message> listener )
{
mBroadcaster.addListener( listener );
}
public void removeMessageListener( Listener<Message> listener )
{
mBroadcaster.removeListener( listener );
}
}

View File

@@ -0,0 +1,171 @@
/*******************************************************************************
* 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 decode.fleetsync2;
import java.util.Iterator;
import java.util.TreeSet;
import message.Message;
import sample.Listener;
import alias.Alias;
import alias.AliasList;
import controller.activity.ActivitySummaryProvider;
public class FleetsyncActivitySummary implements ActivitySummaryProvider,
Listener<Message>
{
private AliasList mAliasList;
private TreeSet<String> mIdents = new TreeSet<String>();
private TreeSet<String> mEmergencyIdents = new TreeSet<String>();
/**
* Compiles a summary of active talkgroups, unique radio ids encountered
* in the decoded messages. Talkgroups have to be heard a minimum of twice
* to be considered valid ... in order to weed out the invalid ones.
*
* Returns a textual summary of the activity
*/
public FleetsyncActivitySummary( AliasList list )
{
mAliasList = list;
}
public void dispose()
{
mIdents.clear();
mEmergencyIdents.clear();
}
@Override
public void receive( Message message )
{
if( message instanceof FleetsyncMessage )
{
FleetsyncMessage fs = ((FleetsyncMessage)message);
if( fs.isValid() )
{
String from = fs.getFromID();
mIdents.add( from );
String to = fs.getToID();
if( !to.startsWith( "xxx" ))
{
mIdents.add( to );
}
}
FleetsyncMessageType type = fs.getMessageType();
switch( type )
{
case EMERGENCY:
case LONE_WORKER_EMERGENCY:
mEmergencyIdents.add( fs.getFromID() );
break;
}
}
}
@Override
public String getSummary()
{
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 ) );
sb.append( " " );
Alias alias = mAliasList.getFleetsyncAlias( ident );
if( alias != null )
{
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 ) );
sb.append( " " );
Alias alias = mAliasList.getFleetsyncAlias( ident );
if( alias != null )
{
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;
}
}
}

View File

@@ -0,0 +1,188 @@
/*******************************************************************************
* 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 decode.fleetsync2;
import alias.Alias;
import alias.AliasList;
import controller.activity.CallEvent;
import decode.DecoderType;
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 int getChannel()
{
return 0;
}
@Override
public int 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.ACKNOWLEDGE;
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();
}
}
}

View 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 decode.fleetsync2;
import message.Message;
import alias.Alias;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
import controller.state.ChannelState.ChangedAttribute;
import controller.state.ChannelState.State;
public class FleetsyncChannelState extends AuxChannelState
{
private static final String PROTOCOL = "FSync II";
private String mFleetFrom;
private Alias mFleetFromAlias;
private String mFleetTo;
private Alias mFleetToAlias;
private String mMessage;
private String mMessageType;
private FleetsyncActivitySummary mActivitySummary;
public FleetsyncChannelState( ChannelState parentState )
{
super( parentState );
mActivitySummary =
new FleetsyncActivitySummary( parentState.getAliasList() );
}
@Override
public void reset()
{
setFleetIDFrom( null );
setFleetIDFromAlias( null );
setFleetIDTo( null );
setFleetIDToAlias( null );
setMessage( null );
setMessageType( null );
}
@Override
public void fade()
{
}
@Override
public void receive( Message message )
{
if( message instanceof FleetsyncMessage )
{
mActivitySummary.receive( message );
FleetsyncMessage fleetsync = (FleetsyncMessage)message;
setFleetIDFrom( fleetsync.getFromID() );
setFleetIDFromAlias( fleetsync.getFromIDAlias() );
FleetsyncMessageType type = fleetsync.getMessageType();
if( type != FleetsyncMessageType.ANI )
{
setFleetIDTo( fleetsync.getToID() );
setFleetIDToAlias( fleetsync.getToIDAlias() );
}
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;
default:
break;
}
/* Set the state to CALL on the parent channel state, so that the
* parent state timer can track turning off any updates we apply */
mParentChannelState.setState( State.CALL );
FleetsyncCallEvent fsCallEvent =
FleetsyncCallEvent.getFleetsync2Event( fleetsync );
fsCallEvent.setAliasList( mParentChannelState.getAliasList() );
mParentChannelState.receiveCallEvent( fsCallEvent );
}
}
public String getFleetIDFrom()
{
return mFleetFrom;
}
public void setFleetIDFrom( String from )
{
mFleetFrom = from;
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
}
public Alias getFleetIDFromAlias()
{
return mFleetFromAlias;
}
public void setFleetIDFromAlias( Alias alias )
{
mFleetFromAlias = alias;
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
}
public String getFleetIDTo()
{
return mFleetTo;
}
public void setFleetIDTo( String to )
{
mFleetTo = to;
broadcastChange( ChangedAttribute.TO_TALKGROUP );
}
public Alias getFleetIDToAlias()
{
return mFleetToAlias;
}
public void setFleetIDToAlias( Alias alias )
{
mFleetToAlias = alias;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
public String getMessage()
{
return mMessage;
}
public void setMessage( String message )
{
mMessage = message;
broadcastChange( ChangedAttribute.MESSAGE );
}
public String getMessageType()
{
return mMessageType;
}
public void setMessageType( String type )
{
mMessageType = type;
broadcastChange( ChangedAttribute.MESSAGE_TYPE );
}
@Override
public String getActivitySummary()
{
return mActivitySummary.getSummary();
}
}

View File

@@ -0,0 +1,863 @@
/*******************************************************************************
* 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 decode.fleetsync2;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import map.Plottable;
import message.Message;
import org.jdesktop.swingx.mapviewer.GeoPosition;
import alias.Alias;
import alias.AliasList;
import bits.BitSetBuffer;
import crc.CRC;
import crc.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 BitSetBuffer mMessage;
private CRC[] mCRC = new CRC[ 8 ];
private AliasList mAliasList;
public FleetsyncMessage( BitSetBuffer 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;
}
}
}

View 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 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;
}
}

View File

@@ -0,0 +1,167 @@
/*******************************************************************************
* 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 decode.fleetsync2;
import java.awt.EventQueue;
import javax.swing.JLabel;
import net.miginfocom.swing.MigLayout;
import settings.ColorSetting;
import settings.ColorSetting.ColorSettingName;
import settings.Setting;
import settings.SettingsManager;
import controller.state.AuxStatePanel;
import controller.state.ChannelState.ChangedAttribute;
public class FleetsyncPanel extends AuxStatePanel
{
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 FleetsyncPanel( SettingsManager settingsManager,
FleetsyncChannelState state )
{
super( settingsManager, state );
init();
}
public void dispose()
{
super.dispose();
}
public FleetsyncChannelState getState()
{
return (FleetsyncChannelState)mAuxChannelState;
}
private void init()
{
setOpaque( false ); //Use the parent panel's background color
setLayout( new MigLayout( "insets 1 0 0 0", "[grow,fill]", "[]0[]0[]") );
addLabel( mFromLabel, false );
addLabel( mFrom, false );
addLabel( mFromAlias, true );
addLabel( mToLabel, false );
addLabel( mTo, false );
addLabel( mToAlias, true );
addLabel( mProtocol, false );
addLabel( mMessageType, false );
addLabel( mMessage, true );
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
switch( changedAttribute )
{
case FROM_TALKGROUP:
String from = getState().getFleetIDFrom();
if( from != null )
{
mFromLabel.setText( "FM:" );
}
else
{
mFromLabel.setText( " " );
}
mFrom.setText( from );
break;
case FROM_TALKGROUP_ALIAS:
updateAliasLabel( mFromAlias,
getState().getFleetIDFromAlias() );
break;
case TO_TALKGROUP:
String to = getState().getFleetIDTo();
if( to != null )
{
mToLabel.setText( "TO:" );
}
else
{
mToLabel.setText( " " );
}
mTo.setText( to );
break;
case TO_TALKGROUP_ALIAS:
updateAliasLabel( mToAlias,
getState().getFleetIDToAlias() );
break;
case MESSAGE:
mMessage.setText( getState().getMessage() );
break;
case MESSAGE_TYPE:
mMessageType.setText( getState().getMessageType() );
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() );
}
}
}
}

View File

@@ -0,0 +1,184 @@
/*******************************************************************************
* 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 decode.ltrnet;
import alias.Alias;
import alias.AliasList;
import controller.activity.CallEvent;
import decode.DecoderType;
public class LTRCallEvent extends CallEvent
{
private int mChannel;
private int mFrequency;
public LTRCallEvent( CallEventType callEventType,
AliasList aliasList,
String fromID,
String toID,
int channel,
int frequency,
String details )
{
super( DecoderType.LTR_STANDARD, 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()
{
if( mCallEventType == CallEventType.CALL_START ||
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 int getChannel()
{
return mChannel;
}
@Override
public int 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 int mChannel;
private int 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( int channel )
{
mChannel = channel;
return this;
}
public Builder details( String details )
{
mDetails = details;
return this;
}
public Builder frequency( int 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 );
}
}

View File

@@ -0,0 +1,424 @@
/*******************************************************************************
* 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 decode.ltrnet;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.TreeSet;
import message.Message;
import alias.Alias;
import alias.AliasList;
import controller.activity.ActivitySummaryProvider;
public class LTRNetActivitySummary implements ActivitySummaryProvider
{
private static final int sINT_NULL_VALUE = -1;
private DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000");
private AliasList mAliasList;
private HashSet<String> mTalkgroupsFirstHeard = new HashSet<String>();
private TreeSet<String> mTalkgroups = new TreeSet<String>();
private TreeSet<String> mESNs = new TreeSet<String>();
private TreeSet<Integer> mUniqueIDs = new TreeSet<Integer>();
private TreeSet<Integer> mNeighborIDs = new TreeSet<Integer>();
private TreeSet<Integer> mSiteIDs = new TreeSet<Integer>();
private HashMap<Integer,Double> mReceiveFrequencies =
new HashMap<Integer,Double>();
private HashMap<Integer,Double> mTransmitFrequencies =
new HashMap<Integer,Double>();
private int mMonitoredLCN;
/**
* Compiles a summary of active talkgroups, unique radio ids encountered
* in the decoded messages. Talkgroups have to be heard a minimum of twice
* to be considered valid ... in order to weed out the invalid ones.
*
* Returns a textual summary of the activity
*/
public LTRNetActivitySummary( AliasList list )
{
mAliasList = list;
}
@Override
public void receive( Message message )
{
if( message instanceof LTRNetOSWMessage )
{
LTRNetOSWMessage ltr = ((LTRNetOSWMessage)message);
if( ltr.isValid() )
{
switch( ltr.getMessageType() )
{
case CA_STRT:
case CA_ENDD:
String talkgroup = ltr.getTalkgroupID( false );
if( mTalkgroupsFirstHeard.contains( talkgroup ) )
{
mTalkgroups.add( talkgroup );
}
else
{
mTalkgroupsFirstHeard.add( talkgroup );
}
break;
case SY_IDLE:
mMonitoredLCN = ltr.getChannel();
break;
case MA_CHNH:
break;
case MA_CHNL:
break;
case FQ_RXHI:
mReceiveFrequencies.put( ltr.getHomeRepeater(),
ltr.getFrequency() );
break;
case FQ_RXLO:
break;
case FQ_TXHI:
mTransmitFrequencies.put( ltr.getHomeRepeater(),
ltr.getFrequency() );
break;
case FQ_TXLO:
break;
case ID_ESNH:
break;
case ID_ESNL:
break;
case ID_NBOR:
int neighborID = ltr.getNeighborID();
if( neighborID != sINT_NULL_VALUE )
{
mNeighborIDs.add( neighborID );
}
break;
case ID_UNIQ:
int uniqueid = ltr.getRadioUniqueID();
if( uniqueid != sINT_NULL_VALUE )
{
mUniqueIDs.add( uniqueid );
}
break;
case ID_SITE:
int siteID = ltr.getSiteID();
if( siteID != sINT_NULL_VALUE )
{
mSiteIDs.add( siteID );
}
break;
case UN_KNWN:
break;
default:
break;
}
}
}
else if( message instanceof LTRNetISWMessage )
{
LTRNetISWMessage ltr = ((LTRNetISWMessage)message);
if( ltr.isValid() )
{
switch( ltr.getMessageType() )
{
case CA_STRT:
case CA_ENDD:
String talkgroup = ltr.getTalkgroupID();
if( mTalkgroupsFirstHeard.contains( talkgroup ) )
{
mTalkgroups.add( talkgroup );
}
else
{
mTalkgroupsFirstHeard.add( talkgroup );
}
break;
case ID_ESNH:
case ID_ESNL:
String esn = ltr.getESN();
if( !esn.contains( "xxxx" ) )
{
mESNs.add( ltr.getESN() );
}
break;
case ID_UNIQ:
int uniqueid = ltr.getRadioUniqueID();
if( uniqueid != sINT_NULL_VALUE )
{
mUniqueIDs.add( uniqueid );
}
break;
default:
break;
}
}
}
}
@Override
public String getSummary()
{
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<Integer> it = mSiteIDs.iterator();
while( it.hasNext() )
{
sb.append( "Site:\t" );
int siteID = it.next();
sb.append( siteID );
Alias siteAlias = mAliasList.getSiteID( 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++ )
{
double rcv = 0.0d;
if( mReceiveFrequencies.containsKey( x ) )
{
rcv = mReceiveFrequencies.get( x );
}
double xmt = 0.0d;
if( mTransmitFrequencies.containsKey( x ) )
{
xmt = mTransmitFrequencies.get( x );
}
if( rcv > 0.0d || xmt > 0.0d )
{
if( x < 10 )
{
sb.append( " " );
}
sb.append( x );
sb.append( ": " );
if( xmt == -1.0d )
{
sb.append( "---.-----" );
}
else
{
sb.append( mDecimalFormatter.format( xmt ) );
}
sb.append( " | " );
if( rcv == -1.0d )
{
sb.append( "---.-----" );
}
else
{
sb.append( mDecimalFormatter.format( rcv ) );
}
if( x == mMonitoredLCN )
{
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( formatTalkgroup( tgid ) );
sb.append( " " );
Alias alias = mAliasList.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( " " );
Alias alias = mAliasList.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( " " );
Alias alias = mAliasList.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<Integer> it = mNeighborIDs.iterator();
while( it.hasNext() )
{
int neighbor = it.next();
sb.append( " " );
sb.append( neighbor );
sb.append( " " );
Alias alias = mAliasList.getSiteID( neighbor );
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;
}
}
}

View File

@@ -0,0 +1,331 @@
/*******************************************************************************
* 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 decode.ltrnet;
import message.Message;
import alias.Alias;
import alias.AliasList;
import controller.activity.CallEvent.CallEventType;
import controller.channel.ProcessingChain;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
public class LTRNetChannelState extends ChannelState
{
private String mToTalkgroup;
private Alias mToTalkgroupAlias;
private String mFromTalkgroup;
private Alias mFromTalkgroupAlias;
private String mFromTalkgroupType;
private int mChannelNumber;
private String mUniqueID;
private LTRCallEvent mCurrentCallEvent;
private LTRNetActivitySummary mActivitySummary;
public LTRNetChannelState( ProcessingChain channel, AliasList aliasList )
{
super( channel, aliasList );
mActivitySummary = new LTRNetActivitySummary( aliasList );
}
/**
* 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 ) )
{
mCallEventModel.add(
new LTRCallEvent.Builder( type )
.aliasList( getAliasList() )
.channel( mChannelNumber )
.frequency( mProcessingChain.getChannel()
.getTunerChannel().getFrequency() )
.to( mToTalkgroup )
.build() );
}
mCurrentCallEvent = null;
super.fade( type );
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( mActivitySummary.getSummary() );
for( AuxChannelState state: mAuxChannelStates )
{
sb.append( state.getActivitySummary() );
sb.append( "\n\n" );
}
return sb.toString();
}
@Override
public void receive( Message message )
{
super.receive( message );
if( message instanceof LTRNetOSWMessage )
{
mActivitySummary.receive( message );
LTRNetOSWMessage ltr = (LTRNetOSWMessage)message;
Alias talkgroupAlias = ltr.getTalkgroupIDAlias();
switch( ltr.getMessageType() )
{
case CA_STRT:
mToTalkgroup = ltr.getTalkgroupID();
broadcastChange( ChangedAttribute.TO_TALKGROUP );
if( talkgroupAlias != null )
{
mToTalkgroupAlias = talkgroupAlias;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
mChannelNumber = ltr.getChannel();
broadcastChange( ChangedAttribute.CHANNEL_NUMBER );
if( getState() != ChannelState.State.CALL )
{
mCallEventModel.add(
new LTRCallEvent.Builder( CallEventType.CALL_START )
.aliasList( mAliasList )
.channel( ltr.getChannel() )
.frequency( mProcessingChain.getChannel()
.getTunerChannel().getFrequency() )
.to( mToTalkgroup )
.build() );
}
setState( State.CALL );
break;
case CA_ENDD:
mToTalkgroup = ltr.getTalkgroupID();
broadcastChange( ChangedAttribute.TO_TALKGROUP );
if( talkgroupAlias != null )
{
mToTalkgroupAlias = talkgroupAlias;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
fade( CallEventType.CALL_END );
break;
case ID_UNIQ:
mFromTalkgroupType = "UID";
broadcastChange( ChangedAttribute.FROM_TALKGROUP_TYPE );
mFromTalkgroup = String.valueOf( ltr.getRadioUniqueID() );
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
mFromTalkgroupAlias = ltr.getRadioUniqueIDAlias();
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
mCallEventModel.add(
new LTRCallEvent.Builder( CallEventType.REGISTER )
.aliasList( mAliasList )
.details( "Radio Unique ID" )
.frequency( mProcessingChain.getChannel()
.getTunerChannel().getFrequency() )
.from( String.valueOf( ltr.getRadioUniqueID() ) )
.build() );
setState( State.CALL );
default:
break;
}
}
else if( message instanceof LTRNetISWMessage )
{
mActivitySummary.receive( message );
LTRNetISWMessage ltr = (LTRNetISWMessage)message;
Alias talkgroupAlias = ltr.getTalkgroupIDAlias();
switch( ltr.getMessageType() )
{
case CA_STRT:
mToTalkgroup = ltr.getTalkgroupID();
broadcastChange( ChangedAttribute.TO_TALKGROUP );
if( talkgroupAlias != null )
{
mToTalkgroupAlias = talkgroupAlias;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
if( getState() != State.CALL )
{
mCurrentCallEvent =
new LTRCallEvent.Builder( CallEventType.CALL_START )
.aliasList( mAliasList )
.channel( ltr.getChannel() )
.frequency( mProcessingChain.getChannel()
.getTunerChannel().getFrequency() )
.to( mToTalkgroup )
.build();
mCallEventModel.add( mCurrentCallEvent );
}
setState( State.CALL );
break;
case CA_ENDD:
mToTalkgroup = ltr.getTalkgroupID();
broadcastChange( ChangedAttribute.TO_TALKGROUP );
if( talkgroupAlias != null )
{
mToTalkgroupAlias = talkgroupAlias;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
fade( CallEventType.CALL_END );
break;
case ID_UNIQ:
mFromTalkgroupType = "UID";
broadcastChange( ChangedAttribute.FROM_TALKGROUP_TYPE );
mFromTalkgroup = String.valueOf( ltr.getRadioUniqueID() );
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
mFromTalkgroupAlias = ltr.getRadioUniqueIDAlias();
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
if( getState() != State.CALL )
{
mCallEventModel.add( new LTRCallEvent.Builder(
CallEventType.ID_UNIQUE )
.aliasList( mAliasList )
.details( "Unique ID" )
.frequency( mProcessingChain.getChannel()
.getTunerChannel().getFrequency() )
.from( String.valueOf( ltr.getRadioUniqueID() ) )
.build() );
}
else
{
if( mCurrentCallEvent != null )
{
mCurrentCallEvent.setFromID(
String.valueOf( ltr.getRadioUniqueID() ) );
mCurrentCallEvent.setDetails( "Unique ID" );
}
}
setState( State.CALL );
break;
case ID_ESNH:
case ID_ESNL:
mFromTalkgroupType = "ESN";
broadcastChange( ChangedAttribute.FROM_TALKGROUP_TYPE );
mFromTalkgroup = ltr.getESN();
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
mFromTalkgroupAlias = ltr.getESNAlias();
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
setState( State.CALL );
mCallEventModel.add(
new LTRCallEvent.Builder( CallEventType.REGISTER_ESN )
.aliasList( mAliasList )
.details( "ESN:" + ltr.getESN() )
.frequency( mProcessingChain.getChannel()
.getTunerChannel().getFrequency() )
.from( ltr.getESN() )
.build() );
break;
default:
break;
}
}
}
public void reset()
{
mToTalkgroup = null;
broadcastChange( ChangedAttribute.TO_TALKGROUP );
mToTalkgroupAlias = null;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
mFromTalkgroup = null;
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
mFromTalkgroupAlias = null;
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
mFromTalkgroupType = " ";
broadcastChange( ChangedAttribute.FROM_TALKGROUP_TYPE );
super.reset();
}
public String getToTalkgroup()
{
return mToTalkgroup;
}
public Alias getToTalkgroupAlias()
{
return mToTalkgroupAlias;
}
public String getFromTalkgroup()
{
return mFromTalkgroup;
}
public Alias getFromTalkgroupAlias()
{
return mFromTalkgroupAlias;
}
public String getFromTalkgroupType()
{
return mFromTalkgroupType;
}
public boolean hasChannelNumber()
{
return mChannelNumber != 0;
}
public int getChannelNumber()
{
return mChannelNumber;
}
}

View 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 decode.ltrnet;
import message.MessageDirection;
import source.Source.SampleType;
import alias.AliasList;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
import decode.DecoderType;
import decode.ltrstandard.LTRFSKDemodulator;
import dsp.filter.DCRemovalFilter2;
import dsp.nbfm.FilteringNBFMDemodulator;
public class LTRNetDecoder extends Decoder
{
/**
* This value determines how quickly the DC remove filter responds to
* changes in frequency.
*/
private static final double sDC_REMOVAL_RATIO = 0.000003;
public static final int sLTR_STANDARD_MESSAGE_LENGTH = 40;
private FilteringNBFMDemodulator mDemodulator;
private DCRemovalFilter2 mDCRemovalFilter;
private LTRFSKDemodulator mLTRFSKDemodulator;
private MessageFramer mLTRMessageFramer;
private LTRNetMessageProcessor mLTRMessageProcessor;
public LTRNetDecoder( SampleType sampleType,
AliasList aliasList,
MessageDirection direction )
{
super( sampleType );
/**
* Only setup a demod chain if we're receiving complex samples. If
* we're receiving demodulated samples, they'll be handled the same
* was as we handle the output of the demodulator.
*/
if( mSourceSampleType == SampleType.COMPLEX )
{
/**
* The Decoder super class is both a Complex listener and a Float
* listener. So, we add the demod to listen to the incoming
* quadrature samples, and we wire the output of the demod right
* back to this class, so we can receive the demodulated output
* to process
*/
mDemodulator = new FilteringNBFMDemodulator();
this.addComplexListener( mDemodulator );
/**
* Remove the DC component that is present when we're mistuned
*/
mDCRemovalFilter = new DCRemovalFilter2( sDC_REMOVAL_RATIO );
mDemodulator.addListener( mDCRemovalFilter );
mDCRemovalFilter.setListener( this.getFloatReceiver() );
}
mLTRFSKDemodulator = new LTRFSKDemodulator();
/**
* The DC removal filter will impact performance of the LTRFSKDemod,
* so we get the samples straight from the NBFM demod
*/
if( mSourceSampleType == SampleType.COMPLEX )
{
mDemodulator.addListener( mLTRFSKDemodulator );
}
else
{
mDCRemovalFilter.setListener( mLTRFSKDemodulator );
}
if( direction == MessageDirection.OSW )
{
mLTRMessageFramer =
new MessageFramer( SyncPattern.LTR_STANDARD_OSW.getPattern(),
sLTR_STANDARD_MESSAGE_LENGTH );
}
else
{
mLTRMessageFramer =
new MessageFramer( SyncPattern.LTR_STANDARD_ISW.getPattern(),
sLTR_STANDARD_MESSAGE_LENGTH );
}
mLTRFSKDemodulator.addListener( mLTRMessageFramer );
mLTRMessageProcessor = new LTRNetMessageProcessor( direction, aliasList );
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
mLTRMessageProcessor.addMessageListener( this );
}
@Override
public DecoderType getType()
{
return DecoderType.LTR_NET;
}
}

View File

@@ -0,0 +1,70 @@
/*******************************************************************************
* 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 decode.ltrnet;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import message.MessageDirection;
import decode.DecodeEditor;
import decode.config.DecodeConfigLTRNet;
import 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( mComboDirection, "span 2,wrap" );
}
}

View 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 decode.ltrnet;
import java.util.Date;
import map.Plottable;
import message.MessageDirection;
import message.MessageType;
import alias.Alias;
import alias.AliasList;
import bits.BitSetBuffer;
import crc.CRC;
import crc.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( BitSetBuffer 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 = sINT_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;
}
}

View 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 decode.ltrnet;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import message.Message;
import message.MessageDirection;
import message.MessageType;
import alias.Alias;
import alias.AliasList;
import bits.BitSetBuffer;
import crc.CRC;
import crc.CRCLTR;
import decode.DecoderType;
public abstract class LTRNetMessage 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 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 BitSetBuffer mMessage;
protected MessageType mMessageType;
protected CRC mCRC;
protected AliasList mAliasList;
public LTRNetMessage( BitSetBuffer 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();
}
}

View File

@@ -0,0 +1,147 @@
/*******************************************************************************
* 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 decode.ltrnet;
import java.util.HashMap;
import message.Message;
import message.MessageDirection;
import message.MessageType;
import sample.Broadcaster;
import sample.Listener;
import alias.AliasList;
import bits.BitSetBuffer;
public class LTRNetMessageProcessor implements Listener<BitSetBuffer>
{
private Broadcaster<Message> mBroadcaster = new Broadcaster<Message>();
private HashMap<Integer,LTRNetOSWMessage> mReceiveAuxMessages =
new HashMap<Integer,LTRNetOSWMessage>();
private HashMap<Integer,LTRNetOSWMessage> mTransmitAuxMessages =
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( BitSetBuffer buffer )
{
LTRNetMessage message;
if( mDirection == MessageDirection.OSW )
{
message = new LTRNetOSWMessage( buffer, mAliasList );
if( message.isValid() )
{
if( message.getMessageType() == MessageType.FQ_TXHI )
{
message.setAuxiliaryMessage(
mTransmitAuxMessages.get( message.getHomeRepeater() ) );
}
else if( message.getMessageType() == MessageType.FQ_TXLO )
{
mTransmitAuxMessages.put( message.getHomeRepeater(),
(LTRNetOSWMessage)message );
}
else if( message.getMessageType() == MessageType.FQ_RXHI )
{
message.setAuxiliaryMessage(
mReceiveAuxMessages.get( message.getHomeRepeater() ) );
}
else if( message.getMessageType() == MessageType.FQ_RXLO )
{
mReceiveAuxMessages.put( message.getHomeRepeater(),
(LTRNetOSWMessage)message );
}
}
}
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;
}
}
}
}
}
mBroadcaster.receive( message );
}
public void addMessageListener( Listener<Message> listener )
{
mBroadcaster.addListener( listener );
}
public void removeMessageListener( Listener<Message> listener )
{
mBroadcaster.removeListener( listener );
}
}

View File

@@ -0,0 +1,579 @@
/*******************************************************************************
* 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 decode.ltrnet;
import java.util.ArrayList;
import java.util.Date;
import map.Plottable;
import message.MessageDirection;
import message.MessageType;
import alias.Alias;
import alias.AliasList;
import bits.BitSetBuffer;
public class LTRNetOSWMessage extends LTRNetMessage
{
private static final int sCHANNEL_FREQUENCY_MESSAGE_TYPE_BIT = 20;
private static final int sCHANNEL_MAP_MESSAGE_TYPE_BIT = 17;
private static final int sCHIU_UNIQUE_ID = 17;
private static final int sCHIU_SITE_ID = 18;
private static final int sCHIU_TRANSMIT_FREQUENCY = 24;
private static final int sCHIU_RECEIVE_FREQUENCY = 25;
private static final int sCHIU_NEIGHBOR = 26;
private static final int sCHIU_MAP = 28;
private static final int sCHIU_CALL_END = 31;
private static final int sHOME_DIRECTED_GROUP_CALL = 29;
private static final int sHOME_CHANNEL_IN_USE_MUTE_AUDIO = 30;
private static final int sGROUP_DO_NOTHING = 253;
private static final int sGROUP_CWID = 254;
private static final int sGROUP_CHANNEL_IDLE = 255;
private static final int sFREE_ALL_CHANNELS_BUSY = 0;
private static final int sFREE_LTR_GROUP_CALL = 30;
private static final int sFREE_LTRNET_GROUP_CALL = 31;
private LTRNetOSWMessage mAuxMessage;
public LTRNetOSWMessage( BitSetBuffer 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 != sINT_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( " = " );
double rxfreqhi = getFrequency();
if( rxfreqhi != sDOUBLE_NULL_VALUE )
{
sb.append( mDecimalFormatter.format( rxfreqhi ) );
}
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( " MSG 1 OF 2" );
break;
case FQ_TXHI:
sb.append( "CHAN TX LOW LCN:" );
sb.append( format( getHomeRepeater(), 2 ) );
sb.append( " = " );
double txfreq = getFrequency();
if( txfreq != sDOUBLE_NULL_VALUE )
{
sb.append( mDecimalFormatter.format( txfreq ) );
}
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( " MSG 1 OF 2" );
break;
case ID_SITE:
int siteId = getSiteID();
if( siteId != sINT_NULL_VALUE )
{
sb.append( "SITE ID: " );
sb.append( siteId );
sb.append( " " );
sb.append( getSiteIDAlias() );
}
break;
case ID_NBOR:
int neighborRank = getNeighborRank();
int neighborId = getNeighborID();
if( neighborRank != sINT_NULL_VALUE &&
neighborId != sINT_NULL_VALUE )
{
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()
{
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()
{
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 = sINT_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 int getSiteID()
{
int retVal = sINT_NULL_VALUE;
if( mMessageType == MessageType.ID_SITE )
{
retVal = mMessage.getInt( 23, 32 );
}
return retVal;
}
public Alias getSiteIDAlias()
{
if( mAliasList != null )
{
return mAliasList.getSiteID( getSiteID() );
}
return null;
}
public int getNeighborID()
{
int retVal = sINT_NULL_VALUE;
if( mMessageType == MessageType.ID_NBOR )
{
retVal = mMessage.getInt( 23, 32 );
}
return retVal;
}
private Alias getNeighborIDAlias()
{
if( mAliasList != null )
{
return mAliasList.getSiteID( getNeighborID() );
}
return null;
}
public int getNeighborRank()
{
int retVal = sINT_NULL_VALUE;
if( mMessageType == MessageType.ID_NBOR )
{
retVal = mMessage.getInt( 15, 18 ) + 1;
}
return retVal;
}
public double getFrequency()
{
double retVal = sDOUBLE_NULL_VALUE;
if( mAuxMessage != null )
{
int high = getHighChannelUnits();
int low = mAuxMessage.getLowChannelUnits();
if( high != sINT_NULL_VALUE && low != sINT_NULL_VALUE )
{
retVal = ( high + low ) * .00125 + 150.0;
}
}
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 = sINT_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 = sINT_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 sCHIU_UNIQUE_ID:
retVal = MessageType.ID_UNIQ;
break;
case sCHIU_SITE_ID:
retVal = MessageType.ID_SITE;
break;
case sCHIU_TRANSMIT_FREQUENCY:
if( getHomeRepeater() == 24 )
{
retVal = MessageType.ID_UNIQ;
}
else
{
if( mMessage.get( sCHANNEL_FREQUENCY_MESSAGE_TYPE_BIT ) )
{
retVal = MessageType.FQ_TXHI;
}
else
{
retVal = MessageType.FQ_TXLO;
}
}
break;
case sCHIU_RECEIVE_FREQUENCY:
if( mMessage.get( sCHANNEL_FREQUENCY_MESSAGE_TYPE_BIT ) )
{
retVal = MessageType.FQ_RXHI;
}
else
{
retVal = MessageType.FQ_RXLO;
}
break;
case sCHIU_NEIGHBOR:
retVal = MessageType.ID_NBOR;
break;
case sCHIU_MAP:
if( mMessage.get( sCHANNEL_MAP_MESSAGE_TYPE_BIT ) )
{
retVal = MessageType.MA_CHNH;
}
else
{
retVal = MessageType.MA_CHNL;
}
break;
case sCHIU_CALL_END:
retVal = MessageType.CA_ENDD;
}
}
else
{
int group = getGroup();
if( group == sGROUP_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;
}
}

View File

@@ -0,0 +1,229 @@
/*******************************************************************************
* 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 decode.ltrnet;
import java.awt.EventQueue;
import javax.swing.JLabel;
import settings.ColorSetting;
import settings.Setting;
import alias.Alias;
import controller.channel.Channel;
import controller.state.ChannelState.ChangedAttribute;
import controller.state.ChannelStatePanel;
public class LTRNetPanel extends ChannelStatePanel
{
private static final long serialVersionUID = 1L;
private JLabel mStateLabel;
private JLabel mSourceLabel;
private JLabel mChannelLabel;
private JLabel mProtocol = new JLabel( "LTR-Net" );
private JLabel mToTalkgroup = new JLabel();
private JLabel mToTalkgroupAlias = new JLabel();
private JLabel mFromType = new JLabel( " " );
private JLabel mFromTalkgroup = new JLabel();
private JLabel mFromTalkgroupAlias = new JLabel();
public LTRNetPanel( Channel channel )
{
super( channel );
init();
}
public void init()
{
mStateLabel = new JLabel( mChannel.getProcessingChain().
getChannelState().getState().getDisplayValue() );
mStateLabel.setFont( mFontDecoder );
mStateLabel.setForeground( mColorLabelDecoder );
mSourceLabel = new JLabel( mChannel.getSourceConfiguration().getDescription() );
mSourceLabel.setFont( mFontDetails );
mSourceLabel.setForeground( mColorLabelDetails );
mChannelLabel = new JLabel( mChannel.getChannelDisplayName() );
mChannelLabel.setFont( mFontDetails );
mChannelLabel.setForeground( mColorLabelDetails );
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
mToTalkgroup.setFont( mFontDecoder );
mToTalkgroup.setForeground( mColorLabelDecoder );
mToTalkgroupAlias.setFont( mFontDecoder );
mToTalkgroupAlias.setForeground( mColorLabelDecoder );
mFromType.setFont( mFontDecoder );
mFromType.setForeground( mColorLabelDecoder );
mFromTalkgroup.setFont( mFontDecoder );
mFromTalkgroup.setForeground( mColorLabelDecoder );
mFromTalkgroupAlias.setFont( mFontDecoder );
mFromTalkgroupAlias.setForeground( mColorLabelDecoder );
add( mStateLabel );
add( mSourceLabel );
add( mChannelLabel, "wrap" );
add( mProtocol );
add( mToTalkgroup );
add( mToTalkgroupAlias, "wrap" );
add( mFromType );
add( mFromTalkgroup );
add( mFromTalkgroupAlias, "wrap" );
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
LTRNetChannelState state = (LTRNetChannelState)mChannel
.getProcessingChain().getChannelState();
switch( changedAttribute )
{
case CHANNEL_STATE:
mStateLabel.setText( state.getState().getDisplayValue() );
break;
case SOURCE:
mSourceLabel.setText( mChannel
.getSourceConfiguration().getDescription() );
break;
case CHANNEL_NAME:
case SITE_NAME:
case SYSTEM_NAME:
mChannelLabel.setText( mChannel.getChannelDisplayName() );
break;
case FROM_TALKGROUP:
mFromTalkgroup.setText( state.getFromTalkgroup() );
break;
case FROM_TALKGROUP_ALIAS:
Alias fromAlias = state.getFromTalkgroupAlias();
if( fromAlias != null )
{
mFromTalkgroupAlias.setText( fromAlias.getName() );
String iconNameString = fromAlias.getIconName();
mFromTalkgroupAlias.setIcon( getSettingsManager()
.getImageIcon( iconNameString, 12 ) );
}
else
{
mFromTalkgroupAlias.setText( null );
mFromTalkgroupAlias.setIcon( null );
}
break;
case FROM_TALKGROUP_TYPE:
mFromType.setText( state.getFromTalkgroupType() );
break;
case TO_TALKGROUP:
mToTalkgroup.setText( state.getToTalkgroup() );
break;
case TO_TALKGROUP_ALIAS:
Alias tgAlias = state.getToTalkgroupAlias();
if( tgAlias != null )
{
mToTalkgroupAlias.setText( tgAlias.getName() );
String iconNameString = tgAlias.getIconName();
mToTalkgroupAlias.setIcon( getSettingsManager()
.getImageIcon( iconNameString, 12 ) );
}
else
{
mToTalkgroupAlias.setText( null );
mToTalkgroupAlias.setIcon( null );
}
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( mStateLabel != null )
{
mStateLabel.setForeground( mColorLabelDecoder );
}
if( mProtocol != null )
{
mProtocol.setForeground( mColorLabelDecoder );
}
if( mToTalkgroup != null )
{
mToTalkgroup.setForeground( mColorLabelDecoder );
}
if( mToTalkgroupAlias != null )
{
mToTalkgroupAlias.setForeground( mColorLabelDecoder );
}
if( mFromType != null )
{
mFromType.setForeground( mColorLabelDecoder );
}
if( mFromTalkgroup != null )
{
mFromTalkgroup.setForeground( mColorLabelDecoder );
}
if( mFromTalkgroupAlias != null )
{
mFromTalkgroupAlias.setForeground( mColorLabelDecoder );
}
break;
case CHANNEL_STATE_LABEL_DETAILS:
if( mSourceLabel != null )
{
mSourceLabel.setForeground( mColorLabelDetails );
}
if( mChannelLabel != null )
{
mChannelLabel.setForeground( mColorLabelDetails );
}
break;
}
}
}
}

View File

@@ -0,0 +1,208 @@
/*******************************************************************************
* 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 decode.ltrstandard;
import java.util.HashMap;
import message.Message;
import alias.Alias;
import alias.AliasList;
import controller.activity.CallEvent.CallEventType;
import controller.channel.ProcessingChain;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
import decode.ltrnet.LTRCallEvent;
public class LTRChannelState extends ChannelState
{
private String mTalkgroup;
private Alias mTalkgroupAlias;
private int mChannelNumber;
private HashMap<Integer,String> mActiveCalls = new HashMap<Integer,String>();
private LTRStandardActivitySummary mActivitySummary;
public LTRChannelState( ProcessingChain channel, AliasList aliasList )
{
super( channel, aliasList );
mActivitySummary = new LTRStandardActivitySummary( aliasList );
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( mActivitySummary.getSummary() );
for( AuxChannelState state: mAuxChannelStates )
{
sb.append( state.getActivitySummary() );
sb.append( "\n\n" );
}
return sb.toString();
}
@Override
public void receive( Message message )
{
super.receive( message );
if( message instanceof LTRStandardOSWMessage )
{
mActivitySummary.receive( message );
LTRStandardOSWMessage ltr = (LTRStandardOSWMessage)message;
switch( ltr.getMessageType() )
{
case CA_STRT:
if( 0 < ltr.getChannel() && ltr.getChannel() <=20 )
{
/* If this channel is the same as the talkgroup's home
* repeater, then the channel number refers to the
* currently tuned frequency and we capture the channel
* number ... otherwise, it might be reflecting a call
* on another LCN */
if( ltr.getChannel() == ltr.getHomeRepeater() )
{
mChannelNumber = ltr.getChannel();
broadcastChange( ChangedAttribute.CHANNEL_NUMBER );
}
/* If this call event is for this channel, then update
* talkgroup and alias */
if( ltr.getChannel() == mChannelNumber )
{
mTalkgroup = ltr.getTalkgroupID();
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
mTalkgroupAlias = ltr.getTalkgroupIDAlias();
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
}
/* If this is a new call, log a call event */
if( !mActiveCalls.containsKey( ltr.getChannel() ) ||
!mActiveCalls.get( ltr.getChannel() )
.contentEquals( ltr.getTalkgroupID() ) )
{
mActiveCalls.put( ltr.getChannel(), ltr.getTalkgroupID() );
CallEventType callEventType =
( ltr.getChannel() == mChannelNumber ) ?
CallEventType.CALL_START :
CallEventType.CALL_DETECT;
mCallEventModel.add(
new LTRCallEvent.Builder( callEventType )
.to( ltr.getTalkgroupID() )
.aliasList( getAliasList() )
.channel( ltr.getChannel() )
.frequency( ( ltr.getChannel() == mChannelNumber ) ?
mProcessingChain.getChannel()
.getTunerChannel().getFrequency() : 0 )
.build() );
}
setState( State.CALL );
}
break;
case CA_ENDD:
mTalkgroup = ltr.getTalkgroupID();
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
mTalkgroupAlias = ltr.getTalkgroupIDAlias();
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
fade( CallEventType.CALL_END );
break;
case SY_IDLE:
int channel = ltr.getChannel();
if( 0 < channel && channel <= 20 &&
channel == ltr.getHomeRepeater() &&
channel == ltr.getFree() )
{
mChannelNumber = channel;
broadcastChange( ChangedAttribute.CHANNEL_NUMBER );
}
break;
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 post a call
* end event for the one that can change the state to fade
*/
if( getState().canChangeTo( State.FADE ) )
{
mActiveCalls.clear();
mCallEventModel.add(
new LTRCallEvent.Builder( type )
.aliasList( getAliasList() )
.channel( mChannelNumber )
.frequency( mProcessingChain.getChannel().getTunerChannel().getFrequency() )
.to( mTalkgroup )
.build() );
}
super.fade( type );
}
public void reset()
{
mTalkgroup = null;
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
mTalkgroupAlias = null;
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
super.reset();
}
public String getTalkgroup()
{
return mTalkgroup;
}
public Alias getTalkgroupAlias()
{
return mTalkgroupAlias;
}
public boolean hasChannelNumber()
{
return mChannelNumber != 0;
}
public int getChannelNumber()
{
return mChannelNumber;
}
}

View File

@@ -0,0 +1,132 @@
/*******************************************************************************
* 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 decode.ltrstandard;
import sample.Broadcaster;
import sample.Listener;
import dsp.BooleanZeroCrossingDetector;
import dsp.FloatToBinarySlicer;
import dsp.filter.BooleanAveragingFilter;
import dsp.filter.Filters;
import dsp.filter.FloatFIRFilter;
import dsp.filter.FloatHalfBandFilter;
import dsp.filter.FloatHalfBandNoDecimateFilter;
import dsp.filter.SquaringFilter;
public class LTRFSKDemodulator implements Listener<Float>
{
FloatHalfBandFilter mHBFilter1;
FloatHalfBandFilter mHBFilter2;
FloatHalfBandFilter mHBFilter3;
FloatHalfBandFilter mHBFilter4;
FloatHalfBandFilter mHBFilter5;
FloatHalfBandNoDecimateFilter mHBFilter6;
FloatFIRFilter mDCBlocker;
SquaringFilter mSquaringFilter;
BooleanAveragingFilter mAveragingFilter;
BooleanZeroCrossingDetector mZeroCrossingDetector;
FloatToBinarySlicer mSlicer;
Broadcaster<Boolean> mBroadcaster = new Broadcaster<Boolean>();
//debug
// FloatWaveRecorder mRecorder;
/**
* Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling
* decoder. Expects a 48k short-valued sample rate, which is decimated
* to a final 1.5k sample rate via five cascaded decimating half-band
* filters, and applies a sixth, non-decimating half-band filter to bring
* the overall bandwidth down to .75k. Each half band filter requires
* 8 multiply/accumulate (MAC) cycles per input sample, totaling 2560
* MAC filtering cycles per output bit, to get from 48k input to 300-baud
* bit stream output.
*
* Applies a balancer to the demodulated fsk waveform to normalize the
* values about the zero-axis and to offset fluctuations induced by the
* DC correction filter.
*
* Converts filtered, decimated samples to squared bit values and then
* applies a zero crossing detector to make a baud decision every 5 samples.
*
* Outputs the decoded bit stream.
*/
public LTRFSKDemodulator()
{
mHBFilter1 = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
mHBFilter2 = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
mHBFilter1.setListener( mHBFilter2 );
mHBFilter3 = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
mHBFilter2.setListener( mHBFilter3 );
mHBFilter4 = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
mHBFilter3.setListener( mHBFilter4 );
mHBFilter5 = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
mHBFilter4.setListener( mHBFilter5 );
mHBFilter6 = new FloatHalfBandNoDecimateFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
mHBFilter5.setListener( mHBFilter6 );
// mDCBlocker = new FloatFIRFilter(
// Filters.FIRHP_LTR_DCBLOCK_750FS_50FC.getCoefficients(), 1.0f );
// mHBFilter6.setListener( mDCBlocker );
/**
* Set the slicer's long buffer to a sample length of 14 baud periods
* ( 5 * 14 )since 12 bauds should be the longest string of ones we'll
* encounter
*
* Set the short buffer to two baud periods (5 * 2)
*/
//Set the buffer to 5 samples per baud times 12 bauds. Set the sync
//lock period to 5 samples per baud times 31 bits (155 samples)
mSlicer = new FloatToBinarySlicer( 70, 5 );
mHBFilter6.setListener( mSlicer );
// mDCBlocker.setListener( mSlicer );
mAveragingFilter = new BooleanAveragingFilter( 5 );
mSlicer.setListener( mAveragingFilter );
mZeroCrossingDetector = new BooleanZeroCrossingDetector( 5 );
mAveragingFilter.setListener( mZeroCrossingDetector );
}
@Override
public void receive( Float sample )
{
mHBFilter1.receive( sample );
}
public void addListener( Listener<Boolean> listener )
{
mZeroCrossingDetector.setListener( listener );
}
public void removeListener( Listener<Boolean> listener )
{
mZeroCrossingDetector.removeListener( listener );
}
}

View File

@@ -0,0 +1,122 @@
/*******************************************************************************
* 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 decode.ltrstandard;
import sample.Listener;
import dsp.BooleanZeroCrossingDetector;
import dsp.FloatToBinarySlicerLTR;
import dsp.SyncDetectListener;
import dsp.filter.BooleanAveragingFilter;
import dsp.filter.Filters;
import dsp.filter.FloatHalfBandFilter;
import dsp.filter.FloatHalfBandNoDecimateFilter;
import dsp.filter.SquaringFilter;
public class LTRFSKDemodulator2 implements Listener<Float>, SyncDetectListener
{
FloatHalfBandFilter mHBFilter1;
FloatHalfBandFilter mHBFilter2;
FloatHalfBandFilter mHBFilter3;
FloatHalfBandFilter mHBFilter4;
FloatHalfBandFilter mHBFilter5;
FloatHalfBandNoDecimateFilter mHBFilter6;
SquaringFilter mSquaringFilter;
BooleanAveragingFilter mAveragingFilter;
BooleanZeroCrossingDetector mZeroCrossingDetector;
FloatToBinarySlicerLTR mSlicer;
/**
* Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling
* decoder. Expects a 48k short-valued sample rate, which is decimated
* to a final 1.5k sample rate via five cascaded decimating half-band
* filters, and applies a sixth, non-decimating half-band filter to bring
* the overall bandwidth down to .75k. Each half band filter requires
* 8 multiply/accumulate (MAC) cycles per input sample, totaling 2560
* MAC filtering cycles per output bit, to get from 48k input to 300-baud
* bit stream output.
*
* Applies a balancer to the demodulated fsk waveform to normalize the
* values about the zero-axis and to offset fluctuations induced by the
* DC correction filter.
*
* Converts filtered, decimated samples to squared bit values and then
* applies a zero crossing detector to make a baud decision every 5 samples.
*
* Outputs the decoded bit stream.
*/
public LTRFSKDemodulator2( int syncLength, int messageLength )
{
mHBFilter1 = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
mHBFilter2 = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
mHBFilter1.setListener( mHBFilter2 );
mHBFilter3 = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
mHBFilter2.setListener( mHBFilter3 );
mHBFilter4 = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
mHBFilter3.setListener( mHBFilter4 );
mHBFilter5 = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
mHBFilter4.setListener( mHBFilter5 );
mHBFilter6 = new FloatHalfBandNoDecimateFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
mHBFilter5.setListener( mHBFilter6 );
mSlicer = new FloatToBinarySlicerLTR( syncLength, messageLength, 5 );
mHBFilter6.setListener( mSlicer );
mAveragingFilter = new BooleanAveragingFilter( 5 );
mSlicer.setListener( mAveragingFilter );
mZeroCrossingDetector = new BooleanZeroCrossingDetector( 5 );
mAveragingFilter.setListener( mZeroCrossingDetector );
}
@Override
public void receive( Float sample )
{
mHBFilter1.receive( sample );
}
public void setListener( Listener<Boolean> listener )
{
mZeroCrossingDetector.setListener( listener );
}
public void removeListener( Listener<Boolean> listener )
{
mZeroCrossingDetector.removeListener( listener );
}
/**
* Pass the sync detection down to the slicer
*/
@Override
public void syncDetected()
{
mSlicer.syncDetected();
}
}

View File

@@ -0,0 +1,219 @@
/*******************************************************************************
* 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 decode.ltrstandard;
import java.awt.EventQueue;
import javax.swing.JLabel;
import settings.ColorSetting;
import settings.Setting;
import settings.SettingsManager;
import alias.Alias;
import controller.channel.Channel;
import controller.state.ChannelState.ChangedAttribute;
import controller.state.ChannelStatePanel;
public class LTRPanel extends ChannelStatePanel
{
private static final long serialVersionUID = 1L;
private JLabel mStateLabel;
private JLabel mSourceLabel;
private JLabel mChannelLabel;
private JLabel mProtocol = new JLabel( "LTR Standard" );
private JLabel mTalkgroup = new JLabel();
private JLabel mTalkgroupAlias = new JLabel();
private JLabel mLCNLabel = new JLabel( "LCN" );
private JLabel mLCN = new JLabel();
private SettingsManager mSettingsManager;
public LTRPanel( SettingsManager settingsManager, Channel channel )
{
super( channel );
channel.getProcessingChain().getChannelState().addListener( this );
mSettingsManager = settingsManager;
init();
}
public void dispose()
{
super.dispose();
mSettingsManager = null;
}
public void init()
{
mStateLabel = new JLabel( mChannel.getProcessingChain().
getChannelState().getState().getDisplayValue() );
mStateLabel.setFont( mFontDecoder );
mStateLabel.setForeground( mColorLabelDecoder );
mSourceLabel = new JLabel( mChannel.getSourceConfiguration().getDescription() );
mSourceLabel.setFont( mFontDetails );
mSourceLabel.setForeground( mColorLabelDetails );
mChannelLabel = new JLabel( mChannel.getChannelDisplayName() );
mChannelLabel.setFont( mFontDetails );
mChannelLabel.setForeground( mColorLabelDetails );
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
mTalkgroup.setFont( mFontDecoder );
mTalkgroup.setForeground( mColorLabelDecoder );
mTalkgroupAlias.setFont( mFontDecoder );
mTalkgroupAlias.setForeground( mColorLabelDecoder );
mLCNLabel.setFont( mFontDecoder );
mLCNLabel.setForeground( mColorLabelDetails );
mLCN.setFont( mFontDecoder );
mLCN.setForeground( mColorLabelDecoder );
add( mStateLabel );
add( mSourceLabel );
add( mChannelLabel, "wrap" );
add( mProtocol );
add( mTalkgroup );
add( mTalkgroupAlias, "wrap" );
add( mLCNLabel );
add( mLCN, "wrap" );
}
@Override
public void receive( final ChangedAttribute attribute )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
LTRChannelState state = (LTRChannelState)mChannel
.getProcessingChain().getChannelState();
switch( attribute )
{
case CHANNEL_NUMBER:
if( state.getChannelNumber() != 0 )
{
mLCN.setText(
String.valueOf( state.getChannelNumber() ) );
}
else
{
mLCN.setText( "" );
}
break;
case FROM_TALKGROUP:
mTalkgroup.setText( state.getTalkgroup() );
/* fall-through */
case FROM_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 );
}
case CHANNEL_STATE:
mStateLabel.setText( state.getState().getDisplayValue() );
break;
case SOURCE:
mSourceLabel.setText( mChannel
.getSourceConfiguration().getDescription() );
break;
case CHANNEL_NAME:
case SITE_NAME:
case SYSTEM_NAME:
mChannelLabel.setText( mChannel.getChannelDisplayName() );
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( mStateLabel != null )
{
mStateLabel.setForeground( mColorLabelDecoder );
}
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;
case CHANNEL_STATE_LABEL_DETAILS:
if( mSourceLabel != null )
{
mSourceLabel.setForeground( mColorLabelDetails );
}
if( mChannelLabel != null )
{
mChannelLabel.setForeground( mColorLabelDetails );
}
if( mLCNLabel != null )
{
mLCNLabel.setForeground( mColorLabelDetails );
}
break;
}
}
}
}

View File

@@ -0,0 +1,209 @@
/*******************************************************************************
* 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 decode.ltrstandard;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.TreeSet;
import message.Message;
import sample.Listener;
import alias.Alias;
import alias.AliasList;
import controller.activity.ActivitySummaryProvider;
public class LTRStandardActivitySummary implements ActivitySummaryProvider,
Listener<Message>
{
private static final int sINT_NULL_VALUE = -1;
private DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000");
private AliasList mAliasList;
private HashSet<String> mTalkgroupsFirstHeard = new HashSet<String>();
private TreeSet<String> mTalkgroups = new TreeSet<String>();
private TreeSet<Integer> mActiveLCNs = new TreeSet<Integer>();
private int mMonitoredLCN;
/**
* Compiles a summary of active talkgroups, unique radio ids encountered
* in the decoded messages. Talkgroups have to be heard a minimum of twice
* to be considered valid ... in order to weed out the invalid ones.
*
* Returns a textual summary of the activity
*/
public LTRStandardActivitySummary( AliasList list )
{
mAliasList = list;
}
@Override
public void receive( Message message )
{
if( message instanceof LTRStandardOSWMessage )
{
LTRStandardOSWMessage ltr = ((LTRStandardOSWMessage)message);
if( ltr.isValid() )
{
switch( ltr.getMessageType() )
{
case CA_STRT:
case CA_ENDD:
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 )
{
mMonitoredLCN = channel;
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() )
{
mMonitoredLCN = idleChannel;
mActiveLCNs.add( idleChannel );
}
break;
case UN_KNWN:
default:
break;
}
}
}
}
@Override
public String getSummary()
{
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( " " );
Alias alias = mAliasList.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;
}
}
}

View File

@@ -0,0 +1,117 @@
/*******************************************************************************
* 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 decode.ltrstandard;
import message.MessageDirection;
import source.Source.SampleType;
import alias.AliasList;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
import decode.DecoderType;
import dsp.filter.DCRemovalFilter2;
import dsp.nbfm.FilteringNBFMDemodulator;
public class LTRStandardDecoder extends Decoder
{
/**
* This value determines how quickly the DC remove filter responds to
* changes in frequency.
*/
private static final double sDC_REMOVAL_RATIO = 0.000003;
public static final int sLTR_STANDARD_MESSAGE_LENGTH = 40;
private FilteringNBFMDemodulator mDemodulator;
private DCRemovalFilter2 mDCRemovalFilter;
private LTRFSKDemodulator mLTRFSKDemodulator;
private MessageFramer mLTRMessageFramer;
private LTRStandardMessageProcessor mLTRMessageProcessor;
public LTRStandardDecoder( SampleType sampleType,
AliasList aliasList,
MessageDirection direction )
{
super( sampleType );
/**
* Only setup a demod chain if we're receiving complex samples. If
* we're receiving demodulated samples, they'll be handled the same
* was as we handle the output of the demodulator.
*/
if( mSourceSampleType == SampleType.COMPLEX )
{
/**
* The Decoder super class is both a Complex listener and a Short
* listener. So, we add the demod to listen to the incoming
* quadrature samples, and we wire the output of the demod right
* back to this class, so we can receive the demodulated output
* to process
*/
mDemodulator = new FilteringNBFMDemodulator();
this.addComplexListener( mDemodulator );
/**
* Remove the DC component that is present when we're mistuned
*/
mDCRemovalFilter = new DCRemovalFilter2( sDC_REMOVAL_RATIO );
mDemodulator.addListener( mDCRemovalFilter );
mDCRemovalFilter.setListener( this.getFloatReceiver() );
}
mLTRFSKDemodulator = new LTRFSKDemodulator();
/**
* The DC removal filter will impact performance of the LTRFSKDemod,
* so we get the samples straight from the NBFM demod
*/
if( mSourceSampleType == SampleType.COMPLEX )
{
mDemodulator.addListener( mLTRFSKDemodulator );
}
else
{
mDCRemovalFilter.setListener( mLTRFSKDemodulator );
}
if( direction == MessageDirection.OSW )
{
mLTRMessageFramer =
new MessageFramer( SyncPattern.LTR_STANDARD_OSW.getPattern(),
sLTR_STANDARD_MESSAGE_LENGTH );
}
else
{
mLTRMessageFramer =
new MessageFramer( SyncPattern.LTR_STANDARD_ISW.getPattern(),
sLTR_STANDARD_MESSAGE_LENGTH );
}
mLTRFSKDemodulator.addListener( mLTRMessageFramer );
mLTRMessageProcessor = new LTRStandardMessageProcessor( direction, aliasList );
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
mLTRMessageProcessor.addMessageListener( this );
}
@Override
public DecoderType getType()
{
return DecoderType.LTR_STANDARD;
}
}

View File

@@ -0,0 +1,70 @@
/*******************************************************************************
* 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 decode.ltrstandard;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import message.MessageDirection;
import decode.DecodeEditor;
import decode.config.DecodeConfigLTRStandard;
import 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( mComboDirection, "span 2" );
}
}

View File

@@ -0,0 +1,212 @@
/*******************************************************************************
* 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 decode.ltrstandard;
import java.util.Date;
import map.Plottable;
import message.MessageDirection;
import message.MessageType;
import alias.Alias;
import alias.AliasList;
import bits.BitSetBuffer;
public class LTRStandardISWMessage extends LTRStandardMessage
{
private static final int sCALL_END = 31;
private static final int sIDLE = 255;
public LTRStandardISWMessage( BitSetBuffer 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;
}
}

View File

@@ -0,0 +1,256 @@
/*******************************************************************************
* 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 decode.ltrstandard;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import log.Log;
import message.Message;
import message.MessageDirection;
import message.MessageType;
import alias.Alias;
import alias.AliasList;
import bits.BitSetBuffer;
import crc.CRC;
import crc.CRCLTR;
import decode.DecoderType;
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 BitSetBuffer mMessage;
protected MessageType mMessageType;
protected CRC mCRC;
protected AliasList mAliasList;
public LTRStandardMessage( BitSetBuffer 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()
{
Log.debug( "LTRStandardMessage - getting alias for:" + getTalkgroupID() );
return mAliasList.getTalkgroupAlias( getTalkgroupID() );
}
public int getFree()
{
return getInt( sFREE );
}
public int getCRCChecksum()
{
return getInt( sCRC );
}
@Override
public String getProtocol()
{
return DecoderType.LTR_STANDARD.getDisplayString();
}
@Override
public String getEventType()
{
if( mMessageType != null )
{
return mMessageType.getDisplayText();
}
else
{
return MessageType.UN_KNWN.getDisplayText();
}
}
@Override
public String getToID()
{
return null;
}
@Override
public Alias getToIDAlias()
{
return null;
}
@Override
public String getErrorStatus()
{
return mCRC.getDisplayText();
}
}

View File

@@ -0,0 +1,67 @@
/*******************************************************************************
* 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 decode.ltrstandard;
import message.Message;
import message.MessageDirection;
import sample.Broadcaster;
import sample.Listener;
import alias.AliasList;
import bits.BitSetBuffer;
public class LTRStandardMessageProcessor implements Listener<BitSetBuffer>
{
private MessageDirection mDirection;
private Broadcaster<Message> mBroadcaster = new Broadcaster<Message>();
private AliasList mAliasList;
public LTRStandardMessageProcessor( MessageDirection direction, AliasList list )
{
mDirection = direction;
mAliasList = list;
}
@Override
public void receive( BitSetBuffer buffer )
{
LTRStandardMessage message;
if( mDirection == MessageDirection.OSW )
{
message = new LTRStandardOSWMessage( buffer, mAliasList );
}
else
{
buffer.flip( 0, 40 );
message = new LTRStandardISWMessage( buffer, mAliasList );
}
mBroadcaster.receive( message );
}
public void addMessageListener( Listener<Message> listener )
{
mBroadcaster.addListener( listener );
}
public void removeMessageListener( Listener<Message> listener )
{
mBroadcaster.removeListener( listener );
}
}

View 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 decode.ltrstandard;
import java.util.Date;
import map.Plottable;
import message.MessageDirection;
import message.MessageType;
import alias.Alias;
import alias.AliasList;
import bits.BitSetBuffer;
public class LTRStandardOSWMessage extends LTRStandardMessage
{
private static final int sCALL_END = 31;
private static final int sIDLE = 255;
public LTRStandardOSWMessage( BitSetBuffer 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;
}
}

View File

@@ -0,0 +1,131 @@
/*******************************************************************************
* 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 decode.mdc1200;
import java.util.Iterator;
import java.util.TreeSet;
import message.Message;
import sample.Listener;
import alias.Alias;
import alias.AliasList;
import controller.activity.ActivitySummaryProvider;
public class MDCActivitySummary implements ActivitySummaryProvider,
Listener<Message>
{
private AliasList mAliasList;
private TreeSet<String> mIdents = new TreeSet<String>();
private TreeSet<String> mEmergencyIdents = new TreeSet<String>();
/**
* Compiles a summary of active talkgroups, unique radio ids encountered
* in the decoded messages. Talkgroups have to be heard a minimum of twice
* to be considered valid ... in order to weed out the invalid ones.
*
* Returns a textual summary of the activity
*/
public MDCActivitySummary( AliasList list )
{
mAliasList = list;
}
@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() );
}
}
}
@Override
public String getSummary()
{
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( " " );
Alias alias = mAliasList.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( " " );
Alias alias = mAliasList.getMDC1200Alias( ident );
if( alias != null )
{
sb.append( alias.getName() );
}
sb.append( "\n" );
}
}
return sb.toString();
}
}

View File

@@ -0,0 +1,159 @@
/*******************************************************************************
* 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 decode.mdc1200;
import alias.Alias;
import alias.AliasList;
import controller.activity.CallEvent;
import decode.DecoderType;
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 int getChannel()
{
return 0;
}
@Override
public int 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.ACKNOWLEDGE;
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();
}
}

View File

@@ -0,0 +1,183 @@
/*******************************************************************************
* 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 decode.mdc1200;
import message.Message;
import alias.Alias;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
import controller.state.ChannelState.ChangedAttribute;
import controller.state.ChannelState.State;
public class MDCChannelState extends AuxChannelState
{
private String mFrom;
private Alias mFromAlias;
private String mTo;
private Alias mToAlias;
private String mMessage;
private String mMessageType;
private MDCActivitySummary mActivitySummary;
public MDCChannelState( ChannelState parentState )
{
super( parentState );
mActivitySummary = new MDCActivitySummary( parentState.getAliasList() );
}
@Override
public void reset()
{
setFrom( null );
setFromAlias( null );
setTo( null);
setToAlias( null );
setMessage( null );
setMessageType( null );
}
@Override
public void fade() {}
@Override
public void receive( Message message )
{
if( message instanceof MDCMessage )
{
mActivitySummary.receive( message );
MDCMessage mdc = (MDCMessage)message;
setFrom( mdc.getFromID() );
setFromAlias( mdc.getFromIDAlias() );
MDCMessageType type = mdc.getMessageType();
setMessageType( type.getLabel() );
StringBuilder sb = new StringBuilder();
switch( type )
{
case ACKNOWLEDGE:
case ANI:
case EMERGENCY:
case PAGING:
case STATUS:
default:
sb.append( "OPCODE " );
sb.append( String.valueOf( mdc.getOpcode() ) );
if( mdc.isBOT() )
{
sb.append( " TYPE:BOT" );
}
if( mdc.isEOT() )
{
sb.append( " TYPE:EOT" );
}
break;
}
setMessage( sb.toString() );
//Set the state to CALL on the parent channel state, so that the
//timer can track turning off any updates we apply
mParentChannelState.setState( State.CALL );
MDCCallEvent event = MDCCallEvent.getMDCCallEvent( mdc );
event.setAliasList( mParentChannelState.getAliasList() );
mParentChannelState.receiveCallEvent( event );
}
}
public String getFrom()
{
return mFrom;
}
public void setFrom( String from )
{
mFrom = from;
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
}
public Alias getFromAlias()
{
return mFromAlias;
}
public void setFromAlias( Alias alias )
{
mFromAlias = alias;
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
}
public String getTo()
{
return mTo;
}
public void setTo( String to )
{
mTo = to;
broadcastChange( ChangedAttribute.TO_TALKGROUP );
}
public Alias getToAlias()
{
return mToAlias;
}
public void setToAlias( Alias alias )
{
mToAlias = alias;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
public String getMessage()
{
return mMessage;
}
public void setMessage( String message )
{
mMessage = message;
broadcastChange( ChangedAttribute.MESSAGE );
}
public String getMessageType()
{
return mMessageType;
}
public void setMessageType( String type )
{
mMessageType = type;
broadcastChange( ChangedAttribute.MESSAGE_TYPE );
}
@Override
public String getActivitySummary()
{
return mActivitySummary.getSummary();
}
}

View 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 decode.mdc1200;
import instrument.Instrumentable;
import instrument.tap.Tap;
import instrument.tap.stream.BinaryTap;
import instrument.tap.stream.FloatTap;
import java.util.ArrayList;
import java.util.List;
import source.Source.SampleType;
import alias.AliasList;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
import decode.DecoderType;
import dsp.NRZDecoder;
import dsp.filter.Filters;
import dsp.filter.FloatFIRFilter;
import dsp.filter.FloatHalfBandFilter;
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 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_INPUT =
"Tap Point: Float Input";
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 FloatHalfBandFilter mDecimationFilter;
private FloatFIRFilter mBandPassFilter;
private NRZDecoder mNRZDecoder;
private MessageFramer mMessageFramer;
private MDCMessageProcessor mMessageProcessor;
public MDCDecoder( AliasList aliasList )
{
super( SampleType.FLOAT );
/* Decimation filter: 48000 / 2 = 24000 */
mDecimationFilter = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
addFloatListener( mDecimationFilter );
/* Bandpass filter - predefined */
mBandPassFilter = new FloatFIRFilter(
Filters.FIRBP_1200FSK_24000FS.getCoefficients(), 1.02 );
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 );
mMessageProcessor.addMessageListener( this );
mMessageFramer.addMessageListener( mMessageProcessor );
}
public void dispose()
{
super.dispose();
mDecimationFilter.dispose();
mBandPassFilter.dispose();
mFSKDecoder.dispose();
mNRZDecoder.dispose();
mMessageFramer.dispose();
mMessageProcessor.dispose();
}
@Override
public DecoderType getType()
{
return DecoderType.MDC1200;
}
/* Instrumentation */
@Override
public List<Tap> getTaps()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
mAvailableTaps.add(
new FloatTap( INSTRUMENT_INPUT, 0, 1.0f ) );
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_INPUT:
addFloatListener( (FloatTap)tap );
break;
case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
/* wire the tap between the bandpass filter and the demod */
FloatTap floatTap = (FloatTap)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_INPUT:
removeFloatListener( (FloatTap)tap );
break;
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:
}
}
}

View File

@@ -0,0 +1,319 @@
/*******************************************************************************
* 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 decode.mdc1200;
import java.text.SimpleDateFormat;
import java.util.Date;
import map.Plottable;
import message.Message;
import alias.Alias;
import alias.AliasList;
import bits.BitSetBuffer;
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 BitSetBuffer mMessage;
private AliasList mAliasList;
public MDCMessage( BitSetBuffer 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;
}
private enum PacketType { CMND, DATA };
private enum Acknowledge { YES, NO };
private enum Direction { IN, OUT };
}

View 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 decode.mdc1200;
import message.Message;
import sample.Broadcaster;
import sample.Listener;
import alias.AliasList;
import bits.BitSetBuffer;
public class MDCMessageProcessor implements Listener<BitSetBuffer>
{
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( BitSetBuffer 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( BitSetBuffer 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( BitSetBuffer buffer, int offset )
{
if( buffer.size() < sMESSAGE_LENGTH + offset )
{
throw new IllegalArgumentException( "MDCMessageProcessor - "
+ "cannot deinterleave message - message buffer too short" );
}
BitSetBuffer deinterleaved = new BitSetBuffer( 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 );
}
}
}
}

View 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 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;
}
}

View File

@@ -0,0 +1,161 @@
/*******************************************************************************
* 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 decode.mdc1200;
import java.awt.EventQueue;
import javax.swing.JLabel;
import net.miginfocom.swing.MigLayout;
import settings.ColorSetting;
import settings.ColorSetting.ColorSettingName;
import settings.Setting;
import settings.SettingsManager;
import controller.state.AuxStatePanel;
import controller.state.ChannelState.ChangedAttribute;
public class MDCPanel extends AuxStatePanel
{
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, MDCChannelState state )
{
super( settingsManager, state );
init();
}
public MDCChannelState getState()
{
return (MDCChannelState)mAuxChannelState;
}
private void init()
{
setOpaque( false ); //Use the parent panel's background color
setLayout( new MigLayout( "insets 1 0 0 0", "[grow,fill]", "[]0[]0[]") );
addLabel( mFromLabel, false );
addLabel( mFrom, false );
addLabel( mFromAlias, true );
addLabel( mToLabel, false );
addLabel( mTo, false );
addLabel( mToAlias, true );
addLabel( mProtocol, false );
addLabel( mMessageType, false );
addLabel( mMessage, true );
}
@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:
updateAliasLabel( mFromAlias,
getState().getFromAlias() );
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:
updateAliasLabel( mToAlias,
getState().getToAlias() );
break;
case MESSAGE:
mMessage.setText( getState().getMessage() );
break;
case MESSAGE_TYPE:
mMessageType.setText( getState().getMessageType() );
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() );
}
}
}
}

View File

@@ -0,0 +1,171 @@
/*******************************************************************************
* 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 decode.mpt1327;
import java.util.Iterator;
import java.util.TreeSet;
import message.Message;
import sample.Listener;
import alias.Alias;
import alias.AliasList;
import controller.activity.ActivitySummaryProvider;
public class MPT1327ActivitySummary implements ActivitySummaryProvider,
Listener<Message>
{
private static final int sINT_NULL_VALUE = -1;
private AliasList mAliasList;
private TreeSet<String> mIdents = new TreeSet<String>();
private int mSite;
/**
* Compiles a summary of active talkgroups, unique radio ids encountered
* in the decoded messages. Talkgroups have to be heard a minimum of twice
* to be considered valid ... in order to weed out the invalid ones.
*
* Returns a textual summary of the activity
*/
public MPT1327ActivitySummary( AliasList list )
{
mAliasList = list;
}
/**
* Cleanup method
*/
public void dispose()
{
mIdents.clear();
}
@Override
public void receive( Message message )
{
if( message instanceof MPT1327Message )
{
MPT1327Message mpt = ((MPT1327Message)message);
if( mpt.isValid() )
{
switch( mpt.getMessageType() )
{
case ACK:
mIdents.add( mpt.getFromID() );
break;
case ACKI:
mIdents.add( mpt.getFromID() );
mIdents.add( mpt.getToID() );
break;
case ALH:
mSite = mpt.getSiteID();
break;
case GTC:
if( mpt.hasFromID() )
{
mIdents.add( mpt.getFromID() );
}
if( mpt.hasToID() )
{
mIdents.add( mpt.getToID() );
}
break;
}
}
}
}
@Override
public String getSummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "Activity Summary\n" );
sb.append( "Decoder:\tMPT-1327\n" );
sb.append( "Site:\t" );
if( mSite != 0 )
{
sb.append( mSite );
if( mAliasList != null )
{
Alias siteAlias = mAliasList.getSiteID( mSite );
if( siteAlias != null )
{
sb.append( " " );
sb.append( siteAlias.getName() );
sb.append( "\n" );
}
}
sb.append( "Network:\t" );
if( ( mSite & 0x4000 ) == 0x4000 )
{
sb.append( "National #" );
int net = (int)Long.rotateRight( ( mSite & 0x3000 ) , 12 );
sb.append( net );
sb.append( "\n" );
}
else
{
sb.append( "Regional\n" );
}
}
else
{
sb.append( "Unknown\n" );
}
sb.append( "\n" );
sb.append( "Talkgroups\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( " " );
Alias alias = mAliasList.getMPT1327Alias( ident );
if( alias != null )
{
sb.append( alias.getName() );
}
sb.append( "\n" );
}
}
return sb.toString();
}
}

View 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 decode.mpt1327;
import alias.Alias;
import alias.AliasList;
import controller.activity.CallEvent;
import decode.DecoderType;
public class MPT1327CallEvent extends CallEvent
{
private int mChannel;
private int mFrequency;
public MPT1327CallEvent( CallEventType callEventType,
AliasList aliasList,
String fromID,
String toID,
int channel,
int 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 int getChannel()
{
return mChannel;
}
@Override
public int 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 int mChannel;
private int 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( int channel )
{
mChannel = channel;
return this;
}
public Builder details( String details )
{
mDetails = details;
return this;
}
public Builder frequency( int 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 );
}
}

View File

@@ -0,0 +1,346 @@
/*******************************************************************************
* 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 decode.mpt1327;
import message.Message;
import sample.Listener;
import source.config.SourceConfigTuner;
import alias.AliasList;
import audio.SquelchListener;
import audio.SquelchListener.SquelchState;
import controller.activity.CallEvent.CallEventType;
import controller.channel.Channel;
import controller.channel.Channel.ChannelType;
import controller.channel.ChannelMap;
import controller.channel.ProcessingChain;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
import decode.config.DecodeConfigMPT1327;
public class MPT1327ChannelState extends ChannelState
{
private int mSite;
private String mFromTalkgroup;
private String mToTalkgroup;
private int mChannelNumber;
private ChannelType mChannelType;
private ChannelMap mChannelMap;
private MPT1327ActivitySummary mActivitySummary;
public MPT1327ChannelState( ProcessingChain processingChain,
AliasList aliasList,
ChannelMap channelMap )
{
super( processingChain, aliasList );
mActivitySummary = new MPT1327ActivitySummary( aliasList );
mChannelMap = channelMap;
mChannelType = processingChain.getChannel().getChannelType();
}
public void dispose()
{
super.dispose();
mActivitySummary.dispose();
mActivitySummary = null;
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( mActivitySummary.getSummary() );
for( AuxChannelState state: mAuxChannelStates )
{
sb.append( state.getActivitySummary() );
sb.append( "\n\n" );
}
return sb.toString();
}
@Override
public void receive( Message message )
{
super.receive( message );
if( message instanceof MPT1327Message )
{
mActivitySummary.receive( message );
MPT1327Message mpt = (MPT1327Message)message;
if( mpt.isValid() )
{
switch( mpt.getMessageType() )
{
case AHYC:
mCallEventModel.add(
new MPT1327CallEvent.Builder( CallEventType.SDM )
.aliasList( getProcessingChain().getAliasList() )
.channel( mChannelNumber )
.details( "Short Data Message" )
.frequency( mChannelMap.getFrequency( mChannelNumber ) )
.from( mpt.getFromID() )
.to( mpt.getToID() )
.build() );
break;
case AHYQ:
mCallEventModel.add(
new MPT1327CallEvent.Builder( CallEventType.STATUS )
.aliasList( getProcessingChain().getAliasList() )
.channel( mChannelNumber )
.details( mpt.getStatusMessage() )
.frequency( mChannelMap.getFrequency( mChannelNumber ) )
.from( mpt.getFromID() )
.to( mpt.getToID() )
.build() );
break;
case ALH:
int sys = mpt.getSiteID();
if( sys != 0 && sys != mSite )
{
mSite = sys;
broadcastChange( ChangedAttribute.CHANNEL_SITE_NUMBER );
}
setState( State.CONTROL );
break;
case GTC:
int channelNumber = mpt.getChannel();
if( !getProcessingChain().getChannel()
.hasTrafficChannel( channelNumber ) )
{
String aliasListName = mProcessingChain
.getChannel().getAliasListName();
Channel traffic = getTrafficChannel( channelNumber,
aliasListName );
/* Set the system and site to same as control channel */
traffic.setSystem( getProcessingChain().getChannel()
.getSystem(), false );
traffic.setSite( getProcessingChain().getChannel()
.getSite(), false );
/* Add the traffic channel to the parent control channel */
getProcessingChain().getChannel()
.addTrafficChannel( channelNumber, traffic );
/* Start the traffic channel */
traffic.setEnabled( true );
/* Set traffic channel state info */
MPT1327ChannelState trafficState =
(MPT1327ChannelState)traffic
.getProcessingChain().getChannelState();
MPT1327ChannelState controlState =
(MPT1327ChannelState)getProcessingChain()
.getChannelState();
trafficState.setChannelMap( controlState.getChannelMap() );
trafficState.setChannelNumber( channelNumber );
trafficState.setFromTalkgroup( mpt.getFromID() );
trafficState.setToTalkgroup( mpt.getToID() );
/* Add this control channel as message listener on the
* traffic channel so we can receive call tear-down
* messages */
traffic.addListener( (Listener<Message>)this );
CallEventType type = traffic.isProcessing() ?
CallEventType.CALL_START :
CallEventType.CALL_START_NO_TUNER;
/*
* Set the traffic channel's call event model to
* share the control channel's call event model
*/
traffic.getProcessingChain().getChannelState()
.setCallEventModel( mCallEventModel );
mCallEventModel.add(
new MPT1327CallEvent.Builder( type )
.aliasList( traffic.getProcessingChain().getAliasList() )
.channel( channelNumber )
.frequency( mChannelMap.getFrequency( channelNumber ) )
.from( mpt.getFromID() )
.to( mpt.getToID() )
.build() );
}
int frequency = mChannelMap.getFrequency( channelNumber );
break;
case CLEAR:
case MAINT:
if( mChannelType == ChannelType.TRAFFIC )
{
fade( CallEventType.CALL_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 ) )
{
mCallEventModel.add(
new MPT1327CallEvent.Builder( type )
.aliasList( getAliasList() )
.channel( mChannelNumber )
.frequency( mChannelMap.getFrequency( mChannelNumber ) )
.from( mFromTalkgroup )
.to( mToTalkgroup )
.build() );
}
super.fade( type );
}
/**
* Make the ConventionalChannelState always unsquelched
*/
public void addListener( SquelchListener listener )
{
super.addListener( listener );
super.setSquelchState( SquelchState.UNSQUELCH );
}
public void reset()
{
mFromTalkgroup = null;
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
mToTalkgroup = null;
broadcastChange( ChangedAttribute.TO_TALKGROUP );
super.reset();
}
public int getSite()
{
return mSite;
}
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;
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
}
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;
broadcastChange( ChangedAttribute.TO_TALKGROUP );
}
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;
broadcastChange( ChangedAttribute.CHANNEL_NUMBER );
}
public String getAliasListName()
{
return mProcessingChain.getChannel().getAliasListName();
}
private Channel getTrafficChannel( int channelNumber, String aliasListName )
{
Channel traffic = new Channel( "Traffic Channel " + channelNumber,
ChannelType.TRAFFIC );
traffic.setResourceManager( getProcessingChain().getResourceManager() );
SourceConfigTuner source = new SourceConfigTuner();
int frequency = mChannelMap.getFrequency( channelNumber );
source.setFrequency( frequency );
traffic.setSourceConfiguration( source );
DecodeConfigMPT1327 decode = new DecodeConfigMPT1327();
traffic.setDecodeConfiguration( decode );
traffic.setAliasListName( aliasListName );
return traffic;
}
}

View File

@@ -0,0 +1,283 @@
/*******************************************************************************
* 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 decode.mpt1327;
import instrument.Instrumentable;
import instrument.tap.Tap;
import instrument.tap.stream.BinaryTap;
import instrument.tap.stream.FloatTap;
import java.util.ArrayList;
import java.util.List;
import sample.Broadcaster;
import source.Source.SampleType;
import alias.AliasList;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
import decode.DecoderType;
import dsp.filter.DCRemovalFilter;
import dsp.filter.FilterFactory;
import dsp.filter.Filters;
import dsp.filter.FloatFIRFilter;
import dsp.filter.FloatHalfBandFilter;
import dsp.filter.Window.WindowType;
import dsp.fsk.FSK2Decoder;
import dsp.fsk.FSK2Decoder.Output;
import dsp.nbfm.NBFMDemodulator;
/**
* 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 Instrumentable
{
/* Determines how quickly the DC remove filter responds */
private static final double sDC_REMOVAL_RATIO = 0.95;
/* 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_INPUT = "Tap Point: Input";
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 NBFMDemodulator mNBFMDemodulator;
private FloatHalfBandFilter mDecimationFilter;
private FloatFIRFilter mLowPassFilter;
private DCRemovalFilter mDCRemovalFilter;
private FSK2Decoder mFSKDecoder;
private Broadcaster<Boolean> mSymbolBroadcaster;
private MessageFramer mControlMessageFramer;
private MessageFramer mTrafficMessageFramer;
private MPT1327MessageProcessor mMessageProcessor;
public MPT1327Decoder( SampleType sampleType, AliasList aliasList )
{
super( sampleType );
/* If we're receiving complex samples, do FM demodulation and DC removal
* and feed the output back to this decoder */
if( mSourceSampleType == SampleType.COMPLEX )
{
/* I/Q low pass filtering narrow band FM demodulator */
mNBFMDemodulator = new NBFMDemodulator(
FilterFactory.getLowPass( 48000, 4000, 73, WindowType.HAMMING ),
1.0002 );
this.addComplexListener( mNBFMDemodulator );
/* DC removal filter */
mDCRemovalFilter = new DCRemovalFilter( sDC_REMOVAL_RATIO );
if( mNBFMDemodulator != null )
{
mNBFMDemodulator.addListener( mDCRemovalFilter );
mDCRemovalFilter.setListener( this.getFloatReceiver() );
}
}
/* Demodulated float sample processing */
/* Decimation filter - 48000 / 2 = 24000 output */
mDecimationFilter = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
addFloatListener( mDecimationFilter );
/* Low pass filter: 2kHz to pass the 1200 & 1800 Hz FSK */
mLowPassFilter = new FloatFIRFilter(
FilterFactory.getLowPass( 24000, //sampleRate,
2000, //passFrequency,
4000, //stopFrequency,
60, //attenuation,
WindowType.HANNING, //windowType,
true ) //forceOddLength
, 2.0 ); //Gain
mDecimationFilter.setListener( mLowPassFilter );
/* 2FSK Decoder with output inverted */
mFSKDecoder = new FSK2Decoder( 24000, 1200, Output.INVERTED );
mLowPassFilter.setListener( mFSKDecoder );
mSymbolBroadcaster = new Broadcaster<Boolean>();
mFSKDecoder.setListener( mSymbolBroadcaster );
/* Message framer for control channel messages */
mControlMessageFramer =
new MessageFramer( SyncPattern.MPT1327_CONTROL.getPattern(),
sMESSAGE_LENGTH );
mSymbolBroadcaster.addListener( mControlMessageFramer );
/* Message framer for traffic channel massages */
mTrafficMessageFramer =
new MessageFramer( SyncPattern.MPT1327_TRAFFIC.getPattern(),
sMESSAGE_LENGTH );
mSymbolBroadcaster.addListener( mTrafficMessageFramer );
/* Fully decoded and framed messages processor */
mMessageProcessor = new MPT1327MessageProcessor( aliasList );
mMessageProcessor.addMessageListener( this );
mControlMessageFramer.addMessageListener( mMessageProcessor );
mTrafficMessageFramer.addMessageListener( mMessageProcessor );
}
@Override
public DecoderType getType()
{
return DecoderType.MPT1327;
}
/**
* Cleanup method
*/
public void dispose()
{
super.dispose();
mSymbolBroadcaster.dispose();
mControlMessageFramer.dispose();
if( mDCRemovalFilter != null )
{
mDCRemovalFilter.dispose();
}
if( mNBFMDemodulator != null )
{
mNBFMDemodulator.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_INPUT, 0, 1.0f ) );
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_INPUT:
FloatTap inputTap = (FloatTap)tap;
if( mNBFMDemodulator != null )
{
mNBFMDemodulator.addListener( inputTap );
}
else
{
addFloatListener( inputTap );
}
break;
case INSTRUMENT_HB1_FILTER_TO_LOW_PASS:
FloatTap hb1Tap = (FloatTap)tap;
mDecimationFilter.setListener( hb1Tap );
hb1Tap.setListener( mLowPassFilter );
break;
case INSTRUMENT_LOW_PASS_TO_DECODER:
FloatTap lowTap = (FloatTap)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_INPUT:
FloatTap inputTap = (FloatTap)tap;
if( mNBFMDemodulator != null )
{
mNBFMDemodulator.removeListener( inputTap );
}
else
{
removeFloatListener( inputTap );
}
break;
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;
}
}
}

View File

@@ -0,0 +1,103 @@
/*******************************************************************************
* 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 decode.mpt1327;
import java.util.ArrayList;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import controller.channel.ChannelMap;
import controller.channel.ChannelNode;
import decode.DecodeEditor;
import decode.config.DecodeConfigMPT1327;
import decode.config.DecodeConfiguration;
public class MPT1327Editor extends DecodeEditor
{
private static final long serialVersionUID = 1L;
private JComboBox<ChannelMap> mComboChannelMaps;
private ChannelNode mChannelNode;
public MPT1327Editor( DecodeConfiguration config, ChannelNode channelNode )
{
super( config );
mChannelNode = channelNode;
initGUI();
}
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" ), "wrap" );
add( mComboChannelMaps, "wrap" );
reset();
}
@Override
public void save()
{
ChannelMap selected = (ChannelMap)mComboChannelMaps.getSelectedItem();
if( selected != null )
{
((DecodeConfigMPT1327)mConfig).setChannelMapName( selected.getName() );
}
}
@Override
public void reset()
{
String name = ((DecodeConfigMPT1327)mConfig).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();
}
}

View File

@@ -0,0 +1,986 @@
/*******************************************************************************
* 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 decode.mpt1327;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import map.Plottable;
import message.Message;
import alias.Alias;
import alias.AliasList;
import bits.BitSetBuffer;
import crc.CRC;
import crc.CRCFleetsync;
public class MPT1327Message 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
private static int BLOCK_1_START = 20;
private static int BLOCK_2_START = 84;
private static int BLOCK_3_START = 148;
private static int BLOCK_4_START = 212;
private static int BLOCK_5_START = 276;
private static int BLOCK_5_END = 340;
private static int[] REVS = { 0,1,2,3 };
private static int[] SYNC = { 4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 };
/* Block 1 Fields */
private static int[] B1_PREFIX = { 21,22,23,24,25,26,27 };
private static int[] B1_IDENT1 = { 28,29,30,31,32,33,34,35,36,37,38,39,40 };
private static int[] B1_IDENT2 = { 53,54,55,56,57,58,59,60,61,62,63,64,65 };
private static int[] B1_GTC_CHAN = { 43,44,45,46,47,48,49,50,51,52 };
private static int[] B1_MESSAGE_TYPE = { 41,42,43,44,45,46,47,48,49 };
private static int[] B1_STATUS_MESSAGE = { 66,67,68,69,70 };
private static int[] B1_SYSTEM_ID = { 26,27,28,29,30,31,32,33,34,35,36,37,38,39,40 };
private static int[] B1_SYSDEF = { 21,22,23,24,25 };
private static int[] B1_CHANNEL = { 35,36,37,38,39,40,41,42,43,44 };
private static int[] B1_TRAFFIC_CHANNEL = { 21,22,23,24,25,26,27,28,29,30 };
private static int[] B1_CONTROL_CHANNEL = { 31,32,33,34,35,36,37,38,39,40 };
private static int[] B1_ADJSITE = { 49,50,51,52 };
/* Block 2 Fields */
private static int[] B2_SYSTEM_ID = { 85,86,87,88,89,90,91,92,93,94,95,96,97,98,99 };
private static int[] B2_PREFIX = { 112,113,114,115,116,117,118 };
private static int[] B2_IDENT2 = { 119,120,121,122,123,124,125,126,127,128,129,130,131 };
private BitSetBuffer mMessage;
private CRC[] mCRC = new CRC[ 5 ];
private AliasList mAliasList;
private MPTMessageType mMessageType;
public MPT1327Message( BitSetBuffer message, AliasList list )
{
mMessage = message;
mAliasList = list;
checkParity( 0, BLOCK_1_START, BLOCK_2_START );
if( isValid() )
{
mMessageType = getMessageType();
switch( mMessageType )
{
/* 1 data block messages */
case CLEAR:
case MAINT:
case MOVE:
break;
/* 2 data block messages */
case AHYQ:
case ALH:
checkParity( 1, BLOCK_2_START, BLOCK_3_START );
break;
/* 3 data block messages */
/* 4 data block messages */
case ACKT:
checkParity( 1, BLOCK_2_START, BLOCK_3_START );
checkParity( 2, BLOCK_3_START, BLOCK_4_START );
checkParity( 3, BLOCK_4_START, BLOCK_5_START );
break;
/* 5 data block messages */
case AHYC:
checkParity( 1, BLOCK_2_START, BLOCK_3_START );
checkParity( 2, BLOCK_3_START, BLOCK_4_START );
checkParity( 3, BLOCK_4_START, BLOCK_5_START );
checkParity( 3, BLOCK_5_START, BLOCK_5_END );
break;
case ACK:
case ACKB:
case ACKE:
case ACKI:
case ACKQ:
case ACKV:
case ACKX:
case AHOY:
case AHYP:
case AHYX:
case ALHD:
case ALHE:
case ALHF:
case ALHR:
case ALHS:
case ALHX:
case BCAST:
case GTC:
case MARK:
break;
default:
break;
}
}
else
{
mMessageType = MPTMessageType.UNKN;
}
}
private void checkParity( int section, int start, int end )
{
mCRC[ section ] = detectAndCorrect( start, end );
}
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;
}
/**
* 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 ) + "]";
}
public boolean isValid()
{
return mCRC[ 0 ] == CRC.PASSED ||
mCRC[ 0 ] == CRC.CORRECTED;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( mSDF.format( new Date() ) );
sb.append( " MPT1327 " );
sb.append( getParity() );
sb.append( " " );
sb.append( getMessage() );
sb.append( getFiller( sb, 100 ) );
sb.append( " [" + mMessage.toString() + "]" );
return sb.toString();
}
public MPTMessageType getMessageType()
{
int type = mMessage.getInt( B1_MESSAGE_TYPE );
return MPTMessageType.fromNumber( type );
}
public int getSiteID()
{
if( mMessageType == MPTMessageType.BCAST )
{
return mMessage.getInt( B1_SYSTEM_ID );
}
else if( mMessageType == MPTMessageType.ALH )
{
return mMessage.getInt( B2_SYSTEM_ID );
}
else
{
return 0;
}
}
public boolean hasSystemID()
{
return getSiteID() != 0;
}
public SystemDefinition getSystemDefinition()
{
int sysdef = mMessage.getInt( B1_SYSDEF );
return SystemDefinition.fromNumber( sysdef );
}
public int getChannel()
{
switch( mMessageType )
{
case CLEAR:
return mMessage.getInt( B1_TRAFFIC_CHANNEL );
case GTC:
return mMessage.getInt( B1_GTC_CHAN );
default:
return mMessage.getInt( B1_CHANNEL );
}
}
public int getReturnToChannel()
{
if( mMessageType == MPTMessageType.CLEAR )
{
return mMessage.getInt( B1_CONTROL_CHANNEL );
}
else
{
return 0;
}
}
public int getAdjacentSiteSerialNumber()
{
return mMessage.getInt( B1_ADJSITE );
}
public int getPrefix()
{
return mMessage.getInt( B1_PREFIX );
}
public int getBlock2Prefix()
{
return mMessage.getInt( B2_PREFIX );
}
public int getIdent1()
{
return mMessage.getInt( B1_IDENT1 );
}
public int getIdent2()
{
return mMessage.getInt( B1_IDENT2 );
}
public int getBlock2Ident2()
{
return mMessage.getInt( B2_IDENT2 );
}
public String getStatusMessage()
{
int status = mMessage.getInt( B1_STATUS_MESSAGE );
switch( status )
{
case 0:
return "STATUS: Request Speech Call";
case 31:
return "STATUS: Cancel Request Speech Call";
default:
return "STATUS: " + status;
}
}
/**
* Returns spaces to the fill the string builder to ensure length is >= index
*/
public String getFiller( StringBuilder sb, int index )
{
if( sb.length() < index )
{
return String.format( "%" + ( index - sb.length() ) + "s", " " );
}
else
{
return "";
}
}
/**
* 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 getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( pad( mMessageType.toString(), 5, " " ) );
switch( mMessageType )
{
case ACK:
sb.append( " MESSAGE ACKNOWLEDGED" );
if( hasFromID() )
{
sb.append( " FROM:" );
sb.append( getFromID() );
}
break;
case ACKI:
sb.append( " SYSTEM:" );
sb.append( format( getSiteID(), 5 ) );
sb.append( " MESSAGE ACKNOWLEDGED - MORE TO FOLLOW" );
if( hasFromID() )
{
sb.append( " FROM:" );
sb.append( getFromID() );
}
if( hasToID() )
{
sb.append( " TO:" );
sb.append( getToID() );
}
break;
case ACKT:
sb.append( " SITE:" );
sb.append( format( getSiteID(), 5 ) );
sb.append( " LONG ACK MESSAGE" );
if( hasFromID() )
{
sb.append( " FROM:" );
sb.append( getFromID() );
}
if( hasToID() )
{
sb.append( " TO:" );
sb.append( getToID() );
}
sb.append( "**********************************" );
break;
case ACKQ:
sb.append( " SYSTEM:" );
sb.append( format( getSiteID(), 5 ) );
sb.append( " CALL QUEUED FROM:" );
if( hasFromID() )
{
sb.append( " FROM:" );
sb.append( getFromID() );
}
if( hasToID() )
{
sb.append( " TO:" );
sb.append( getToID() );
}
break;
case ACKX:
sb.append( " SYSTEM:" );
sb.append( format( getSiteID(), 5 ) );
sb.append( " MESSAGE REJECTED FROM:" );
if( hasFromID() )
{
sb.append( " FROM:" );
sb.append( getFromID() );
}
if( hasToID() )
{
sb.append( " TO:" );
sb.append( getToID() );
}
break;
case AHYC:
/* SDM */
sb.append( " SHORT DATA INVITATION MESSAGE ********* " );
break;
case AHYQ:
/* Status Message */
sb.append( " STATUS MESSAGE" );
if( hasFromID() )
{
sb.append( " FROM:" );
sb.append( getFromID() );
}
if( hasToID() )
{
sb.append( " TO:" );
sb.append( getToID() );
}
sb.append( " " );
sb.append( getStatusMessage() );
break;
case ALH:
case ALHD:
case ALHS:
case ALHE:
case ALHR:
case ALHX:
case ALHF:
sb.append( " SYSTEM:" );
sb.append( format( getSiteID(), 5 ) );
if( hasToID() )
{
sb.append( " ID:" );
sb.append( getToID() );
}
break;
case BCAST:
SystemDefinition sysdef = getSystemDefinition();
sb.append( " " );
sb.append( sysdef.getLabel() );
switch( sysdef )
{
case ANNOUNCE_CONTROL_CHANNEL:
case WITHDRAW_CONTROL_CHANNEL:
sb.append( " CHAN:" );
sb.append( getChannel() );
break;
case BROADCAST_ADJACENT_SITE_CONTROL_CHANNEL_NUMBER:
sb.append( " CHAN:" );
sb.append( getChannel() );
sb.append( " SER:" );
sb.append( getAdjacentSiteSerialNumber() );
//debug
sb.append( " *************************" );
break;
}
break;
case CLEAR:
sb.append( " TRAFFIC CHANNEL:" );
sb.append( getChannel() );
sb.append( " RETURN TO CONTROL CHANNEL:" );
sb.append( getReturnToChannel() );
break;
case GTC:
if( hasFromID() )
{
sb.append( " FROM:" );
sb.append( getFromID() );
}
if( hasToID() )
{
sb.append( " TO:" );
sb.append( getToID() );
}
sb.append( " CHAN:" );
sb.append( getChannel() );
break;
case MAINT:
if( hasToID() )
{
sb.append( " ID:" );
sb.append( getToID() );
}
break;
}
return sb.toString();
}
@Override
public String getBinaryMessage()
{
return mMessage.toString();
}
@Override
public String getProtocol()
{
return "MPT-1327";
}
@Override
public String getEventType()
{
// TODO Auto-generated method stub
return null;
}
@Override
public String getFromID()
{
StringBuilder sb = new StringBuilder();
int ident2 = getIdent2();
IdentType type = IdentType.fromIdent( ident2 );
/* Inter-Prefix - the from and to idents are different prefixes */
if( type == IdentType.IPFIXI )
{
sb.append( format( getBlock2Prefix(), 3 ) );
sb.append( "-" );
sb.append( format( getBlock2Ident2(), 4) );
}
else
{
sb.append( format( getPrefix(), 3 ) );
sb.append( "-" );
sb.append( format( ident2, 4) );
}
return sb.toString();
}
public boolean hasFromID()
{
return getFromID() != null;
}
@Override
public Alias getFromIDAlias()
{
return null;
}
@Override
public String getToID()
{
StringBuilder sb = new StringBuilder();
int prefix = getPrefix();
int ident = getIdent1();
switch( IdentType.fromIdent( ident ) )
{
case USER:
sb.append( format( prefix, 3 ) );
sb.append( "-" );
sb.append( format( ident, 4) );
break;
case IPFIXI:
sb.append( "INTER-PREFIX" );
break;
case ALLI:
sb.append( "ALL RADIOS" );
break;
case PABXI:
sb.append( "PABX EXT" );
break;
case PSTNSI1:
case PSTNSI2:
case PSTNSI3:
case PSTNSI4:
case PSTNSI5:
case PSTNSI6:
case PSTNSI7:
case PSTNSI8:
case PSTNSI9:
case PSTNSI10:
case PSTNSI11:
case PSTNSI12:
case PSTNSI13:
case PSTNSI14:
case PSTNSI15:
sb.append( "PRE-DEFINED PSTN" );
break;
case PSTNGI:
sb.append( "PSTN GATEWAY" );
break;
case TSCI:
sb.append( "CONTROLLER" );
break;
case DIVERTI:
sb.append( "CALL DIVERT" );
break;
}
return sb.toString();
}
public boolean hasToID()
{
return getToID() != null;
}
@Override
public Alias getToIDAlias()
{
if( hasToID() )
{
return mAliasList.getMPT1327Alias( getToID() );
}
else
{
return null;
}
}
@Override
public String getErrorStatus()
{
return getParity();
}
@Override
public Plottable getPlottable()
{
return null;
}
public enum IdentType
{
ALLI( "All Subscribers" ),
DIVERTI( "Divert" ),
DNI( "Data Network Gateway" ),
DUMMYI( "Dummy Ident" ),
INCI( "Include in Call" ),
IPFIXI( "Inter-Prefix" ),
PABXI( "PABX Gateway" ),
PSTNGI( "PSTN Gateway" ),
PSTNSI1( "PSTN or Network 1" ),
PSTNSI2( "PSTN or Network 2" ),
PSTNSI3( "PSTN or Network 3" ),
PSTNSI4( "PSTN or Network 4" ),
PSTNSI5( "PSTN or Network 5" ),
PSTNSI6( "PSTN or Network 6" ),
PSTNSI7( "PSTN or Network 7" ),
PSTNSI8( "PSTN or Network 8" ),
PSTNSI9( "PSTN or Network 9" ),
PSTNSI10( "PSTN or Network 10" ),
PSTNSI11( "PSTN or Network 11" ),
PSTNSI12( "PSTN or Network 12" ),
PSTNSI13( "PSTN or Network 13" ),
PSTNSI14( "PSTN or Network 14" ),
PSTNSI15( "PSTN or Network 15" ),
REGI( "Registration" ),
RESERVED( "Reserved" ),
SDMI( "Short Data Message" ),
SPARE( "Spare" ),
TSCI( "TSC" ),
USER( "Individual or Group Ident" ),
UNKNOWN( "Unknown" );
private String mLabel;
private IdentType( String label )
{
mLabel = label;
}
public String getLabel()
{
return mLabel;
}
public static IdentType fromIdent( int ident )
{
switch( ident )
{
case 0:
return DUMMYI;
case 8101:
return PSTNGI;
case 8102:
return PABXI;
case 8103:
return DNI;
case 8121:
return PSTNSI1;
case 8122:
return PSTNSI2;
case 8123:
return PSTNSI3;
case 8124:
return PSTNSI4;
case 8125:
return PSTNSI5;
case 8126:
return PSTNSI6;
case 8127:
return PSTNSI7;
case 8128:
return PSTNSI8;
case 8129:
return PSTNSI9;
case 8130:
return PSTNSI10;
case 8131:
return PSTNSI11;
case 8132:
return PSTNSI12;
case 8133:
return PSTNSI13;
case 8134:
return PSTNSI14;
case 8135:
return PSTNSI15;
case 8181:
case 8182:
case 8183:
case 8184:
return RESERVED;
case 8185:
return REGI;
case 8186:
return INCI;
case 8187:
return DIVERTI;
case 8188:
return SDMI;
case 8189:
return IPFIXI;
case 8190:
return TSCI;
case 8191:
return ALLI;
default:
if( 1 <= ident && ident <= 8100 )
{
return USER;
}
else if( ( 8104 <= ident && ident <= 8120 ) ||
( 8136 <= ident && ident <= 8180 ) )
{
return SPARE;
}
else
{
return UNKNOWN;
}
}
}
}
public enum SystemDefinition
{
UNKNOWN( "UNKNOWN" ),
ANNOUNCE_CONTROL_CHANNEL( "ANNOUNCE CONTROL CHANNEL" ),
WITHDRAW_CONTROL_CHANNEL( "WITHDRAW CONTROL CHANNEL" ),
SPECIFY_CALL_MAINTENANCE_PARAMETERS( "CALL MAINT PARAMETERS" ),
SPECIFY_REGISTRATION_PARAMETERS( "REGISTRATION PARAMETERS" ),
BROADCAST_ADJACENT_SITE_CONTROL_CHANNEL_NUMBER( "NEIGHBOR" ),
VOTE_NOW_ADVICE( "VOTE NOW" );
private String mLabel;
private SystemDefinition( String label )
{
mLabel = label;
}
public String getLabel()
{
return mLabel;
}
public static SystemDefinition fromNumber( int number )
{
switch( number )
{
case 0:
return ANNOUNCE_CONTROL_CHANNEL;
case 1:
return WITHDRAW_CONTROL_CHANNEL;
case 2:
return SPECIFY_CALL_MAINTENANCE_PARAMETERS;
case 3:
return SPECIFY_REGISTRATION_PARAMETERS;
case 4:
return BROADCAST_ADJACENT_SITE_CONTROL_CHANNEL_NUMBER;
case 5:
return VOTE_NOW_ADVICE;
default:
return UNKNOWN;
}
}
}
public enum MPTMessageType
{
UNKN( -1, "Unknown" ),
GTC( 0, "GTC - Goto Channel" ),
ALH( 256, "ALH - Aloha" ),
ALHS( 257, "ALHS - Standard Data Excluded" ),
ALHD( 258, "ALHD - Simple Calls Excluded" ),
ALHE( 259, "ALHE - Emergency Calls Only" ),
ALHR( 260, "ALHR - Emergency or Registration" ),
ALHX( 261, "ALHX - Registration Excluded" ),
ALHF( 262, "ALHF - Fallback Mode" ),
ACK( 264, "ACK - Acknowledge" ),
ACKI( 265, "ACKI - More To Follow" ),
ACKQ( 266, "ACKQ - Call Queued" ),
ACKX( 267, "ACKX - Message Rejected" ),
ACKV( 268, "ACKV - Called Unit Unavailable" ),
ACKE( 269, "ACKE - Emergency" ),
ACKT( 270, "ACKT - Try On Given Address" ),
ACKB( 271, "ACKB - Call Back/Negative Ack" ),
AHOY( 272, "AHOY - General Availability Check" ),
AHYX( 274, "AHYX - Cancel Alert/Waiting Status" ),
AHYP( 277, "AHYP - Called Unit Presence Monitoring" ),
AHYQ( 278, "AHYQ - Status Message" ),
AHYC( 279, "AHYC - Short Data Message" ),
MARK( 280, "MARK - Control Channel Marker" ),
MAINT(281, "MAINT - Call Maintenance Message"),
CLEAR(282, "CLEAR - Down From Allocated Channel" ),
MOVE( 283, "MOVE - To Specified Channel" ),
BCAST(284, "BCAST - System Parameters" );
/* There are more to fill in here */
private int mNumber;
private String mDescription;
private MPTMessageType( int number, String description )
{
mNumber = number;
mDescription = description;
}
public int getMessageNumber()
{
return mNumber;
}
public String getDescription()
{
return mDescription;
}
public static MPTMessageType fromNumber( int number )
{
if( number < 256 )
{
return GTC;
}
switch( number )
{
case 256:
return ALH;
case 257:
return ALHS;
case 258:
return ALHD;
case 259:
return ALHE;
case 260:
return ALHR;
case 261:
return ALHX;
case 262:
return ALHF;
case 264:
return ACK;
case 265:
return ACKI;
case 266:
return ACKQ;
case 267:
return ACKX;
case 268:
return ACKV;
case 269:
return ACKE;
case 270:
return ACKT;
case 271:
return ACKB;
case 272:
return AHOY;
case 274:
return AHYX;
case 277:
return AHYP;
case 278:
return AHYQ;
case 279:
return AHYC;
case 280:
return MARK;
case 281:
return MAINT;
case 282:
return CLEAR;
case 283:
return MOVE;
case 284:
return BCAST;
default:
return UNKN;
}
}
}
}

View File

@@ -0,0 +1,59 @@
/*******************************************************************************
* 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 decode.mpt1327;
import message.Message;
import sample.Broadcaster;
import sample.Listener;
import alias.AliasList;
import bits.BitSetBuffer;
public class MPT1327MessageProcessor implements Listener<BitSetBuffer>
{
private Broadcaster<Message> mBroadcaster = new Broadcaster<Message>();
private AliasList mAliasList;
public MPT1327MessageProcessor( AliasList list )
{
mAliasList = list;
}
public void dispose()
{
mBroadcaster.dispose();
}
@Override
public void receive( BitSetBuffer buffer )
{
MPT1327Message message = new MPT1327Message( buffer, mAliasList );
mBroadcaster.receive( message );
}
public void addMessageListener( Listener<Message> listener )
{
mBroadcaster.addListener( listener );
}
public void removeMessageListener( Listener<Message> listener )
{
mBroadcaster.removeListener( listener );
}
}

View File

@@ -0,0 +1,282 @@
/*******************************************************************************
* 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 decode.mpt1327;
import java.awt.EventQueue;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import settings.ColorSetting;
import settings.Setting;
import alias.Alias;
import alias.AliasList;
import controller.channel.Channel;
import controller.state.ChannelState.ChangedAttribute;
import controller.state.ChannelStatePanel;
public class MPT1327Panel extends ChannelStatePanel
{
private static final long serialVersionUID = 1L;
private JLabel mProtocol;
private JLabel mSourceLabel;
private JLabel mChannelLabel;
private JLabel mStateLabel;
private JLabel mTalkgroup;
private JLabel mTalkgroupAlias;
private JLabel mSiteLabel;
private JLabel mSite;
private JLabel mSiteAliasLabel;
private MPT1327ChannelState mState;
private AliasList mAliasList;
public MPT1327Panel( Channel channel )
{
super( channel );
mState = (MPT1327ChannelState)channel.getProcessingChain().getChannelState();
mState.addListener( this );
mAliasList = mState.getAliasList();
init();
}
public void dispose()
{
super.dispose();
mState = null;
mAliasList = null;
}
public void init()
{
mProtocol = new JLabel( "MPT1327" );
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
mSourceLabel = new JLabel( mChannel.getSourceConfiguration().getDescription() );
mSourceLabel.setFont( mFontDetails );
mSourceLabel.setForeground( mColorLabelDetails );
mChannelLabel = new JLabel( mChannel.getChannelDisplayName() );
mChannelLabel.setFont( mFontDetails );
mChannelLabel.setForeground( mColorLabelDetails );
mStateLabel = new JLabel( mChannel.getProcessingChain().
getChannelState().getState().getDisplayValue() );
mStateLabel.setFont( mFontDecoder );
mStateLabel.setForeground( mColorLabelDecoder );
mTalkgroup = new JLabel( mState.getFromTalkgroup() );
mTalkgroup.setFont( mFontDecoder );
mTalkgroup.setForeground( mColorLabelDecoder );
mTalkgroupAlias = new JLabel();
mTalkgroupAlias.setFont( mFontDecoder );
mTalkgroupAlias.setForeground( mColorLabelDecoder );
Alias alias = getTalkgroupAlias( mState.getFromTalkgroup() );
if( alias != null )
{
mTalkgroupAlias.setText( alias.getName() );
mTalkgroupAlias.setIcon( getIcon( alias ) );
}
mSiteLabel = new JLabel( "Site:" );
mSiteLabel.setFont( mFontDetails );
mSiteLabel.setForeground( mColorLabelDetails );
mSite = new JLabel( String.valueOf( mState.getSite() ) );
mSite.setFont( mFontDecoder );
mSite.setForeground( mColorLabelDecoder );
mSiteAliasLabel = new JLabel();
mSiteAliasLabel.setFont( mFontDecoder );
mSiteAliasLabel.setForeground( mColorLabelDecoder );
Alias siteAlias = getSiteAlias( mState.getSite() );
if( siteAlias != null )
{
mSiteAliasLabel.setText( siteAlias.getName() );
}
add( mProtocol );
add( mSourceLabel );
add( mChannelLabel, "wrap" );
add( mStateLabel );
add( mTalkgroup );
add( mTalkgroupAlias, "wrap" );
add( mSiteLabel );
add( mSite );
add( mSiteAliasLabel, "wrap" );
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
switch( changedAttribute )
{
case CHANNEL_STATE:
mStateLabel.setText( mState.getState().getDisplayValue() );
break;
case CHANNEL_SITE_NUMBER:
mSite.setText( String.valueOf( mState.getSite() ) );
Alias siteAlias = getSiteAlias( mState.getSite() );
if( siteAlias != null )
{
mSiteAliasLabel.setText( siteAlias.getName() );
}
repaint();
break;
case FROM_TALKGROUP:
mTalkgroup.setText( mState.getFromTalkgroup() );
Alias alias = getTalkgroupAlias( mState.getFromTalkgroup() );
if( alias != null )
{
mTalkgroupAlias.setText( alias.getName() );
mTalkgroupAlias.setIcon( getIcon( alias ) );
}
repaint();
break;
}
}
} );
}
private ImageIcon getIcon( Alias alias )
{
if( alias != null )
{
String iconName = alias.getIconName();
if( iconName != null )
{
return getSettingsManager().getImageIcon( iconName, 12 );
}
}
return null;
}
private Alias getSiteAlias( int site )
{
if( site != 0 && mAliasList != null )
{
return mAliasList.getSiteID( site );
}
return null;
}
private Alias getTalkgroupAlias( String talkgroup )
{
if( talkgroup != null && mAliasList != null )
{
return mAliasList.getTalkgroupAlias( talkgroup );
}
return null;
}
@Override
public void trafficChannelAdded( Channel traffic )
{
if( !getTrafficPanels().keySet().contains( traffic ) )
{
MPT1327TrafficPanel panel =
new MPT1327TrafficPanel( traffic );
getTrafficPanels().put( traffic, panel );
addPanel( panel );
}
}
@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( mStateLabel != null )
{
mStateLabel.setForeground( mColorLabelDecoder );
}
if( mTalkgroup != null )
{
mTalkgroup.setForeground( mColorLabelDecoder );
}
if( mTalkgroupAlias != null )
{
mTalkgroupAlias.setForeground( mColorLabelDecoder );
}
if( mSite != null )
{
mSite.setForeground( mColorLabelDecoder );
}
if( mSiteAliasLabel != null )
{
mSiteAliasLabel.setForeground( mColorLabelDecoder );
}
break;
case CHANNEL_STATE_LABEL_DETAILS:
if( mSourceLabel != null )
{
mSourceLabel.setForeground( mColorLabelDetails );
}
if( mChannelLabel != null )
{
mChannelLabel.setForeground( mColorLabelDetails );
}
if( mSiteLabel != null )
{
mSiteLabel.setForeground( mColorLabelDetails );
}
break;
}
}
}
}

View File

@@ -0,0 +1,258 @@
/*******************************************************************************
* 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 decode.mpt1327;
import java.awt.EventQueue;
import javax.swing.JLabel;
import settings.ColorSetting;
import settings.Setting;
import alias.Alias;
import alias.AliasList;
import controller.channel.Channel;
import controller.channel.ProcessingChain;
import controller.state.ChannelState.ChangedAttribute;
import controller.state.ChannelStatePanel;
public class MPT1327TrafficPanel extends ChannelStatePanel
{
private static final long serialVersionUID = 1L;
private JLabel mProtocolLabel;
private JLabel mFrequencyLabel;
private JLabel mChannelLabel;
private JLabel mStateLabel;
private JLabel mFromTalkgroupLabel;
private JLabel mFromTalkgroupAliasLabel;
private JLabel mToLabel;
private JLabel mToTalkgroupLabel;
private JLabel mToTalkgroupAliasLabel;
private AliasList mAliasList;
public MPT1327TrafficPanel( Channel channel )
{
super( channel );
mAliasList = channel.getProcessingChain().getChannelState().getAliasList();
init();
}
public void dispose()
{
super.dispose();
mAliasList = null;
}
public void init()
{
mProtocolLabel = new JLabel( "MPT1327" );
mProtocolLabel.setFont( mFontDecoder );
mProtocolLabel.setForeground( mColorLabelDecoder );
mFrequencyLabel = new JLabel( mChannel.getSourceConfiguration().getDescription() );
mFrequencyLabel.setFont( mFontDetails );
mFrequencyLabel.setForeground( mColorLabelDetails );
MPT1327ChannelState state =
(MPT1327ChannelState)mChannel.getProcessingChain().getChannelState();
int channelNumber = state.getChannelNumber();
mChannelLabel = new JLabel( "Channel " + channelNumber );
mChannelLabel.setFont( mFontDetails );
mChannelLabel.setForeground( mColorLabelDetails );
mStateLabel = new JLabel( mChannel.getProcessingChain().
getChannelState().getState().getDisplayValue() );
mStateLabel.setFont( mFontDecoder );
mStateLabel.setForeground( mColorLabelDecoder );
mFromTalkgroupLabel = new JLabel( state.getFromTalkgroup() );
mFromTalkgroupLabel.setFont( mFontDecoder );
mFromTalkgroupLabel.setForeground( mColorLabelDecoder );
mFromTalkgroupAliasLabel = new JLabel();
mFromTalkgroupAliasLabel.setFont( mFontDecoder );
mFromTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
if( mAliasList != null )
{
Alias alias = mAliasList.getMPT1327Alias( state.getFromTalkgroup() );
if( alias != null )
{
mFromTalkgroupAliasLabel.setText( alias.getName() );
mFromTalkgroupAliasLabel.setIcon( getChannel()
.getResourceManager().getSettingsManager()
.getImageIcon( alias.getIconName(), 12 ) );
}
}
mToLabel = new JLabel( "TO:" );
mToLabel.setFont( mFontDetails );
mToLabel.setForeground( mColorLabelDetails );
mToTalkgroupLabel = new JLabel( state.getFromTalkgroup() );
mToTalkgroupLabel.setFont( mFontDecoder );
mToTalkgroupLabel.setForeground( mColorLabelDecoder );
mToTalkgroupAliasLabel = new JLabel();
mToTalkgroupAliasLabel.setFont( mFontDecoder );
mToTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
if( mAliasList != null )
{
Alias alias = mAliasList.getMPT1327Alias( state.getFromTalkgroup() );
if( alias != null )
{
mToTalkgroupAliasLabel.setText( alias.getName() );
mToTalkgroupAliasLabel.setIcon( getChannel()
.getResourceManager().getSettingsManager()
.getImageIcon( alias.getIconName(), 12 ) );
}
}
add( mProtocolLabel );
add( mFrequencyLabel );
add( mChannelLabel, "wrap" );
add( mStateLabel );
add( mFromTalkgroupLabel );
add( mFromTalkgroupAliasLabel, "wrap" );
add( mToLabel );
add( mToTalkgroupLabel );
add( mToTalkgroupAliasLabel, "wrap" );
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
ProcessingChain chain = getChannel().getProcessingChain();
if( chain != null )
{
final MPT1327ChannelState state =
(MPT1327ChannelState)chain.getChannelState();
if( state != null )
{
switch( changedAttribute )
{
case CHANNEL_STATE:
mStateLabel.setText( state.getState().getDisplayValue() );
break;
case CHANNEL_NUMBER:
mChannelLabel.setText( "Channel " + state.getChannelNumber() );
break;
case FROM_TALKGROUP:
String talkgroup = state.getFromTalkgroup();
mFromTalkgroupLabel.setText( talkgroup );
if( mAliasList != null )
{
Alias alias = mAliasList.getMPT1327Alias( talkgroup );
if( alias != null )
{
mFromTalkgroupAliasLabel.setText( alias.getName() );
String icon = alias.getIconName();
mFromTalkgroupAliasLabel.setIcon( getChannel()
.getResourceManager().getSettingsManager()
.getImageIcon( icon, 12 ) );
}
}
break;
case TO_TALKGROUP:
String toTalkgroup = state.getToTalkgroup();
mToTalkgroupLabel.setText( toTalkgroup );
if( mAliasList != null )
{
Alias alias = mAliasList.getMPT1327Alias( toTalkgroup );
if( alias != null )
{
mToTalkgroupAliasLabel.setText( alias.getName() );
String icon = alias.getIconName();
if( icon != null )
{
mToTalkgroupAliasLabel.setIcon( getChannel()
.getResourceManager().getSettingsManager()
.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 );
mStateLabel.setForeground( mColorLabelDecoder );
mFromTalkgroupLabel.setForeground( mColorLabelDecoder );
mFromTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
mToTalkgroupLabel.setForeground( mColorLabelDecoder );
mToTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
break;
case CHANNEL_STATE_LABEL_DETAILS:
mFrequencyLabel.setForeground( mColorLabelDetails );
mChannelLabel.setForeground( mColorLabelDetails );
mToLabel.setForeground( mColorLabelDetails );
break;
}
}
}
}

View File

@@ -0,0 +1,69 @@
/*******************************************************************************
* 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 decode.nbfm;
import alias.AliasList;
import audio.SquelchListener;
import audio.SquelchListener.SquelchState;
import controller.channel.ProcessingChain;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
/**
* Conventional channel state - this class doesn't do anything at the moment,
* it is a place holder for future expansion for the conventional decoder
*/
public class NBFMChannelState extends ChannelState
{
public NBFMChannelState( ProcessingChain channel, AliasList aliasList )
{
super( channel, aliasList );
}
/**
* Make the ConventionalChannelState always unsquelched
*/
public void addListener( SquelchListener listener )
{
super.addListener( listener );
super.setSquelchState( SquelchState.UNSQUELCH );
}
@Override
public void setSquelchState( SquelchState state )
{
//do nothing ... we want the squelch state always unsquelched (for now)
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "Activity Summary\n" );
sb.append( "\tDecoder:\tNBFM\n\n" );
for( AuxChannelState state: mAuxChannelStates )
{
sb.append( state.getActivitySummary() );
sb.append( "\n\n" );
}
return sb.toString();
}
}

View 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 decode.nbfm;
import source.Source.SampleType;
import decode.Decoder;
import decode.DecoderType;
import dsp.filter.DCRemovalFilter2;
import dsp.nbfm.FilteringNBFMDemodulator;
public class NBFMConventionalDecoder extends Decoder
{
/**
* This value determines how quickly the DC remove filter responds to
* changes in frequency.
*/
private static final double sDC_REMOVAL_RATIO = 0.000003;
private FilteringNBFMDemodulator mDemodulator;
private DCRemovalFilter2 mDCRemovalFilter;
public NBFMConventionalDecoder( SampleType sampleType )
{
super( sampleType );
/**
* Only setup a demod chain if we're receiving complex samples. If
* we're receiving demodulated samples, they'll be handled the same
* was as we handle the output of the demodulator.
*/
if( mSourceSampleType == SampleType.COMPLEX )
{
/**
* The Decoder super class is both a Complex listener and a float
* listener. So, we add the demod to listen to the incoming
* quadrature samples, and we wire the output of the demod right
* back to this class, so we can receive the demodulated output
* to process
*/
mDemodulator = new FilteringNBFMDemodulator();
this.addComplexListener( mDemodulator );
/**
* Remove the DC component that is present when we're mistuned
*/
mDCRemovalFilter = new DCRemovalFilter2( sDC_REMOVAL_RATIO );
mDemodulator.addListener( mDCRemovalFilter );
/**
* Route the demodulated, filtered samples back to this class to send
* to all registered listeners
*/
mDCRemovalFilter.setListener( this.getFloatReceiver() );
}
}
@Override
public DecoderType getType()
{
return DecoderType.NBFM;
}
}

View 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 decode.nbfm;
import decode.DecodeEditor;
import decode.config.DecodeConfiguration;
public class NBFMEditor extends DecodeEditor
{
private static final long serialVersionUID = 1L;
public NBFMEditor( DecodeConfiguration config )
{
super( config );
}
}

View File

@@ -0,0 +1,136 @@
/*******************************************************************************
* 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 decode.nbfm;
import java.awt.EventQueue;
import javax.swing.JLabel;
import settings.ColorSetting;
import settings.Setting;
import controller.channel.Channel;
import controller.state.ChannelState.ChangedAttribute;
import controller.state.ChannelStatePanel;
public class NBFMPanel extends ChannelStatePanel
{
private static final long serialVersionUID = 1L;
private JLabel mStateLabel;
private JLabel mSourceLabel;
private JLabel mChannelLabel;
private JLabel mProtocol = new JLabel( "NBFM" );
public NBFMPanel( Channel channel )
{
super( channel );
init();
}
public void init()
{
mStateLabel = new JLabel( mChannel.getProcessingChain().
getChannelState().getState().getDisplayValue() );
mStateLabel.setFont( mFontDecoder );
mStateLabel.setForeground( mColorLabelDecoder );
mSourceLabel = new JLabel( mChannel.getSourceConfiguration().getDescription() );
mSourceLabel.setFont( mFontDetails );
mSourceLabel.setForeground( mColorLabelDetails );
mChannelLabel = new JLabel( mChannel.getChannelDisplayName() );
mChannelLabel.setFont( mFontDetails );
mChannelLabel.setForeground( mColorLabelDetails );
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
add( mStateLabel );
add( mSourceLabel );
add( mChannelLabel, "wrap" );
add( mProtocol, "wrap" );
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
switch( changedAttribute )
{
case CHANNEL_STATE:
mStateLabel.setText( mChannel.getProcessingChain().
getChannelState().getState().getDisplayValue() );
break;
case SOURCE:
mSourceLabel.setText( mChannel.getSourceConfiguration()
.getDescription() );
break;
case CHANNEL_NAME:
case SITE_NAME:
case SYSTEM_NAME:
mChannelLabel.setText( mChannel.getChannelDisplayName() );
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( mStateLabel != null )
{
mStateLabel.setForeground( mColorLabelDecoder );
}
if( mProtocol != null )
{
mProtocol.setForeground( mColorLabelDecoder );
}
break;
case CHANNEL_STATE_LABEL_DETAILS:
if( mSourceLabel != null )
{
mSourceLabel.setForeground( mColorLabelDetails );
}
if( mChannelLabel != null )
{
mChannelLabel.setForeground( mColorLabelDetails );
}
break;
}
}
}
}

View File

@@ -0,0 +1,233 @@
/*******************************************************************************
* 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 decode.passport;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.TreeSet;
import message.Message;
import sample.Listener;
import alias.Alias;
import alias.AliasList;
import controller.activity.ActivitySummaryProvider;
public class PassportActivitySummary implements ActivitySummaryProvider,
Listener<Message>
{
private static final int sINT_NULL_VALUE = -1;
private DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000");
private AliasList mAliasList;
private HashSet<String> mTalkgroupsFirstHeard = new HashSet<String>();
private TreeSet<String> mTalkgroups = new TreeSet<String>();
private TreeSet<String> mMobileIDs = new TreeSet<String>();
private HashMap<Integer,String> mSiteLCNs = new HashMap<Integer,String>();
private HashMap<Integer,String> mNeighborLCNs = new HashMap<Integer,String>();
/**
* Compiles a summary of active talkgroups, unique radio ids encountered
* in the decoded messages. Talkgroups have to be heard a minimum of twice
* to be considered valid ... in order to weed out the invalid ones.
*
* Returns a textual summary of the activity
*/
public PassportActivitySummary( AliasList list )
{
mAliasList = list;
}
@Override
public void receive( Message message )
{
if( message instanceof PassportMessage )
{
PassportMessage passport = ((PassportMessage)message);
if( passport.isValid() )
{
switch( passport.getMessageType() )
{
case CA_STRT:
mSiteLCNs.put( passport.getLCN(),
passport.getLCNFrequencyFormatted() );
case CA_ENDD:
String talkgroup =
String.valueOf( passport.getTalkgroupID() );
if( mTalkgroupsFirstHeard.contains( talkgroup ) )
{
mTalkgroups.add( talkgroup );
}
else
{
mTalkgroupsFirstHeard.add( talkgroup );
}
break;
case ID_RDIO:
String min = passport.getMobileID();
if( min != null )
{
mMobileIDs.add( min );
}
break;
case SY_IDLE:
if( passport.getFree() != 0 )
{
mNeighborLCNs.put( passport.getFree(),
passport.getFreeFrequencyFormatted() );
}
break;
default:
break;
}
}
}
}
@Override
public String getSummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "Activity Summary\n" );
sb.append( "Decoder:\tPassport\n\n" );
sb.append( "Site Channels\n" );
if( mSiteLCNs.isEmpty() )
{
sb.append( " None\n" );
}
else
{
ArrayList<Integer> channels = new ArrayList( mSiteLCNs.keySet() );
Collections.sort( channels );
for( Integer channel: channels )
{
sb.append( " " + channel );
sb.append( "\t" + mSiteLCNs.get( channel ) );
sb.append( "\n" );
}
}
sb.append( "\nNeighbor Channels\n" );
if( mNeighborLCNs.isEmpty() )
{
sb.append( " None\n" );
}
else
{
ArrayList<Integer> channels = new ArrayList( mNeighborLCNs.keySet() );
Collections.sort( channels );
for( Integer channel: channels )
{
sb.append( " " + channel );
sb.append( "\t" + mNeighborLCNs.get( channel ) );
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( " " );
Alias alias = mAliasList.getTalkgroupAlias( tgid );
if( alias != null )
{
sb.append( alias.getName() );
}
sb.append( "\n" );
}
}
sb.append( "\nMobile ID Numbers\n" );
if( mMobileIDs.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mMobileIDs.iterator();
while( it.hasNext() )
{
String min = it.next();
sb.append( " " );
sb.append( min );
sb.append( " " );
Alias alias = mAliasList.getMobileIDNumberAlias( min );
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;
}
}
}

View 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 decode.passport;
public enum PassportBand
{
BAND_800( "800", 800000000 ),
BAND_900( "900", 900000000 ),
BAND_400( "400", 400000000 ),
BAND_420( "420", 420000000 ),
BAND_440( "440", 440000000 ),
BAND_450( "450", 450000000 ),
BAND_460( "460", 460000000 ),
BAND_470( "470", 470000000 ),
BAND_480( "480", 480000000 ),
BAND_490( "490", 490000000 ),
BAND_CANADA( "CAN", 0 ),
BAND_NTIA( "NTIA", 0 ),
BAND_RESERVED12( "RESVD", 0 ),
BAND_216( "216", 216000000 ),
BAND_RESERVED14( "RESVD", 0 ),
BAND_700( "700", 700000000 ),
BAND_VHF( "VHF", 0 ),
BAND_UNKNOWN( "UNK", 0 );
private String mDescription;
private int mBase;
private PassportBand( String description, int base )
{
mDescription = description;
mBase = base;
}
public static PassportBand lookup( int band )
{
if( band >= 0 && band < 16 )
{
return PassportBand.values()[ band ];
}
else
{
return PassportBand.BAND_UNKNOWN;
}
}
public String getDescription()
{
return mDescription;
}
public int getBase()
{
return mBase;
}
}

View File

@@ -0,0 +1,164 @@
/*******************************************************************************
* 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 decode.passport;
import alias.Alias;
import alias.AliasList;
import controller.activity.CallEvent;
import decode.DecoderType;
public class PassportCallEvent extends CallEvent
{
private int mChannel;
private int mFrequency;
public PassportCallEvent( CallEventType callEventType,
AliasList aliasList,
String fromID,
String toID,
int channel,
int frequency,
String details )
{
super( DecoderType.PASSPORT, callEventType,
aliasList, fromID, toID, details );
mChannel = channel;
mFrequency = frequency;
}
private Alias getAlias( String talkgroup )
{
if( hasAliasList() )
{
return getAliasList().getTalkgroupAlias( talkgroup );
}
return null;
}
private Alias getMobileIDAlias( String mobileID )
{
if( hasAliasList() )
{
return getAliasList().getMobileIDNumberAlias( mobileID );
}
return null;
}
@Override
public Alias getFromIDAlias()
{
return getMobileIDAlias( getFromID() );
}
@Override
public Alias getToIDAlias()
{
return getAlias( getToID() );
}
@Override
public int getChannel()
{
return mChannel;
}
@Override
public int 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 int mChannel;
private int 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( int channel )
{
mChannel = channel;
return this;
}
public Builder details( String details )
{
mDetails = details;
return this;
}
public Builder frequency( int frequency )
{
mFrequency = frequency;
return this;
}
public Builder to( String toID )
{
mToID = toID;
return this;
}
public PassportCallEvent build()
{
return new PassportCallEvent( this );
}
}
/**
* Private constructor for the builder
*/
private PassportCallEvent( Builder builder )
{
this( builder.mCallEventType,
builder.mAliasList,
builder.mFromID,
builder.mToID,
builder.mChannel,
builder.mFrequency,
builder.mDetails );
}
}

View File

@@ -0,0 +1,236 @@
/*******************************************************************************
* 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 decode.passport;
import message.Message;
import alias.Alias;
import alias.AliasList;
import audio.AudioType;
import audio.SquelchListener;
import controller.activity.CallEvent.CallEventType;
import controller.channel.ProcessingChain;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
public class PassportChannelState extends ChannelState
{
private String mTalkgroup;
private Alias mTalkgroupAlias;
private String mMobileID;
private Alias mMobileIDAlias;
private int mChannelNumber;
private int mFrequency;
private PassportCallEvent mCurrentCallEvent;
private PassportActivitySummary mActivitySummary;
public PassportChannelState( ProcessingChain channel, AliasList aliasList )
{
super( channel, aliasList );
mActivitySummary = new PassportActivitySummary( aliasList );
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( mActivitySummary.getSummary() );
for( AuxChannelState state: mAuxChannelStates )
{
sb.append( state.getActivitySummary() );
sb.append( "\n\n" );
}
return sb.toString();
}
@Override
@SuppressWarnings( "incomplete-switch" )
public void receive( Message message )
{
super.receive( message );
if( message instanceof PassportMessage )
{
mActivitySummary.receive( message );
PassportMessage passport = (PassportMessage)message;
if( passport.isValid() )
{
switch( passport.getMessageType() )
{
case CA_STRT:
setTalkgroup( String.valueOf( passport.getTalkgroupID() ) );
setTalkgroupAlias( passport.getTalkgroupIDAlias() );
if( mTalkgroupAlias != null )
{
try
{
if( mTalkgroupAlias.hasAudioType() )
{
mAudioTypeListener.setAudioType(
mTalkgroupAlias.getAudioType() );
}
}
catch( Exception e )
{
e.printStackTrace();
}
}
mChannelNumber = passport.getLCN();
mFrequency = passport.getLCNFrequency();
if( getState() != State.CALL )
{
mCurrentCallEvent = new PassportCallEvent
.Builder( CallEventType.CALL_START )
.aliasList( mAliasList )
.channel( mChannelNumber )
.frequency( mFrequency )
.to( String.valueOf( passport.getTalkgroupID() ) )
.build();
mCallEventModel.add( mCurrentCallEvent );
}
setState( State.CALL );
break;
case DA_STRT:
setTalkgroup( String.valueOf( passport.getTalkgroupID() ) );
setTalkgroupAlias( passport.getTalkgroupIDAlias() );
setState( State.DATA );
break;
case CA_ENDD:
setTalkgroup( String.valueOf( passport.getTalkgroupID() ) );
setTalkgroupAlias( passport.getTalkgroupIDAlias() );
fade( CallEventType.CALL_END );
break;
case ID_RDIO:
setMobileID( passport.getMobileID() );
setMobileIDAlias( passport.getMobileIDAlias() );
if( mCurrentCallEvent != null )
{
mCurrentCallEvent.setFromID( mMobileID );
}
setState( State.CALL );
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 ) )
{
mCallEventModel.add(
new PassportCallEvent.Builder( type )
.aliasList( getAliasList() )
.channel( mChannelNumber )
.frequency( mProcessingChain.getChannel()
.getTunerChannel().getFrequency() )
.to( mTalkgroup )
.build() );
}
super.fade( type );
}
/**
* Make the ConventionalChannelState always unsquelched
*/
public void addListener( SquelchListener listener )
{
super.addListener( listener );
}
public void reset()
{
mAudioTypeListener.setAudioType( AudioType.NORMAL );
mCurrentCallEvent = null;
setTalkgroup( null );
setTalkgroupAlias( null );
setMobileID( null );
setMobileIDAlias( null );
super.reset();
}
public String getTalkgroup()
{
return mTalkgroup;
}
public void setTalkgroup( String talkgroup )
{
mTalkgroup = talkgroup;
broadcastChange( ChangedAttribute.TO_TALKGROUP );
}
public Alias getTalkgroupAlias()
{
return mTalkgroupAlias;
}
public void setTalkgroupAlias( Alias alias )
{
mTalkgroupAlias = alias;
broadcastChange( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
public String getMobileID()
{
return mMobileID;
}
public void setMobileID( String id )
{
mMobileID = id;
broadcastChange( ChangedAttribute.FROM_TALKGROUP );
}
public Alias getMobileIDAlias()
{
return mMobileIDAlias;
}
public void setMobileIDAlias( Alias alias )
{
mMobileIDAlias = alias;
broadcastChange( ChangedAttribute.FROM_TALKGROUP_ALIAS );
}
}

View File

@@ -0,0 +1,108 @@
/*******************************************************************************
* 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 decode.passport;
import source.Source.SampleType;
import alias.AliasList;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
import decode.DecoderType;
import decode.ltrstandard.LTRFSKDemodulator;
import dsp.filter.DCRemovalFilter2;
import dsp.nbfm.FilteringNBFMDemodulator;
public class PassportDecoder extends Decoder
{
/**
* This value determines how quickly the DC remove filter responds to
* changes in frequency.
*/
private static final double sDC_REMOVAL_RATIO = 0.000003;
public static final int sPASSPORT_MESSAGE_LENGTH = 68;
public static final int sPASSPORT_SYNC_LENGTH = 9;
private FilteringNBFMDemodulator mNBFMDemodulator;
private DCRemovalFilter2 mDCRemovalFilter;
private LTRFSKDemodulator mPassportFSKDemodulator;
private MessageFramer mPassportMessageFramer;
private PassportMessageProcessor mPassportMessageProcessor;
public PassportDecoder( SampleType sampleType, AliasList aliasList )
{
super( sampleType );
/**
* Only setup a demod chain if we're receiving complex samples. If
* we're receiving demodulated samples, they'll be handled the same
* was as we handle the output of the demodulator.
*/
if( mSourceSampleType == SampleType.COMPLEX )
{
/**
* The Decoder super class is both a Complex listener and a Short
* listener. So, we add the demod to listen to the incoming
* quadrature samples, and we wire the output of the demod right
* back to this class, so we can receive the demodulated output
* to process
*/
mNBFMDemodulator = new FilteringNBFMDemodulator();
this.addComplexListener( mNBFMDemodulator );
/**
* Remove the DC component that is present when we're mistuned
*/
mDCRemovalFilter = new DCRemovalFilter2( sDC_REMOVAL_RATIO );
mNBFMDemodulator.addListener( mDCRemovalFilter );
mDCRemovalFilter.setListener( this.getFloatReceiver() );
}
mPassportFSKDemodulator =
new LTRFSKDemodulator();
/**
* The DC removal filter will impact performance of the LTRFSKDemod,
* so we get the samples straight from the NBFM demod
*/
if( mSourceSampleType == SampleType.COMPLEX )
{
mNBFMDemodulator.addListener( mPassportFSKDemodulator );
}
else
{
addFloatListener( mPassportFSKDemodulator );
}
mPassportMessageFramer =
new MessageFramer( SyncPattern.PASSPORT.getPattern(),
sPASSPORT_MESSAGE_LENGTH );
mPassportFSKDemodulator.addListener( mPassportMessageFramer );
mPassportMessageProcessor = new PassportMessageProcessor( aliasList );
mPassportMessageFramer.addMessageListener( mPassportMessageProcessor );
mPassportMessageProcessor.addMessageListener( this );
}
@Override
public DecoderType getType()
{
return DecoderType.PASSPORT;
}
}

View 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 decode.passport;
import decode.DecodeEditor;
import decode.config.DecodeConfiguration;
public class PassportEditor extends DecodeEditor
{
private static final long serialVersionUID = 1L;
public PassportEditor( DecodeConfiguration config )
{
super( config );
}
}

View File

@@ -0,0 +1,634 @@
/*******************************************************************************
* 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 decode.passport;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import map.Plottable;
import message.Message;
import message.MessageType;
import alias.Alias;
import alias.AliasList;
import bits.BitSetBuffer;
import crc.CRC;
import crc.CRCPassport;
import decode.DecoderType;
public class PassportMessage extends Message
{
private static final String sUNKNOWN = "**UNKNOWN**";
private static final int sINT_NULL_VALUE = -1;
private static final double sDOUBLE_NULL_VALUE = -1.0D;
private SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" );
private DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000");
@SuppressWarnings( "unused" )
private static final int[] sSYNC = { 8,7,6,5,4,3,2,1,0 };
private static final int[] sDCC = { 10,9 }; //Digital Color Code
private static final int[] sLCN = { 21,20,19,18,17,16,15,14,13,12,11 };
private static final int[] sSITE = { 28,27,26,25,24,23,22 };
private static final int[] sGROUP = { 44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29 };
private static final int[] sRADIO_ID = { 44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22 };
// private static final int[] sNEIGHBOR_OFFSET = { 32,31,30 };
private static final int[] sNEIGHBOR_BAND = { 36,35,34,33 };
private static final int[] sNEIGHBOR_REGISTRATION = { 29 };
private static final int[] sSITE_OFFSET = { 40,39,38 };
private static final int[] sSITE_BAND = { 44,43,42,41 };
private static final int[] sSITE_REGISTRATION = { 37 };
private static final int[] sTYPE = { 48,47,46,45 };
private static final int[] sFREE = { 59,58,57,56,55,54,53,52,51,50,49 };
private static final int[] sCHECKSUM = { 67,66,65,64,63,62,61,60 };
private BitSetBuffer mMessage;
private CRC mCRC;
private AliasList mAliasList;
private PassportMessage mIdleMessage;
public PassportMessage( BitSetBuffer message,
PassportMessage idleMessage,
AliasList list )
{
mMessage = CRCPassport.correct( message );
mIdleMessage = idleMessage;
mCRC = CRCPassport.check( mMessage );
mAliasList = list;
}
public PassportMessage( BitSetBuffer message, AliasList list )
{
this( message, null, list );
}
public BitSetBuffer getBitSetBuffer()
{
return mMessage;
}
public boolean isValid()
{
return mCRC != CRC.FAILED_CRC &&
mCRC != CRC.FAILED_PARITY &&
mCRC != CRC.UNKNOWN;
}
public CRC getCRC()
{
return mCRC;
}
public MessageType getMessageType()
{
MessageType retVal = MessageType.UN_KNWN;
int type = getMessageTypeNumber();
int lcn = getLCN();
switch( type )
{
case 0: //Group Call
retVal = MessageType.CA_STRT;
break;
case 1:
if( getFree() == 2042 )
{
retVal = MessageType.ID_TGAS;
}
else if( lcn < 1792 )
{
retVal = MessageType.CA_STRT;
}
else if( lcn == 1792 || lcn == 1793 )
{
retVal = MessageType.SY_IDLE;
}
else if( lcn == 2047 )
{
retVal = MessageType.CA_ENDD;
}
break;
case 2:
retVal = MessageType.CA_STRT;
break;
case 5:
retVal = MessageType.CA_PAGE;
break;
case 6:
retVal = MessageType.ID_RDIO;
break;
case 9:
retVal = MessageType.DA_STRT;
break;
case 11:
retVal = MessageType.RA_REGI;
break;
default:
break;
}
return retVal;
}
public int getDCC()
{
return getInt( sDCC );
}
public int getSite()
{
return getInt( sSITE );
}
public int getMessageTypeNumber()
{
return getInt( sTYPE );
}
public int getTalkgroupID()
{
if( getMessageType() == MessageType.SY_IDLE )
{
return 0;
}
else
{
return getInt( sGROUP );
}
}
public Alias getTalkgroupIDAlias()
{
int tg = getTalkgroupID();
return mAliasList.getTalkgroupAlias( String.valueOf( tg ) );
}
public int getLCN()
{
return getInt( sLCN );
}
public int getLCNFrequency()
{
return getSiteFrequency( getLCN() );
}
public String getLCNFrequencyFormatted()
{
return mDecimalFormatter.format( (double)getLCNFrequency() / 1000000.0d );
}
public PassportBand getSiteBand()
{
return PassportBand.lookup( getInt( sSITE_BAND ) );
}
public PassportBand getNeighborBand()
{
return PassportBand.lookup( getInt( sNEIGHBOR_BAND ) );
}
public int getFrequency( int base, int channel )
{
return ( base + ( channel * 12500 ) );
}
public int getFree()
{
return getInt( sFREE );
}
public int getFreeFrequency()
{
return getSiteFrequency( getFree() );
}
public String getFreeFrequencyFormatted()
{
return mDecimalFormatter.format( (double)getFreeFrequency() / 1000000.0d );
}
public int getNeighborFrequency()
{
int retVal = 0;
if( getMessageType() == MessageType.SY_IDLE )
{
PassportBand band = getNeighborBand();
retVal = getFrequency( band.getBase(), getFree() );
}
return retVal;
}
/**
* Returns the radio id in hex format, or null if not the correct message
* type
*
* @return - radio id or null
*/
public String getMobileID()
{
if( getMessageType() == MessageType.ID_RDIO )
{
int radioId = getInt( sRADIO_ID );
return String.format("%06X", radioId & 0xFFFFFF );
}
else
{
return null;
}
}
public Alias getMobileIDAlias()
{
String min = getMobileID();
if( min != null )
{
return mAliasList.getMobileIDNumberAlias( min );
}
return null;
}
/**
* Appends spaces to the end of the stringbuilder to make it length long
*/
private 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
*/
private static String getHex( int value, int digits )
{
return String.format( "%0" + digits + "X", value );
}
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;
}
/**
* 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();
}
@Override
public String getProtocol()
{
return DecoderType.PASSPORT.getDisplayString();
}
@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;
}
public int getSiteFrequency( int channel )
{
int freq = 0;
if( mIdleMessage != null && 0 < channel && channel < 1792 )
{
PassportBand band = mIdleMessage.getSiteBand();
if( band != PassportBand.BAND_UNKNOWN )
{
int base = band.getBase();
freq = getFrequency( base, channel );
}
}
return freq;
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
switch( getMessageType() )
{
case SY_IDLE:
sb.append( "IDLE SITE:" );
sb.append( format( getSite(), 3 ) );
sb.append( " NEIGHBOR:" );
sb.append( format( getFree(), 3 ) );
sb.append( "/" );
sb.append( getFreeFrequencyFormatted() );
break;
case CA_PAGE:
sb.append( "PAGING TG:" );
sb.append( format( getTalkgroupID(), 5 ) );
sb.append( "/" );
Alias pageAlias = getTalkgroupIDAlias();
if( pageAlias != null )
{
sb.append( pageAlias.getName() );
}
else
{
sb.append( sUNKNOWN );
}
sb.append( " SITE:" );
sb.append( format( getSite(), 3 ) );
sb.append( " CHAN:" );
sb.append( format( getLCN(), 4 ) );
sb.append( "/" );
sb.append( getLCNFrequencyFormatted() );
sb.append( " FREE:" );
sb.append( format( getFree(), 3 ) );
sb.append( "/" );
sb.append( getFreeFrequencyFormatted() );
break;
case CA_STRT:
sb.append( "CALL TG:" );
sb.append( format( getTalkgroupID(), 5 ) );
sb.append( "/" );
Alias startAlias = getTalkgroupIDAlias();
if( startAlias != null )
{
sb.append( startAlias.getName() );
}
else
{
sb.append( sUNKNOWN );
}
sb.append( " SITE:" );
sb.append( format( getSite(), 3 ) );
sb.append( " CHAN:" );
sb.append( format( getLCN(), 4 ) );
sb.append( "/" );
sb.append( getLCNFrequencyFormatted() );
sb.append( " FREE:" );
sb.append( format( getFree(), 3 ) );
sb.append( "/" );
sb.append( getFreeFrequencyFormatted() );
break;
case DA_STRT:
sb.append( "** DATA TG:" );
sb.append( format( getTalkgroupID(), 5 ) );
sb.append( "/" );
Alias dataStartAlias = getTalkgroupIDAlias();
if( dataStartAlias != null )
{
sb.append( dataStartAlias.getName() );
}
else
{
sb.append( sUNKNOWN );
}
sb.append( " SITE:" );
sb.append( format( getSite(), 3 ) );
sb.append( " CHAN:" );
sb.append( format( getLCN(), 4 ) );
sb.append( "/" );
sb.append( getLCNFrequencyFormatted() );
sb.append( " FREE:" );
sb.append( format( getFree(), 3 ) );
sb.append( "/" );
sb.append( getFreeFrequencyFormatted() );
break;
case CA_ENDD:
sb.append( "END TG:" );
sb.append( format( getTalkgroupID(), 5 ) );
sb.append( "/" );
Alias endAlias = getTalkgroupIDAlias();
if( endAlias != null )
{
sb.append( endAlias.getName() );
}
else
{
sb.append( sUNKNOWN );
}
sb.append( " SITE:" );
sb.append( format( getSite(), 3 ) );
sb.append( " CHAN:" );
sb.append( format( getLCN(), 4 ) );
sb.append( "/" );
sb.append( getLCNFrequencyFormatted() );
sb.append( " FREE:" );
sb.append( format( getFree(), 3 ) );
sb.append( "/" );
sb.append( getFreeFrequencyFormatted() );
break;
case ID_RDIO:
sb.append( "MOBILE ID MIN:" );
sb.append( getMobileID() );
sb.append( "/" );
Alias mobileIDAlias = getMobileIDAlias();
if( mobileIDAlias != null )
{
sb.append( mobileIDAlias.getName() );
}
else
{
sb.append( "UNKNOWN" );
}
sb.append( " FREE:" );
sb.append( format( getFree(), 3 ) );
sb.append( "/" );
sb.append( getFreeFrequencyFormatted() );
break;
case ID_TGAS:
sb.append( "ASSIGN TALKGROUP:" );
sb.append( format( getTalkgroupID(), 5 ) );
sb.append( "/" );
Alias assignAlias = getTalkgroupIDAlias();
if( assignAlias != null )
{
sb.append( assignAlias.getName() );
}
else
{
sb.append( sUNKNOWN );
}
sb.append( " SITE:" );
sb.append( format( getSite(), 3 ) );
sb.append( " CHAN:" );
sb.append( format( getLCN(), 4 ) );
sb.append( "/" );
sb.append( getLCNFrequencyFormatted() );
break;
case RA_REGI:
sb.append( "RADIO REGISTER TG: " );
sb.append( format( getTalkgroupID(), 5 ) );
sb.append( "/" );
Alias regAlias = getTalkgroupIDAlias();
if( regAlias != null )
{
sb.append( regAlias.getName() );
}
else
{
sb.append( sUNKNOWN );
}
break;
default:
sb.append( "UNKNOWN SITE:" );
sb.append( format( getSite(), 3 ) );
sb.append( " CHAN:" );
sb.append( format( getLCN(), 4 ) );
sb.append( "/" );
sb.append( getLCNFrequencyFormatted() );
sb.append( " FREE:" );
int free = getFree();
sb.append( format( free, 3 ) );
if( free > 0 && free < 896 )
{
sb.append( "/" );
sb.append( getFreeFrequencyFormatted() );
}
sb.append( " TYP:" );
sb.append( format( getMessageTypeNumber(), 2 ) );
sb.append( " TG:" );
sb.append( format( getTalkgroupID(), 5 ) );
break;
}
return sb.toString();
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( mSDF.format( mTimeReceived ) );
sb.append( " " );
sb.append( getMessage() );
pad( sb, 100 );
sb.append( mCRC.toString() );
pad( sb, 110 );
sb.append( " [" );
sb.append( mMessage );
sb.append( "]" );
return sb.toString();
}
@Override
public String getErrorStatus()
{
return mCRC.getDisplayText();
}
public boolean matches( PassportMessage otherMessage )
{
return this.getBitSetBuffer().equals( otherMessage.getBitSetBuffer() );
}
@Override
public Plottable getPlottable()
{
// TODO Auto-generated method stub
return null;
}
}

View File

@@ -0,0 +1,109 @@
/*******************************************************************************
* 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 decode.passport;
import java.util.HashMap;
import message.Message;
import message.MessageType;
import sample.Broadcaster;
import sample.Listener;
import alias.AliasList;
import bits.BitSetBuffer;
public class PassportMessageProcessor implements Listener<BitSetBuffer>
{
private Broadcaster<Message> mBroadcaster = new Broadcaster<Message>();
private IdleMessageFinder mIdleFinder = new IdleMessageFinder();
private AliasList mAliasList;
private PassportMessage mIdleMessage;
public PassportMessageProcessor( AliasList list )
{
mAliasList = list;
}
@Override
public void receive( BitSetBuffer buffer )
{
PassportMessage message;
if( mIdleMessage != null )
{
message = new PassportMessage( buffer, mIdleMessage, mAliasList );
}
else
{
message = new PassportMessage( buffer, mAliasList );
mIdleFinder.receive( message );
}
mBroadcaster.receive( message );
}
public void addMessageListener( Listener<Message> listener )
{
mBroadcaster.addListener( listener );
}
public void removeMessageListener( Listener<Message> listener )
{
mBroadcaster.removeListener( listener );
}
public class IdleMessageFinder
{
public HashMap<String,Integer> mMessageCounts =
new HashMap<String,Integer>();
public boolean mIdleMessageFound = false;
public IdleMessageFinder()
{
}
public void receive( PassportMessage message )
{
if( !mIdleMessageFound &&
message.isValid() &&
message.getMessageType() == MessageType.SY_IDLE )
{
if( mMessageCounts.containsKey( message.getBinaryMessage() ) )
{
int count = mMessageCounts.get( message.getBinaryMessage() );
if( count >= 3 )
{
mIdleMessageFound = true;
mIdleMessage = message;
mMessageCounts = null;
}
else
{
count++;
mMessageCounts.put( message.getBinaryMessage(), count );
}
}
else
{
mMessageCounts.put( message.getBinaryMessage(), 1 );
}
}
}
}
}

View File

@@ -0,0 +1,255 @@
/*******************************************************************************
* 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 decode.passport;
import java.awt.EventQueue;
import javax.swing.JLabel;
import settings.ColorSetting;
import settings.Setting;
import settings.SettingsManager;
import alias.Alias;
import controller.channel.Channel;
import controller.state.ChannelState.ChangedAttribute;
import controller.state.ChannelStatePanel;
public class PassportPanel extends ChannelStatePanel
{
private static final long serialVersionUID = 1L;
private JLabel mStateLabel;
private JLabel mSourceLabel;
private JLabel mChannelLabel;
private JLabel mProtocol = new JLabel( "Passport" );
private JLabel mToTalkgroup = new JLabel();
private JLabel mToTalkgroupAlias = new JLabel();
private JLabel mFromTalkgroupType = new JLabel( " " );
private JLabel mFromTalkgroup = new JLabel();
private JLabel mFromTalkgroupAlias = new JLabel();
private SettingsManager mSettingsManager;
public PassportPanel( SettingsManager settingsManager, Channel channel )
{
super( channel );
mSettingsManager = settingsManager;
init();
}
public void dispose()
{
super.dispose();
mSettingsManager = null;
}
public void init()
{
mStateLabel = new JLabel( mChannel.getProcessingChain().
getChannelState().getState().getDisplayValue() );
mStateLabel.setFont( mFontDecoder );
mStateLabel.setForeground( mColorLabelDecoder );
mSourceLabel = new JLabel( mChannel.getSourceConfiguration().getDescription() );
mSourceLabel.setFont( mFontDetails );
mSourceLabel.setForeground( mColorLabelDetails );
mChannelLabel = new JLabel( mChannel.getChannelDisplayName() );
mChannelLabel.setFont( mFontDetails );
mChannelLabel.setForeground( mColorLabelDetails );
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
mToTalkgroup.setFont( mFontDecoder );
mToTalkgroup.setForeground( mColorLabelDecoder );
mToTalkgroupAlias.setFont( mFontDecoder );
mToTalkgroupAlias.setForeground( mColorLabelDecoder );
mFromTalkgroupType.setFont( mFontDecoder );
mFromTalkgroupType.setForeground( mColorLabelDecoder );
mFromTalkgroup.setFont( mFontDecoder );
mFromTalkgroup.setForeground( mColorLabelDecoder );
mFromTalkgroupAlias.setFont( mFontDecoder );
mFromTalkgroupAlias.setForeground( mColorLabelDecoder );
add( mStateLabel );
add( mSourceLabel );
add( mChannelLabel, "wrap" );
add( mProtocol );
add( mToTalkgroup );
add( mToTalkgroupAlias, "wrap" );
add( mFromTalkgroupType );
add( mFromTalkgroup );
add( mFromTalkgroupAlias, "wrap" );
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
PassportChannelState state = (PassportChannelState)getChannel()
.getProcessingChain().getChannelState();
switch( changedAttribute )
{
case CHANNEL_STATE:
mStateLabel.setText( state.getState().getDisplayValue() );
break;
case SOURCE:
mSourceLabel.setText( mChannel.getSourceConfiguration()
.getDescription() );
break;
case CHANNEL_NAME:
case SITE_NAME:
case SYSTEM_NAME:
mChannelLabel.setText( mChannel.getChannelDisplayName() );
break;
case TO_TALKGROUP:
mToTalkgroup.setText( state.getTalkgroup() );
break;
case TO_TALKGROUP_ALIAS:
Alias toAlias = state.getTalkgroupAlias();
if( toAlias != null )
{
mToTalkgroupAlias.setText( toAlias.getName() );
mToTalkgroupAlias.setIcon( mSettingsManager
.getImageIcon( toAlias.getIconName(), 12 ) );
}
else
{
mToTalkgroupAlias.setText( null );
mToTalkgroupAlias.setIcon( null );
}
break;
case FROM_TALKGROUP:
String mid = state.getMobileID();
mFromTalkgroup.setText( state.getMobileID() );
if( mid != null )
{
mFromTalkgroupType.setText( "Mobile ID" );
}
else
{
mFromTalkgroupType.setText( " " );
}
break;
case FROM_TALKGROUP_ALIAS:
Alias fromAlias = state.getMobileIDAlias();
if( fromAlias != null )
{
mFromTalkgroupAlias.setText( fromAlias.getName() );
mFromTalkgroupAlias.setIcon( mSettingsManager
.getImageIcon( fromAlias.getIconName(), 12 ) );
}
else
{
mFromTalkgroupAlias.setText( null );
mFromTalkgroupAlias.setIcon( null );
}
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( mColorLabelDecoder != null )
{
if( mStateLabel != null )
{
mStateLabel.setForeground( mColorLabelDecoder );
}
if( mProtocol != null )
{
mProtocol.setForeground( mColorLabelDecoder );
}
if( mToTalkgroup != null )
{
mToTalkgroup.setForeground( mColorLabelDecoder );
}
if( mToTalkgroupAlias != null )
{
mToTalkgroupAlias.setForeground( mColorLabelDecoder );
}
if( mFromTalkgroupType != null )
{
mFromTalkgroupType.setForeground( mColorLabelDecoder );
}
if( mFromTalkgroup != null )
{
mFromTalkgroup.setForeground( mColorLabelDecoder );
}
if( mFromTalkgroupAlias != null )
{
mFromTalkgroupAlias.setForeground( mColorLabelDecoder );
}
}
break;
case CHANNEL_STATE_LABEL_DETAILS:
if( mColorLabelDetails != null )
{
if( mSourceLabel != null )
{
mSourceLabel.setForeground( mColorLabelDetails );
}
if( mChannelLabel != null )
{
mChannelLabel.setForeground( mColorLabelDetails );
}
}
break;
}
}
}
}