Updates pull request from Roman Tsypuk to convert the build system from

ant to gradle.  Removes linux and windows application launch scripts and
produces an executable jar with all resources included.
This commit is contained in:
Denny
2017-12-17 07:28:03 -05:00
parent a1535c4157
commit 9316599dec
981 changed files with 8368 additions and 9049 deletions

View File

@@ -0,0 +1,191 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.module.decode.fleetsync2;
import io.github.dsheirer.bits.MessageFramer;
import io.github.dsheirer.bits.SyncPattern;
import io.github.dsheirer.instrument.Instrumentable;
import io.github.dsheirer.instrument.tap.Tap;
import io.github.dsheirer.instrument.tap.TapGroup;
import io.github.dsheirer.instrument.tap.stream.BinaryTap;
import io.github.dsheirer.instrument.tap.stream.FloatBufferTap;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.IFilteredRealBufferListener;
import io.github.dsheirer.sample.real.RealBuffer;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.dsp.filter.Filters;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter_RB_RB;
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter_RB_RB;
import io.github.dsheirer.dsp.fsk.FSK2Decoder;
import io.github.dsheirer.dsp.fsk.FSK2Decoder.Output;
import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.module.decode.DecoderType;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
/**
* 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 IFilteredRealBufferListener,
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<TapGroup> 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 )
{
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( this );
}
public void dispose()
{
super.dispose();
mBandPassFilter.dispose();
mDecimationFilter.dispose();
mFSKDecoder.dispose();
mMessageFramer.dispose();
mMessageProcessor.dispose();
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.FLEETSYNC2;
}
@Override
public List<TapGroup> getTapGroups()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<TapGroup>();
TapGroup group = new TapGroup( "Fleetsync 2 Decoder" );
group.add( new FloatBufferTap(
INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD, 0, 0.5f ) );
group.add( new BinaryTap(
INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER, 0, 0.025f ) );
mAvailableTaps.add( group );
mAvailableTaps.addAll( mFSKDecoder.getTapGroups() );
}
return mAvailableTaps;
}
@Override
public void registerTap( Tap tap )
{
mFSKDecoder.registerTap( 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 unregisterTap( Tap tap )
{
mFSKDecoder.unregisterTap( 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> getFilteredRealBufferListener()
{
return mDecimationFilter;
}
@Override
public void reset()
{
}
@Override
public void start( ScheduledExecutorService executor )
{
}
@Override
public void stop()
{
}
}

View File

@@ -0,0 +1,316 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 io.github.dsheirer.module.decode.fleetsync2;
import io.github.dsheirer.channel.metadata.AliasedStringAttributeMonitor;
import io.github.dsheirer.channel.metadata.Attribute;
import io.github.dsheirer.channel.metadata.AttributeChangeRequest;
import io.github.dsheirer.channel.state.DecoderState;
import io.github.dsheirer.channel.state.DecoderStateEvent;
import io.github.dsheirer.channel.state.DecoderStateEvent.Event;
import io.github.dsheirer.channel.state.State;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.util.StringUtils;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.id.AliasIDType;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.concurrent.ScheduledExecutorService;
public class Fleetsync2DecoderState extends DecoderState
{
private TreeSet<String> mIdents = new TreeSet<String>();
private TreeSet<String> mEmergencyIdents = new TreeSet<String>();
private AliasedStringAttributeMonitor mFromAttribute;
private AliasedStringAttributeMonitor mToAttribute;
private String mMessage;
private String mMessageType;
private long mFrequency;
public Fleetsync2DecoderState(AliasList aliasList)
{
super(aliasList);
mFromAttribute = new AliasedStringAttributeMonitor(Attribute.SECONDARY_ADDRESS_FROM,
getAttributeChangeRequestListener(), getAliasList(), AliasIDType.FLEETSYNC);
mToAttribute = new AliasedStringAttributeMonitor(Attribute.SECONDARY_ADDRESS_TO,
getAttributeChangeRequestListener(), getAliasList(), AliasIDType.FLEETSYNC);
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.FLEETSYNC2;
}
@Override
public void start(ScheduledExecutorService executor)
{
}
@Override
public void stop()
{
}
@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()
{
mFromAttribute.reset();
mToAttribute.reset();
mMessage = null;
mMessageType = null;
}
@Override
public void receive(Message message)
{
if(message instanceof FleetsyncMessage)
{
FleetsyncMessage fleetsync = (FleetsyncMessage) message;
if(fleetsync.isValid())
{
State state = State.CALL;
mFromAttribute.process(fleetsync.getFromID());
mIdents.add(fleetsync.getFromID());
FleetsyncMessageType type = fleetsync.getMessageType();
if(type != FleetsyncMessageType.ANI)
{
mToAttribute.process(fleetsync.getToID());
mIdents.add(fleetsync.getToID());
}
setMessageType(type.getLabel());
switch(type)
{
case GPS:
setMessage(fleetsync.getGPSLocation());
state = State.DATA;
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());
state = State.DATA;
break;
case EMERGENCY:
case LONE_WORKER_EMERGENCY:
mEmergencyIdents.add(fleetsync.getFromID());
state = State.DATA;
break;
default:
break;
}
FleetsyncCallEvent fsCallEvent =
FleetsyncCallEvent.getFleetsync2Event(fleetsync, mFrequency);
fsCallEvent.setAliasList(getAliasList());
broadcast(fsCallEvent);
/* Broadcast decode event so that the channel state will
* kick in and reset everything after a short delay */
broadcast(new DecoderStateEvent(this, Event.DECODE, state));
}
}
}
public String getMessage()
{
return mMessage;
}
public void setMessage(String message)
{
if(!StringUtils.isEqual(mMessage, message))
{
mMessage = message;
broadcast(new AttributeChangeRequest<String>(Attribute.MESSAGE, mMessage));
}
}
public String getMessageType()
{
return "Fleetsync " + mMessageType;
}
public void setMessageType(String type)
{
if(!StringUtils.isEqual(mMessageType, type))
{
mMessageType = type;
broadcast(new AttributeChangeRequest<String>(Attribute.MESSAGE_TYPE, getMessageType()));
}
}
/**
* Responds to reset events issued by the channel state
*/
@Override
public void receiveDecoderStateEvent(DecoderStateEvent event)
{
switch(event.getEvent())
{
case RESET:
resetState();
break;
case SOURCE_FREQUENCY:
mFrequency = event.getFrequency();
break;
default:
break;
}
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append("=============================\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;
}
}
}

