Initial commit of redesigned internal processing and audio management.

Note: Several of the decoders are currently non-functioning in this
commit.  If you want the latest functioning codebase, please checkout
the commit prior to this one.  All decoders will eventually be updated
with this new architecture.

Currently broken:
- Logging and recording
- Aux decoders (Fleetsync/MDC/LoJack/Tait)
- LTR/Passport decoders

Changes in this commit:
- Module architecture for decoders and components with central
provisioning by the DecoderFactory
- Transition from Complex sample stream processing to float buffer
processing for improved memory management
- Redesigned filter structures - to support buffers and streams
- Resolved several memory leak issues
- Universal sharing of the thread pool manager
- Added centralized audio management with call/user prioritization and
new gui with mute/volume controls
- Embedded audio metadata for eventual streaming support
- Redesigned ProcessingChain to support independent re-use
- Added new TrafficChannelManager to handle traffic channel allocations
- Traffic Channel pool size and Call timeout available in P25 and
MPT1327 configs
- Redesigned channel and decoder gui components
This commit is contained in:
denny
2015-09-01 07:10:26 -04:00
parent 538a299246
commit f4d8ea281d
570 changed files with 21578 additions and 17012 deletions

View File

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

View File

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

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 module.decode.passport;
import module.decode.DecoderType;
import module.decode.event.CallEvent;
import alias.Alias;
import alias.AliasList;
public class PassportCallEvent extends CallEvent
{
private String mChannel;
private long mFrequency;
private int mValidCallMessages = 1;
public PassportCallEvent( CallEventType callEventType,
AliasList aliasList,
String fromID,
String toID,
String channel,
long frequency,
String details )
{
super( DecoderType.PASSPORT, callEventType,
aliasList, fromID, toID, details );
mChannel = channel;
mFrequency = frequency;
}
public boolean addMessage( PassportMessage message )
{
String tg = message.getToID();
if( tg.contentEquals( getToID() ) )
{
mValidCallMessages++;
return true;
}
return false;
}
public boolean isValid()
{
if( getCallEventType() == CallEventType.CALL )
{
return mValidCallMessages > 1;
}
return true;
}
private Alias getAlias( String talkgroup )
{
if( hasAliasList() )
{
return getAliasList().getTalkgroupAlias( talkgroup );
}
return null;
}
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 String getChannel()
{
return mChannel;
}
@Override
public long getFrequency()
{
return mFrequency;
}
public static class Builder
{
/* Required parameters */
private CallEventType mCallEventType;
/* Optional parameters */
private AliasList mAliasList;
private String mFromID;
private String mToID;
private String mDetails;
private String mChannel;
private long mFrequency;
public Builder( CallEventType callEventType )
{
mCallEventType = callEventType;
}
public Builder aliasList( AliasList aliasList )
{
mAliasList = aliasList;
return this;
}
public Builder from( String val )
{
mFromID = val;
return this;
}
public Builder channel( String channel )
{
mChannel = channel;
return this;
}
public Builder details( String details )
{
mDetails = details;
return this;
}
public Builder frequency( long frequency )
{
mFrequency = frequency;
return this;
}
public Builder to( String toID )
{
mToID = toID;
return this;
}
public 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,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 module.decode.passport;
import instrument.Instrumentable;
import instrument.tap.Tap;
import java.util.ArrayList;
import java.util.List;
import module.decode.Decoder;
import module.decode.DecoderType;
import module.decode.config.DecodeConfiguration;
import sample.Listener;
import sample.real.IRealBufferListener;
import sample.real.RealBuffer;
import alias.AliasList;
import bits.MessageFramer;
import bits.SyncPattern;
import dsp.fsk.LTRFSKDecoder;
public class PassportDecoder extends Decoder
implements IRealBufferListener, Instrumentable
{
public static final int PASSPORT_MESSAGE_LENGTH = 68;
public static final int PASSPORT_SYNC_LENGTH = 9;
private LTRFSKDecoder mPassportFSKDecoder;
private MessageFramer mPassportMessageFramer;
private PassportMessageProcessor mPassportMessageProcessor;
private List<Tap> mAvailableTaps;
public PassportDecoder( DecodeConfiguration config,
AliasList aliasList )
{
super( new PassportDecoderState( aliasList ) );
mPassportFSKDecoder = new LTRFSKDecoder();
mPassportMessageFramer =
new MessageFramer( SyncPattern.PASSPORT.getPattern(),
PASSPORT_MESSAGE_LENGTH );
mPassportFSKDecoder.addListener( mPassportMessageFramer );
mPassportMessageProcessor = new PassportMessageProcessor( aliasList );
mPassportMessageFramer.addMessageListener( mPassportMessageProcessor );
mPassportMessageProcessor.setMessageListener( mMessageBroadcaster );
}
@Override
public Listener<RealBuffer> getRealBufferListener()
{
return mPassportFSKDecoder;
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.PASSPORT;
}
@Override
public List<Tap> getTaps()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
mAvailableTaps.addAll( mPassportFSKDecoder.getTaps() );
}
return mAvailableTaps;
}
@Override
public void addTap( Tap tap )
{
mPassportFSKDecoder.addTap( tap );
}
@Override
public void removeTap( Tap tap )
{
mPassportFSKDecoder.removeTap( tap );
}
}

