mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
Initial commit of redesigned internal processing and audio management.
Note: Several of the decoders are currently non-functioning in this commit. If you want the latest functioning codebase, please checkout the commit prior to this one. All decoders will eventually be updated with this new architecture. Currently broken: - Logging and recording - Aux decoders (Fleetsync/MDC/LoJack/Tait) - LTR/Passport decoders Changes in this commit: - Module architecture for decoders and components with central provisioning by the DecoderFactory - Transition from Complex sample stream processing to float buffer processing for improved memory management - Redesigned filter structures - to support buffers and streams - Resolved several memory leak issues - Universal sharing of the thread pool manager - Added centralized audio management with call/user prioritization and new gui with mute/volume controls - Embedded audio metadata for eventual streaming support - Redesigned ProcessingChain to support independent re-use - Added new TrafficChannelManager to handle traffic channel allocations - Traffic Channel pool size and Call timeout available in P25 and MPT1327 configs - Redesigned channel and decoder gui components
This commit is contained in:
173
src/module/decode/fleetsync2/Fleetsync2Decoder.java
Normal file
173
src/module/decode/fleetsync2/Fleetsync2Decoder.java
Normal file
@@ -0,0 +1,173 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package module.decode.fleetsync2;
|
||||
|
||||
import instrument.Instrumentable;
|
||||
import instrument.tap.Tap;
|
||||
import instrument.tap.stream.BinaryTap;
|
||||
import instrument.tap.stream.FloatBufferTap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import module.decode.Decoder;
|
||||
import module.decode.DecoderType;
|
||||
import sample.Listener;
|
||||
import sample.real.IRealBufferListener;
|
||||
import sample.real.RealBuffer;
|
||||
import alias.AliasList;
|
||||
import bits.MessageFramer;
|
||||
import bits.SyncPattern;
|
||||
import dsp.filter.Filters;
|
||||
import dsp.filter.fir.real.RealFIRFilter_RB_RB;
|
||||
import dsp.filter.halfband.real.HalfBandFilter_RB_RB;
|
||||
import dsp.fsk.FSK2Decoder;
|
||||
import dsp.fsk.FSK2Decoder.Output;
|
||||
|
||||
/**
|
||||
* Fleetsync II Decoder - 1200 baud 2FSK decoder that can process 48k sample rate
|
||||
* floating point samples and output fully framed Fleetsync II messages
|
||||
*/
|
||||
public class Fleetsync2Decoder extends Decoder implements IRealBufferListener,
|
||||
Instrumentable
|
||||
{
|
||||
/* Decimated sample rate ( 48,000 / 2 = 24,000 ) feeding the decoder */
|
||||
private static final int sDECIMATED_SAMPLE_RATE = 24000;
|
||||
|
||||
/* Baud or Symbol Rate */
|
||||
private static final int sSYMBOL_RATE = 1200;
|
||||
|
||||
/* Message length - 5 x REVS + 16 x SYNC + 8 x 64Bit Blocks */
|
||||
private static final int sMESSAGE_LENGTH = 537;
|
||||
|
||||
/* Instrumentation Taps */
|
||||
private ArrayList<Tap> mAvailableTaps;
|
||||
private static final String INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD =
|
||||
"Tap Point: Bandpass Filter > < FSK2 Decoder";
|
||||
private static final String INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER =
|
||||
"Tap Point: FSK2 Decoder > < Message Framer";
|
||||
|
||||
private FSK2Decoder mFSKDecoder;
|
||||
private HalfBandFilter_RB_RB mDecimationFilter;
|
||||
private RealFIRFilter_RB_RB mBandPassFilter;
|
||||
private MessageFramer mMessageFramer;
|
||||
private Fleetsync2MessageProcessor mMessageProcessor;
|
||||
|
||||
public Fleetsync2Decoder( AliasList aliasList )
|
||||
{
|
||||
super( new FleetsyncDecoderState( aliasList ) );
|
||||
|
||||
mDecimationFilter = new HalfBandFilter_RB_RB(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0f, true );
|
||||
|
||||
mBandPassFilter = new RealFIRFilter_RB_RB(
|
||||
Filters.FIRBP_1200FSK_24000FS.getCoefficients(), 1.02f );
|
||||
|
||||
mDecimationFilter.setListener( mBandPassFilter );
|
||||
|
||||
mFSKDecoder = new FSK2Decoder( sDECIMATED_SAMPLE_RATE,
|
||||
sSYMBOL_RATE, Output.INVERTED );
|
||||
mBandPassFilter.setListener( mFSKDecoder );
|
||||
|
||||
mMessageFramer = new MessageFramer(
|
||||
SyncPattern.FLEETSYNC2.getPattern(), sMESSAGE_LENGTH );
|
||||
mFSKDecoder.setListener( mMessageFramer );
|
||||
|
||||
mMessageProcessor = new Fleetsync2MessageProcessor( aliasList );
|
||||
mMessageFramer.addMessageListener( mMessageProcessor );
|
||||
|
||||
mMessageProcessor.setMessageListener( mMessageBroadcaster );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mBandPassFilter.dispose();
|
||||
mDecimationFilter.dispose();
|
||||
mDecoderState.dispose();
|
||||
mFSKDecoder.dispose();
|
||||
mMessageFramer.dispose();
|
||||
mMessageProcessor.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.FLEETSYNC2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Tap> getTaps()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<Tap>();
|
||||
|
||||
mAvailableTaps.add( new FloatBufferTap(
|
||||
INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD, 0, 0.5f ) );
|
||||
mAvailableTaps.addAll( mFSKDecoder.getTaps() );
|
||||
mAvailableTaps.add( new BinaryTap(
|
||||
INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER, 0, 0.025f ) );
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTap( Tap tap )
|
||||
{
|
||||
mFSKDecoder.addTap( tap );
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
|
||||
FloatBufferTap bpTap = (FloatBufferTap)tap;
|
||||
mBandPassFilter.setListener( bpTap );
|
||||
bpTap.setListener( mFSKDecoder );
|
||||
break;
|
||||
case INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER:
|
||||
BinaryTap decoderTap = (BinaryTap)tap;
|
||||
mFSKDecoder.setListener( decoderTap );
|
||||
decoderTap.setListener( mMessageFramer );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTap( Tap tap )
|
||||
{
|
||||
mFSKDecoder.removeTap( tap );
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
|
||||
mBandPassFilter.setListener( mFSKDecoder );
|
||||
break;
|
||||
case INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER:
|
||||
mFSKDecoder.setListener( mMessageFramer );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<RealBuffer> getRealBufferListener()
|
||||
{
|
||||
return mDecimationFilter;
|
||||
}
|
||||
}
|
||||
189
src/module/decode/fleetsync2/Fleetsync2DecoderPanel.java
Normal file
189
src/module/decode/fleetsync2/Fleetsync2DecoderPanel.java
Normal file
@@ -0,0 +1,189 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package module.decode.fleetsync2;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import alias.Alias;
|
||||
import module.decode.state.ChangedAttribute;
|
||||
import module.decode.state.DecoderPanel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import settings.ColorSetting;
|
||||
import settings.ColorSetting.ColorSettingName;
|
||||
import settings.Setting;
|
||||
import settings.SettingsManager;
|
||||
|
||||
public class Fleetsync2DecoderPanel extends DecoderPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String PROTOCOL = "FSync II";
|
||||
|
||||
private JLabel mFromLabel = new JLabel( " " );
|
||||
private JLabel mFrom = new JLabel();
|
||||
private JLabel mFromAlias = new JLabel();
|
||||
|
||||
private JLabel mToLabel = new JLabel();
|
||||
private JLabel mTo = new JLabel( " " );
|
||||
private JLabel mToAlias = new JLabel();
|
||||
|
||||
private JLabel mProtocol = new JLabel( PROTOCOL );
|
||||
private JLabel mMessage = new JLabel();
|
||||
private JLabel mMessageType = new JLabel();
|
||||
|
||||
public Fleetsync2DecoderPanel( SettingsManager settingsManager,
|
||||
Fleetsync2Decoder decoder )
|
||||
{
|
||||
super( settingsManager, decoder );
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private FleetsyncDecoderState getState()
|
||||
{
|
||||
return (FleetsyncDecoderState)mDecoder.getDecoderState();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
protected void init()
|
||||
{
|
||||
/* Calling super init will get and broadcast color settings to properly
|
||||
* setup the jlabel colors */
|
||||
super.init();
|
||||
|
||||
setLayout( new MigLayout( "insets 1 0 0 0", "[grow,fill]", "[]0[]0[]") );
|
||||
|
||||
mFromLabel.setFont( mFontAuxDecoder );
|
||||
add( mFromLabel );
|
||||
mFrom.setFont( mFontAuxDecoder );
|
||||
add( mFrom );
|
||||
mFromAlias.setFont( mFontAuxDecoder );
|
||||
add( mFromAlias, "wrap" );
|
||||
|
||||
mToLabel.setFont( mFontAuxDecoder );
|
||||
add( mToLabel );
|
||||
mTo.setFont( mFontAuxDecoder );
|
||||
add( mTo );
|
||||
mToAlias.setFont( mFontAuxDecoder );
|
||||
add( mToAlias, "wrap" );
|
||||
|
||||
mProtocol.setFont( mFontAuxDecoder );
|
||||
add( mProtocol );
|
||||
mMessageType.setFont( mFontAuxDecoder );
|
||||
add( mMessageType );
|
||||
mMessage.setFont( mFontAuxDecoder );
|
||||
add( mMessage, "wrap" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( final ChangedAttribute changedAttribute )
|
||||
{
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
switch( changedAttribute )
|
||||
{
|
||||
case FROM_TALKGROUP:
|
||||
String from = getState().getFromID();
|
||||
|
||||
if( from != null )
|
||||
{
|
||||
mFromLabel.setText( "FM:" );
|
||||
}
|
||||
else
|
||||
{
|
||||
mFromLabel.setText( " " );
|
||||
}
|
||||
mFrom.setText( from );
|
||||
break;
|
||||
case FROM_TALKGROUP_ALIAS:
|
||||
Alias fromAlias = getState().getFromIDAlias();
|
||||
|
||||
if( fromAlias != null )
|
||||
{
|
||||
mFromAlias.setText( fromAlias.getName() );
|
||||
}
|
||||
break;
|
||||
case TO_TALKGROUP:
|
||||
String to = getState().getToID();
|
||||
|
||||
if( to != null )
|
||||
{
|
||||
mToLabel.setText( "TO:" );
|
||||
}
|
||||
else
|
||||
{
|
||||
mToLabel.setText( " " );
|
||||
}
|
||||
mTo.setText( to );
|
||||
break;
|
||||
case TO_TALKGROUP_ALIAS:
|
||||
Alias toAlias = getState().getToIDAlias();
|
||||
|
||||
if( toAlias != null )
|
||||
{
|
||||
mToAlias.setText( toAlias.getName() );
|
||||
}
|
||||
break;
|
||||
case MESSAGE:
|
||||
mMessage.setText( getState().getMessage() );
|
||||
break;
|
||||
case MESSAGE_TYPE:
|
||||
mMessageType.setText( getState().getMessageType() );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingChanged( Setting setting )
|
||||
{
|
||||
super.settingChanged( setting );
|
||||
|
||||
if( setting instanceof ColorSetting )
|
||||
{
|
||||
ColorSetting color = (ColorSetting)setting;
|
||||
|
||||
if( color.getColorSettingName() ==
|
||||
ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER )
|
||||
{
|
||||
mFromLabel.setForeground( color.getColor() );
|
||||
mFrom.setForeground( color.getColor() );
|
||||
mFromAlias.setForeground( color.getColor() );
|
||||
mToLabel.setForeground( color.getColor() );
|
||||
mTo.setForeground( color.getColor() );
|
||||
mToAlias.setForeground( color.getColor() );
|
||||
mProtocol.setForeground( color.getColor() );
|
||||
mMessageType.setForeground( color.getColor() );
|
||||
mMessage.setForeground( color.getColor() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/module/decode/fleetsync2/Fleetsync2MessageProcessor.java
Normal file
62
src/module/decode/fleetsync2/Fleetsync2MessageProcessor.java
Normal file
@@ -0,0 +1,62 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package module.decode.fleetsync2;
|
||||
|
||||
import message.Message;
|
||||
import sample.Listener;
|
||||
import alias.AliasList;
|
||||
import bits.BinaryMessage;
|
||||
|
||||
public class Fleetsync2MessageProcessor implements Listener<BinaryMessage>
|
||||
{
|
||||
private Listener<Message> mMessageListener;
|
||||
|
||||
private AliasList mAliasList;
|
||||
|
||||
public Fleetsync2MessageProcessor( AliasList list )
|
||||
{
|
||||
mAliasList = list;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mMessageListener = null;
|
||||
mAliasList = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( BinaryMessage buffer )
|
||||
{
|
||||
FleetsyncMessage message = new FleetsyncMessage( buffer, mAliasList );
|
||||
|
||||
if( mMessageListener != null )
|
||||
{
|
||||
mMessageListener.receive( message );
|
||||
}
|
||||
}
|
||||
|
||||
public void setMessageListener( Listener<Message> listener )
|
||||
{
|
||||
mMessageListener = listener;
|
||||
}
|
||||
|
||||
public void removeMessageListener()
|
||||
{
|
||||
mMessageListener = null;
|
||||
}
|
||||
}
|
||||
189
src/module/decode/fleetsync2/FleetsyncCallEvent.java
Normal file
189
src/module/decode/fleetsync2/FleetsyncCallEvent.java
Normal file
@@ -0,0 +1,189 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package module.decode.fleetsync2;
|
||||
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.event.CallEvent;
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
|
||||
public class FleetsyncCallEvent extends CallEvent
|
||||
{
|
||||
public FleetsyncCallEvent( CallEventType callEventType,
|
||||
AliasList aliasList,
|
||||
String fromID,
|
||||
String toID,
|
||||
String details )
|
||||
{
|
||||
super( DecoderType.FLEETSYNC2, callEventType,
|
||||
aliasList, fromID, toID, details );
|
||||
}
|
||||
|
||||
private Alias getAlias( String ident )
|
||||
{
|
||||
if( hasAliasList() )
|
||||
{
|
||||
return getAliasList().getFleetsyncAlias( ident );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
return getAlias( getFromID() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getToIDAlias()
|
||||
{
|
||||
return getAlias( getToID() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannel()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrequency()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static class Builder
|
||||
{
|
||||
/* Required parameters */
|
||||
private CallEventType mCallEventType;
|
||||
|
||||
/* Optional parameters */
|
||||
private AliasList mAliasList;
|
||||
private String mFromID;
|
||||
private String mToID;
|
||||
private String mDetails;
|
||||
|
||||
public Builder( CallEventType callEventType )
|
||||
{
|
||||
mCallEventType = callEventType;
|
||||
}
|
||||
|
||||
public Builder aliasList( AliasList aliasList )
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder from( String val )
|
||||
{
|
||||
mFromID = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder details( String details )
|
||||
{
|
||||
mDetails = details;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder to( String toID )
|
||||
{
|
||||
mToID = toID;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FleetsyncCallEvent build()
|
||||
{
|
||||
return new FleetsyncCallEvent( this );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor for the builder
|
||||
*/
|
||||
private FleetsyncCallEvent( Builder builder )
|
||||
{
|
||||
this( builder.mCallEventType,
|
||||
builder.mAliasList,
|
||||
builder.mFromID,
|
||||
builder.mToID,
|
||||
builder.mDetails );
|
||||
}
|
||||
|
||||
public static FleetsyncCallEvent
|
||||
getFleetsync2Event( FleetsyncMessage message )
|
||||
{
|
||||
CallEventType type = CallEventType.UNKNOWN;
|
||||
StringBuilder sbDetails = new StringBuilder();
|
||||
sbDetails.append( "Fleetsync " );
|
||||
|
||||
switch( message.getMessageType() )
|
||||
{
|
||||
case ACKNOWLEDGE:
|
||||
type = CallEventType.RESPONSE;
|
||||
sbDetails.append( "ACK" );
|
||||
break;
|
||||
case ANI:
|
||||
type = CallEventType.ID_ANI;
|
||||
break;
|
||||
case EMERGENCY:
|
||||
type = CallEventType.EMERGENCY;
|
||||
break;
|
||||
case GPS:
|
||||
type = CallEventType.GPS;
|
||||
sbDetails.append( message.getGPSLocation() );
|
||||
break;
|
||||
case LONE_WORKER_EMERGENCY:
|
||||
type = CallEventType.EMERGENCY;
|
||||
sbDetails.append( "LONE WORKER EMERGENCY ALERT" );
|
||||
break;
|
||||
case PAGING:
|
||||
type = CallEventType.PAGE;
|
||||
break;
|
||||
case STATUS:
|
||||
type = CallEventType.STATUS;
|
||||
sbDetails.append( "Status: " );
|
||||
sbDetails.append( message.getStatus() );
|
||||
|
||||
if( message.getStatusAlias() != null )
|
||||
{
|
||||
sbDetails.append( "/" );
|
||||
sbDetails.append( message.getStatusAlias().getName() );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if( type == CallEventType.ID_ANI ||
|
||||
type == CallEventType.EMERGENCY )
|
||||
{
|
||||
return new FleetsyncCallEvent.Builder( type )
|
||||
.details( sbDetails.toString() )
|
||||
.from( message.getFromID() )
|
||||
.build();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new FleetsyncCallEvent.Builder( type )
|
||||
.details( sbDetails.toString() )
|
||||
.from( message.getFromID() )
|
||||
.to( message.getToID() )
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
328
src/module/decode/fleetsync2/FleetsyncDecoderState.java
Normal file
328
src/module/decode/fleetsync2/FleetsyncDecoderState.java
Normal file
@@ -0,0 +1,328 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package module.decode.fleetsync2;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import message.Message;
|
||||
import module.decode.state.ChangedAttribute;
|
||||
import module.decode.state.DecoderState;
|
||||
import module.decode.state.DecoderStateEvent;
|
||||
import module.decode.state.DecoderStateEvent.Event;
|
||||
import module.decode.state.State;
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
|
||||
public class FleetsyncDecoderState extends DecoderState
|
||||
{
|
||||
private TreeSet<String> mIdents = new TreeSet<String>();
|
||||
private TreeSet<String> mEmergencyIdents = new TreeSet<String>();
|
||||
|
||||
private String mFrom;
|
||||
private Alias mFromAlias;
|
||||
|
||||
private String mTo;
|
||||
private Alias mToAlias;
|
||||
|
||||
private String mMessage;
|
||||
private String mMessageType;
|
||||
|
||||
public FleetsyncDecoderState( AliasList aliasList )
|
||||
{
|
||||
super( aliasList );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the overall decoder state and clears any accumulated event details
|
||||
*/
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
mIdents.clear();
|
||||
mEmergencyIdents.clear();
|
||||
|
||||
resetState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this decoder state
|
||||
*/
|
||||
public void resetState()
|
||||
{
|
||||
setFromID( null );
|
||||
setFromIDAlias( null );
|
||||
setToID( null );
|
||||
setToIDAlias( null );
|
||||
setMessage( null );
|
||||
setMessageType( null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Message message )
|
||||
{
|
||||
if( message instanceof FleetsyncMessage )
|
||||
{
|
||||
FleetsyncMessage fleetsync = (FleetsyncMessage)message;
|
||||
|
||||
if( fleetsync.isValid() )
|
||||
{
|
||||
setFromID( fleetsync.getFromID() );
|
||||
setFromIDAlias( fleetsync.getFromIDAlias() );
|
||||
mIdents.add( fleetsync.getFromID() );
|
||||
|
||||
FleetsyncMessageType type = fleetsync.getMessageType();
|
||||
|
||||
if( type != FleetsyncMessageType.ANI )
|
||||
{
|
||||
setToID( fleetsync.getToID() );
|
||||
setToIDAlias( fleetsync.getToIDAlias() );
|
||||
mIdents.add( fleetsync.getToID() );
|
||||
}
|
||||
|
||||
setMessageType( type.getLabel() );
|
||||
|
||||
switch( type )
|
||||
{
|
||||
case GPS:
|
||||
setMessage( fleetsync.getGPSLocation() );
|
||||
break;
|
||||
case STATUS:
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( fleetsync.getStatus() );
|
||||
|
||||
Alias status = fleetsync.getStatusAlias();
|
||||
|
||||
if( status != null )
|
||||
{
|
||||
sb.append( "/" );
|
||||
sb.append( status.getName() );
|
||||
}
|
||||
|
||||
setMessage( sb.toString() );
|
||||
break;
|
||||
case EMERGENCY:
|
||||
case LONE_WORKER_EMERGENCY:
|
||||
mEmergencyIdents.add( fleetsync.getFromID() );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
FleetsyncCallEvent fsCallEvent =
|
||||
FleetsyncCallEvent.getFleetsync2Event( fleetsync );
|
||||
|
||||
fsCallEvent.setAliasList( getAliasList() );
|
||||
|
||||
broadcast( fsCallEvent );
|
||||
|
||||
/* Broadcast decode event. If the primary state is not already
|
||||
* in a call state, then we'll use a default decoded data state */
|
||||
broadcast( new DecoderStateEvent( this, Event.DECODE, State.DATA ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getFromID()
|
||||
{
|
||||
return mFrom;
|
||||
}
|
||||
|
||||
public void setFromID( String from )
|
||||
{
|
||||
mFrom = from;
|
||||
broadcast( ChangedAttribute.FROM_TALKGROUP );
|
||||
}
|
||||
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
return mFromAlias;
|
||||
}
|
||||
|
||||
public void setFromIDAlias( Alias alias )
|
||||
{
|
||||
mFromAlias = alias;
|
||||
broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS );
|
||||
}
|
||||
|
||||
public String getToID()
|
||||
{
|
||||
return mTo;
|
||||
}
|
||||
|
||||
public void setToID( String to )
|
||||
{
|
||||
mTo = to;
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||
}
|
||||
|
||||
public Alias getToIDAlias()
|
||||
{
|
||||
return mToAlias;
|
||||
}
|
||||
|
||||
public void setToIDAlias( Alias alias )
|
||||
{
|
||||
mToAlias = alias;
|
||||
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
||||
}
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
public void setMessage( String message )
|
||||
{
|
||||
mMessage = message;
|
||||
broadcast( ChangedAttribute.MESSAGE );
|
||||
}
|
||||
|
||||
public String getMessageType()
|
||||
{
|
||||
return mMessageType;
|
||||
}
|
||||
|
||||
public void setMessageType( String type )
|
||||
{
|
||||
mMessageType = type;
|
||||
broadcast( ChangedAttribute.MESSAGE_TYPE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to reset events issued by the channel state
|
||||
*/
|
||||
@Override
|
||||
public void receiveDecoderStateEvent( DecoderStateEvent event )
|
||||
{
|
||||
switch( event.getEvent() )
|
||||
{
|
||||
case RESET:
|
||||
resetState();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActivitySummary()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "=============================\n" );
|
||||
sb.append( "Decoder:\tFleetsync II\n\n" );
|
||||
sb.append( "Fleetsync Idents\n" );
|
||||
|
||||
if( mIdents.isEmpty() )
|
||||
{
|
||||
sb.append( " None\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Iterator<String> it = mIdents.iterator();
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
String ident = it.next();
|
||||
|
||||
sb.append( " " );
|
||||
sb.append( formatIdent( ident ) );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias alias = getAliasList().getFleetsyncAlias( ident );
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
sb.append( " " );
|
||||
sb.append( alias.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
sb.append( "\nFleetsync Emergency Activations\n" );
|
||||
|
||||
if( mEmergencyIdents.isEmpty() )
|
||||
{
|
||||
sb.append( " None\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Iterator<String> it = mEmergencyIdents.iterator();
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
String ident = it.next();
|
||||
|
||||
sb.append( " " );
|
||||
sb.append( formatIdent( ident ) );
|
||||
|
||||
if( hasAliasList() )
|
||||
{
|
||||
Alias alias = getAliasList().getFleetsyncAlias( ident );
|
||||
|
||||
if( alias != null )
|
||||
{
|
||||
sb.append( " " );
|
||||
sb.append( alias.getName() );
|
||||
}
|
||||
|
||||
sb.append( "\n" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String formatIdent( String ident )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if( ident.length() == 7 )
|
||||
{
|
||||
sb.append( ident.substring( 0, 3 ) );
|
||||
sb.append( "-" );
|
||||
sb.append( ident.substring( 3, 7 ) );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return ident;
|
||||
}
|
||||
}
|
||||
}
|
||||
896
src/module/decode/fleetsync2/FleetsyncMessage.java
Normal file
896
src/module/decode/fleetsync2/FleetsyncMessage.java
Normal file
@@ -0,0 +1,896 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package module.decode.fleetsync2;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
|
||||
import map.Plottable;
|
||||
import message.Message;
|
||||
|
||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||
|
||||
import alias.Alias;
|
||||
import alias.AliasList;
|
||||
import bits.BinaryMessage;
|
||||
import edac.CRC;
|
||||
import edac.CRCFleetsync;
|
||||
|
||||
public class FleetsyncMessage extends Message
|
||||
{
|
||||
private static DecimalFormat mDecimalFormatter = new DecimalFormat("#.#####");
|
||||
private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" );
|
||||
|
||||
//Calendar to use in calculating time hacks
|
||||
Calendar mCalendar = new GregorianCalendar();
|
||||
|
||||
//Message parts are identified in big-endian order for correct translation
|
||||
|
||||
//Message Header
|
||||
private static int[] sRevs = { 4,3,2,1,0 };
|
||||
private static int[] sSync = { 20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5 };
|
||||
|
||||
//Message Block 1
|
||||
private static int[] sStatusMessage = { 27,26,25,24,23,22,21 };
|
||||
private static int[] sMessageType = { 33,32,31,30,29 };
|
||||
|
||||
private static int sEmergencyFlag = 22;
|
||||
private static int sLoneWorkerFlag = 24;
|
||||
private static int sPagingFlag = 26;
|
||||
private static int sEndOfTransmissionFlag = 27;
|
||||
private static int sManualFlag = 28;
|
||||
private static int sANIFlag = 29;
|
||||
private static int sStatusFlag = 30;
|
||||
private static int sAcknowledgeFlag = 31;
|
||||
//32 - unknown - always 0
|
||||
//33 - unknown - always 0
|
||||
//34 - unknown - set for ACKNOWLEDGE
|
||||
private static int sGPSExtensionFlag = 35;
|
||||
private static int sFleetExtensionFlag = 36;
|
||||
private static int[] sFleetFrom = { 44,43,42,41,40,39,38,37 };
|
||||
private static int[] sIdentFrom = { 56,55,54,53,52,51,50,49,48,47,46,45 };
|
||||
private static int[] sIdentTo = { 68,67,66,65,64,63,62,61,60,59,58,57 };
|
||||
private static int[] sCRC1 = { 84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69 };
|
||||
|
||||
//Message Block 2
|
||||
private static int[] sFleetTo = { 92,91,90,89,88,87,86,85 };
|
||||
private static int[] sCRC2 = { 148,147,146,145,144,143,142,141,140,139,138,137,136,135,134,132 };
|
||||
|
||||
//Message Block 3
|
||||
private static int[] sGPSHours = { 176,175,174,173,172 };
|
||||
private static int[] sGPSMinutes = { 182,181,180,179,178,177 };
|
||||
private static int[] sGPSSeconds = { 188,187,186,185,184,183 };
|
||||
private static int[] sCRC3 = { 212,211,210,209,208,207,206,205,204,203,202,201,200,199,198,197 };
|
||||
|
||||
//Message Block 4
|
||||
private static int[] sGPSChecksum = { 220,219,218,217,216,215,214,213 };
|
||||
private static int[] sLatitudeDegreesMinutes = { 236,235,234,233,232,231,230,229,228,227,226,225,224,223,222,221 };
|
||||
private static int[] sLatitudeDecimalMinutes = { 251,250,249,248,247,246,245,244,243,242,241,240,239,238 };
|
||||
private static int[] sSpeed = { 276,275,274,273,272,271,270,269,268,267,266,265,264,263,262,261,260,259,258,257,256,255,254,253,252 };
|
||||
private static int[] sCRC4 = { 276,275,274,273,272,271,270,269,268,267,266,265,264,263,262,261 };
|
||||
|
||||
//Message Block 5
|
||||
private static int[] sGPSCentury = { 284,283,282,281,280,279,278,277 };
|
||||
private static int[] sGPSYear = { 291,290,289,288,287,286,285 };
|
||||
private static int[] sGPSMonth = { 295,294,293,292 };
|
||||
private static int[] sGPSDay = { 300,299,298,297,296 };
|
||||
private static int[] sLongitudeDegreesMinutes = { 316,315,314,313,312,311,310,309,308,307,306,305,304,303,302,301 };
|
||||
private static int[] sLongitudeDecimalMinutes = { 331,330,329,328,327,326,325,324,323,322,321,320,319,318 };
|
||||
private static int[] sCRC5 = { 340,339,338,337,336,335,334,333,332,331,330,329,328,327,326,325 };
|
||||
|
||||
//Message Block 6
|
||||
private static int[] sGPSUnknown1 = { 352,351,350,349 };
|
||||
private static int[] sGPSHeading = { 365,363,362,361,360,359,358,357,356,355,354,353 };
|
||||
private static int[] sCRC6 = { 404,403,402,401,400,399,398,397,396,395,394,393,392,391,390,389 };
|
||||
|
||||
//Message Block 7
|
||||
private static int[] sBLK7WhatIsIt = { 444,443,442,441,440,439,438,437,436,435,434,433,432,431,430,429 };
|
||||
private static int[] sCRC7 = { 468,467,466,465,464,463,462,461,460,459,458,457,456,455,454,453 };
|
||||
|
||||
//Message Block 8
|
||||
private static int[] sGPSSpeed = { 491,490,489,488,487,486,485,484 };
|
||||
private static int[] sGPSSpeedFractional = { 499,498,497,496,495,494,493,492 };
|
||||
private static int[] sCRC8 = { 532,531,530,529,528,527,526,525,524,523,522,521,520,519,518,517 };
|
||||
|
||||
private BinaryMessage mMessage;
|
||||
private CRC[] mCRC = new CRC[ 8 ];
|
||||
private AliasList mAliasList;
|
||||
|
||||
public FleetsyncMessage( BinaryMessage message, AliasList list )
|
||||
{
|
||||
mMessage = message;
|
||||
mAliasList = list;
|
||||
checkParity();
|
||||
}
|
||||
|
||||
private void checkParity()
|
||||
{
|
||||
//Check message block 1
|
||||
mCRC[ 0 ] = detectAndCorrect( 21, 85 );
|
||||
|
||||
//Only check subsequent blocks if we know block 1 is correct
|
||||
if( mCRC[ 0 ] == CRC.PASSED || mCRC[ 0 ] == CRC.CORRECTED )
|
||||
{
|
||||
if( hasFleetExtensionFlag() )
|
||||
{
|
||||
//Check message block 2
|
||||
mCRC[ 1 ] = detectAndCorrect( 85, 149 );
|
||||
}
|
||||
|
||||
if( hasGPSFlag() )
|
||||
{
|
||||
//Check message block 3
|
||||
mCRC[ 2 ] = detectAndCorrect( 149, 213 );
|
||||
//Check message block 4
|
||||
mCRC[ 3 ] = detectAndCorrect( 213, 277 );
|
||||
//Check message block 5
|
||||
mCRC[ 4 ] = detectAndCorrect( 277, 341 );
|
||||
//Check message block 6
|
||||
mCRC[ 5 ] = detectAndCorrect( 341, 405 );
|
||||
//Check message block 7
|
||||
mCRC[ 6 ] = detectAndCorrect( 405, 469 );
|
||||
//Check message block 8
|
||||
mCRC[ 7 ] = detectAndCorrect( 469, 533 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CRC detectAndCorrect( int start, int end )
|
||||
{
|
||||
BitSet original = mMessage.get( start, end );
|
||||
|
||||
CRC retVal = CRCFleetsync.check( original );
|
||||
|
||||
//Attempt to correct single-bit errors
|
||||
if( retVal == CRC.FAILED_PARITY )
|
||||
{
|
||||
int[] errorBitPositions = CRCFleetsync.findBitErrors( original );
|
||||
|
||||
if( errorBitPositions != null )
|
||||
{
|
||||
for( int errorBitPosition: errorBitPositions )
|
||||
{
|
||||
mMessage.flip( start + errorBitPosition );
|
||||
}
|
||||
|
||||
retVal = CRC.CORRECTED;
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public boolean isValid()
|
||||
{
|
||||
boolean valid = true;
|
||||
|
||||
for( int x = 0; x < mCRC.length; x++ )
|
||||
{
|
||||
CRC crc = mCRC[ x ];
|
||||
|
||||
if( crc != null )
|
||||
{
|
||||
if( crc == CRC.FAILED_CRC ||
|
||||
crc == CRC.FAILED_PARITY ||
|
||||
crc == CRC.UNKNOWN )
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( mSDF.format( new Date( System.currentTimeMillis() ) ) );
|
||||
|
||||
sb.append( " FSync2 " + getParity() );
|
||||
|
||||
sb.append( getMessage() );
|
||||
|
||||
sb.append( " [" + mMessage.toString() + "]" );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads spaces onto the end of the value to make it 'places' long
|
||||
*/
|
||||
public String pad( String value, int places, String padCharacter )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( value );
|
||||
|
||||
while( sb.length() < places )
|
||||
{
|
||||
sb.append( padCharacter );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads an integer value with additional zeroes to make it decimalPlaces long
|
||||
*/
|
||||
public String format( int number, int decimalPlaces )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() );
|
||||
|
||||
for( int x = 0; x < paddingRequired; x++)
|
||||
{
|
||||
sb.append( "0" );
|
||||
}
|
||||
|
||||
sb.append( number );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public FleetsyncMessageType getMessageType()
|
||||
{
|
||||
if( hasStatusFlag() )
|
||||
{
|
||||
if( hasAcknowledgeFlag() )
|
||||
{
|
||||
return FleetsyncMessageType.ACKNOWLEDGE;
|
||||
}
|
||||
|
||||
if( hasGPSFlag() )
|
||||
{
|
||||
return FleetsyncMessageType.GPS;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FleetsyncMessageType.STATUS;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( hasAcknowledgeFlag() )
|
||||
{
|
||||
return FleetsyncMessageType.ACKNOWLEDGE;
|
||||
}
|
||||
|
||||
if( hasANIFlag() )
|
||||
{
|
||||
return FleetsyncMessageType.ANI;
|
||||
}
|
||||
|
||||
if( hasGPSFlag() )
|
||||
{
|
||||
return FleetsyncMessageType.GPS;
|
||||
}
|
||||
|
||||
if( hasPagingFlag() )
|
||||
{
|
||||
return FleetsyncMessageType.PAGING;
|
||||
}
|
||||
|
||||
if( hasEmergencyFlag() )
|
||||
{
|
||||
if( hasLoneWorkerFlag() )
|
||||
{
|
||||
return FleetsyncMessageType.LONE_WORKER_EMERGENCY;
|
||||
}
|
||||
else
|
||||
return FleetsyncMessageType.EMERGENCY;
|
||||
}
|
||||
}
|
||||
|
||||
return FleetsyncMessageType.UNKNOWN;
|
||||
}
|
||||
|
||||
public int getStatus()
|
||||
{
|
||||
return getStatusNumber() + 9;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RAW status number. The actual status number should be
|
||||
* accessed via the getStatus() method.
|
||||
* @return
|
||||
*/
|
||||
public int getStatusNumber()
|
||||
{
|
||||
return getInt( sStatusMessage );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public Alias getStatusAlias()
|
||||
{
|
||||
if( mAliasList != null )
|
||||
{
|
||||
return mAliasList.getStatus( getStatus() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getFleetFrom()
|
||||
{
|
||||
return getInt( sFleetFrom ) + 99;
|
||||
}
|
||||
|
||||
public int getIdentifierFrom()
|
||||
{
|
||||
return getInt( sIdentFrom ) + 999;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverted Flags - 0 = flag is true
|
||||
* @return
|
||||
*/
|
||||
public boolean hasEndOfTransmissionFlag()
|
||||
{
|
||||
return !mMessage.get( sEndOfTransmissionFlag );
|
||||
}
|
||||
|
||||
public boolean hasEmergencyFlag()
|
||||
{
|
||||
return !mMessage.get( sEmergencyFlag );
|
||||
}
|
||||
|
||||
public boolean hasLoneWorkerFlag()
|
||||
{
|
||||
return !mMessage.get( sLoneWorkerFlag );
|
||||
}
|
||||
|
||||
public boolean hasPagingFlag()
|
||||
{
|
||||
return !mMessage.get( sPagingFlag );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normal Flags - 1 = flag is true
|
||||
* @return
|
||||
*/
|
||||
public boolean hasANIFlag()
|
||||
{
|
||||
return mMessage.get( sANIFlag );
|
||||
}
|
||||
|
||||
public boolean hasAcknowledgeFlag()
|
||||
{
|
||||
return mMessage.get( sAcknowledgeFlag );
|
||||
}
|
||||
|
||||
public boolean hasFleetExtensionFlag()
|
||||
{
|
||||
return mMessage.get( sFleetExtensionFlag );
|
||||
}
|
||||
|
||||
public boolean hasGPSFlag()
|
||||
{
|
||||
return mMessage.get( sGPSExtensionFlag );
|
||||
}
|
||||
|
||||
public boolean hasStatusFlag()
|
||||
{
|
||||
return mMessage.get( sStatusFlag );
|
||||
}
|
||||
|
||||
|
||||
public int getFleetTo()
|
||||
{
|
||||
if( hasFleetExtensionFlag() )
|
||||
{
|
||||
return getInt( sFleetTo ) + 99;
|
||||
}
|
||||
else
|
||||
{
|
||||
return getInt( sFleetFrom ) + 99;
|
||||
}
|
||||
}
|
||||
|
||||
public int getIdentifierTo()
|
||||
{
|
||||
return getInt( sIdentTo ) + 999;
|
||||
}
|
||||
|
||||
/**
|
||||
* GPS Heading
|
||||
* @return
|
||||
*/
|
||||
public double getHeading()
|
||||
{
|
||||
double retVal = 0.0;
|
||||
|
||||
int heading = getInt( sGPSHeading );
|
||||
|
||||
if( heading != 4095 )
|
||||
{
|
||||
retVal = (double)(heading / 10.0 );
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public int getBlock7WhatIsIt()
|
||||
{
|
||||
int value = getInt( sBLK7WhatIsIt );
|
||||
|
||||
if( value == 65535 )
|
||||
{
|
||||
value = -1;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Speed in Kph - whole numbers and three decimal digits of precision
|
||||
* @return
|
||||
*/
|
||||
public double getSpeed()
|
||||
{
|
||||
double retVal = 0.0;
|
||||
|
||||
int temp = getInt( sSpeed );
|
||||
|
||||
if( temp != 0 )
|
||||
{
|
||||
retVal = (double)temp / 1000.0D;
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public double getLatitude()
|
||||
{
|
||||
//TODO: determine the correct hemisphere indicator and replace this
|
||||
//hardcoded "0" with the correct hemisphere value
|
||||
|
||||
return convertDDMToDD( 0,
|
||||
getInt( sLatitudeDegreesMinutes ),
|
||||
getInt( sLatitudeDecimalMinutes ) );
|
||||
}
|
||||
|
||||
public double getLongitude()
|
||||
{
|
||||
//TODO: determine the correct hemisphere indicator and replace this
|
||||
//hardcoded "1" with the correct hemisphere value
|
||||
|
||||
return convertDDMToDD( 1,
|
||||
getInt( sLongitudeDegreesMinutes ),
|
||||
getInt( sLongitudeDecimalMinutes ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Degrees Decimal Minutes to Decimal Degrees
|
||||
*
|
||||
* Latitude and Longitude values are represented by:
|
||||
*
|
||||
* @param hemisphere - 0=North & East, 1=South & West
|
||||
* @param degreesMinutes - an integer value with the first 2-3 digits representing
|
||||
* the degrees and the last two digits representing the minutes
|
||||
*
|
||||
* @param decimalDegrees - an integer value representing the fractional
|
||||
* minutes
|
||||
*
|
||||
* @return - decimal degrees formatted value
|
||||
*/
|
||||
public double convertDDMToDD( int hemisphere, int degreesMinutes, int decimalDegrees )
|
||||
{
|
||||
double retVal = 0.0;
|
||||
|
||||
if( degreesMinutes != 0 )
|
||||
{
|
||||
//Degrees - divide value by 100 and retain the whole number value (ie degrees)
|
||||
retVal += (double)( degreesMinutes / 100 );
|
||||
|
||||
//Minutes - modulus by 100 to get the whole minutes value
|
||||
int wholeMinutes = degreesMinutes % 100;
|
||||
|
||||
if( wholeMinutes != 0 )
|
||||
{
|
||||
retVal += (double)( wholeMinutes / 60.0D );
|
||||
}
|
||||
}
|
||||
|
||||
if( decimalDegrees != 0 )
|
||||
{
|
||||
//Fractional Minutes - divide by 10,000 to get the decimal place correct
|
||||
//then divide by 60 (minutes) to get the decimal value
|
||||
//10,000 * 60 = 600,000
|
||||
retVal += (double)( decimalDegrees / 600000.0D );
|
||||
}
|
||||
|
||||
//Adjust the value +/- for the hemisphere
|
||||
if( hemisphere == 1 ) //South and West values
|
||||
{
|
||||
retVal = -retVal;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public String getGPSTime()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( format( getInt( sGPSHours ), 2 ) );
|
||||
sb.append( ":" );
|
||||
sb.append( format( getInt( sGPSMinutes ), 2 ) );
|
||||
sb.append( ":" );
|
||||
sb.append( format( getInt( sGPSSeconds ), 2 ) );
|
||||
sb.append( "z" );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 7 bit checksum that is part of the GPGGA message, I think
|
||||
* @return
|
||||
*/
|
||||
public int getGPSChecksum()
|
||||
{
|
||||
return getInt( sGPSChecksum );
|
||||
}
|
||||
|
||||
public String getGPSChecksumString()
|
||||
{
|
||||
String sum = Integer.toHexString( getGPSChecksum() );
|
||||
|
||||
if( sum.length() == 1 )
|
||||
{
|
||||
return "*0" + sum;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "*" + sum;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 1-based calendar day of month
|
||||
* 1 = 1st day of month
|
||||
*/
|
||||
public int getGPSDay()
|
||||
{
|
||||
return getInt( sGPSDay ) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 0-based GPS Month
|
||||
* 0 = January
|
||||
*
|
||||
* Note: actual day of month value is 1-based, so we subtract 1 to get
|
||||
* the actual day of month value
|
||||
*/
|
||||
public int getGPSMonth()
|
||||
{
|
||||
return getInt( sGPSMonth );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns year, which is a combination of the century bit field + 1, times
|
||||
* 100, plus the year bit field
|
||||
*/
|
||||
public int getGPSYear()
|
||||
{
|
||||
// return ( ( getInt( sGPSCentury ) + 1 ) * 100 ) + getInt( sGPSYear );
|
||||
return ( 2000 ) + getInt( sGPSYear );
|
||||
}
|
||||
|
||||
public String getGPSLocation()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( pad( String.valueOf( mDecimalFormatter.format( getLatitude() ) ), 8, "0" ) + " " );
|
||||
sb.append( pad( String.valueOf( mDecimalFormatter.format( getLongitude() ) ), 10, "0" ) );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
public String getGPSDate()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( getGPSYear() + "-" );
|
||||
|
||||
int month = getGPSMonth();
|
||||
|
||||
if( month < 10 )
|
||||
{
|
||||
sb.append( "0" );
|
||||
}
|
||||
|
||||
sb.append( month + "-" );
|
||||
|
||||
int day = getGPSDay();
|
||||
|
||||
if( day < 10 )
|
||||
{
|
||||
sb.append( "0" );
|
||||
}
|
||||
|
||||
sb.append( day );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private int getInt( int[] bits )
|
||||
{
|
||||
int retVal = 0;
|
||||
|
||||
for( int x = 0; x < bits.length; x++ )
|
||||
{
|
||||
if( mMessage.get( bits[ x ] ) )
|
||||
{
|
||||
retVal += 1<<x;
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBinaryMessage()
|
||||
{
|
||||
return mMessage.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* String representing results of the parity check
|
||||
*
|
||||
* [P] = passes parity check
|
||||
* [f] = fails parity check
|
||||
* [C] = corrected message
|
||||
* [-] = message section not present
|
||||
*/
|
||||
public String getParity()
|
||||
{
|
||||
return "[" + CRC.format( mCRC ) + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol()
|
||||
{
|
||||
return "Fleetsync II";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType()
|
||||
{
|
||||
return getMessageType().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFromID()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( getFleetFrom() );
|
||||
sb.append( "-" );
|
||||
sb.append( getIdentifierFrom() );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getFromIDAlias()
|
||||
{
|
||||
if( mAliasList != null )
|
||||
{
|
||||
return mAliasList.getFleetsyncAlias( getFromID() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToID()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( getFleetTo() );
|
||||
sb.append( "-" );
|
||||
sb.append( getIdentifierTo() );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getToIDAlias()
|
||||
{
|
||||
if( mAliasList != null )
|
||||
{
|
||||
return mAliasList.getFleetsyncAlias( getToID() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getFromTo( boolean includeFrom, boolean includeTo )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if( includeFrom )
|
||||
{
|
||||
sb.append( "FM:" );
|
||||
sb.append( getFromID() );
|
||||
|
||||
Alias from = getFromIDAlias();
|
||||
|
||||
if( from != null )
|
||||
{
|
||||
sb.append( " " );
|
||||
sb.append( from.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
if( includeFrom && includeTo )
|
||||
{
|
||||
sb.append( " " );
|
||||
}
|
||||
|
||||
if( includeTo )
|
||||
{
|
||||
sb.append( "TO:" );
|
||||
sb.append( getToID() );
|
||||
|
||||
Alias to = getToIDAlias();
|
||||
|
||||
if( to != null )
|
||||
{
|
||||
sb.append( " " );
|
||||
sb.append( to.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
FleetsyncMessageType type = getMessageType();
|
||||
|
||||
switch( type )
|
||||
{
|
||||
case ACKNOWLEDGE:
|
||||
sb.append( "ACKNOWLEDGE " );
|
||||
sb.append( getFromTo( true, true ) );
|
||||
break;
|
||||
case ANI:
|
||||
sb.append( "ANI " );
|
||||
sb.append( getFromTo( true, false ) );
|
||||
break;
|
||||
case EMERGENCY:
|
||||
sb.append( "**EMERGENCY** " );
|
||||
sb.append( getFromTo( true, true ) );
|
||||
break;
|
||||
case GPS:
|
||||
sb.append( "GPS [" );
|
||||
sb.append( getGPSLocation() );
|
||||
sb.append( "] " );
|
||||
|
||||
sb.append( getFromTo( true, true ) );
|
||||
|
||||
sb.append( " HDG:" );
|
||||
sb.append( pad( String.valueOf( getHeading() ), 5, " " ) );
|
||||
|
||||
sb.append( " GPSDate[" );
|
||||
sb.append( getGPSDate() );
|
||||
sb.append( " " );
|
||||
sb.append( getGPSTime() );
|
||||
sb.append( "]" );
|
||||
break;
|
||||
case LONE_WORKER_EMERGENCY:
|
||||
sb.append( "**LONE WORKER EMERGENCY** " );
|
||||
sb.append( getFromTo( true, true ) );
|
||||
break;
|
||||
case PAGING:
|
||||
sb.append( "PAGING " );
|
||||
sb.append( getFromTo( true, true ) );
|
||||
break;
|
||||
case STATUS:
|
||||
sb.append( "STATUS: " + format( getStatus(), 2 ) );
|
||||
|
||||
Alias status = getStatusAlias();
|
||||
if( status != null )
|
||||
{
|
||||
sb.append( "/" );
|
||||
sb.append( status.getName() );
|
||||
}
|
||||
|
||||
sb.append( getFromTo( true, true ) );
|
||||
break;
|
||||
case UNKNOWN:
|
||||
default:
|
||||
sb.append( "*** UNKNOWN *** " );
|
||||
sb.append( getFromTo( true, true ) );
|
||||
break;
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorStatus()
|
||||
{
|
||||
return CRC.format( mCRC );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plottable getPlottable()
|
||||
{
|
||||
if( isValid() && getMessageType() == FleetsyncMessageType.GPS )
|
||||
{
|
||||
GeoPosition position =
|
||||
new GeoPosition( getLatitude(), getLongitude() );
|
||||
|
||||
Alias alias = mAliasList.getFleetsyncAlias( getFromID() );
|
||||
|
||||
return new Plottable( mTimeReceived, position, getFromID(), alias );
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a listing of aliases contained in the message.
|
||||
*/
|
||||
public List<Alias> getAliases()
|
||||
{
|
||||
List<Alias> aliases = new ArrayList<Alias>();
|
||||
|
||||
Alias from = getFromIDAlias();
|
||||
|
||||
if( from != null )
|
||||
{
|
||||
aliases.add( from );
|
||||
}
|
||||
|
||||
Alias to = getToIDAlias();
|
||||
|
||||
if( to != null )
|
||||
{
|
||||
aliases.add( to );
|
||||
}
|
||||
|
||||
Alias status = getStatusAlias();
|
||||
|
||||
if( status != null )
|
||||
{
|
||||
aliases.add( status );
|
||||
}
|
||||
|
||||
return aliases;
|
||||
}
|
||||
}
|
||||
56
src/module/decode/fleetsync2/FleetsyncMessageFilter.java
Normal file
56
src/module/decode/fleetsync2/FleetsyncMessageFilter.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package module.decode.fleetsync2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import message.Message;
|
||||
import filter.Filter;
|
||||
import filter.FilterElement;
|
||||
|
||||
public class FleetsyncMessageFilter extends Filter<Message>
|
||||
{
|
||||
private HashMap<FleetsyncMessageType, FilterElement<FleetsyncMessageType>> mElements =
|
||||
new HashMap<FleetsyncMessageType, FilterElement<FleetsyncMessageType>>();
|
||||
|
||||
public FleetsyncMessageFilter()
|
||||
{
|
||||
super( "Fleetsync Message Filter" );
|
||||
|
||||
for( FleetsyncMessageType type: FleetsyncMessageType.values() )
|
||||
{
|
||||
if( type != FleetsyncMessageType.UNKNOWN )
|
||||
{
|
||||
mElements.put( type, new FilterElement<FleetsyncMessageType>( type ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes( Message message )
|
||||
{
|
||||
if( mEnabled && canProcess( message ) )
|
||||
{
|
||||
FleetsyncMessage fleet = (FleetsyncMessage)message;
|
||||
|
||||
if( mElements.containsKey( fleet.getMessageType() ) )
|
||||
{
|
||||
return mElements.get( fleet.getMessageType() ).isEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess( Message message )
|
||||
{
|
||||
return message instanceof FleetsyncMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FilterElement<?>> getFilterElements()
|
||||
{
|
||||
return new ArrayList<FilterElement<?>>( mElements.values() );
|
||||
}
|
||||
}
|
||||
42
src/module/decode/fleetsync2/FleetsyncMessageType.java
Normal file
42
src/module/decode/fleetsync2/FleetsyncMessageType.java
Normal file
@@ -0,0 +1,42 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package module.decode.fleetsync2;
|
||||
|
||||
public enum FleetsyncMessageType
|
||||
{
|
||||
ACKNOWLEDGE( "ACK" ),
|
||||
ANI( "ANI" ),
|
||||
EMERGENCY( "EMERG" ),
|
||||
GPS( "GPS" ),
|
||||
LONE_WORKER_EMERGENCY( "LONE WORKER" ),
|
||||
PAGING( "PAGE" ),
|
||||
STATUS( "STATUS" ),
|
||||
UNKNOWN( "UNK" );
|
||||
|
||||
private String mLabel;
|
||||
|
||||
private FleetsyncMessageType( String label )
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user