View 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 io.github.dsheirer.module.decode.fleetsync2;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.alias.AliasList;
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;
}
}

View File

@@ -0,0 +1,206 @@
/*******************************************************************************
* 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 io.github.dsheirer.module.decode.fleetsync2;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.module.decode.event.CallEvent;
public class FleetsyncCallEvent extends CallEvent
{
private long mFrequency = 0;
public FleetsyncCallEvent( CallEventType callEventType,
AliasList aliasList,
String fromID,
String toID,
String details,
long frequency )
{
super( DecoderType.FLEETSYNC2, callEventType,
aliasList, fromID, toID, details );
mFrequency = frequency;
}
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 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 long mFrequency;
public Builder( CallEventType callEventType )
{
mCallEventType = callEventType;
}
public Builder aliasList( AliasList aliasList )
{
mAliasList = aliasList;
return this;
}
public Builder frequency( long frequency )
{
mFrequency = frequency;
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,
builder.mFrequency );
}
public static FleetsyncCallEvent getFleetsync2Event(
FleetsyncMessage message, long frequency )
{
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;
default:
break;
}
if( type == CallEventType.ID_ANI ||
type == CallEventType.EMERGENCY )
{
return new FleetsyncCallEvent.Builder( type )
.details( sbDetails.toString() )
.frequency( frequency )
.from( message.getFromID() )
.build();
}
else
{
return new FleetsyncCallEvent.Builder( type )
.details( sbDetails.toString() )
.frequency( frequency )
.from( message.getFromID() )
.to( message.getToID() )
.build();
}
}
}

View File

@@ -0,0 +1,894 @@
/*******************************************************************************
* 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 io.github.dsheirer.module.decode.fleetsync2;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.edac.CRC;
import io.github.dsheirer.edac.CRCFleetsync;
import io.github.dsheirer.map.Plottable;
import io.github.dsheirer.message.Message;
import org.jdesktop.swingx.mapviewer.GeoPosition;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
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;
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;
}
}

View File

@@ -0,0 +1,56 @@
package io.github.dsheirer.module.decode.fleetsync2;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.Message;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
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() );
}
}

View File

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