View File

@@ -0,0 +1,221 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.passport;
import java.awt.EventQueue;
import javax.swing.JLabel;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderPanel;
import settings.ColorSetting;
import settings.Setting;
import settings.SettingsManager;
import alias.Alias;
import controller.channel.Channel;
public class PassportDecoderPanel extends DecoderPanel
{
private static final long serialVersionUID = 1L;
private JLabel mProtocol = new JLabel( "Passport" );
private JLabel mToTalkgroup = new JLabel();
private JLabel mToTalkgroupAlias = new JLabel();
private JLabel mSiteAndChannel = new JLabel( "Site:" );
private JLabel mFromTalkgroup = new JLabel();
private JLabel mFromTalkgroupAlias = new JLabel();
public PassportDecoderPanel( SettingsManager settingsManager, PassportDecoder decoder )
{
super( settingsManager, decoder );
init();
}
public void dispose()
{
super.dispose();
}
public void init()
{
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
mToTalkgroup.setFont( mFontDecoder );
mToTalkgroup.setForeground( mColorLabelDecoder );
mToTalkgroupAlias.setFont( mFontDecoder );
mToTalkgroupAlias.setForeground( mColorLabelDecoder );
mSiteAndChannel.setFont( mFontDecoder );
mSiteAndChannel.setForeground( mColorLabelDecoder );
mFromTalkgroup.setFont( mFontDecoder );
mFromTalkgroup.setForeground( mColorLabelDecoder );
mFromTalkgroupAlias.setFont( mFontDecoder );
mFromTalkgroupAlias.setForeground( mColorLabelDecoder );
add( mProtocol );
add( mToTalkgroup );
add( mToTalkgroupAlias, "wrap" );
add( mSiteAndChannel );
add( mFromTalkgroup );
add( mFromTalkgroupAlias, "wrap" );
}
private PassportDecoderState getDecoderState()
{
return (PassportDecoderState)getDecoder().getDecoderState();
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
PassportDecoderState state = getDecoderState();
switch( changedAttribute )
{
case TO_TALKGROUP:
String tg = state.getTalkgroup();
if( tg != null )
{
mToTalkgroup.setText( "TG:" + tg );
}
else
{
mToTalkgroup.setText( null );
}
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();
if( mid != null )
{
mFromTalkgroup.setText( "MIN:" + mid );
}
else
{
mFromTalkgroup.setText( null );
}
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;
case CHANNEL_NUMBER:
StringBuilder sb = new StringBuilder();
sb.append( "Site: " );
sb.append( state.getSiteNumber() );
sb.append( "/" );
sb.append( state.getChannelNumber() );
mSiteAndChannel.setText( sb.toString() );
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( mProtocol != null )
{
mProtocol.setForeground( mColorLabelDecoder );
}
if( mToTalkgroup != null )
{
mToTalkgroup.setForeground( mColorLabelDecoder );
}
if( mToTalkgroupAlias != null )
{
mToTalkgroupAlias.setForeground( mColorLabelDecoder );
}
if( mSiteAndChannel != null )
{
mSiteAndChannel.setForeground( mColorLabelDecoder );
}
if( mFromTalkgroup != null )
{
mFromTalkgroup.setForeground( mColorLabelDecoder );
}
if( mFromTalkgroupAlias != null )
{
mFromTalkgroupAlias.setForeground( mColorLabelDecoder );
}
}
break;
default:
break;
}
}
}
}

View File

@@ -0,0 +1,529 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.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 module.decode.state.ChangedAttribute;
import module.decode.state.DecoderState;
import module.decode.state.DecoderStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.Alias;
import alias.AliasList;
public class PassportDecoderState extends DecoderState
{
private final static Logger mLog =
LoggerFactory.getLogger( PassportDecoderState.class );
private static final int sINT_NULL_VALUE = -1;
private DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000");
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>();
private String mTalkgroup;
private Alias mTalkgroupAlias;
private String mMobileID;
private Alias mMobileIDAlias;
private int mChannelNumber;
private int mSiteNumber;
private PassportBand mSiteBand;
private HashMap<Integer,String> mActiveCalls = new HashMap<Integer,String>();
public PassportDecoderState( AliasList aliasList )
{
super( aliasList );
}
@Override
@SuppressWarnings( "incomplete-switch" )
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;
// }
// }
// }
//
// if( message instanceof PassportMessage )
// {
// mActivitySummary.receive( message );
//
// PassportMessage passport = (PassportMessage)message;
//
// if( passport.isValid() )
// {
// switch( passport.getMessageType() )
// {
// case CA_STRT:
// if( mChannelNumber == 0 )
// {
// mChannelNumber = passport.getLCN();
// }
//
// if( passport.getLCN() == mChannelNumber )
// {
// setTalkgroup( String.valueOf( passport.getTalkgroupID() ) );
// setTalkgroupAlias( passport.getTalkgroupIDAlias() );
//
// if( mTalkgroupAlias != null )
// {
// try
// {
// if( mTalkgroupAlias.hasAudioType() )
// {
// mAudioTypeListener.setAudioType(
// mTalkgroupAlias.getAudioType() );
// }
// }
// catch( Exception e )
// {
// mLog.error( "Error setting audio for "
// + "passport call", e );
// }
// }
//
// PassportCallEvent current =
// getCurrentPassportCallEvent();
//
// /* If we're already in a call event, add the message
// * to the current call event ... if false, then we
// * have a different call ... cleanup the old one. */
// if( current != null && !current.addMessage( passport ) )
// {
// if( current.isValid() )
// {
// final PassportCallEvent endEvent =
// getCurrentPassportCallEvent();
//
// mCallEventModel.setEnd( endEvent );
// }
// else
// {
// mCallEventModel.remove( current );
// }
//
// current = null;
// setCurrentCallEvent( null );
// }
//
// if( current == null )
// {
// mCurrentCallEvent = new PassportCallEvent
// .Builder( CallEventType.CALL )
// .aliasList( mAliasList )
// .channel( String.valueOf( mChannelNumber ) )
// .frequency( passport.getLCNFrequency() )
// .to( String.valueOf( passport.getTalkgroupID() ) )
// .build();
//
// mCallEventModel.add( mCurrentCallEvent );
// }
//
// State state = getState();
//
// if( state == State.IDLE || state == State.CALL )
// {
// setState( State.CALL );
// }
// else if( state == State.DATA )
// {
// setState( State.DATA );
// }
// }
// else
// {
// //Call Detection
// int lcn = passport.getLCN();
// String tg = String.valueOf( passport.getTalkgroupID() );
//
// if( !mActiveCalls.containsKey( lcn ) ||
// !mActiveCalls.get( lcn ).contentEquals( tg ) )
// {
// mActiveCalls.put( lcn, tg );
//
// PassportCallEvent event = new PassportCallEvent
// .Builder( CallEventType.CALL_DETECT )
// .aliasList( mAliasList )
// .channel( String.valueOf( lcn ) )
// .details( "Site: " + passport.getSite() )
// .frequency( passport.getLCNFrequency() )
// .to( tg )
// .build();
//
// mCallEventModel.add( event );
// }
// }
// break;
// case DA_STRT:
////TODO: add channel number checking and log a call event if on another site
// 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() );
//
// final String mid = passport.getMobileID();
//
// final PassportCallEvent event =
// getCurrentPassportCallEvent();
//
// if( event != null && mid != null )
// {
// mCallEventModel.setFromID( event, mid );
// }
//
// setState( State.CALL );
// break;
// case RA_REGI:
// mCallEventModel.add( new PassportCallEvent
// .Builder( CallEventType.REGISTER )
// .aliasList( mAliasList )
// .channel( String.valueOf( passport.getLCN() ) )
// .frequency( passport.getLCNFrequency() )
// .to( passport.getToID() )
// .build() );
////TODO: add logic to recognize the register event and suppress the subsequent
////call messages, but capture the group assignments.
// setState( State.DATA );
// break;
// case SY_IDLE:
// PassportBand band = passport.getSiteBand();
//
// if( mSiteBand == null || mSiteBand != band )
// {
// mSiteBand = band;
//
// mSiteNumber = passport.getSite();
//
// long frequency = mProcessingChain.getChannel()
// .getTunerChannel().getFrequency();
//
// mChannelNumber = mSiteBand.getChannel( frequency );
//
// broadcast( ChangedAttribute.CHANNEL_NUMBER );
// }
// default:
// break;
// }
// }
// }
}
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;
}
}
@Override
public String getActivitySummary()
{
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( " " );
if( hasAliasList() )
{
Alias alias = getAliasList().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( " " );
if( hasAliasList() )
{
Alias alias = getAliasList().getMobileIDNumberAlias( min );
if( alias != null )
{
sb.append( alias.getName() );
}
}
sb.append( "\n" );
}
}
return sb.toString();
}
// /**
// * 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 ) )
// {
// final PassportCallEvent current = getCurrentPassportCallEvent();
//
// if( current != null )
// {
// mCallEventModel.setEnd( current );
// }
// }
//
// setCurrentCallEvent( null );
//
// mActiveCalls.clear();
//
// super.fade( type );
// }
public void reset()
{
//TODO: finish this
resetState();
}
private void resetState()
{
setTalkgroup( null );
setTalkgroupAlias( null );
setMobileID( null );
setMobileIDAlias( null );
}
public int getChannelNumber()
{
return mChannelNumber;
}
public int getSiteNumber()
{
return mSiteNumber;
}
public String getTalkgroup()
{
return mTalkgroup;
}
public void setTalkgroup( String talkgroup )
{
mTalkgroup = talkgroup;
broadcast( ChangedAttribute.TO_TALKGROUP );
}
public Alias getTalkgroupAlias()
{
return mTalkgroupAlias;
}
public void setTalkgroupAlias( Alias alias )
{
mTalkgroupAlias = alias;
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
public String getMobileID()
{
return mMobileID;
}
public void setMobileID( String id )
{
mMobileID = id;
broadcast( ChangedAttribute.FROM_TALKGROUP );
}
public Alias getMobileIDAlias()
{
return mMobileIDAlias;
}
public void setMobileIDAlias( Alias alias )
{
mMobileIDAlias = alias;
broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS );
}
@Override
public void init()
{
// TODO Auto-generated method stub
}
@Override
public void receiveDecoderStateEvent( DecoderStateEvent event )
{
// TODO Auto-generated method stub
}
}

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 module.decode.passport;
import module.decode.DecodeEditor;
import module.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,649 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.passport;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import map.Plottable;
import message.Message;
import message.MessageType;
import module.decode.DecoderType;
import alias.Alias;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
import edac.CRCPassport;
public class PassportMessage extends Message
{
private static final String sUNKNOWN = "**UNKNOWN**";
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 BinaryMessage mMessage;
private CRC mCRC;
private AliasList mAliasList;
private PassportMessage mIdleMessage;
public PassportMessage( BinaryMessage message,
PassportMessage idleMessage,
AliasList list )
{
mMessage = CRCPassport.correct( message );
mIdleMessage = idleMessage;
mCRC = CRCPassport.check( mMessage );
mAliasList = list;
}
public PassportMessage( BinaryMessage message, AliasList list )
{
this( message, null, list );
}
public BinaryMessage 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();
if( mAliasList != null )
{
return mAliasList.getTalkgroupAlias( String.valueOf( tg ) );
}
return null;
}
public int getLCN()
{
return getInt( sLCN );
}
public long 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 getFree()
{
return getInt( sFREE );
}
public long getFreeFrequency()
{
return getSiteFrequency( getFree() );
}
public String getFreeFrequencyFormatted()
{
return mDecimalFormatter.format( (double)getFreeFrequency() / 1000000.0d );
}
public long getNeighborFrequency()
{
if( getMessageType() == MessageType.SY_IDLE )
{
PassportBand band = getNeighborBand();
return band.getFrequency( getFree() );
}
return 0;
}
/**
* 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( mAliasList != null && 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()
{
return String.valueOf( getTalkgroupID() );
}
@Override
public Alias getToIDAlias()
{
return getTalkgroupIDAlias();
}
public long getSiteFrequency( int channel )
{
if( mIdleMessage != null && 0 < channel && channel < 1792 )
{
PassportBand band = mIdleMessage.getSiteBand();
if( band != PassportBand.BAND_UNKNOWN )
{
return band.getFrequency( channel );
}
}
return 0;
}
@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;
}
/**
* Provides a listing of aliases contained in the message.
*/
public List<Alias> getAliases()
{
List<Alias> aliases = new ArrayList<Alias>();
Alias talkgroup = getTalkgroupIDAlias();
if( talkgroup != null )
{
aliases.add( talkgroup );
}
Alias mid = getMobileIDAlias();
if( mid != null )
{
aliases.add( mid );
}
return aliases;
}
}

View File

@@ -0,0 +1,68 @@
package module.decode.passport;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import message.Message;
import message.MessageType;
import filter.Filter;
import filter.FilterElement;
public class PassportMessageFilter extends Filter<Message>
{
private HashMap<MessageType, FilterElement<MessageType>> mElements =
new HashMap<MessageType, FilterElement<MessageType>>();
public PassportMessageFilter()
{
super( "Passport Message Filter" );
mElements.put( MessageType.CA_STRT,
new FilterElement<MessageType>( MessageType.CA_STRT ) );
mElements.put( MessageType.CA_ENDD,
new FilterElement<MessageType>( MessageType.CA_ENDD ) );
mElements.put( MessageType.SY_IDLE,
new FilterElement<MessageType>( MessageType.SY_IDLE ) );
mElements.put( MessageType.ID_TGAS,
new FilterElement<MessageType>( MessageType.ID_TGAS ) );
mElements.put( MessageType.ID_ESNH,
new FilterElement<MessageType>( MessageType.ID_ESNH ) );
mElements.put( MessageType.CA_PAGE,
new FilterElement<MessageType>( MessageType.CA_PAGE ) );
mElements.put( MessageType.ID_RDIO,
new FilterElement<MessageType>( MessageType.ID_RDIO ) );
mElements.put( MessageType.DA_STRT,
new FilterElement<MessageType>( MessageType.DA_STRT ) );
mElements.put( MessageType.RA_REGI,
new FilterElement<MessageType>( MessageType.RA_REGI ) );
}
@Override
public boolean passes( Message message )
{
if( mEnabled && canProcess( message ) )
{
PassportMessage passport = (PassportMessage)message;
if( mElements.containsKey( passport.getMessageType() ) )
{
return mElements.get( passport.getMessageType() ).isEnabled();
}
}
return false;
}
@Override
public boolean canProcess( Message message )
{
return message instanceof PassportMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return new ArrayList<FilterElement<?>>( mElements.values() );
}
}

View File

@@ -0,0 +1,111 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.passport;
import java.util.HashMap;
import message.Message;
import message.MessageType;
import sample.Listener;
import alias.AliasList;
import bits.BinaryMessage;
public class PassportMessageProcessor implements Listener<BinaryMessage>
{
private Listener<Message> mMessageListener;
private IdleMessageFinder mIdleFinder = new IdleMessageFinder();
private AliasList mAliasList;
private PassportMessage mIdleMessage;
public PassportMessageProcessor( AliasList list )
{
mAliasList = list;
}
@Override
public void receive( BinaryMessage buffer )
{
if( mMessageListener != null )
{
PassportMessage message;
if( mIdleMessage != null )
{
message = new PassportMessage( buffer, mIdleMessage, mAliasList );
}
else
{
message = new PassportMessage( buffer, mAliasList );
mIdleFinder.receive( message );
}
mMessageListener.receive( message );
}
}
public void setMessageListener( Listener<Message> listener )
{
mMessageListener = listener;
}
public void removeMessageListener()
{
mMessageListener = null;
}
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 );
}
}
}
}
}