Initial commit of redesigned internal processing and audio management.

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

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

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

57
src/module/Module.java Normal file
View File

@@ -0,0 +1,57 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module;
public abstract class Module
{
/**
* Defines the basic component level class for all processing, demodulation
* and decoding components that can operate within a processing chain.
*
* Modules can optionally implement any of the following interfaces:
*
* IAudioPacketProvider produces audio packets
* ICallEventProvider call events
* IChannelEventProvider channel events (reset, selection, start, etc)
* IComplexBufferListener consumes complex samples and normally
* produces demodulated real sample buffers
* IFrequencyCorrectionController provides tuned frequency error corrections
* IMessageProvider produces messages
* IRealBufferListener consumes demodulated real sample buffers
* IRealBufferListener produces demodulated real sample buffers
* ISquelchStateListener consumes squelch states
* ISquelchStateProvider provides squelch states
*/
public Module()
{
}
/**
* Dispose of all resources and listeners and prepare for garbage collection
*/
public abstract void dispose();
/**
* Initializes or reset all internal states to default - prepare to process
* again. This method is usually invoked between stopping a module and
* starting it again processing a newly tuned channel, like a traffic
* channel.
*/
public abstract void init();
}

View File

@@ -0,0 +1,661 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import message.IMessageProvider;
import message.Message;
import module.decode.Decoder;
import module.decode.event.CallEvent;
import module.decode.event.ICallEventProvider;
import module.decode.state.DecoderStateEvent;
import module.decode.state.DecoderStateEvent.Event;
import module.decode.state.IDecoderStateEventListener;
import module.decode.state.IDecoderStateEventProvider;
import module.decode.state.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Broadcaster;
import sample.Listener;
import sample.complex.ComplexBuffer;
import sample.complex.IComplexBufferListener;
import sample.real.IRealBufferListener;
import sample.real.IRealBufferProvider;
import sample.real.RealBuffer;
import source.ComplexSource;
import source.RealSource;
import source.Source;
import source.SourceException;
import source.tuner.TunerChannelSource;
import source.tuner.frequency.FrequencyCorrectionControl;
import source.tuner.frequency.IFrequencyCorrectionController;
import alias.Metadata;
import audio.AudioPacket;
import audio.IAudioPacketProvider;
import audio.metadata.IMetadataListener;
import audio.metadata.IMetadataProvider;
import audio.squelch.ISquelchStateListener;
import audio.squelch.ISquelchStateProvider;
import audio.squelch.SquelchState;
import controller.ThreadPoolManager;
import controller.ThreadPoolManager.ThreadType;
import controller.channel.ChannelEvent;
import controller.channel.IChannelEventListener;
import controller.channel.IChannelEventProvider;
/**
* Processing chain provides a framework for connecting a complex or real sample
* source to a set of one primary decoder and zero or more auxiliary decoders.
* All decoded messages and call events produced by the decoders and the decoder
* call states are aggregated by the various broadcasters. You can register
* listeners to receive aggregated messages, call events, and audio packets.
*
* Normal setup sequence:
*
* 1) Add one or more modules
* 2) Register listeners to receive messages, call events, audio, etc.
* 3) Add a valid source
* 4) Invoke the start() method to start processing.
* 5) Invoke the stop() method to stop processing.
*
* Optional: if you want to reuse the processing chain with a new sample source,
* invoke the following method sequence: stop(), reset(), setSource(), start()
*/
public class ProcessingChain implements IChannelEventListener
{
private final static Logger mLog =
LoggerFactory.getLogger( ProcessingChain.class );
private BufferReceiver<ComplexBuffer> mComplexReceiver = new BufferReceiver<>( 5 );
private BufferReceiver<RealBuffer> mRealReceiver = new BufferReceiver<>( 5 );
private Broadcaster<AudioPacket> mAudioPacketBroadcaster = new Broadcaster<>();
private Broadcaster<Metadata> mMetadataBroadcaster = new Broadcaster<>();
private Broadcaster<CallEvent> mCallEventBroadcaster = new Broadcaster<>();
private Broadcaster<ChannelEvent> mChannelEventBroadcaster = new Broadcaster<>();
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
private Broadcaster<DecoderStateEvent> mDecoderStateEventBroadcaster = new Broadcaster<>();
private Broadcaster<Message> mMessageBroadcaster = new Broadcaster<>();
private Broadcaster<RealBuffer> mRealBufferBroadcaster = new Broadcaster<>();
private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
private ThreadPoolManager mThreadPoolManager;
private ScheduledFuture<?> mBufferProcessingTask;
private AtomicBoolean mRunning = new AtomicBoolean();
private Source mSource;
private List<Module> mModules = new ArrayList<>();
private IFrequencyCorrectionController mFrequencyCorrectionController;
public ProcessingChain()
{
this( new ThreadPoolManager() );
}
public ProcessingChain( ThreadPoolManager threadManager )
{
mThreadPoolManager = threadManager;
}
public void dispose()
{
stop();
for( Module module: mModules )
{
module.dispose();
}
mModules.clear();
mComplexReceiver.dispose();
mComplexReceiver = null;
mRealReceiver.dispose();
mRealReceiver = null;
mThreadPoolManager = null;
mBufferProcessingTask = null;
mFrequencyCorrectionController = null;
mAudioPacketBroadcaster.dispose();
mCallEventBroadcaster.dispose();
mChannelEventBroadcaster.dispose();
mComplexBufferBroadcaster.dispose();
mMessageBroadcaster.dispose();
mRealBufferBroadcaster.dispose();
mSquelchStateBroadcaster.dispose();
}
/**
* Indicates if this processing chain is currently receiving samples from
* a source and sending those samples to the decoders.
*/
public boolean processing()
{
return mRunning.get();
}
/**
* Indicates if this chain currently has a valid sample source.
*/
public boolean hasSource()
{
return mSource != null;
}
/**
* Indicates if one of the modules provides frequency error control.
*/
public boolean hasFrequencyCorrectionControl()
{
return mFrequencyCorrectionController != null &&
mFrequencyCorrectionController.hasFrequencyCorrectionControl();
}
/**
* Frequency correction controller. Can be null.
*/
public FrequencyCorrectionControl getFrequencyCorrectionControl()
{
if( mFrequencyCorrectionController != null )
{
return mFrequencyCorrectionController.getFrequencyCorrectionControl();
}
return null;
}
/**
* Applies a sample source to this processing chain. Processing won't
* start until the start() method is invoked.
*
* @param source - real or complex sample source
*
* @throws IllegalStateException if the processing chain is currently
* processing with another source. Invoke stop() before applying a new
* source.
*/
public void setSource( Source source ) throws IllegalStateException
{
if( processing() )
{
throw new IllegalStateException( "Processing chain is currently "
+ "processing. Invoke stop() on the processing chain before "
+ "applying a new sample source" );
}
mSource = source;
/* Establish two-way communication between the tuner channel source and
* the decoder for fine tuning frequency error correction feedback from
* the decoder. The tuner channel source will also notify the frequency
* controller during frequency changes to allow the controller to reset
* frequency error tracking */
if( mSource != null &&
mSource instanceof TunerChannelSource &&
hasFrequencyCorrectionControl() )
{
TunerChannelSource channel = (TunerChannelSource)mSource;
FrequencyCorrectionControl control = getFrequencyCorrectionControl();
channel.setFrequencyChangeListener( control );
control.setFrequencyChangeListener( channel );
}
}
/**
* List of current modules for this processing chain
*/
public List<Module> getModules()
{
return mModules;
}
/**
* List of decoders for this processing chain
*/
public List<Decoder> getDecoders()
{
List<Decoder> decoders = new ArrayList<>();
for( Module module: mModules )
{
if( module instanceof Decoder )
{
decoders.add( (Decoder)module );
}
}
return decoders;
}
/**
* Adds a module to the processing chain. Each module is tested for the
* interfaces that it supports and is registered or receives a listener
* to consume or produce the supported interface data type. All elements
* that are produced by any component are automatically routed to all other
* components that support the corresponding listener interface.
*
* At least one module should consume complex samples and either produce
* decoded messages and/or audio, or produce decoded real sample buffers
* for all other modules to consume.
*
* @param module - processing module, demodulator or decoder
*/
public void addModule( Module module )
{
mModules.add( module );
/* Listeners */
if( module instanceof IMetadataListener )
{
mMetadataBroadcaster.addListener(
((IMetadataListener)module).getMetadataListener() );
}
if( module instanceof IChannelEventListener )
{
mChannelEventBroadcaster.addListener(
((IChannelEventListener)module).getChannelEventListener() );
}
if( module instanceof IComplexBufferListener )
{
mComplexBufferBroadcaster.addListener(
((IComplexBufferListener)module).getComplexBufferListener() );
}
if( module instanceof IDecoderStateEventListener )
{
mDecoderStateEventBroadcaster.addListener(
((IDecoderStateEventListener)module).getDecoderStateListener() );
}
if( module instanceof IRealBufferListener )
{
mRealBufferBroadcaster.addListener(
((IRealBufferListener)module).getRealBufferListener() );
}
if( module instanceof ISquelchStateListener )
{
mSquelchStateBroadcaster.addListener(
((ISquelchStateListener)module).getSquelchStateListener() );
}
/* Providers */
if( module instanceof IMetadataProvider )
{
((IMetadataProvider)module).setMetadataListener(
mMetadataBroadcaster );
}
if( module instanceof IAudioPacketProvider )
{
((IAudioPacketProvider)module).setAudioPacketListener(
mAudioPacketBroadcaster );
}
if( module instanceof ICallEventProvider )
{
((ICallEventProvider)module).setCallEventListener( mCallEventBroadcaster );
}
if( module instanceof IChannelEventProvider )
{
((IChannelEventProvider)module).setChannelEventListener(
mChannelEventBroadcaster );
}
if( module instanceof IDecoderStateEventProvider )
{
((IDecoderStateEventProvider)module).setDecoderStateListener(
mDecoderStateEventBroadcaster );
}
if( module instanceof IFrequencyCorrectionController )
{
mFrequencyCorrectionController = (IFrequencyCorrectionController)module;
}
if( module instanceof IMessageProvider )
{
((IMessageProvider)module).addMessageListener( mMessageBroadcaster );
}
if( module instanceof IRealBufferProvider )
{
((IRealBufferProvider)module).setRealBufferListener( mRealBufferBroadcaster );
}
if( module instanceof ISquelchStateProvider )
{
((ISquelchStateProvider)module).setSquelchStateListener(
mSquelchStateBroadcaster );
}
}
/**
* Adds the list of modules to this processing chain
*/
public void addModules( List<Module> modules )
{
for( Module module: modules )
{
addModule( module );
}
}
/**
* Starts processing if the chain has a valid source. Invocations on an
* already started chain have no effect.
*/
public void start()
{
if( mRunning.compareAndSet( false, true ) )
{
if( mSource != null )
{
/* Initialize each of the modules */
for( Module module: mModules )
{
module.init();
}
/* Register with the source to receive sample data. Setup a
* timer task to process the buffer queues 50 times a second
* (every 20 ms) */
mBufferProcessingTask = mThreadPoolManager.scheduleFixedRate(
ThreadType.DECODER, new BufferProcessor(), 20, TimeUnit.MILLISECONDS );
switch( mSource.getSampleType() )
{
case COMPLEX:
((ComplexSource)mSource).setListener( mComplexBufferBroadcaster );
break;
case REAL:
((RealSource)mSource).setListener( mRealBufferBroadcaster );
break;
default:
throw new IllegalArgumentException( "Unrecognized source "
+ "sample type - cannot start processing chain" );
}
/* If this is a tuner source, broadcast the frequency to all
* of the decoder state's */
if( mSource instanceof TunerChannelSource )
{
try
{
long frequency = ((TunerChannelSource)mSource).getFrequency();
mDecoderStateEventBroadcaster.broadcast(
new DecoderStateEvent( this, Event.SOURCE_FREQUENCY,
State.IDLE, frequency ) );
}
catch( SourceException e )
{
mLog.error( "Error getting frequency from tuner channel source", e );
}
}
}
}
}
/**
* Stops processing if the chain is currently processing. Invocations on
* an already stopped chain have no effect.
*/
public void stop()
{
if( mRunning.compareAndSet( true, false ) )
{
if( mSource != null )
{
switch( mSource.getSampleType() )
{
case COMPLEX:
((ComplexSource)mSource).removeListener( mComplexBufferBroadcaster );
break;
case REAL:
((RealSource)mSource).removeListener( mRealBufferBroadcaster );
break;
default:
throw new IllegalArgumentException( "Unrecognized source "
+ "sample type - cannot start processing chain" );
}
/* Release the source */
mSource.dispose();
mSource = null;
}
if( mBufferProcessingTask != null )
{
mThreadPoolManager.cancel( mBufferProcessingTask );
mBufferProcessingTask = null;
}
}
}
/**
* Adds the listener to receive audio packets from all modules.
*/
public void addAudioPacketListener( Listener<AudioPacket> listener )
{
mAudioPacketBroadcaster.addListener( listener );
}
public void removeAudioPacketListener( Listener<AudioPacket> listener )
{
mAudioPacketBroadcaster.removeListener( listener );
}
/**
* Adds the listener to receive call events from all modules.
*/
public void addCallEventListener( Listener<CallEvent> listener )
{
mCallEventBroadcaster.addListener( listener );
}
public void removeCallEventListener( Listener<CallEvent> listener )
{
mCallEventBroadcaster.removeListener( listener );
}
/**
* Adds the listener to receive call events from all modules.
*/
public void addChannelEventListener( Listener<ChannelEvent> listener )
{
mChannelEventBroadcaster.addListener( listener );
}
public void removeChannelEventListener( Listener<ChannelEvent> listener )
{
mChannelEventBroadcaster.removeListener( listener );
}
/**
* Adds the listener to receive decoder state events from decoder modules
*/
public void addDecoderStateEventListener( Listener<DecoderStateEvent> listener )
{
mDecoderStateEventBroadcaster.addListener( listener );
}
public void removeDecoderStateEventListener( Listener<DecoderStateEvent> listener)
{
mDecoderStateEventBroadcaster.removeListener( listener );
}
public Listener<DecoderStateEvent> getDecoderStateEventListener()
{
return mDecoderStateEventBroadcaster;
}
/**
* Adds the listener to receive decoded messages from all decoders.
*/
public void addMessageListener( Listener<Message> listener )
{
mMessageBroadcaster.addListener( listener );
}
/**
* Adds the list of listeners to receive decoded messages from all decoders.
*/
public void addMessageListeners( List<Listener<Message>> listeners )
{
for( Listener<Message> listener: listeners )
{
mMessageBroadcaster.addListener( listener );
}
}
public void removeMessageListener( Listener<Message> listener )
{
mMessageBroadcaster.removeListener( listener );
}
/**
* Adds the listener to receive call events from all modules.
*/
public void addSquelchStateListener( Listener<SquelchState> listener )
{
mSquelchStateBroadcaster.addListener( listener );
}
public void removeSquelchStateListener( Listener<SquelchState> listener )
{
mSquelchStateBroadcaster.removeListener( listener );
}
public void addRealBufferListener( Listener<RealBuffer> listener )
{
mRealBufferBroadcaster.addListener( listener );
}
public void removeRealBufferListener( Listener<RealBuffer> listener )
{
mRealBufferBroadcaster.removeListener( listener );
}
/**
* Processes complex and real sample buffers and broadcasts buffers to any
* registered listeners.
*/
private class BufferProcessor implements Runnable
{
private AtomicBoolean mProcessing = new AtomicBoolean();
@Override
public void run()
{
/* Allow only one instance of this processor to run to ensure
* correct serial processing of incoming buffers. Overlapping
* invocations of this processor by the timer service will end
* immediately without doing anything for cases where a single
* iteration runs longer than the scheduled processing interval */
if( mProcessing.compareAndSet( false, true ) )
{
try
{
for( ComplexBuffer buffer: mComplexReceiver.get() )
{
mComplexBufferBroadcaster.broadcast( buffer );
}
}
catch( Exception e )
{
mLog.error( "Error processing complex sample buffers", e );
}
try
{
for( RealBuffer buffer: mRealReceiver.get() )
{
mRealBufferBroadcaster.broadcast( buffer );
}
}
catch( Exception e )
{
mLog.error( "Error processing real sample buffers", e );
}
mProcessing.set( false );
}
}
}
/**
* Buffer receiver - non-blocking receiver to allow calling thread to return
* without blocking on any internal processing. This ensures that we don't
* impair any receiver sample processing.
*/
private class BufferReceiver<T> implements Listener<T>
{
private LinkedTransferQueue<T> mQueue = new LinkedTransferQueue<>();
private int mMaxFetchSize;
public BufferReceiver( int maxFetchSize )
{
mMaxFetchSize = maxFetchSize;
}
@Override
public void receive( T t )
{
mQueue.add( t );
}
public List<T> get()
{
List<T> buffers = new ArrayList<>();
mQueue.drainTo( buffers, mMaxFetchSize );
return buffers;
}
public void dispose()
{
mQueue.clear();
mQueue = null;
}
}
@Override
public Listener<ChannelEvent> getChannelEventListener()
{
return mChannelEventBroadcaster;
}
}

View File

@@ -0,0 +1,747 @@
/*******************************************************************************
* 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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import message.Message;
import module.decode.Decoder;
import module.decode.state.ChannelState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import record.Recorder;
import record.wave.ComplexWaveRecorder;
import record.wave.FloatWaveRecorder;
import sample.Broadcaster;
import sample.Listener;
import sample.complex.Complex;
import sample.real.RealBuffer;
import sample.real.RealSampleListener;
import source.ComplexSource;
import source.RealSource;
import source.Source;
import source.config.SourceConfigMixer;
import source.tuner.Tuner;
import source.tuner.TunerChannelSource;
import source.tuner.frequency.AutomaticFrequencyControl_R;
import alias.AliasList;
import controller.ResourceManager;
import controller.ThreadPoolManager.ThreadType;
import controller.channel.Channel;
import controller.channel.Channel.ChannelType;
import eventlog.EventLogType;
import eventlog.EventLogger;
/**
* A channel has all of the pieces needed to wire together a source, decoder,
* event logger and recorder and be started and stopped.
*/
public class ProcessingChainOld implements Listener<Message>
{
private final static Logger mLog = LoggerFactory.getLogger( ProcessingChainOld.class );
protected AtomicBoolean mRunning = new AtomicBoolean();
protected Channel mChannel;
protected Source mSource;
protected Decoder mDecoder;
protected List<Recorder> mRecorders = new ArrayList<Recorder>();
protected ArrayList<EventLogger> mEventLoggers = new ArrayList<EventLogger>();
protected AutomaticFrequencyControl_R mAFC;
protected ChannelState mChannelState;
protected ResourceManager mResourceManager;
private ScheduledFuture<?> mSampleProcessorTask;
private AliasList mAliasList;
private ComplexSampleReceiver mComplexReceiver = new ComplexSampleReceiver();
private LinkedTransferQueue<List<Complex>> mComplexQueue =
new LinkedTransferQueue<List<Complex>>();
private RealSampleReceiver mRealReceiver = new RealSampleReceiver();
private LinkedTransferQueue<RealBuffer> mRealQueue =
new LinkedTransferQueue<RealBuffer>();
protected Broadcaster<Message> mBroadcaster = new Broadcaster<Message>();
public ProcessingChainOld( Channel channel, ResourceManager resourceManager )
{
mChannel = channel;
mResourceManager = resourceManager;
if( mResourceManager != null )
{
mAliasList = mResourceManager.getPlaylistManager()
.getPlayist().getAliasDirectory().getAliasList(
mChannel.getAliasListName() );
}
}
// public IAudioOutput getAudioOutput()
// {
// if( mDecoder != null )
// {
// return mDecoder.getAudioOutput();
// }
//
// return null;
// }
public Listener<List<Complex>> getComplexReceiver()
{
return mComplexReceiver;
}
public Listener<RealBuffer> getRealReceiver()
{
return (Listener<RealBuffer>)mRealReceiver;
}
public String toString()
{
return mChannel.getName();
}
public AliasList getAliasList()
{
return mAliasList;
}
public void dispose()
{
if( mSampleProcessorTask != null )
{
mResourceManager.getThreadPoolManager().cancel( mSampleProcessorTask );
}
if( mChannelState != null )
{
mChannelState.dispose();
}
mChannelState = null;
if( mDecoder != null )
{
mDecoder.dispose();
}
mBroadcaster.dispose();
mEventLoggers.clear();
mRecorders.clear();
mResourceManager = null;
}
public ResourceManager getResourceManager()
{
return mResourceManager;
}
public void start()
{
if( !mRunning.get() && mChannel.getEnabled() )
{
updateSource();
updateDecoder();
updateEventLogging();
updateRecording();
mRunning.set( true );
}
else
{
mLog.info( getLogPrefix() + "start() invoked, but processing chain is "
+ "already started" );
}
}
/**
* Performs closeout operations to prepare for channel deletion
*/
public void stop()
{
if( mRunning.get() )
{
updateSource();
updateDecoder();
updateRecording();
/* Flush call events to the logger */
if( getChannel().getChannelType() == ChannelType.STANDARD )
{
// getChannelState().getCallEventModel().flush();
}
updateEventLogging();
mRunning.set( false );
}
else
{
mLog.info( getLogPrefix() + "stop() invoked, but processing chain was "
+ "already stopped" );
}
}
private String getLogPrefix()
{
return "Channel [" + mChannel.getName() + "] - ";
}
private void releaseSource()
{
if( mSource != null )
{
if( mSource instanceof ComplexSource )
{
// ((ComplexSource)mSource).removeAttributeListener( this.getComplexReceiver() );
}
else if( mSource instanceof RealSource )
{
((RealSource)mSource)
.removeListener( this.getRealReceiver() );
}
if( mSampleProcessorTask != null )
{
mResourceManager.getThreadPoolManager().cancel( mSampleProcessorTask );
}
mSource.dispose();
mSource = null;
}
}
private void obtainSource()
{
if( mSource != null )
{
releaseSource();
}
if( mChannel.getEnabled() && mResourceManager != null )
{
// try
// {
// mSource = mResourceManager.getSourceManager()
// .getSource( ProcessingChainOld.this );
// }
// catch ( SourceException e )
// {
// mLog.error( "Couldn't obtain source for processing chain [" +
// toString() + "]", e );
//
// mSource = null;
// }
if( mSource != null )
{
if( mSource instanceof ComplexSource )
{
try
{
mSampleProcessorTask =
mResourceManager.getThreadPoolManager()
.scheduleFixedRate( ThreadType.DECODER,
new ComplexProcessor(),
50, TimeUnit.MILLISECONDS );
// ((ComplexSource)mSource)
// .setListener( this.getComplexReceiver() );
}
catch( RejectedExecutionException ree )
{
mLog.error( getLogPrefix() + "ProcessingChain - "
+ "error scheduling complex sample processing "
+ "thread - " + ree.getLocalizedMessage() );
mSource.dispose();
}
}
else if( mSource instanceof RealSource )
{
try
{
mSampleProcessorTask =
mResourceManager.getThreadPoolManager()
.scheduleFixedRate( ThreadType.DECODER,
new RealProcessor(),
50, TimeUnit.MILLISECONDS );
((RealSource)mSource)
.setListener( this.getRealReceiver() );
}
catch( RejectedExecutionException ree )
{
mLog.error( getLogPrefix() + "ProcessingChain - "
+ "error scheduling float sample processing "
+ "thread", ree );
mSource.dispose();
}
}
}
}
}
public void updateSource()
{
/* Cleanup existing source */
releaseSource();
/* Get a new source if needed and start the sample processor thread */
obtainSource();
}
public void updateDecoder()
{
if( mChannel.getEnabled() )
{
if( mDecoder != null )
{
mDecoder.init();
}
else
{
// /* Setup the channel state */
// mChannelState = DecoderFactory.getChannelStateNew( this, mAliasList );
if( mSource == null )
{
// mChannelState.setState( State.NO_TUNER );
}
else
{
// mDecoder = DecoderFactory.
// getDecoder( this, mSource.getSampleType(), mAliasList );
if( mDecoder != null )
{
/* Register to receive decoded messages and auxiliary messages */
mDecoder.addMessageListener( ProcessingChainOld.this );
/* Establish two-way communication between the frequency
* correction controller and the tuner channel source */
// if( mSource instanceof TunerChannelSource &&
// mDecoder.hasFrequencyCorrectionControl() )
// {
// TunerChannelSource tcs = (TunerChannelSource)mSource;
//
// FrequencyCorrectionControl fcc =
// mDecoder.getFrequencyCorrectionController();
//
// if( fcc != null )
// {
// tcs.addListener( fcc );
// fcc.setListener( tcs );
// }
}
/* Register audio output to be controlled by the channel state */
// mChannelState.addListener( (SquelchListener)mDecoder.getAudioOutput() );
// mChannelState.setListener( (IAudioTypeListener)mDecoder.getAudioOutput() );
}
}
}
}
// }
/**
* Starts, stops or changes event logging
*/
public void updateEventLogging()
{
/* Shutdown and remove existing event loggers */
if( mEventLoggers != null )
{
for( EventLogger logger: mEventLoggers )
{
logger.stop();
}
mEventLoggers.clear();
}
/* Construct event loggers and add as message listeners */
if( mChannel.getEnabled() &&
mResourceManager != null )
{
//Get the event loggers
ArrayList<EventLogType> types = getChannel()
.getEventLogConfiguration().getLoggers();
for( EventLogType type: types )
{
EventLogger logger;
if( type == EventLogType.CALL_EVENT )
{
logger = mResourceManager.getEventLogManager()
.getLogger( this, type );
// getChannelState().getCallEventModel()
// .addListener( (Listener<CallEvent>)logger );
}
else
{
logger = mResourceManager.getEventLogManager()
.getLogger( this, type );
addListener( (Listener<Message>)logger );
}
logger.start();
mEventLoggers.add( logger );
}
}
}
/**
* Starts, stops or changes data recording
*/
public void updateRecording()
{
/* Remove any existing recorders */
if( mRecorders != null )
{
for( Recorder recorder: mRecorders )
{
if( recorder != null )
{
try
{
recorder.stop();
}
catch ( IOException ioe )
{
mLog.error( getLogPrefix() + "Couldn't stop recorder [" +
recorder.getFileName() + "]", ioe );
}
}
}
}
/* Start any new recorders */
if( mChannel.getEnabled() &&
mResourceManager != null &&
mDecoder != null )
{
mRecorders = mResourceManager.getRecorderManager()
.getRecorder( this );
/* Iterate any returned recorders and start/add them */
for( Recorder recorder: mRecorders )
{
try
{
recorder.start();
if( recorder instanceof FloatWaveRecorder )
{
mLog.info( getLogPrefix() + "- started audio recording [" +
recorder.getFileName() + "]" );
// mDecoder.addRealSampleListener( (FloatWaveRecorder)recorder );
}
else if( recorder instanceof ComplexWaveRecorder )
{
mLog.info( getLogPrefix() + "- started baseband recording [" +
recorder.getFileName() + "]" );
// mDecoder.addComplexListener( (ComplexWaveRecorder )recorder );
}
}
catch ( IOException e )
{
mLog.error( getLogPrefix() + "Unable to start recorder [" +
recorder.getFileName() + "]" );
}
}
}
}
public Channel getChannel()
{
return mChannel;
}
public Source getSource()
{
return mSource;
}
/**
* Returns the tuner that is sourcing this channel, or null if it's being
* sourced from some other source
*/
public Tuner getTuner()
{
Tuner retVal = null;
if( mSource instanceof TunerChannelSource )
{
retVal = ((TunerChannelSource)mSource).getTuner();
}
return retVal;
}
public Decoder getDecoder()
{
return mDecoder;
}
public ChannelState getChannelState()
{
return mChannelState;
}
public boolean isRunning()
{
return mRunning.get();
}
public boolean isProcessing()
{
return isRunning() && mSource != null;
}
private source.mixer.MixerChannel getMixerChannel()
{
source.mixer.MixerChannel retVal = null;
if( mChannel.getSourceConfiguration() instanceof SourceConfigMixer )
{
SourceConfigMixer mixer = (SourceConfigMixer)mChannel.getSourceConfiguration();
retVal = mixer.getChannel();
}
return retVal;
}
@Override
public void receive( Message message )
{
if( message.isValid() )
{
mBroadcaster.receive( message );
}
}
/**
* Adds the listener to receive demodulated samples from the decoder
*/
public void addRealListener( RealSampleListener listener )
{
if( mDecoder != null )
{
// mDecoder.addRealSampleListener( listener );
}
}
/**
* Removes the listener from receiving demodulated samples from the decoder
*/
public void removeRealListener( RealSampleListener listener )
{
if( mDecoder != null )
{
// mDecoder.removeRealSampleListener( listener );
}
}
/**
* Adds a listener to receive a copy of complex (I/Q) baseband samples
* received by this processing chain
*/
public void addComplexListener( Listener<Complex> listener )
{
if( mDecoder != null )
{
// mDecoder.addComplexListener( listener );
}
else
{
mLog.error( getLogPrefix() + "attempt to add a complex sample "
+ "listener, but the decoder is null" );
}
}
/**
* Removes the listener from receiving a copy of complex (I/Q) baseband
* samples received by this processing chain
*/
public void removeComplexListener( Listener<Complex> listener )
{
if( mDecoder != null )
{
// mDecoder.removeComplexListener( listener );
}
else
{
mLog.error( getLogPrefix() + "attempt to remove a complex sample "
+ "listener, but the decoder is null" );
}
}
/**
* Registers the listener to receive messages
*/
public void addListener( Listener<Message> listener )
{
mBroadcaster.addListener( listener );
}
/**
* Registers all listeners in the list to receive messages
*/
public void addListeners( List<Listener<Message>> listeners )
{
for( Listener<Message> listener: listeners )
{
addListener( listener );
}
}
/**
* Removes the listener from receiving messages
*/
public void removeListener( Listener<Message> listener )
{
mBroadcaster.removeListener( listener );
}
/**
* Manages the real sample queue and distributes samples to the decoder.
*/
private class RealProcessor implements Runnable
{
@Override
public void run()
{
try
{
List<RealBuffer> sampleBuffers = new ArrayList<RealBuffer>();
mRealQueue.drainTo( sampleBuffers, 4 );
for( RealBuffer sampleBuffer: sampleBuffers )
{
if( mDecoder != null )
{
float[] samples = sampleBuffer.getSamples();
for( float sample: samples )
{
// mDecoder.getRealReceiver().receive( sample );
}
}
sampleBuffer.dispose();
}
sampleBuffers.clear();
}
catch( Exception e )
{
mLog.error( "error during processing chain real processor run", e );
}
}
}
/**
* Manages the complex sample queue and distributes samples to the decoder.
*/
private class ComplexProcessor implements Runnable
{
@Override
public void run()
{
try
{
List<List<Complex>> sampleSets =
new ArrayList<List<Complex>>();
mComplexQueue.drainTo( sampleSets, 16 );
for( List<Complex> samples: sampleSets )
{
for( Complex sample: samples )
{
if( sample != null && mDecoder != null )
{
// mDecoder.getComplexReceiver().receive( sample );
}
}
samples.clear();
}
sampleSets.clear();
}
catch( Exception e )
{
mLog.error( "error during processing chain complex processor run", e );
}
}
}
/**
* Internal listener to receive complex (I/Q) baseband samples. Places
* received samples into the queue managed by the ComplexProcessor.
*/
public class ComplexSampleReceiver implements Listener<List<Complex>>
{
@Override
public void receive( List<Complex> samples )
{
mComplexQueue.add( samples );
}
}
/**
* Internal listener to receive real(ie demodulated) samples. Places
* received samples into the queue managed by the RealProcessor
*/
public class RealSampleReceiver implements Listener<RealBuffer>
{
@Override
public void receive( RealBuffer samples )
{
mRealQueue.add( samples );
}
}
}

View File

@@ -0,0 +1,112 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import javax.swing.JCheckBox;
import javax.swing.SwingUtilities;
import module.decode.config.AuxDecodeConfiguration;
import controller.Editor;
import controller.channel.AbstractChannelEditor;
import controller.channel.ChannelNode;
import controller.channel.ChannelValidationException;
public class AuxDecodeComponentEditor extends AbstractChannelEditor
{
private static final long serialVersionUID = 1L;
private HashMap<DecoderType,AuxDecoderCheckBox> mControls =
new HashMap<DecoderType,AuxDecoderCheckBox>();
public AuxDecodeComponentEditor( ChannelNode channelNode )
{
super( channelNode );
List<DecoderType> decoders = DecoderType.getAuxDecoders();
Collections.sort( decoders );
for( DecoderType decoder: decoders )
{
AuxDecoderCheckBox control = new AuxDecoderCheckBox( decoder );
if( getConfig().getAuxDecoders().contains( decoder ) )
{
control.setSelected( true );
}
add( control, "wrap" );
mControls.put( decoder, control );
}
}
public AuxDecodeConfiguration getConfig()
{
return getChannelNode().getChannel().getAuxDecodeConfiguration();
}
@Override
public void save()
{
getConfig().clearAuxDecoders();
for( DecoderType decoder: mControls.keySet() )
{
if( mControls.get( decoder ) .isSelected() )
{
getConfig().addAuxDecoder( decoder );
}
}
}
@Override
public void reset()
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
for( DecoderType decoder: mControls.keySet() )
{
mControls.get( decoder ).setSelected(
getConfig().getAuxDecoders().contains( decoder ) );
}
}
});
}
public class AuxDecoderCheckBox extends JCheckBox
{
private static final long serialVersionUID = 1L;
private DecoderType mDecoderType;
public AuxDecoderCheckBox( DecoderType decoder )
{
super( decoder.getDisplayString() );
mDecoderType = decoder;
}
}
}

View File

@@ -0,0 +1,154 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import net.miginfocom.swing.MigLayout;
import module.decode.config.DecodeConfigFactory;
import module.decode.config.DecodeConfiguration;
import controller.Editor;
import controller.channel.AbstractChannelEditor;
import controller.channel.ChannelNode;
import controller.channel.ChannelValidationException;
public class DecodeComponentEditor extends AbstractChannelEditor
{
private static final long serialVersionUID = 1L;
private JComboBox<DecoderType> mComboDecoders;
private DecodeEditor mEditor;
public DecodeComponentEditor( ChannelNode channelNode )
{
super( channelNode );
setLayout( new MigLayout( "fill,wrap 2", "[right,grow][grow]", "[][][grow]" ) );
/**
* ComboBox: Decoders
*/
mComboDecoders = new JComboBox<DecoderType>();
DefaultComboBoxModel<DecoderType> model =
new DefaultComboBoxModel<DecoderType>();
for( DecoderType type: DecoderType.getPrimaryDecoders() )
{
model.addElement( type );
}
mComboDecoders.setModel( model );
mComboDecoders.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
DecoderType selected = mComboDecoders
.getItemAt( mComboDecoders.getSelectedIndex() );
if( selected != null )
{
DecodeConfiguration config;
if( mChannelNode.getChannel()
.getDecodeConfiguration().getDecoderType() ==
selected )
{
config = mChannelNode.getChannel()
.getDecodeConfiguration();
}
else
{
config = DecodeConfigFactory
.getDecodeConfiguration( selected );
}
//Remove the existing one
if( mEditor != null )
{
remove( mEditor );
}
//Change to the new one
mEditor = DecoderFactory.getPanel( config, mChannelNode );
add( mEditor, "span 2,center" );
revalidate();
repaint();
}
}
});
add( new JLabel( "Decoder:" ) );
add( mComboDecoders, "wrap" );
reset();
}
public void reset()
{
final DecoderType decoder = mChannelNode.getChannel()
.getDecodeConfiguration().getDecoderType();
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run() {
mComboDecoders.setSelectedItem( decoder );
mComboDecoders.requestFocus();
mComboDecoders.requestFocusInWindow();
}
});
}
@Override
public void save()
{
if( mEditor != null )
{
mEditor.save();
}
//The component that calls save will invoke the change broadcast
mChannelNode.getChannel()
.setDecodeConfiguration( mEditor.getConfig(), false );
}
/**
* Validates the editor against the current decode editor
*/
@Override
public void validate( Editor editor ) throws ChannelValidationException
{
super.validate( editor );
mEditor.validate( editor );
}
}

View File

@@ -0,0 +1,118 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JSpinner;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import net.miginfocom.swing.MigLayout;
import module.decode.config.DecodeConfiguration;
import controller.Editor;
import controller.channel.Channel;
import controller.channel.ChannelValidationException;
public class DecodeEditor extends Editor
{
private static final long serialVersionUID = 1L;
protected DecodeConfiguration mConfig;
protected JCheckBox mAFC;
protected JSpinner mAFCMaximumCorrection;
public DecodeEditor( DecodeConfiguration config )
{
setLayout( new MigLayout( "fill,wrap 2", "[right,grow][grow]", "[][][][][grow]" ) );
mConfig = config;
if( config.supportsAFC() )
{
mAFC = new JCheckBox( "Automatic Frequency Control" );
mAFC.setSelected( mConfig.isAFCEnabled() );
mAFC.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
mConfig.setAFC( mAFC.isSelected() );
}
} );
mAFC.setToolTipText( "AFC automatically adjusts the center "
+ "frequency of the channel to correct/compensate for "
+ "inaccuracies and frequency drift in the tuner" );
add( mAFC, "span" );
int initial = mConfig.getAFCMaximumCorrection();
SpinnerModel model = new SpinnerNumberModel( initial, 0, 15000, 1 );
mAFCMaximumCorrection = new JSpinner( model );
JSpinner.NumberEditor editor =
(JSpinner.NumberEditor)mAFCMaximumCorrection.getEditor();
editor.getTextField().setHorizontalAlignment( SwingConstants.CENTER );
mAFCMaximumCorrection.setToolTipText( "Sets the maximum frequency "
+ "correction value in hertz, 0 - 15kHz" );
mAFCMaximumCorrection.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent e )
{
int value = ((SpinnerNumberModel)mAFCMaximumCorrection
.getModel()).getNumber().intValue();
mConfig.setAFCMaximumCorrection( value );
save();
}
} );
add( new JLabel( "Correction Limit Hz:" ) );
add( mAFCMaximumCorrection, "wrap" );
}
}
public DecodeConfiguration getConfig()
{
return mConfig;
}
@Override
public void save()
{
}
@Override
public void reset()
{
}
}

View File

@@ -0,0 +1,143 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode;
import message.IMessageProvider;
import message.Message;
import module.Module;
import module.decode.event.CallEvent;
import module.decode.event.ICallEventProvider;
import module.decode.state.DecoderState;
import module.decode.state.DecoderStateEvent;
import module.decode.state.IDecoderStateEventListener;
import module.decode.state.IDecoderStateEventProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Broadcaster;
import sample.Listener;
public abstract class Decoder extends Module
implements ICallEventProvider,
IDecoderStateEventListener,
IDecoderStateEventProvider,
IMessageProvider
{
private final static Logger mLog = LoggerFactory.getLogger( Decoder.class );
/**
* Broadcasts decoded messages both to the internal decoder state and any
* external registered message listeners
*/
protected Broadcaster<Message> mMessageBroadcaster =
new Broadcaster<Message>();
protected DecoderState mDecoderState;
/**
* Decoder - parent class for all decoders, demodulators and components.
*/
public Decoder( DecoderState decoderState )
{
assert( decoderState != null );
mDecoderState = decoderState;
mMessageBroadcaster.addListener( mDecoderState );
}
@Override
public void dispose()
{
mMessageBroadcaster.dispose();
mMessageBroadcaster = null;
}
/**
* Allows decoder to broadcast initialization messages/events so that
* external modules can be correctly configured. This method is invoked
* prior to processing start.
*/
public void init()
{
mDecoderState.init();
}
/**
* Identifies the decoder type (ie protocol)
*/
public abstract DecoderType getDecoderType();
/**
* Adds a listener for receiving decoded messages from this decoder
*/
@Override
public void addMessageListener( Listener<Message> listener )
{
mMessageBroadcaster.addListener( listener );
}
/**
* Removes the listener from receiving decoded messages from all attached
* decoders
*/
@Override
public void removeMessageListener( Listener<Message> listener )
{
mMessageBroadcaster.removeListener( listener );
}
/**
* Decoder State - tracks the state of the decoder
*/
public DecoderState getDecoderState()
{
return mDecoderState;
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{
return mDecoderState.getDecoderStateListener();
}
@Override
public void setDecoderStateListener( Listener<DecoderStateEvent> listener )
{
mDecoderState.setDecoderStateListener( listener );
}
@Override
public void removeDecoderStateListener()
{
mDecoderState.removeDecoderStateListener();
}
@Override
public void setCallEventListener( Listener<CallEvent> listener )
{
mDecoderState.setCallEventListener( listener );
}
@Override
public void removeCallEventListener()
{
mDecoderState.removeCallEventListener();
}
}

View File

@@ -0,0 +1,416 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import message.Message;
import message.MessageDirection;
import module.Module;
import module.decode.config.AuxDecodeConfiguration;
import module.decode.config.DecodeConfiguration;
import module.decode.fleetsync2.Fleetsync2Decoder;
import module.decode.fleetsync2.FleetsyncMessageFilter;
import module.decode.lj1200.LJ1200Decoder;
import module.decode.lj1200.LJ1200MessageFilter;
import module.decode.ltrnet.DecodeConfigLTRNet;
import module.decode.ltrnet.LTRNetDecoder;
import module.decode.ltrnet.LTRNetDecoderPanel;
import module.decode.ltrnet.LTRNetEditor;
import module.decode.ltrnet.LTRNetMessageFilter;
import module.decode.ltrstandard.DecodeConfigLTRStandard;
import module.decode.ltrstandard.LTRStandardDecoder;
import module.decode.ltrstandard.LTRStandardDecoderPanel;
import module.decode.ltrstandard.LTRStandardEditor;
import module.decode.ltrstandard.LTRStandardMessageFilter;
import module.decode.mdc1200.MDCDecoder;
import module.decode.mdc1200.MDCMessageFilter;
import module.decode.mpt1327.DecodeConfigMPT1327;
import module.decode.mpt1327.MPT1327ConfigEditor;
import module.decode.mpt1327.MPT1327Decoder;
import module.decode.mpt1327.MPT1327Decoder.Sync;
import module.decode.mpt1327.MPT1327DecoderPanel;
import module.decode.mpt1327.MPT1327MessageFilter;
import module.decode.nbfm.NBFMDecoder;
import module.decode.nbfm.NBFMDecoderPanel;
import module.decode.nbfm.NBFMEditor;
import module.decode.p25.DecodeConfigP25Phase1;
import module.decode.p25.P25Decoder;
import module.decode.p25.P25Decoder.Modulation;
import module.decode.p25.P25DecoderPanel;
import module.decode.p25.P25Editor;
import module.decode.p25.P25_C4FMDecoder;
import module.decode.p25.P25_LSMDecoder;
import module.decode.p25.audio.P25AudioModule;
import module.decode.p25.message.filter.P25MessageFilterSet;
import module.decode.passport.PassportEditor;
import module.decode.passport.PassportMessageFilter;
import module.decode.state.DecoderPanel;
import module.decode.state.TrafficChannelManager;
import module.decode.tait.Tait1200Decoder;
import module.demodulate.fm.FMDemodulatorModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import properties.SystemProperties;
import settings.SettingsManager;
import util.TimeStamp;
import alias.AliasList;
import audio.AudioModule;
import controller.ResourceManager;
import controller.channel.Channel.ChannelType;
import controller.channel.ChannelNode;
import controller.channel.map.ChannelMap;
import filter.AllPassFilter;
import filter.FilterSet;
import filter.IFilter;
public class DecoderFactory
{
private final static Logger mLog = LoggerFactory.getLogger( DecoderFactory.class );
public static final boolean NO_DC_REMOVAL = false;
public static final boolean REMOVE_DC = true;
public static final int NO_FREQUENCY_CORRECTION = 0;
/**
* Returns a list of one primary decoder and any auxiliary decoders, as
* specified in the configurations.
*
* @param config - primary decoder configuration
* @param auxConfig - auxiliary decoder(s) configuration
*
* @return list of configured decoders
*/
public static List<Module> getModules( ChannelType channelType,
ResourceManager resourceManager,
DecodeConfiguration config,
AuxDecodeConfiguration auxConfig,
AliasList aliasList )
{
List<Module> modules = getPrimaryModules( channelType, config,
aliasList, resourceManager );
modules.addAll( getAuxiliaryDecoders( auxConfig, aliasList ) );
return modules;
}
public static FMDemodulatorModule getFMDemodulator( DecodeConfiguration config, int pass, int stop, boolean removeDC )
{
if( config.isAFCEnabled() )
{
return new FMDemodulatorModule( pass, stop, config.getAFCMaximumCorrection(), removeDC );
}
else
{
return new FMDemodulatorModule( pass, stop, NO_FREQUENCY_CORRECTION, removeDC );
}
}
/**
* Constructs a primary decoder as specified in the decode configuration
* @param config - primary decoder configuration
* @param aliasList - optional alias list
* @return configured decoder or null
*/
public static List<Module> getPrimaryModules( ChannelType channelType,
DecodeConfiguration config,
AliasList aliasList,
ResourceManager resourceManager )
{
List<Module> modules = new ArrayList<Module>();
/* Baseband low-pass filter pass and stop frequencies */
int pass = config.getDecoderType().getChannelBandwidth() / 2;
int stop = pass + 1250;
switch( config.getDecoderType() )
{
case AM:
// decoder = new AMDecoder();
break;
case NBFM:
modules.add( new AudioModule() );
modules.add( new NBFMDecoder( config ) );
modules.add( getFMDemodulator( config, pass, stop, REMOVE_DC ) );
break;
case LTR_STANDARD:
modules.add( new AudioModule( REMOVE_DC ) );
MessageDirection direction = ((DecodeConfigLTRStandard)config).getMessageDirection();
modules.add( new LTRStandardDecoder( aliasList, direction ) );
modules.add( getFMDemodulator( config, pass, stop, NO_DC_REMOVAL ) );
break;
case LTR_NET:
modules.add( new AudioModule( REMOVE_DC ) );
modules.add( new LTRNetDecoder( (DecodeConfigLTRNet)config, aliasList ) );
// modules.add( new ComplexBufferRecorderModule( getRecordingFilename( "decimated_baseband" ) ) );
modules.add( getFMDemodulator( config, pass, stop, NO_DC_REMOVAL ) );
break;
case MPT1327:
DecodeConfigMPT1327 mptConfig = (DecodeConfigMPT1327)config;
modules.add( new AudioModule() );
ChannelMap channelMap = resourceManager.getPlaylistManager().getPlayist()
.getChannelMapList().getChannelMap( mptConfig.getChannelMapName() );
Sync sync = mptConfig.getSync();
modules.add( new MPT1327Decoder( channelMap, channelType, aliasList, sync ) );
if( channelType == ChannelType.STANDARD )
{
long timeout = mptConfig.getCallTimeout() * 1000; //convert to milliseconds
modules.add( new TrafficChannelManager( resourceManager,
config, timeout,
mptConfig.getTrafficChannelPoolSize() ) );
}
// modules.add( new RealBufferRecorderModule( getRecordingFilename( "demodulated_mpt1327" ) ) );
modules.add( getFMDemodulator( config, pass, stop, REMOVE_DC ) );
break;
case PASSPORT:
// decoder = new PassportDecoder( config, aliasList );
break;
case P25_PHASE1:
DecodeConfigP25Phase1 p25Config = (DecodeConfigP25Phase1)config;
Modulation modulation = p25Config.getModulation();
switch( modulation )
{
case C4FM:
modules.add( getFMDemodulator( config, 6750, 7500, REMOVE_DC ) );
modules.add( new P25_C4FMDecoder( aliasList, channelType ) );
break;
case CQPSK:
modules.add( new P25_LSMDecoder( aliasList, channelType ) );
break;
default:
throw new IllegalArgumentException( "Unrecognized P25 "
+ "Phase 1 Modulation [" + modulation + "]" );
}
if( channelType == ChannelType.STANDARD )
{
long timeout = p25Config.getCallTimeout() * 1000; //convert to milliseconds
modules.add( new TrafficChannelManager( resourceManager,
config, timeout,
p25Config.getTrafficChannelPoolSize() ) );
}
modules.add( new P25AudioModule() );
break;
default:
throw new IllegalArgumentException(
"Unknown decoder type [" +
config.getDecoderType().toString() + "]" );
}
return modules;
}
/**
* Constructs a list of auxiliary decoders, as specified in the configuration
*
* @param config - auxiliary configuration
* @param aliasList - optional alias list
* @return - list of auxiliary decoders
*/
public static List<Decoder> getAuxiliaryDecoders( AuxDecodeConfiguration config,
AliasList aliasList )
{
List<Decoder> decoders = new ArrayList<Decoder>();
for( DecoderType auxDecoder: config.getAuxDecoders() )
{
switch( auxDecoder )
{
case FLEETSYNC2:
decoders.add( new Fleetsync2Decoder( aliasList ) );
break;
case MDC1200:
decoders.add( new MDCDecoder( aliasList ) );
break;
case LJ_1200:
decoders.add( new LJ1200Decoder( aliasList ) );
break;
case TAIT_1200:
decoders.add( new Tait1200Decoder( aliasList ) );
break;
}
}
return decoders;
}
public static DecoderPanel getDecoderPanel( SettingsManager settingsManager,
Decoder decoder )
{
switch( decoder.getDecoderType() )
{
// case AM:
// break;
// case FLEETSYNC2:
// break;
// case LJ_1200:
// break;
case LTR_NET:
return new LTRNetDecoderPanel( settingsManager, (LTRNetDecoder)decoder );
case LTR_STANDARD:
return new LTRStandardDecoderPanel( settingsManager, (LTRStandardDecoder)decoder );
// case MDC1200:
// break;
case MPT1327:
return new MPT1327DecoderPanel( settingsManager, (MPT1327Decoder)decoder );
case NBFM:
return new NBFMDecoderPanel( settingsManager, decoder );
case P25_PHASE1:
return new P25DecoderPanel( settingsManager, (P25Decoder)decoder );
// case PASSPORT:
// break;
case TAIT_1200:
break;
default:
throw new IllegalArgumentException( "Unrecognized decoder: " +
decoder.getDecoderType().getDisplayString() );
}
return null;
}
/**
* Assembles a filter set containing filters for the primary channel
* decoder and each of the auxiliary decoders
*/
public static FilterSet<Message> getMessageFilters( List<Module> modules )
{
FilterSet<Message> filterSet = new FilterSet<>();
for( Module module: modules )
{
if( module instanceof Decoder )
{
filterSet.addFilters( getMessageFilter(
((Decoder)module).getDecoderType() ) );
}
}
/* If we don't have any filters, add an ALL-PASS filter */
if( filterSet.getFilters().isEmpty() )
{
filterSet.addFilter( new AllPassFilter<Message>() );
}
return filterSet;
}
/**
* Returns a set of IMessageFilter objects (FilterSets or Filters) that
* can process all of the messages produced by the specified decoder type.
*/
public static List<IFilter<Message>> getMessageFilter( DecoderType decoder )
{
ArrayList<IFilter<Message>> filters = new ArrayList<IFilter<Message>>();
switch( decoder )
{
case FLEETSYNC2:
filters.add( new FleetsyncMessageFilter() );
break;
case LJ_1200:
filters.add( new LJ1200MessageFilter() );
break;
case LTR_NET:
filters.add( new LTRNetMessageFilter() );
break;
case LTR_STANDARD:
filters.add( new LTRStandardMessageFilter() );
break;
case MDC1200:
filters.add( new MDCMessageFilter() );
break;
case MPT1327:
filters.add( new MPT1327MessageFilter() );
break;
case P25_PHASE1:
case P25_PHASE2:
filters.add( new P25MessageFilterSet() );
break;
case PASSPORT:
filters.add( new PassportMessageFilter() );
break;
default:
break;
}
return filters;
}
public static DecodeEditor getPanel( DecodeConfiguration config,
ChannelNode channelNode )
{
DecodeEditor configuredPanel;
switch( config.getDecoderType() )
{
case NBFM:
configuredPanel = new NBFMEditor( config );
break;
case LTR_STANDARD:
configuredPanel = new LTRStandardEditor( config );
break;
case LTR_NET:
configuredPanel = new LTRNetEditor( config );
break;
case MPT1327:
configuredPanel = new MPT1327ConfigEditor( config, channelNode );
break;
case PASSPORT:
configuredPanel = new PassportEditor( config );
break;
case P25_PHASE1:
configuredPanel = new P25Editor( config );
break;
default:
configuredPanel = new DecodeEditor( config );
break;
}
return configuredPanel;
}
public static String getRecordingFilename( String token )
{
//Get the base recording filename
StringBuilder sb = new StringBuilder();
sb.append( SystemProperties.getInstance()
.getApplicationFolder( "recordings" ) );
sb.append( File.separator );
sb.append( TimeStamp.getTimeStamp( "_" ) );
sb.append( "_" );
sb.append( token );
return sb.toString();
}
}

View File

@@ -0,0 +1,130 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode;
import java.util.ArrayList;
import java.util.EnumSet;
/**
* Enumeration of decoder types
*/
public enum DecoderType
{
ACARS( "ACARS", "ACARS", "images/acars.png", 12500 ),
AIS( "AIS", "AIS", "images/ais.png", 12500 ),
AM( "AM", "AM", "images/am.png", 10000 ),
APRS( "APRS", "APRS", "images/aprs.png", 12500 ),
DMR( "DMR", "DMR", "images/dmr.png", 12500 ),
FLEETSYNC2( "Fleetsync II", "Fsync2", "images/fm.png", 12500 ),
LJ_1200( "LJ1200 173.075", "LJ1200", "images/lj1200.png", 12500 ),
LTR_STANDARD( "LTR-Standard", "LTR", "images/ltr.png", 10000 ),
LTR_NET( "LTR-Net", "LTR-Net", "images/ltr_net.png", 12500 ),
MDC1200( "MDC1200", "MDC1200", "images/fm.png", 12500 ),
MOTOROLA_TYPE_2( "MOTO T2", "MOTO T2", "images/motorola_type_2.png", 15000 ),
MPT1327( "MPT1327", "MPT1327", "images/mpt1327.png", 12500 ),
NXDN( "NXDN", "NXDN", "images/nxdn.png", 8000 ),
NBFM( "NBFM", "NBFM", "images/fm.png", 12500 ),
PASSPORT( "Passport", "Passport", "images/passport.png", 12500 ),
P25_PHASE1( "P25 Phase I", "P25-1", "images/p25_1.png", 12500 ),
P25_PHASE2( "P25 Phase II", "P25-2", "images/p25_2.png", 15000 ),
TAIT_1200( "Tait 1200", "Tait 1200", "images/tait.png", 12500 );
private String mDisplayString;
private String mShortDisplayString;
private String mIconFilename;
private int mChannelBandwidth;
DecoderType( String displayString, String shortDisplayString,
String iconFilename, int bandwidth )
{
mDisplayString = displayString;
mShortDisplayString = shortDisplayString;
mIconFilename = iconFilename;
mChannelBandwidth = bandwidth;
}
/**
* Primary decoders
*/
public static EnumSet<DecoderType> getPrimaryDecoders()
{
return EnumSet.of( DecoderType.AM,
DecoderType.LTR_NET,
DecoderType.LTR_STANDARD,
DecoderType.MPT1327,
DecoderType.NBFM,
DecoderType.P25_PHASE1,
DecoderType.PASSPORT );
}
/**
* Decoders that can be used in the viewer application
*/
public static EnumSet<DecoderType> getInstrumentableDecoders()
{
return EnumSet.of( DecoderType.FLEETSYNC2,
DecoderType.LJ_1200,
DecoderType.LTR_NET,
DecoderType.MDC1200,
DecoderType.MPT1327,
DecoderType.PASSPORT,
DecoderType.P25_PHASE1,
DecoderType.TAIT_1200 );
}
/**
* Available auxiliary decoders.
*/
public static ArrayList<DecoderType> getAuxDecoders()
{
ArrayList<DecoderType> decoders = new ArrayList<DecoderType>();
decoders.add( DecoderType.FLEETSYNC2 );
decoders.add( DecoderType.LJ_1200 );
decoders.add( DecoderType.MDC1200 );
decoders.add( DecoderType.TAIT_1200 );
return decoders;
}
public String getDisplayString()
{
return mDisplayString;
}
public String getShortDisplayString()
{
return mShortDisplayString;
}
public String getIconFilename()
{
return mIconFilename;
}
public int getChannelBandwidth()
{
return mChannelBandwidth;
}
@Override
public String toString()
{
return mDisplayString;
}
}

View File

@@ -0,0 +1,79 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode;
import module.decode.config.DecodeConfiguration;
import module.decode.state.DecoderState;
import sample.Listener;
import audio.squelch.ISquelchStateProvider;
import audio.squelch.SquelchState;
/**
* Primary decoder adds the following functionality over the basic decoder:
*
* - Provides audio squelch control
*/
public abstract class PrimaryDecoder extends Decoder implements ISquelchStateProvider
{
protected Listener<SquelchState> mSquelchStateListener;
protected DecodeConfiguration mDecodeConfiguration;
public PrimaryDecoder( DecodeConfiguration config, DecoderState decoderState )
{
super( decoderState );
mDecodeConfiguration = config;
}
public DecodeConfiguration getDecodeConfiguration()
{
return mDecodeConfiguration;
}
@Override
public void dispose()
{
super.dispose();
mSquelchStateListener = null;
mDecodeConfiguration = null;
}
public void broadcast( SquelchState state )
{
if( mSquelchStateListener != null )
{
mSquelchStateListener.receive( state );
}
}
@Override
public void setSquelchStateListener( Listener<SquelchState> listener )
{
mSquelchStateListener = listener;
}
@Override
public void removeSquelchStateListener()
{
mSquelchStateListener = null;
}
}

View File

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

View File

@@ -0,0 +1,75 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.am;
import javax.swing.JLabel;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderPanel;
import settings.ColorSetting;
import settings.Setting;
import settings.SettingsManager;
public class AMDecoderPanel extends DecoderPanel
{
private static final long serialVersionUID = 1L;
private JLabel mProtocol = new JLabel( "AM" );
public AMDecoderPanel( SettingsManager settingsManager, AMDecoder decoder )
{
super( settingsManager, decoder );
init();
}
public void init()
{
mProtocol.setFont( mFontDecoder );
add( mProtocol, "wrap" );
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
}
@Override
public void settingChanged( Setting setting )
{
super.settingChanged( setting );
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case CHANNEL_STATE_LABEL_DECODER:
if( mProtocol != null )
{
mProtocol.setForeground( mColorLabelDecoder );
}
break;
default:
break;
}
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,60 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.config;
import java.util.ArrayList;
import javax.xml.bind.annotation.XmlElement;
import module.decode.DecoderType;
import controller.config.Configuration;
public class AuxDecodeConfiguration extends Configuration
{
ArrayList<DecoderType> mAuxDecoders = new ArrayList<DecoderType>();
public AuxDecodeConfiguration()
{
}
@XmlElement( name="aux_decoder" )
public ArrayList<DecoderType> getAuxDecoders()
{
return mAuxDecoders;
}
public void setAuxDecoders( ArrayList<DecoderType> decoders )
{
mAuxDecoders = decoders;
}
public void addAuxDecoder( DecoderType decoder )
{
mAuxDecoders.add( decoder );
}
public void removeAuxDecoder( DecoderType decoder )
{
mAuxDecoders.remove( decoder );
}
public void clearAuxDecoders()
{
mAuxDecoders.clear();
}
}

View File

@@ -0,0 +1,71 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.config;
import module.decode.DecoderType;
import module.decode.am.DecodeConfigAM;
import module.decode.ltrnet.DecodeConfigLTRNet;
import module.decode.ltrstandard.DecodeConfigLTRStandard;
import module.decode.mpt1327.DecodeConfigMPT1327;
import module.decode.nbfm.DecodeConfigNBFM;
import module.decode.p25.DecodeConfigP25Phase1;
import module.decode.passport.DecodeConfigPassport;
public class DecodeConfigFactory
{
public static DecodeConfiguration getDefaultDecodeConfiguration()
{
return getDecodeConfiguration( DecoderType.NBFM );
}
public static DecodeConfiguration
getDecodeConfiguration( DecoderType decoder )
{
DecodeConfiguration retVal;
switch( decoder )
{
case AM:
retVal = new DecodeConfigAM();
break;
case NBFM:
retVal = new DecodeConfigNBFM();
break;
case LTR_STANDARD:
retVal = new DecodeConfigLTRStandard();
break;
case LTR_NET:
retVal = new DecodeConfigLTRNet();
break;
case MPT1327:
retVal = new DecodeConfigMPT1327();
break;
case PASSPORT:
retVal = new DecodeConfigPassport();
break;
case P25_PHASE1:
retVal = new DecodeConfigP25Phase1();
break;
default:
throw new IllegalArgumentException( "DecodeConfigFactory - "
+ "unknown decoder type [" + decoder.toString() + "]" );
}
return retVal;
}
}

View File

@@ -0,0 +1,101 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.config;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import module.decode.DecoderType;
import module.decode.am.DecodeConfigAM;
import module.decode.ltrnet.DecodeConfigLTRNet;
import module.decode.ltrstandard.DecodeConfigLTRStandard;
import module.decode.mpt1327.DecodeConfigMPT1327;
import module.decode.nbfm.DecodeConfigNBFM;
import module.decode.p25.DecodeConfigP25Phase1;
import module.decode.passport.DecodeConfigPassport;
import controller.config.Configuration;
@XmlSeeAlso( { DecodeConfigAM.class,
DecodeConfigNBFM.class,
DecodeConfigLTRNet.class,
DecodeConfigLTRStandard.class,
DecodeConfigMPT1327.class,
DecodeConfigPassport.class,
DecodeConfigP25Phase1.class } )
@XmlRootElement( name = "decode_configuration" )
public abstract class DecodeConfiguration extends Configuration
{
public static final int DEFAULT_CALL_TIMEOUT_SECONDS = 45;
public static final int CALL_TIMEOUT_MINIMUM = 10;
public static final int CALL_TIMEOUT_MAXIMUM = 300; //5 minutes
public static final int TRAFFIC_CHANNEL_LIMIT_DEFAULT = 3;
public static final int TRAFFIC_CHANNEL_LIMIT_MINIMUM = 0;
public static final int TRAFFIC_CHANNEL_LIMIT_MAXIMUM = 30;
private DecoderType mDecoderType = DecoderType.NBFM;
private boolean mAFCEnabled = true;
private int mAFCMaxCorrection = 3000;
public DecodeConfiguration()
{
this( DecoderType.NBFM );
}
public DecodeConfiguration( DecoderType type )
{
mDecoderType = type;
}
public DecoderType getDecoderType()
{
return mDecoderType;
}
@XmlElement( name = "afc" )
public boolean getAFC()
{
return mAFCEnabled;
}
public boolean isAFCEnabled()
{
return mAFCEnabled;
}
public void setAFC( boolean enabled )
{
mAFCEnabled = enabled;
}
public int getAFCMaximumCorrection()
{
return mAFCMaxCorrection;
}
public void setAFCMaximumCorrection( int max )
{
mAFCMaxCorrection = max;
}
public boolean supportsAFC()
{
return true;
}
}

View File

@@ -0,0 +1,77 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.event;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import net.miginfocom.swing.MigLayout;
public class ActivitySummaryFrame extends JFrame
{
private static final long serialVersionUID = 1L;
public ActivitySummaryFrame( String summary )
{
this( summary, null );
}
public ActivitySummaryFrame( String summary, Component displayOver )
{
setTitle( "Activity Summary" );
setLocationRelativeTo( displayOver );
setSize( 400, 400 );
setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
setLayout( new MigLayout( "", "[grow,fill]", "[grow,fill][]" ) );
JTextArea summaryText = new JTextArea( summary );
JScrollPane scroller = new JScrollPane( summaryText );
scroller.setViewportView( summaryText );
add( scroller, "wrap" );
JButton close = new JButton( "Close" );
close.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
dispose();
}
} );
add( close );
EventQueue.invokeLater( new Runnable()
{
public void run()
{
setVisible( true );
}
} );
}
}

View File

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

View File

@@ -0,0 +1,312 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.event;
import java.text.SimpleDateFormat;
import java.util.Date;
import module.decode.DecoderType;
import alias.Alias;
import alias.AliasList;
import alias.MetadataType;
public abstract class CallEvent implements Comparable<CallEvent>
{
protected long mEventStart = System.currentTimeMillis();
protected long mEventEnd;
private SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd','HHmmss" );
protected DecoderType mDecoderType;
protected CallEventType mCallEventType;
protected AliasList mAliasList;
protected String mFromID;
protected String mToID;
protected String mDetails;
private boolean mValid = true;
public CallEvent( DecoderType decoder,
CallEventType callEventType,
AliasList aliasList,
String fromID,
String toID,
String details )
{
mDecoderType = decoder;
mCallEventType = callEventType;
mAliasList = aliasList;
mFromID = fromID;
mToID = toID;
mDetails = details;
}
/**
* Indicates if this event is valid. Use this method to mark a previously
* dispatched event as invalid and resend it so that any downstream
* processing can remove the invalid event.
*/
public void setValid( boolean valid )
{
mValid = valid;
}
public boolean isValid()
{
return mValid;
}
public void setEnd( long end )
{
mEventEnd = end;
}
public void end()
{
setEnd( System.currentTimeMillis() );
}
public DecoderType getDecoderType()
{
return mDecoderType;
}
public CallEventType getCallEventType()
{
return mCallEventType;
}
public void setCallEventType( CallEventType type )
{
mCallEventType = type;
}
public AliasList getAliasList()
{
return mAliasList;
}
public void setAliasList( AliasList aliasList )
{
mAliasList = aliasList;
}
public boolean hasAliasList()
{
return mAliasList != null;
}
public long getEventStartTime()
{
return mEventStart;
}
public long getEventEndTime()
{
return mEventEnd;
}
public String getFromID()
{
return mFromID;
}
public boolean hasFromID()
{
return mFromID != null;
}
public MetadataType getFromIDType()
{
return MetadataType.FROM_TALKGROUP;
}
public void setFromID( String id )
{
mFromID = id;
}
public String getToID()
{
return mToID;
}
public boolean hasToID()
{
return mToID != null;
}
public void setToID( String id )
{
mToID = id;
}
public MetadataType getToIDType()
{
return MetadataType.TO;
}
public String getDetails()
{
return mDetails;
}
public void setDetails( String details )
{
mDetails = details;
}
public abstract Alias getFromIDAlias();
public abstract Alias getToIDAlias();
public abstract String getChannel();
public abstract long getFrequency();
public static String getCSVHeader()
{
return "START_DATE,START_TIME,END_DATE,END_TIME,"
+ "DECODER,EVENT,FROM,FROM_ALIAS,TO,TO_ALIAS,"
+ "CHANNEL,FREQUENCY,DETAILS";
}
public String toCSV()
{
StringBuilder sb = new StringBuilder();
sb.append( "'" );
sb.append( mSDF.format( new Date( getEventStartTime() ) ) );
sb.append( "','" );
if( mEventEnd != 0 )
{
sb.append( mSDF.format( new Date( getEventEndTime() ) ) );
}
else
{
sb.append( "','" );
}
sb.append( "','" );
sb.append( getDecoderType().toString() );
sb.append( "','" );
sb.append( getCallEventType().toString() );
sb.append( "'," );
if( getFromID() != null )
{
sb.append( "'" );
sb.append( getFromID() );
sb.append( "'" );
}
sb.append( "," );
Alias fromAlias = getFromIDAlias();
if( fromAlias != null )
{
sb.append( "'" );
sb.append( fromAlias.getName() );
sb.append( "'" );
}
sb.append( "," );
if( getToID() != null )
{
sb.append( "'" );
sb.append( getToID() );
sb.append( "'" );
}
sb.append( "," );
Alias toAlias = getToIDAlias();
if( toAlias != null )
{
sb.append( "'" );
sb.append( toAlias.getName() );
sb.append( "'" );
}
sb.append( ",'" );
sb.append( getChannel() );
sb.append( "','" );
sb.append( getFrequency() );
sb.append( "'," );
if( getDetails() != null )
{
sb.append( "'" );
sb.append( getDetails() );
sb.append( "'" );
}
return sb.toString();
}
public enum CallEventType
{
ANNOUNCEMENT( "Announcement" ),
CALL( "Call" ),
CALL_ALERT( "Call Alert" ),
CALL_END( "Call End" ),
CALL_DETECT( "Call Detect" ),
CALL_UNIQUE_ID( "UID Call" ),
CALL_NO_TUNER( "Call - No Tuner" ),
CALL_TIMEOUT( "Call Timeout" ),
COMMAND( "Command" ),
DATA_CALL( "Data Call" ),
DEREGISTER( "Deregister" ),
EMERGENCY( "EMERGENCY" ),
ENCRYPTED_CALL( "Encrypted Call" ),
FUNCTION( "Function" ),
GPS( "GPS" ),
ID_ANI( "ANI" ),
ID_UNIQUE( "Unique ID" ),
PAGE( "Page" ),
QUERY( "Query" ),
REGISTER( "Register" ),
REGISTER_ESN( "ESN" ),
RESPONSE( "Response" ),
SDM( "Short Data Message" ),
STATION_ID( "Station ID" ),
STATUS( "Status" ),
TELEPHONE_CALL( "Telephone Call" ),
UNKNOWN( "Unknown" ),
INVALID( "Invalid" );
private String mLabel;
private CallEventType( String label )
{
mLabel = label;
}
public String getLabel()
{
return mLabel;
}
public String toString()
{
return mLabel;
}
}
@Override
public int compareTo( CallEvent other )
{
return Long.compare( mEventStart, other.getEventStartTime() );
}
}

View File

@@ -0,0 +1,72 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.event;
import javax.swing.ImageIcon;
import javax.swing.table.DefaultTableCellRenderer;
import settings.SettingsManager;
import alias.Alias;
/**
* Custom renderer for JTable cells that contain Alias objects. Renders the
* alias name, and if there is an icon name, attempts to get that icon from
* the settings manager and render it.
*/
public class CallEventAliasCellRenderer extends DefaultTableCellRenderer
{
private SettingsManager mSettingsManager;
public CallEventAliasCellRenderer( SettingsManager settingsManager )
{
super();
mSettingsManager = settingsManager;
}
public void setValue( Object obj )
{
if( obj != null && obj instanceof Alias )
{
Alias alias = (Alias)obj;
setText( alias.getName() );
ImageIcon icon = getIcon( alias, 12 );
if( icon != null )
{
setIcon( icon );
}
}
else
{
setText( " " );
setIcon( null );
}
}
private ImageIcon getIcon( Alias alias, int height )
{
if( mSettingsManager != null && alias != null )
{
return mSettingsManager.getImageIcon( alias.getIconName(), height );
}
return null;
}
}

View File

@@ -0,0 +1,223 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.event;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import javax.swing.table.AbstractTableModel;
import module.decode.event.CallEvent.CallEventType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
public class CallEventModel extends AbstractTableModel implements Listener<CallEvent>
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger( CallEventModel.class );
private static DecimalFormat mFrequencyFormatter =
new DecimalFormat( "0.000000" );
public static final int TIME = 0;
public static final int EVENT = 1;
public static final int FROM_ID = 2;
public static final int FROM_ALIAS = 3;
public static final int TO_ID = 4;
public static final int TO_ALIAS = 5;
public static final int CHANNEL = 6;
public static final int FREQUENCY = 7;
public static final int DETAILS = 8;
protected int mMaxMessages = 500;
protected List<CallEvent> mEvents = new ArrayList<CallEvent>();
protected String[] mHeaders = new String[] { "Time",
"Event",
"From",
"Alias",
"To",
"Alias",
"Channel",
"Frequency",
"Details" };
private SimpleDateFormat mSDFTime = new SimpleDateFormat( "HH:mm:ss" );
private boolean mNewMessagesFirst = true;
public CallEventModel()
{
}
public void dispose()
{
mEvents.clear();
}
public void reset()
{
mEvents.clear();
fireTableDataChanged();
}
public int getMaxMessageCount()
{
return mMaxMessages;
}
public void setMaxMessageCount( int count )
{
mMaxMessages = count;
}
/**
* Adds, updates or deletes the event from the model. Producers can send
* the same call event multiple times to indicate that information in the
* event is updated. Producers can also mark the event as invalid and the
* event will be removed from the model.
*/
public void receive( final CallEvent event )
{
//TODO: reverse this so that the new events go to the end and the oldest
//events are at the beginning ... use the getObjectAt() to control
//correct access to the ordering imposed.
if( event.isValid() )
{
if( !mEvents.contains( event ) )
{
mEvents.add( 0, event );
fireTableRowsInserted( 0, 0 );
prune();
}
else
{
int row = mEvents.indexOf( event );
fireTableRowsUpdated( row, row );
}
}
else
{
if( mEvents.contains( event ) )
{
int row = mEvents.indexOf( event );
mEvents.remove( event );
fireTableRowsDeleted( row, row );
}
}
}
private void prune()
{
while( mEvents.size() > mMaxMessages )
{
int index = mEvents.size() - 1;
mEvents.remove( index );
fireTableRowsDeleted( index, index );
}
}
@Override
public int getRowCount()
{
return mEvents.size();
}
@Override
public int getColumnCount()
{
return mHeaders.length;
}
public String getColumnName( int column )
{
return mHeaders[ column ];
}
@Override
public Object getValueAt( int rowIndex, int columnIndex )
{
synchronized( mEvents )
{
switch( columnIndex )
{
case TIME:
StringBuilder sb = new StringBuilder();
sb.append( mSDFTime.format(
mEvents.get( rowIndex ).getEventStartTime() ) );
if( mEvents.get( rowIndex ).getEventEndTime() != 0 )
{
sb.append( " - " );
sb.append( mSDFTime.format(
mEvents.get( rowIndex ).getEventEndTime() ) );
}
else if( mEvents.get( rowIndex )
.getCallEventType() == CallEventType.CALL )
{
sb.append( " - In Progress" );
}
return sb.toString();
case EVENT:
return mEvents.get( rowIndex ).getCallEventType();
case FROM_ID:
return mEvents.get( rowIndex ).getFromID();
case FROM_ALIAS:
return mEvents.get( rowIndex ).getFromIDAlias();
case TO_ID:
return mEvents.get( rowIndex ).getToID();
case TO_ALIAS:
return mEvents.get( rowIndex ).getToIDAlias();
case CHANNEL:
return mEvents.get( rowIndex ).getChannel();
case FREQUENCY:
long frequency = mEvents.get( rowIndex ).getFrequency();
if( frequency != 0 )
{
return mFrequencyFormatter.format( (double)frequency / 1E6d );
}
else
{
return null;
}
case DETAILS:
return mEvents.get( rowIndex ).getDetails();
}
}
return null;
}
}

View File

@@ -0,0 +1,105 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.event;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import net.miginfocom.swing.MigLayout;
import controller.channel.Channel;
import controller.channel.ChannelEvent;
import controller.channel.ChannelEvent.Event;
import controller.channel.ChannelEventListener;
public class CallEventPanel extends JPanel implements ChannelEventListener
{
private static final long serialVersionUID = 1L;
private JScrollPane mEmptyScroller;
private Channel mDisplayedChannel;
public CallEventPanel()
{
setLayout( new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]") );
JTable table = new JTable( new CallEventModel() );
table.setAutoCreateRowSorter( true );
table.setAutoResizeMode( JTable.AUTO_RESIZE_LAST_COLUMN );
mEmptyScroller = new JScrollPane( table );
add( mEmptyScroller );
}
// if( mCallEventTable == null )
// {
// mCallEventTable = new JTable( mCallEventModel );
// mCallEventTable.setAutoCreateRowSorter( true );
// mCallEventTable.setAutoResizeMode( JTable.AUTO_RESIZE_LAST_COLUMN );
//
// CallEventAliasCellRenderer renderer =
// new CallEventAliasCellRenderer( mProcessingChain
// .getResourceManager().getSettingsManager() );
//
// mCallEventTable.getColumnModel().getColumn( 3 )
// .setCellRenderer( renderer );
//
// mCallEventTable.getColumnModel().getColumn( 5 )
// .setCellRenderer( renderer );
// }
//
// return mCallEventTable;
@Override
public void channelChanged( ChannelEvent event )
{
if( event.getEvent() == Event.CHANGE_SELECTED &&
event.getChannel().isSelected() )
{
if( mDisplayedChannel == null ||
( mDisplayedChannel != null &&
mDisplayedChannel != event.getChannel() ) )
{
removeAll();
JScrollPane scroller = new JScrollPane( new JTable(
event.getChannel().getCallEventModel() ) );
add( scroller );
mDisplayedChannel = event.getChannel();
revalidate();
repaint();
}
}
else if( event.getEvent() == Event.CHANNEL_PROCESSING_STOPPED ||
event.getEvent() == Event.CHANNEL_DISABLED )
{
if( mDisplayedChannel != null &&
mDisplayedChannel == event.getChannel() )
{
mDisplayedChannel = null;
removeAll();
add( mEmptyScroller );
revalidate();
repaint();
}
}
}
}

View File

@@ -0,0 +1,9 @@
package module.decode.event;
import sample.Listener;
public interface ICallEventProvider
{
public void setCallEventListener( Listener<CallEvent> listener );
public void removeCallEventListener();
}

View File

@@ -0,0 +1,190 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.event;
import java.awt.EventQueue;
import java.text.SimpleDateFormat;
import java.util.LinkedList;
import javax.swing.table.AbstractTableModel;
import message.Message;
import sample.Listener;
import filter.FilterSet;
public class MessageActivityModel extends AbstractTableModel
implements Listener<Message>
{
private static final long serialVersionUID = 1L;
private static final int sTIME = 0;
private static final int sPROTOCOL = 1;
private static final int sERROR_STATUS = 2;
private static final int sMESSAGE = 3;
private static final int sMESSAGE_BITS = 4;
protected int mMaxMessages = 500;
protected LinkedList<Message> mMessages = new LinkedList<Message>();
protected int[] mColumnWidths = { 110, 110, 110, -1, -1 };
protected String[] mHeaders = new String[] { "Time",
"Protocol",
"Error Check",
"Message",
"Binary" };
private SimpleDateFormat mSDFTime = new SimpleDateFormat( "HH:mm:ss" );
private boolean mNewMessagesFirst = true;
private FilterSet<Message> mMessageFilter;
public MessageActivityModel( FilterSet<Message> messageFilter )
{
mMessageFilter = messageFilter;
}
public MessageActivityModel()
{
}
/**
* Clears all messages from history
*/
public void clear()
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
int messageCount = mMessages.size();
mMessages.clear();
fireTableRowsDeleted( 0, messageCount - 1 );
}
});
}
public FilterSet<Message> getMessageFilter()
{
return mMessageFilter;
}
public void dispose()
{
mMessages.clear();
}
public int[] getColumnWidths()
{
return mColumnWidths;
}
public void setColumnWidths( int[] widths )
{
if( widths.length != 5 )
{
throw new IllegalArgumentException( "MessageActivityModel - "
+ "column widths array should have 5 elements" );
}
else
{
mColumnWidths = widths;
}
}
public int getMaxMessageCount()
{
return mMaxMessages;
}
public void setMaxMessageCount( int count )
{
mMaxMessages = count;
}
public void receive( final Message message )
{
if( message.isValid() && mMessageFilter.passes( message ) )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
mMessages.addFirst( message );
MessageActivityModel.this.fireTableRowsInserted( 0, 0 );
prune();
}
} );
}
}
private void prune()
{
while( mMessages.size() > mMaxMessages )
{
mMessages.removeLast();
super.fireTableRowsDeleted( mMessages.size() - 1, mMessages.size() - 1 );
}
}
@Override
public int getRowCount()
{
return mMessages.size();
}
@Override
public int getColumnCount()
{
return mHeaders.length;
}
public String getColumnName( int column )
{
return mHeaders[ column ];
}
@Override
public Object getValueAt( int rowIndex, int columnIndex )
{
Message message = mMessages.get( rowIndex );
switch( columnIndex )
{
case sTIME:
return mSDFTime.format( message.getDateReceived() );
case sPROTOCOL:
return message.getProtocol();
case sERROR_STATUS:
return message.getErrorStatus();
case sMESSAGE:
return message.getMessage();
case sMESSAGE_BITS:
return message.getBinaryMessage();
}
return null;
}
}

View File

@@ -0,0 +1,310 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.event;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import message.Message;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jidesoft.swing.JideButton;
import com.jidesoft.swing.JideSplitButton;
import controller.channel.Channel;
import controller.channel.ChannelEvent;
import controller.channel.ChannelEvent.Event;
import controller.channel.ChannelEventListener;
import filter.FilterEditorPanel;
import filter.FilterSet;
public class MessageActivityPanel extends JPanel implements ChannelEventListener
{
private static final long serialVersionUID = 1L;
private final static Logger mLog =
LoggerFactory.getLogger( MessageActivityPanel.class );
private static MessageActivityModel EMPTY_MODEL = new MessageActivityModel();
private JTable mMessageTable;
private Channel mDisplayedChannel;
private MessageManagementPanel mManagementPanel = new MessageManagementPanel();
public MessageActivityPanel()
{
setLayout( new MigLayout("insets 0 0 0 0", "[][grow,fill]", "[]0[grow,fill]") );
add( mManagementPanel, "span,growx" );
mMessageTable = new JTable( EMPTY_MODEL );
add( new JScrollPane( mMessageTable ), "span,grow" );
}
@Override
public void channelChanged( ChannelEvent event )
{
boolean changed = false;
if( event.getEvent() == Event.CHANGE_SELECTED &&
event.getChannel().isSelected() )
{
if( mDisplayedChannel == null ||
( mDisplayedChannel != null &&
mDisplayedChannel != event.getChannel() ) )
{
mDisplayedChannel = event.getChannel();
mMessageTable.setModel( event.getChannel().getMessageActivityModel() );
if( mDisplayedChannel != null )
{
mManagementPanel.enable();
}
else
{
mManagementPanel.disable();
}
changed = true;
}
}
else if( event.getEvent() == Event.CHANNEL_PROCESSING_STOPPED ||
event.getEvent() == Event.CHANNEL_DISABLED )
{
if( mDisplayedChannel != null &&
mDisplayedChannel == event.getChannel() )
{
mDisplayedChannel = null;
mMessageTable.setModel( EMPTY_MODEL );
mManagementPanel.disable();
changed = true;
}
}
if( changed )
{
revalidate();
repaint();
}
}
public class MessageManagementPanel extends JPanel
{
private static final long serialVersionUID = 1L;
private MessageHistoryButton mHistoryButton = new MessageHistoryButton();
private MessageFilterButton mFilterButton = new MessageFilterButton();
public MessageManagementPanel()
{
setLayout( new MigLayout("insets 2 2 5 5", "[]5[left,grow]", "") );
disable();
add( mFilterButton );
add( mHistoryButton );
}
public void enable()
{
mHistoryButton.setEnabled( true );
mFilterButton.setEnabled( true );
}
public void disable()
{
mHistoryButton.setEnabled( false );
mFilterButton.setEnabled( false );
}
}
public class MessageHistoryButton extends JideSplitButton
{
private static final long serialVersionUID = 1L;
public MessageHistoryButton()
{
super( "Clear" );
JPanel historyPanel = new JPanel();
historyPanel.add( new JLabel( "Message History:" ) );
final JSlider slider = new JSlider();
slider.setMinimum( 0 );
slider.setMaximum( 2000 );
slider.setMajorTickSpacing( 500 );
slider.setPaintTicks( true );
slider.setPaintLabels( true );
slider.addMouseListener( new MouseListener()
{
@Override
public void mouseClicked( MouseEvent arg0 )
{
if( SwingUtilities.isLeftMouseButton( arg0 ) &&
arg0.getClickCount() == 2 )
{
slider.setValue( 500 ); //default
}
}
public void mouseEntered( MouseEvent arg0 ) {}
public void mouseExited( MouseEvent arg0 ) {}
public void mousePressed( MouseEvent arg0 ) {}
public void mouseReleased( MouseEvent arg0 ) {}
} );
if( mDisplayedChannel != null )
{
slider.setValue( mDisplayedChannel.getMessageActivityModel()
.getMaxMessageCount() );
}
else
{
slider.setValue( 500 );
}
slider.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent arg0 )
{
if( mDisplayedChannel != null )
{
mDisplayedChannel.getMessageActivityModel()
.setMaxMessageCount( slider.getValue() );
}
}
});
historyPanel.add( slider );
add( historyPanel );
/* Clear messages */
addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
if( mDisplayedChannel != null )
{
mDisplayedChannel.getMessageActivityModel().clear();
}
}
} );
}
}
public class MessageFilterButton extends JideButton
{
private static final long serialVersionUID = 1L;
public MessageFilterButton()
{
super( "Filter" );
addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
if( mDisplayedChannel == null )
{
JOptionPane.showMessageDialog( MessageFilterButton.this,
"Please enable and select a decoding channel to "
+ "adjust the channel's message display filter" );
}
else
{
final JFrame editor = new JFrame();
editor.setTitle( "Message Filter Editor" );
editor.setSize( 600, 400 );
editor.setLocationRelativeTo( MessageFilterButton.this );
editor.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
editor.setLayout( new MigLayout( "", "[grow,fill]",
"[grow,fill][][]" ) );
@SuppressWarnings( "unchecked" )
FilterSet<Message> filter = (FilterSet<Message>)
mDisplayedChannel.getMessageActivityModel()
.getMessageFilter();
FilterEditorPanel<Message> panel =
new FilterEditorPanel<Message>( filter );
JScrollPane scroller = new JScrollPane( panel );
scroller.setViewportView( panel );
editor.add( scroller, "wrap" );
editor.add( new JLabel( "Right-click to select/deselect "
+ "all nodes" ), "wrap" );
JButton close = new JButton( "Close" );
close.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
editor.dispose();
}
} );
editor.add( close );
EventQueue.invokeLater( new Runnable()
{
public void run()
{
editor.setVisible( true );
}
} );
}
}
} );
}
}
}

View File

@@ -0,0 +1,80 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.event;
import alias.Alias;
/**
* Getter methods to support the Message Activity Table/Model
*/
public interface MessageDetailsProvider
{
/**
* Indicates if the message is valid and has passed crc/integrity checks
*/
public abstract boolean isValid();
/**
* Status of the CRC check of the message
*/
public abstract String getErrorStatus();
/**
* Parsed Message
* @return
*/
public abstract String getMessage();
/**
* Raw ( 0 & 1 ) message bits
*/
public abstract String getBinaryMessage();
/**
* Decoded protocol
*/
public abstract String getProtocol();
/**
* Event - call, data, idle, etc.
*/
public abstract String getEventType();
/**
* Formatted from identifier
*/
public abstract String getFromID();
/**
* From identifier alias (from AliasManager)
*/
public abstract Alias getFromIDAlias();
/**
* Formatted to identifier
*/
public abstract String getToID();
/**
* To identifier alias (from AliasManager)
* @return
*/
public abstract Alias getToIDAlias();
}

View File

@@ -0,0 +1,202 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.fleetsync1;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import map.Plottable;
import message.Message;
import alias.Alias;
import bits.BinaryMessage;
public class Fleetsync1Message extends Message
{
private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
private BinaryMessage mMessage;
public Fleetsync1Message( BinaryMessage message )
{
mMessage = message;
}
public boolean isValid()
{
return true;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( mSDF.format( new Date( System.currentTimeMillis() ) ) );
sb.append( " " );
sb.append( "FS1 [" + mMessage.toString() + "]" );
return sb.toString();
}
/**
* Pads spaces onto the end of the value to make it 'places' long
*/
public String pad( String value, int places, String padCharacter )
{
StringBuilder sb = new StringBuilder();
sb.append( value );
while( sb.length() < places )
{
sb.append( padCharacter );
}
return sb.toString();
}
/**
* Pads an integer value with additional zeroes to make it decimalPlaces long
*/
public String format( int number, int decimalPlaces )
{
StringBuilder sb = new StringBuilder();
int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() );
for( int x = 0; x < paddingRequired; x++)
{
sb.append( "0" );
}
sb.append( number );
return sb.toString();
}
@SuppressWarnings( "unused" )
private int getInt( int[] bits )
{
int retVal = 0;
for( int x = 0; x < bits.length; x++ )
{
if( mMessage.get( bits[ x ] ) )
{
retVal += 1<<x;
}
}
return retVal;
}
@Override
public String getBinaryMessage()
{
return mMessage.toString();
}
@Override
public String getProtocol()
{
// TODO Auto-generated method stub
return null;
}
@Override
public String getEventType()
{
// TODO Auto-generated method stub
return null;
}
@Override
public String getFromID()
{
// TODO Auto-generated method stub
return null;
}
@Override
public Alias getFromIDAlias()
{
// TODO Auto-generated method stub
return null;
}
@Override
public String getToID()
{
// TODO Auto-generated method stub
return null;
}
@Override
public Alias getToIDAlias()
{
// TODO Auto-generated method stub
return null;
}
@Override
public String getMessage()
{
// TODO Auto-generated method stub
return null;
}
@Override
public String getErrorStatus()
{
// TODO Auto-generated method stub
return null;
}
@Override
public Plottable getPlottable()
{
return null;
}
/**
* Provides a listing of aliases contained in the message.
*/
public List<Alias> getAliases()
{
List<Alias> aliases = new ArrayList<Alias>();
Alias from = getFromIDAlias();
if( from != null )
{
aliases.add( from );
}
Alias to = getToIDAlias();
if( to != null )
{
aliases.add( to );
}
return aliases;
}
}

View File

@@ -0,0 +1,53 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.fleetsync1;
import message.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Broadcaster;
import sample.Listener;
import bits.BinaryMessage;
public class Fleetsync1MessageProcessor implements Listener<BinaryMessage>
{
private final static Logger mLog =
LoggerFactory.getLogger( Fleetsync1MessageProcessor.class );
private Broadcaster<Message> mBroadcaster = new Broadcaster<Message>();
@Override
public void receive( BinaryMessage buffer )
{
Fleetsync1Message message = new Fleetsync1Message( buffer );
mBroadcaster.receive( message );
}
public void addMessageListener( Listener<Message> listener )
{
mBroadcaster.addListener( listener );
}
public void removeMessageListener( Listener<Message> listener )
{
mBroadcaster.removeListener( listener );
}
}

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

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

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

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

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

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

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

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

View File

@@ -0,0 +1,138 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.lj1200;
import module.decode.DecoderType;
import module.decode.event.CallEvent;
import alias.Alias;
import alias.AliasList;
public class LJ1200CallEvent extends CallEvent
{
public LJ1200CallEvent( CallEventType callEventType,
AliasList aliasList,
String fromID,
String toID,
String details )
{
super( DecoderType.LJ_1200, callEventType,
aliasList, fromID, toID, details );
}
private Alias getAlias( String ident )
{
if( hasAliasList() )
{
return getAliasList().getFleetsyncAlias( ident );
}
return null;
}
@Override
public Alias getFromIDAlias()
{
return getAlias( getFromID() );
}
@Override
public Alias getToIDAlias()
{
return getAlias( getToID() );
}
@Override
public String getChannel()
{
return null;
}
@Override
public long getFrequency()
{
return 173075000;
}
public static class Builder
{
/* Required parameters */
private CallEventType mCallEventType;
/* Optional parameters */
private AliasList mAliasList;
private String mFromID;
private String mToID;
private String mDetails;
public Builder( CallEventType callEventType )
{
mCallEventType = callEventType;
}
public Builder aliasList( AliasList aliasList )
{
mAliasList = aliasList;
return this;
}
public Builder from( String val )
{
mFromID = val;
return this;
}
public Builder details( String details )
{
mDetails = details;
return this;
}
public Builder to( String toID )
{
mToID = toID;
return this;
}
public LJ1200CallEvent build()
{
return new LJ1200CallEvent( this );
}
}
/**
* Private constructor for the builder
*/
private LJ1200CallEvent( Builder builder )
{
this( builder.mCallEventType,
builder.mAliasList,
builder.mFromID,
builder.mToID,
builder.mDetails );
}
public static LJ1200CallEvent getLJ1200Event( LJ1200Message message )
{
CallEventType type = CallEventType.COMMAND;
return new LJ1200CallEvent.Builder( type )
.to( message.getAddress() )
.details( message.getMessage() )
.build();
}
}

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 module.decode.lj1200;
import instrument.Instrumentable;
import instrument.tap.Tap;
import instrument.tap.stream.BinaryTap;
import instrument.tap.stream.FloatBufferTap;
import instrument.tap.stream.FloatTap;
import java.util.ArrayList;
import java.util.List;
import module.decode.Decoder;
import module.decode.DecoderType;
import sample.Broadcaster;
import sample.Listener;
import sample.real.IRealBufferListener;
import sample.real.RealBuffer;
import sample.real.RealSampleListener;
import alias.AliasList;
import bits.MessageFramer;
import bits.SyncPattern;
import dsp.filter.Filters;
import dsp.filter.fir.real.RealFIRFilter_RB_RB;
import dsp.filter.halfband.real.HalfBandFilter_RB_RB;
import dsp.fsk.FSK2Decoder;
import dsp.fsk.FSK2Decoder.Output;
/**
* LJ1200 - 1200 baud 2FSK decoder
*/
public class LJ1200Decoder extends Decoder implements IRealBufferListener,
Instrumentable
{
/* Decimated sample rate ( 48,000 / 2 = 24,000 ) feeding the decoder */
private static final int DECIMATED_SAMPLE_RATE = 24000;
/* Baud or Symbol Rate */
private static final int SYMBOL_RATE = 1200;
/* Message length - 16-bit sync plus 64 bit message */
private static final int MESSAGE_LENGTH = 80;
/* Instrumentation Taps */
private ArrayList<Tap> mAvailableTaps;
private static final String INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD =
"Tap Point: Bandpass Filter > < FSK2 Decoder";
private static final String INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER =
"Tap Point: FSK2 Decoder > < Message Framer";
private FSK2Decoder mFSKDecoder;
private Broadcaster<Boolean> mFSKDecoderBroadcaster =
new Broadcaster<Boolean>();
private HalfBandFilter_RB_RB mDecimationFilter;
private RealFIRFilter_RB_RB mBandPassFilter;
private MessageFramer mTowerMessageFramer;
private MessageFramer mTransponderMessageFramer;
private LJ1200MessageProcessor mMessageProcessor;
public LJ1200Decoder( AliasList aliasList )
{
super( new LJ1200DecoderState( aliasList ) );
mDecimationFilter = new HalfBandFilter_RB_RB(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0f, true );
mBandPassFilter = new RealFIRFilter_RB_RB(
Filters.FIRBP_1200FSK_24000FS.getCoefficients(), 1.02f );
mDecimationFilter.setListener( mBandPassFilter );
mFSKDecoder = new FSK2Decoder( DECIMATED_SAMPLE_RATE,
SYMBOL_RATE, Output.INVERTED );
mBandPassFilter.setListener( mFSKDecoder );
mFSKDecoder.setListener( mFSKDecoderBroadcaster );
mTowerMessageFramer = new MessageFramer(
SyncPattern.LJ1200.getPattern(), MESSAGE_LENGTH );
mTransponderMessageFramer = new MessageFramer(
SyncPattern.LJ1200_TRANSPONDER.getPattern(), MESSAGE_LENGTH );
mFSKDecoderBroadcaster.addListener( mTowerMessageFramer );
mFSKDecoderBroadcaster.addListener( mTransponderMessageFramer );
mMessageProcessor = new LJ1200MessageProcessor( aliasList );
mTowerMessageFramer.addMessageListener( mMessageProcessor );
mTransponderMessageFramer.addMessageListener( mMessageProcessor );
mMessageProcessor.setMessageListener( mMessageBroadcaster );
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.LJ_1200;
}
public void dispose()
{
super.dispose();
mDecimationFilter.dispose();
mBandPassFilter.dispose();
mFSKDecoder.dispose();
mTowerMessageFramer.dispose();
mMessageProcessor.dispose();
}
/**
* Returns a float listener interface for connecting this decoder to a
* float stream provider
*/
public RealSampleListener getRealReceiver()
{
return (RealSampleListener)mDecimationFilter;
}
@Override
public List<Tap> getTaps()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
mAvailableTaps.add( new FloatTap(
INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD, 0, 0.5f ) );
mAvailableTaps.addAll( mFSKDecoder.getTaps() );
mAvailableTaps.add( new BinaryTap(
INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER, 0, 0.025f ) );
}
return mAvailableTaps;
}
@Override
public void addTap( Tap tap )
{
mFSKDecoder.addTap( tap );
switch( tap.getName() )
{
case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
FloatBufferTap bpTap = (FloatBufferTap)tap;
mBandPassFilter.setListener( bpTap );
bpTap.setListener( mFSKDecoder );
break;
case INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER:
BinaryTap decoderTap = (BinaryTap)tap;
mFSKDecoder.setListener( decoderTap );
decoderTap.setListener( mTowerMessageFramer );
break;
}
}
@Override
public void removeTap( Tap tap )
{
mFSKDecoder.removeTap( tap );
switch( tap.getName() )
{
case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
mBandPassFilter.setListener( mFSKDecoder );
break;
case INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER:
mFSKDecoder.setListener( mTowerMessageFramer );
break;
}
}
@Override
public Listener<RealBuffer> getRealBufferListener()
{
return mDecimationFilter;
}
}

View File

@@ -0,0 +1,144 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.lj1200;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import message.Message;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderState;
import module.decode.state.DecoderStateEvent;
import module.decode.state.DecoderStateEvent.Event;
import module.decode.state.State;
import alias.Alias;
import alias.AliasList;
public class LJ1200DecoderState extends DecoderState
{
private Set<String> mAddresses = new TreeSet<String>();
private String mAddress;
private Alias mAddressAlias;
public LJ1200DecoderState( AliasList aliasList )
{
super( aliasList );
}
public String getAddress()
{
return mAddress;
}
public Alias getAddressAlias()
{
return mAddressAlias;
}
@Override
public void receive( Message message )
{
if( message instanceof LJ1200Message )
{
LJ1200Message lj = (LJ1200Message)message;
if( lj.isValid() )
{
setAddress( lj.getAddress() );
setAddressAlias( lj.getAddressAlias() );
mAddresses.add( mAddress );
broadcast( LJ1200CallEvent.getLJ1200Event( lj ) );
broadcast( new DecoderStateEvent( this, Event.DECODE, State.DATA ) );
}
}
else if( message instanceof LJ1200TransponderMessage )
{
}
}
private void setAddress( String address )
{
mAddress = address;
broadcast( ChangedAttribute.TO_TALKGROUP );
}
private void setAddressAlias( Alias alias )
{
mAddressAlias = alias;
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "=============================\n" );
sb.append( "Decoder:\tLJ-1200I\n\n" );
if( !mAddresses.isEmpty() )
{
sb.append( "Transponder Addresses:\n" );
List<String> addresses = new ArrayList<String>( mAddresses );
Collections.sort( addresses );
for( String address: addresses )
{
sb.append( "\t" );
sb.append( address );
sb.append( "\n" );
}
}
return sb.toString();
}
@Override
public void init()
{
/* No initialization steps */
}
@Override
public void receiveDecoderStateEvent( DecoderStateEvent event )
{
if( event.getEvent() == Event.RESET )
{
}
}
@Override
public void reset()
{
mAddresses.clear();
setAddress( null );
setAddressAlias( null );
}
}

View File

@@ -0,0 +1,424 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.lj1200;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import map.Plottable;
import message.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.Alias;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
import edac.CRCLJ;
public class LJ1200Message extends Message
{
private final static Logger mLog = LoggerFactory.getLogger( LJ1200Message.class );
public static final String[] REPLY_CODE = { "0","1","2","3","4","5","6","7",
"8","9","A","C","D","E","F","G","H","J","K","L","M","N","P","Q","R","S",
"T","U","V","W","X","Y" };
public static int[] SYNC = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
public static int[] VRC = { 23,22,21,20,19,18,17,16 };
public static int[] LRC = { 31,30,29,28,27,26,25,24 };
public static int[] FUNCTION = { 35,34,33,32 };
public static int[] ADDRESS = { 63,62,61,60,59,58,57,56,55,54,53,52,51,50,
49,48,47,46,45,44,43,42,41,40,39,38,37,36 };
public static int[] REPLY_1 = { 39,38,37,36,43 };
public static int[] REPLY_2 = { 42,41,40,47,46 };
public static int[] REPLY_3 = { 45,44,51,50,49 };
public static int[] REPLY_4 = { 48,55,54,53,52 };
public static int[] REPLY_5 = { 59,58,57,56,63 };
public static int[] MESSAGE_CRC = { 79,78,77,76,75,74,73,72,71,70,69,68,67,
66,65,64 };
private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" );
private BinaryMessage mMessage;
private AliasList mAliasList;
private CRC mCRC;
public LJ1200Message( BinaryMessage message, AliasList list )
{
mMessage = message;
mAliasList = list;
checkCRC();
switch( mCRC )
{
case CORRECTED:
mLog.debug( "CORR:" + message.toString() );
break;
case FAILED_CRC:
mLog.debug( "FAIL:" + message.toString() );
break;
case PASSED:
mLog.debug( "PASS:" + message.toString() );
break;
}
}
private void checkCRC()
{
mCRC = CRCLJ.checkAndCorrect( mMessage );
}
public boolean isValid()
{
return mCRC == CRC.PASSED || mCRC == CRC.CORRECTED;
}
public String getVRC()
{
return mMessage.getHex( VRC, 2 );
}
public String getLRC()
{
return mMessage.getHex( LRC, 2 );
}
public String getCRC()
{
return mMessage.getHex( MESSAGE_CRC, 4 );
}
public Function getFunction()
{
return Function.fromValue( mMessage.getInt( FUNCTION ),
mMessage.getInt( REPLY_3 ) );
}
public String getAddress()
{
return mMessage.getHex( ADDRESS, 7 );
}
public Alias getAddressAlias()
{
if( mAliasList != null )
{
return mAliasList.getESNAlias( getAddress() );
}
return null;
}
public Alias getSiteAndReplyCodeAlias()
{
if( mAliasList != null &&
( getFunction() == Function.F1_SITE_ID ||
getFunction() == Function.F1_SPEED_UP ) )
{
return mAliasList.getSiteID( getReplyCode() );
}
return null;
}
/**
* 5 character reply code for function E and F, transponder activation.
* @return - reply code
*/
public String getReplyCode()
{
StringBuilder sb = new StringBuilder();
sb.append( REPLY_CODE[ mMessage.getInt( REPLY_1 ) ] );
sb.append( REPLY_CODE[ mMessage.getInt( REPLY_2 ) ] );
sb.append( REPLY_CODE[ mMessage.getInt( REPLY_3 ) ] );
sb.append( REPLY_CODE[ mMessage.getInt( REPLY_4 ) ] );
sb.append( REPLY_CODE[ mMessage.getInt( REPLY_5 ) ] );
return sb.toString();
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
Function function = getFunction();
sb.append( "FUNCTION: " );
sb.append( function.toString() );
switch( function )
{
case F1_SITE_ID:
sb.append( " SITE [" );
break;
case F1_SPEED_UP:
case F2_TEST:
case F3_DEACTIVATE:
case F4_ACTIVATE:
case FF_TRACK_PULSE:
default:
sb.append( " REPLY CODE [" );
break;
}
sb.append( getReplyCode() );
Alias site = getSiteAndReplyCodeAlias();
if( site != null )
{
sb.append( "/" );
sb.append( site.getName() );
}
sb.append( "] ADDRESS [" );
sb.append( getAddress() );
sb.append( "] VRC [" + getVRC() );
sb.append( "] LRC [" + getLRC() );
sb.append( "] CRC [" + getCRC() );
sb.append( "]" );
return sb.toString();
}
/**
* Pads spaces onto the end of the value to make it 'places' long
*/
public String pad( String value, int places, String padCharacter )
{
StringBuilder sb = new StringBuilder();
sb.append( value );
while( sb.length() < places )
{
sb.append( padCharacter );
}
return sb.toString();
}
/**
* Pads an integer value with additional zeroes to make it decimalPlaces long
*/
public String format( int number, int decimalPlaces )
{
StringBuilder sb = new StringBuilder();
int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() );
for( int x = 0; x < paddingRequired; x++)
{
sb.append( "0" );
}
sb.append( number );
return sb.toString();
}
@Override
public String getBinaryMessage()
{
return mMessage.toString();
}
@Override
public String getProtocol()
{
return "LJ-1200";
}
@Override
public String getEventType()
{
if( getFunction() == Function.F1_SPEED_UP )
{
return "TRANSPONDER";
}
return "TOWER";
}
@Override
public String getFromID()
{
return getReplyCode();
}
@Override
public Alias getFromIDAlias()
{
return getSiteAndReplyCodeAlias();
}
@Override
public String getToID()
{
return getAddress();
}
@Override
public Alias getToIDAlias()
{
return null;
}
@Override
public String getMessage()
{
return toString();
}
@Override
public String getErrorStatus()
{
return mCRC.getDisplayText();
}
@Override
public Plottable getPlottable()
{
return null;
}
public enum Function
{
/* Big Endian Format */
F0_UNKNOWN( "0-UNKNOWN" ),
F1_SITE_ID( "1Y-SITE ID" ),
F1_SPEED_UP( "1-SPEED UP" ),
F2_TEST( "2-TOWER TEST" ),
F3_DEACTIVATE( "3-DEACTIVATE" ),
F4_ACTIVATE( "4-ACTIVATE" ),
F5_TRANSPONDER_TEST_REPLY( "5-TRANSPONDER TEST REPLY" ),
F6_TRANSPONDER_TEST_COMMAND( "6-TRANSPONDER TEST COMMAND" ),
F7_QUIET_COMMAND( "7-QUIET COMMAND" ),
F8_UNKNOWN( "8-UNKNOWN" ),
F9_UNKNOWN( "9-UNKNOWN" ),
FA_UNKNOWN( "A-UNKNOWN" ),
FB_UNKNOWN( "B-UNKNOWN" ),
FC_ALT_DEACTIVATE( "C-ALT-DEACTIVATE" ),
FD_UNKNOWN( "D-UNKNOWN" ),
FE_UNKNOWN( "E-UNKNOWN" ),
FF_TRACK_PULSE( "F-TRACK PULSE" ),
UNKNOWN( "UNKNOWN" );
private String mLabel;
private Function( String label )
{
mLabel = label;
}
public String getLabel()
{
return mLabel;
}
public String toString()
{
return getLabel();
}
public static Function fromValue( int value, int replyCodeDigit3 )
{
switch( value )
{
case 0:
return Function.F0_UNKNOWN;
case 1:
if( replyCodeDigit3 == 31 ) /* 'Y' middle character */
{
return Function.F1_SITE_ID;
}
else
{
return Function.F1_SPEED_UP;
}
case 2:
return Function.F2_TEST;
case 3:
return Function.F3_DEACTIVATE;
case 4:
return Function.F4_ACTIVATE;
case 5:
return Function.F5_TRANSPONDER_TEST_REPLY;
case 6:
return Function.F6_TRANSPONDER_TEST_COMMAND;
case 7:
return Function.F7_QUIET_COMMAND;
case 8:
return Function.F8_UNKNOWN;
case 9:
return Function.F9_UNKNOWN;
case 10:
return Function.FA_UNKNOWN;
case 11:
return Function.FB_UNKNOWN;
case 12:
return Function.FC_ALT_DEACTIVATE;
case 13:
return Function.FD_UNKNOWN;
case 14:
return Function.FE_UNKNOWN;
case 15:
return Function.FF_TRACK_PULSE;
default:
return Function.UNKNOWN;
}
}
}
/**
* Provides a listing of aliases contained in the message.
*/
public List<Alias> getAliases()
{
List<Alias> aliases = new ArrayList<Alias>();
Alias siteAndReply = getSiteAndReplyCodeAlias();
if( siteAndReply != null )
{
aliases.add( siteAndReply );
}
Alias address = getAddressAlias();
if( address != null )
{
aliases.add( address );
}
return aliases;
}
}

View File

@@ -0,0 +1,53 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.lj1200;
import java.util.Collections;
import java.util.List;
import message.Message;
import filter.Filter;
import filter.FilterElement;
public class LJ1200MessageFilter extends Filter<Message>
{
public LJ1200MessageFilter()
{
super( "LJ-1200 Message Filter" );
}
@Override
public boolean passes( Message message )
{
return mEnabled && canProcess( message );
}
@Override
public boolean canProcess( Message message )
{
return message instanceof LJ1200Message ||
message instanceof LJ1200TransponderMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return Collections.EMPTY_LIST;
}
}

View File

@@ -0,0 +1,84 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.lj1200;
import message.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import alias.AliasList;
import bits.BinaryMessage;
public class LJ1200MessageProcessor implements Listener<BinaryMessage>
{
private final static Logger mLog =
LoggerFactory.getLogger( LJ1200MessageProcessor.class );
public static int[] SYNC = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
public static int SYNC_TOWER = 0x550F;
public static int SYNC_TRANSPONDER = 0x2AD5;
private Listener<Message> mMessageListener;
private AliasList mAliasList;
public LJ1200MessageProcessor( AliasList list )
{
mAliasList = list;
}
public void dispose()
{
mAliasList = null;
mMessageListener = null;
}
@Override
public void receive( BinaryMessage buffer )
{
int sync = buffer.getInt( SYNC );
if( sync == SYNC_TOWER )
{
if( mMessageListener != null )
{
mMessageListener.receive( new LJ1200Message( buffer, mAliasList ) );
}
}
else if( sync == SYNC_TRANSPONDER )
{
if( mMessageListener != null )
{
mMessageListener.receive( new LJ1200TransponderMessage( buffer, mAliasList ) );
}
}
}
public void setMessageListener( Listener<Message> listener )
{
mMessageListener = listener;
}
public void removeMessageListener()
{
mMessageListener = null;
}
}

View File

@@ -0,0 +1,111 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.lj1200;
import javax.swing.JLabel;
import alias.Alias;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderPanel;
import net.miginfocom.swing.MigLayout;
import settings.ColorSetting;
import settings.ColorSetting.ColorSettingName;
import settings.Setting;
import settings.SettingsManager;
public class LJ1200Panel extends DecoderPanel
{
private static final long serialVersionUID = 1L;
private static final String PROTOCOL = "LJ-1200";
private JLabel mProtocol = new JLabel( PROTOCOL );
private JLabel mTo = new JLabel();
private JLabel mToAlias = new JLabel();
public LJ1200Panel( SettingsManager settingsManager, LJ1200Decoder decoder )
{
super( settingsManager, decoder );
init();
}
public void dispose()
{
super.dispose();
}
protected void init()
{
/* Calling super init will get and broadcast color settings to properly
* setup the jlabel colors */
super.init();
setLayout( new MigLayout( "insets 1 0 0 0", "[grow,fill]", "" ) );
add( mProtocol );
add( mTo );
add( mToAlias, "wrap" );
}
private LJ1200DecoderState getState()
{
return (LJ1200DecoderState)getDecoder().getDecoderState();
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
switch( changedAttribute )
{
case TO_TALKGROUP:
mTo.setText( getState().getAddress() );
repaint();
break;
case FROM_TALKGROUP_ALIAS:
Alias alias = getState().getAddressAlias();
if( alias != null )
{
mToAlias.setText( alias.getName() );
repaint();
}
break;
default:
break;
}
}
@Override
public void settingChanged( Setting setting )
{
super.settingChanged( setting );
if( setting instanceof ColorSetting )
{
ColorSetting color = (ColorSetting)setting;
if( color.getColorSettingName() ==
ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER )
{
mProtocol.setForeground( color.getColor() );
mTo.setForeground( color.getColor() );
mToAlias.setForeground( color.getColor() );
}
}
}
}

View File

@@ -0,0 +1,195 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.lj1200;
import java.text.SimpleDateFormat;
import map.Plottable;
import message.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.Alias;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
import edac.CRCLJ;
public class LJ1200TransponderMessage extends Message
{
private final static Logger mLog = LoggerFactory.getLogger( LJ1200TransponderMessage.class );
public static int[] SYNC = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
public static int[] MESSAGE_CRC = { 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79 };
private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" );
private BinaryMessage mMessage;
private AliasList mAliasList;
private CRC mCRC;
public LJ1200TransponderMessage( BinaryMessage message, AliasList list )
{
mMessage = message;
mAliasList = list;
checkCRC();
switch( mCRC )
{
case CORRECTED:
mLog.debug( "CORR:" + message.toString() );
break;
case FAILED_CRC:
mLog.debug( "FAIL:" + message.toString() );
break;
case PASSED:
mLog.debug( "PASS:" + message.toString() );
break;
case UNKNOWN:
mLog.debug( "UNKNOWN:" + message.toString() );
break;
}
}
private void checkCRC()
{
mCRC = CRC.UNKNOWN;
}
public boolean isValid()
{
// return mCRC == CRC.PASSED || mCRC == CRC.CORRECTED;
/* Override validity check until proper CRC is implemented */
return true;
}
public String getCRC()
{
return mMessage.getHex( MESSAGE_CRC, 4 );
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( "TRANSPONDER TEST" );
return sb.toString();
}
/**
* Pads spaces onto the end of the value to make it 'places' long
*/
public String pad( String value, int places, String padCharacter )
{
StringBuilder sb = new StringBuilder();
sb.append( value );
while( sb.length() < places )
{
sb.append( padCharacter );
}
return sb.toString();
}
/**
* Pads an integer value with additional zeroes to make it decimalPlaces long
*/
public String format( int number, int decimalPlaces )
{
StringBuilder sb = new StringBuilder();
int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() );
for( int x = 0; x < paddingRequired; x++)
{
sb.append( "0" );
}
sb.append( number );
return sb.toString();
}
@Override
public String getBinaryMessage()
{
return mMessage.toString();
}
@Override
public String getProtocol()
{
return "LJ-1200";
}
@Override
public String getEventType()
{
return "TRANSPONDER";
}
@Override
public String getFromID()
{
return null;
}
@Override
public Alias getFromIDAlias()
{
return null;
}
@Override
public String getToID()
{
return null;
}
@Override
public Alias getToIDAlias()
{
return null;
}
@Override
public String getMessage()
{
return toString();
}
@Override
public String getErrorStatus()
{
return mCRC.getDisplayText();
}
@Override
public Plottable getPlottable()
{
return null;
}
}

View File

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

View File

@@ -0,0 +1,238 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrnet;
import module.decode.DecoderType;
import module.decode.event.CallEvent;
import module.decode.ltrstandard.LTRStandardMessage;
import alias.Alias;
import alias.AliasList;
public class LTRCallEvent extends CallEvent
{
private String mChannel;
private long mFrequency;
private int mValidCallMessages = 1;
public LTRCallEvent( CallEventType callEventType,
AliasList aliasList,
String fromID,
String toID,
String channel,
long frequency,
String details )
{
super( DecoderType.LTR_STANDARD, callEventType,
aliasList, fromID, toID, details );
mChannel = channel;
mFrequency = frequency;
}
/**
* LTR sends multiple CALL messages during a call. In order to detect noise
* bursts from valid calls, send every valid call message to this method
* and the event will keep a tally of valid call messages in order to
* determine if the call was a valid (and loggable) call event.
*/
public boolean addMessage( LTRStandardMessage message )
{
String talkgroup = message.getTalkgroupID();
if( talkgroup.contentEquals( getToID() ) )
{
mValidCallMessages++;
return true;
}
return false;
}
/**
* Checks if the message's talkgroup matches this call event. Counts the
* number of messages that match this call event to use in determining if
* a call event is valid or an isolated (errant) message.
*
* See: isValid()
*/
public boolean isMatchingTalkgroup( LTRNetMessage message )
{
String talkgroup = message.getTalkgroupID();
if( talkgroup != null && getToID() != null &&
talkgroup.contentEquals( getToID() ) )
{
mValidCallMessages++;
return true;
}
return false;
}
public boolean isValid()
{
if( getCallEventType() == CallEventType.CALL )
{
return mValidCallMessages > 1;
}
return true;
}
private Alias getAlias( String talkgroup )
{
if( hasAliasList() )
{
return getAliasList().getTalkgroupAlias( talkgroup );
}
return null;
}
@Override
public Alias getFromIDAlias()
{
if( mCallEventType == CallEventType.CALL ||
mCallEventType == CallEventType.REGISTER ||
mCallEventType == CallEventType.ID_UNIQUE )
{
if( hasAliasList() )
{
try
{
int radioID = Integer.parseInt( getFromID() );
return mAliasList.getUniqueID( radioID );
}
catch( Exception e )
{
//Do nothing, we couldn't parse the int radio id value
}
}
}
else if( mCallEventType == CallEventType.REGISTER_ESN )
{
if( hasAliasList() )
{
return mAliasList.getESNAlias( getFromID() );
}
}
else
{
return getAlias( getFromID() );
}
return null;
}
@Override
public Alias getToIDAlias()
{
return getAlias( getToID() );
}
@Override
public String getChannel()
{
return mChannel;
}
@Override
public long getFrequency()
{
return mFrequency;
}
public static class Builder
{
/* Required parameters */
private CallEventType mCallEventType;
/* Optional parameters */
private AliasList mAliasList;
private String mFromID;
private String mToID;
private String mDetails;
private String mChannel;
private long mFrequency;
public Builder( CallEventType callEventType )
{
mCallEventType = callEventType;
}
public Builder aliasList( AliasList aliasList )
{
mAliasList = aliasList;
return this;
}
public Builder from( String val )
{
mFromID = val;
return this;
}
public Builder channel( String channel )
{
mChannel = channel;
return this;
}
public Builder details( String details )
{
mDetails = details;
return this;
}
public Builder frequency( long frequency )
{
mFrequency = frequency;
return this;
}
public Builder to( String toID )
{
mToID = toID;
return this;
}
public LTRCallEvent build()
{
return new LTRCallEvent( this );
}
}
/**
* Private constructor for the builder
*/
private LTRCallEvent( Builder builder )
{
this( builder.mCallEventType,
builder.mAliasList,
builder.mFromID,
builder.mToID,
builder.mChannel,
builder.mFrequency,
builder.mDetails );
}
}

View File

@@ -0,0 +1,113 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrnet;
import instrument.Instrumentable;
import instrument.tap.Tap;
import java.util.ArrayList;
import java.util.List;
import message.MessageDirection;
import module.decode.Decoder;
import module.decode.DecoderType;
import sample.Listener;
import sample.real.IRealBufferListener;
import sample.real.RealBuffer;
import alias.AliasList;
import bits.MessageFramer;
import bits.SyncPattern;
import dsp.fsk.LTRFSKDecoder;
public class LTRNetDecoder extends Decoder
implements IRealBufferListener, Instrumentable
{
public static final int LTR_STANDARD_MESSAGE_LENGTH = 40;
private LTRFSKDecoder mLTRFSKDecoder;
private MessageFramer mLTRMessageFramer;
private LTRNetMessageProcessor mLTRMessageProcessor;
private List<Tap> mAvailableTaps;
public LTRNetDecoder( DecodeConfigLTRNet config, AliasList aliasList )
{
super( new LTRNetDecoderState( aliasList ) );
mLTRFSKDecoder = new LTRFSKDecoder();
if( config.getMessageDirection() == MessageDirection.OSW )
{
mLTRMessageFramer =
new MessageFramer( SyncPattern.LTR_STANDARD_OSW.getPattern(),
LTR_STANDARD_MESSAGE_LENGTH );
}
else
{
mLTRMessageFramer =
new MessageFramer( SyncPattern.LTR_STANDARD_ISW.getPattern(),
LTR_STANDARD_MESSAGE_LENGTH );
}
mLTRFSKDecoder.addListener( mLTRMessageFramer );
mLTRMessageProcessor = new LTRNetMessageProcessor(
config.getMessageDirection(), aliasList );
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
mLTRMessageProcessor.setMessageListener( mMessageBroadcaster );
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.LTR_NET;
}
@Override
public Listener<RealBuffer> getRealBufferListener()
{
return mLTRFSKDecoder;
}
@Override
public List<Tap> getTaps()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
mAvailableTaps.addAll( mLTRFSKDecoder.getTaps() );
}
return mAvailableTaps;
}
@Override
public void addTap( Tap tap )
{
mLTRFSKDecoder.addTap( tap );
}
@Override
public void removeTap( Tap tap )
{
mLTRFSKDecoder.removeTap( tap );
}
}

View File

@@ -0,0 +1,166 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrnet;
import java.awt.EventQueue;
import javax.swing.JLabel;
import net.miginfocom.swing.MigLayout;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderPanel;
import settings.ColorSetting;
import settings.Setting;
import settings.SettingsManager;
import alias.Alias;
public class LTRNetDecoderPanel extends DecoderPanel
{
private static final long serialVersionUID = 1L;
private JLabel mProtocol = new JLabel( "LTR-Net" );
private JLabel mTalkgroup = new JLabel();
private JLabel mTalkgroupAlias = new JLabel();
private JLabel mLCN = new JLabel( "LCN:" );
private JLabel mDescription = new JLabel();
public LTRNetDecoderPanel( SettingsManager settingsManager, LTRNetDecoder decoder )
{
super( settingsManager, decoder );
init();
}
public void init()
{
super.init();
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]0[]") );
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
mTalkgroup.setFont( mFontDecoder );
mTalkgroup.setForeground( mColorLabelDecoder );
mTalkgroupAlias.setFont( mFontDecoder );
mTalkgroupAlias.setForeground( mColorLabelDecoder );
mLCN.setFont( mFontDecoder );
mLCN.setForeground( mColorLabelDecoder );
mDescription.setFont( mFontDecoder );
mDescription.setForeground( mColorLabelDecoder );
add( mProtocol );
add( mTalkgroup );
add( mTalkgroupAlias, "wrap" );
add( mLCN );
add( mDescription, "span,wrap" );
}
private LTRNetDecoderState getDecoderState()
{
return (LTRNetDecoderState)getDecoder().getDecoderState();
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
LTRNetDecoderState state = getDecoderState();
switch( changedAttribute )
{
case TO_TALKGROUP:
mTalkgroup.setText( state.getToTalkgroup() );
break;
case TO_TALKGROUP_ALIAS:
Alias tgAlias = state.getToTalkgroupAlias();
if( tgAlias != null )
{
mTalkgroupAlias.setText( tgAlias.getName() );
String iconNameString = tgAlias.getIconName();
mTalkgroupAlias.setIcon( mSettingsManager
.getImageIcon( iconNameString, 12 ) );
}
else
{
mTalkgroupAlias.setText( null );
mTalkgroupAlias.setIcon( null );
}
break;
case DESCRIPTION:
mDescription.setText( state.getDescription() );
break;
case CHANNEL_NUMBER:
mLCN.setText( "LCN: " + state.getChannelNumber() );
break;
default:
break;
}
repaint();
}
});
}
@Override
public void settingChanged( Setting setting )
{
super.settingChanged( setting );
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case CHANNEL_STATE_LABEL_DECODER:
if( mProtocol != null )
{
mProtocol.setForeground( mColorLabelDecoder );
}
if( mTalkgroup != null )
{
mTalkgroup.setForeground( mColorLabelDecoder );
}
if( mTalkgroupAlias != null )
{
mTalkgroupAlias.setForeground( mColorLabelDecoder );
}
if( mLCN != null )
{
mLCN.setForeground( mColorLabelDecoder );
}
if( mDescription != null )
{
mDescription.setForeground( mColorLabelDecoder );
}
break;
}
}
}
}

View File

@@ -0,0 +1,707 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrnet;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.TreeSet;
import message.Message;
import module.decode.event.CallEvent;
import module.decode.event.CallEvent.CallEventType;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderState;
import module.decode.state.DecoderStateEvent;
import module.decode.state.DecoderStateEvent.Event;
import module.decode.state.State;
import alias.Alias;
import alias.AliasList;
public class LTRNetDecoderState extends DecoderState
{
private DecimalFormat mDecimalFormatter = new DecimalFormat( "0.00000" );
private TreeSet<String> mTalkgroups = new TreeSet<String>();
private TreeSet<String> mTalkgroupsFirstHeard = new TreeSet<String>();
private TreeSet<String> mESNs = new TreeSet<String>();
private TreeSet<Integer> mUniqueIDs = new TreeSet<Integer>();
private TreeSet<String> mNeighborIDs = new TreeSet<String>();
private TreeSet<String> mSiteIDs = new TreeSet<String>();
private HashMap<Integer,Long> mReceiveFrequencies =
new HashMap<Integer,Long>();
private HashMap<Integer,Long> mTransmitFrequencies =
new HashMap<Integer,Long>();
private HashMap<Integer,String> mActiveCalls = new HashMap<Integer,String>();
private String mTalkgroup;
private Alias mTalkgroupAlias;
private String mDescription;
private int mChannelNumber;
private long mFrequency = 0;
public LTRNetDecoderState( AliasList aliasList )
{
super( aliasList );
}
@Override
public void receive( Message message )
{
if( message.isValid() )
{
if( message instanceof LTRNetOSWMessage )
{
LTRNetOSWMessage ltr = (LTRNetOSWMessage)message;
switch( ltr.getMessageType() )
{
case CA_STRT:
case CA_ENDD:
if( mChannelNumber == 0 )
{
mChannelNumber = ltr.getChannel();
broadcast( ChangedAttribute.CHANNEL_NUMBER );
}
if( ltr.getChannel() == 31 )
{
processCallEndMessage( ltr );
}
else if( ltr.getChannel() == mChannelNumber )
{
processCallMessage( ltr );
}
else
{
processCallDetectMessage( ltr );
}
break;
case SY_IDLE:
if( mChannelNumber != ltr.getChannel() )
{
mChannelNumber = ltr.getChannel();
broadcast( ChangedAttribute.CHANNEL_NUMBER );
}
break;
case MA_CHNH:
break;
case MA_CHNL:
break;
case FQ_RXHI:
case FQ_RXLO:
if( ltr.getFrequency() > 0 )
{
mReceiveFrequencies.put( ltr.getHomeRepeater(),
ltr.getFrequency() );
}
break;
case FQ_TXHI:
case FQ_TXLO:
if( ltr.getFrequency() > 0 )
{
mTransmitFrequencies.put( ltr.getHomeRepeater(),
ltr.getFrequency() );
}
break;
case ID_NBOR:
String neighborID = ltr.getNeighborID();
if( neighborID != null )
{
mNeighborIDs.add( neighborID );
}
break;
case ID_UNIQ:
int uniqueID = ltr.getRadioUniqueID();
if( uniqueID != LTRNetOSWMessage.INT_NULL_VALUE )
{
mUniqueIDs.add( uniqueID );
}
if( getCurrentLTRCallEvent() == null )
{
mCurrentCallEvent = new LTRCallEvent.Builder( CallEventType.REGISTER )
.aliasList( getAliasList() )
.channel( String.valueOf( mChannelNumber ) )
.frequency( mFrequency )
.from( String.valueOf( uniqueID ) )
.build();
}
else
{
mCurrentCallEvent.setFromID(
String.valueOf( ltr.getRadioUniqueID() ) );
mCurrentCallEvent.setDetails( "Unique ID" );
broadcast( mCurrentCallEvent );
}
mTalkgroup = String.valueOf( uniqueID );
broadcast( ChangedAttribute.TO_TALKGROUP );
mTalkgroupAlias = ltr.getRadioUniqueIDAlias();
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
break;
case ID_SITE:
String siteID = ltr.getSiteID();
if( siteID != null )
{
mSiteIDs.add( siteID );
}
break;
default:
break;
}
}
else if( message instanceof LTRNetISWMessage )
{
LTRNetISWMessage ltr = ((LTRNetISWMessage)message);
switch( ltr.getMessageType() )
{
case CA_STRT:
processCallMessage( ltr );
break;
case CA_ENDD:
processCallEndMessage( ltr );
break;
case ID_ESNH:
case ID_ESNL:
String esn = ltr.getESN();
if( !esn.contains( "xxxx" ) )
{
mESNs.add( ltr.getESN() );
}
mDescription = "ESN";
broadcast( ChangedAttribute.DESCRIPTION );
mTalkgroup = ltr.getESN();
broadcast( ChangedAttribute.TO_TALKGROUP );
mTalkgroupAlias = ltr.getESNAlias();
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
broadcast( new DecoderStateEvent( this, Event.DECODE, State.DATA ) );
if( mCurrentCallEvent == null )
{
mCurrentCallEvent = new LTRCallEvent.Builder( CallEventType.REGISTER_ESN )
.aliasList( getAliasList() )
.details( "ESN:" + ltr.getESN() )
.frequency( mFrequency )
.from( ltr.getESN() )
.build();
broadcast( mCurrentCallEvent );
}
break;
case ID_UNIQ:
int uniqueid = ltr.getRadioUniqueID();
if( uniqueid != LTRNetISWMessage.INT_NULL_VALUE )
{
mUniqueIDs.add( uniqueid );
mDescription = "REGISTER UID";
broadcast( ChangedAttribute.DESCRIPTION );
mTalkgroup = String.valueOf( uniqueid );
broadcast( ChangedAttribute.TO_TALKGROUP );
mTalkgroupAlias = ltr.getRadioUniqueIDAlias();
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
if( getCurrentLTRCallEvent() == null )
{
mCurrentCallEvent = new LTRCallEvent.Builder( CallEventType.REGISTER )
.aliasList( getAliasList() )
.channel( String.valueOf( mChannelNumber ) )
.frequency( mFrequency )
.from( String.valueOf( uniqueid ) )
.build();
}
else
{
mCurrentCallEvent.setFromID(
String.valueOf( ltr.getRadioUniqueID() ) );
mCurrentCallEvent.setDetails( "Unique ID" );
broadcast( mCurrentCallEvent );
}
}
break;
default:
break;
}
}
}
}
public LTRCallEvent getCurrentLTRCallEvent()
{
if( mCurrentCallEvent != null )
{
return (LTRCallEvent)mCurrentCallEvent;
}
return null;
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "Activity Summary\n" );
sb.append( "Decoder:\tLTR-Net\n\n" );
if( mSiteIDs.isEmpty() )
{
sb.append( "Site:\tUnknown\n" );
}
else
{
Iterator<String> it = mSiteIDs.iterator();
while( it.hasNext() )
{
sb.append( "Site:\t" );
String siteID = it.next();
sb.append( siteID );
if( hasAliasList() )
{
Alias siteAlias = getAliasList().getSiteID( String.valueOf( siteID ) );
if( siteAlias != null )
{
sb.append( " " );
sb.append( siteAlias.getName() );
}
}
sb.append( "\n" );
}
}
sb.append( "\nLCNs (transmit | receive)\n" );
if( mReceiveFrequencies.isEmpty() && mTransmitFrequencies.isEmpty() )
{
sb.append( " None\n" );
}
else
{
for( int x = 1; x < 21; x++ )
{
long rcv = 0;
if( mReceiveFrequencies.containsKey( x ) )
{
rcv = mReceiveFrequencies.get( x );
}
long xmt = 0;
if( mTransmitFrequencies.containsKey( x ) )
{
xmt = mTransmitFrequencies.get( x );
}
if( rcv > 0 || xmt > 0 )
{
if( x < 10 )
{
sb.append( " " );
}
sb.append( x );
sb.append( ": " );
if( xmt == 0 )
{
sb.append( "---.-----" );
}
else
{
sb.append( mDecimalFormatter.format( (double)xmt/1E6d ) );
}
sb.append( " | " );
if( rcv == 0 )
{
sb.append( "---.-----" );
}
else
{
sb.append( mDecimalFormatter.format( (double)rcv/1E6d ) );
}
if( x == mChannelNumber )
{
sb.append( " **" );
}
sb.append( "\n" );
}
}
}
sb.append( "\nTalkgroups\n" );
if( mTalkgroups.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mTalkgroups.iterator();
while( it.hasNext() )
{
String tgid = it.next();
sb.append( " " );
sb.append( tgid );
sb.append( " " );
if( hasAliasList() )
{
Alias alias = getAliasList().getTalkgroupAlias( tgid );
if( alias != null )
{
sb.append( alias.getName() );
}
}
sb.append( "\n" );
}
}
sb.append( "\nRadio Unique IDs\n" );
if( mUniqueIDs.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<Integer> it = mUniqueIDs.iterator();
while( it.hasNext() )
{
int uid = it.next();
sb.append( " " );
sb.append( uid );
sb.append( " " );
if( hasAliasList() )
{
Alias alias = getAliasList().getUniqueID( uid );
if( alias != null )
{
sb.append( alias.getName() );
}
}
sb.append( "\n" );
}
}
sb.append( "\nESNs\n" );
if( mESNs.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mESNs.iterator();
while( it.hasNext() )
{
String esn = it.next();
sb.append( " " );
sb.append( esn );
sb.append( " " );
if( hasAliasList() )
{
Alias alias = getAliasList().getESNAlias( esn );
if( alias != null )
{
sb.append( alias.getName() );
}
}
sb.append( "\n" );
}
}
sb.append( "\nNeighbor Sites\n" );
if( mNeighborIDs.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mNeighborIDs.iterator();
while( it.hasNext() )
{
String neighbor = it.next();
sb.append( " " );
sb.append( neighbor );
sb.append( " " );
if( hasAliasList() )
{
Alias alias = getAliasList().getSiteID( String.valueOf( neighbor ) );
if( alias != null )
{
sb.append( alias.getName() );
}
}
sb.append( "\n" );
}
}
return sb.toString();
}
/**
* Call Detect - current channel messages indicate a call on another channel
*/
private void processCallDetectMessage( LTRNetMessage message )
{
if( !mActiveCalls.containsKey( message.getChannel() ) ||
!mActiveCalls.get( message.getChannel() ).contentEquals( message.getTalkgroupID() ) )
{
mActiveCalls.put( message.getChannel(), message.getTalkgroupID() );
int channel = message.getChannel();
long frequency = 0;
if( mTransmitFrequencies.containsKey( channel ) )
{
frequency = mTransmitFrequencies.get( channel );
}
broadcast( new LTRCallEvent.Builder( CallEventType.CALL_DETECT )
.aliasList( getAliasList() )
.channel( String.valueOf( message.getChannel() ) )
.frequency( frequency )
.to( message.getTalkgroupID() )
.build() );
}
}
private void processCallMessage( LTRNetMessage message )
{
LTRCallEvent current = getCurrentLTRCallEvent();
int group = message.getGroup();
/* If this is a new call or the talkgroup is different from the current
* call, create a new call event */
if( current == null || !current.isMatchingTalkgroup( message ) )
{
/* Invalidate the current call */
if( current != null )
{
current.setValid( false );
broadcast( current );
}
mTalkgroup = message.getTalkgroupID();
broadcast( ChangedAttribute.TO_TALKGROUP );
/* A talkgroup must be seen at least once before it will be added
* to the mTalkgroups list that is used in the activity summary,
* so that we don't pollute the summary with one-off error talkgroups */
if( mTalkgroupsFirstHeard.contains( mTalkgroup ) )
{
mTalkgroups.add( mTalkgroup );
}
else
{
mTalkgroupsFirstHeard.add( mTalkgroup );
}
mTalkgroupAlias = message.getTalkgroupIDAlias();
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
CallEventType type = CallEventType.CALL;
if( group == 253 )
{
type = CallEventType.REGISTER;
mDescription = "REGISTER";
broadcast( ChangedAttribute.DESCRIPTION );
}
else if( group == 254 )
{
type = CallEventType.STATION_ID;
mDescription = "STATION ID";
broadcast( ChangedAttribute.DESCRIPTION );
}
mCurrentCallEvent = new LTRCallEvent.Builder( type )
.aliasList( getAliasList() )
.channel( String.valueOf( message.getChannel() ) )
.frequency( mFrequency )
.to( message.getTalkgroupID() )
.build();
broadcast( mCurrentCallEvent );
broadcast( new DecoderStateEvent( this, Event.START,
( type == CallEventType.CALL ) ? State.CALL : State.DATA ) );
}
else
{
broadcast( new DecoderStateEvent( this, Event.CONTINUATION,
( group == 253 || group == 254 ) ? State.DATA : State.CALL ) );
}
}
private void processCallEndMessage( LTRNetMessage message )
{
if( mCurrentCallEvent != null &&
mCurrentCallEvent.getCallEventType() == CallEventType.CALL )
{
mCurrentCallEvent.setEnd( System.currentTimeMillis() );
broadcast( mCurrentCallEvent );
}
mCurrentCallEvent = null;
broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
}
/**
* Resets the decoder state for re-use on another channel
*/
public void reset()
{
mChannelNumber = 0;
mActiveCalls.clear();
mESNs.clear();
mNeighborIDs.clear();
mReceiveFrequencies.clear();
mSiteIDs.clear();
mTalkgroups.clear();
mTransmitFrequencies.clear();
mUniqueIDs.clear();
resetState();
}
/**
* Resets the decoder state after a call event
*/
private void resetState()
{
if( mCurrentCallEvent != null && mCurrentCallEvent
.getCallEventType() == CallEventType.CALL )
{
mCurrentCallEvent.setEnd( System.currentTimeMillis() );
broadcast( mCurrentCallEvent );
}
mCurrentCallEvent = null;
mTalkgroup = null;
broadcast( ChangedAttribute.TO_TALKGROUP );
mTalkgroupAlias = null;
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
mDescription = null;
broadcast( ChangedAttribute.DESCRIPTION );
}
public String getToTalkgroup()
{
return mTalkgroup;
}
public Alias getToTalkgroupAlias()
{
return mTalkgroupAlias;
}
public String getDescription()
{
return mDescription;
}
public boolean hasChannelNumber()
{
return mChannelNumber != 0;
}
public int getChannelNumber()
{
return mChannelNumber;
}
@Override
public void init()
{
/* No initialization steps required */
}
@Override
public void receiveDecoderStateEvent( DecoderStateEvent event )
{
switch( event.getEvent() )
{
case RESET:
resetState();
break;
case SOURCE_FREQUENCY:
mFrequency = event.getFrequency();
break;
default:
break;
}
}
}

View File

@@ -0,0 +1,72 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrnet;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import message.MessageDirection;
import module.decode.DecodeEditor;
import module.decode.config.DecodeConfiguration;
public class LTRNetEditor extends DecodeEditor
{
private static final long serialVersionUID = 1L;
private JComboBox<MessageDirection> mComboDirection;
public LTRNetEditor( DecodeConfiguration config )
{
super( config );
initGUI();
}
private void initGUI()
{
mComboDirection = new JComboBox<MessageDirection>();
mComboDirection.setModel(
new DefaultComboBoxModel<MessageDirection>( MessageDirection.values() ) );
mComboDirection.setSelectedItem( ((DecodeConfigLTRNet)mConfig).getMessageDirection() );
mComboDirection.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
MessageDirection selected = mComboDirection
.getItemAt( mComboDirection.getSelectedIndex() );
if( selected != null )
{
((DecodeConfigLTRNet)mConfig).setMessageDirection( selected );
}
}
} );
add( new JLabel( "Format:" ) );
add( mComboDirection, "wrap" );
}
}

View File

@@ -0,0 +1,345 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrnet;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import map.Plottable;
import message.MessageDirection;
import message.MessageType;
import alias.Alias;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
import edac.CRCLTR;
public class LTRNetISWMessage extends LTRNetMessage
{
private static final int sCHIU_UNIQUE_ID = 24;
private static final int sCHIU_ESN_LOW = 27;
private static final int sCHIU_ESN_HIGH = 29;
private static final int sFREE_CALL = 21;
private static final int sFREE_END_CALL = 23;
private static final int sFREE_REQUEST_ACCESS = 31;
private LTRNetISWMessage mAuxMessage;
public LTRNetISWMessage( BinaryMessage message, AliasList list )
{
super( message, MessageDirection.ISW, list );
/**
* If the CRC fails, test for a transmitted CRC of 127 and then check
* the Free field for special messages
*/
if( mCRC == CRC.FAILED_CRC && CRCLTR.getTransmittedChecksum( mMessage ) == 127 )
{
if( getFree() == sFREE_REQUEST_ACCESS ||
getFree() == sFREE_END_CALL )
{
mCRC = CRC.PASSED;
}
}
mMessageType = getMessageType();
}
public void setAuxiliaryMessage( LTRNetISWMessage message )
{
if( message != null )
{
mAuxMessage = message;
}
}
public String getESN()
{
if( mMessageType == MessageType.ID_ESNH )
{
if( mAuxMessage != null &&
mAuxMessage.getMessageType() == MessageType.ID_ESNL )
{
return getESNHigh() + mAuxMessage.getESNLow();
}
else
{
return getESNHigh() + "xxxx";
}
}
else
{
if( mAuxMessage != null &&
mAuxMessage.getMessageType() == MessageType.ID_ESNH )
{
return mAuxMessage.getESNHigh() + getESNLow();
}
else
{
return "xxxx" + getESNLow();
}
}
}
public Alias getESNAlias()
{
if( mAliasList != null )
{
return mAliasList.getESNAlias( getESN() );
}
return null;
}
public String getESNHigh()
{
int esnHigh = getInt( sSIXTEEN_BITS );
return String.format("%04X", esnHigh & 0xFFFF );
}
public String getESNLow()
{
int esnLow = getInt( sSIXTEEN_BITS );
return String.format("%04X", esnLow & 0xFFFF );
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( mDatestampFormatter.format( new Date( System.currentTimeMillis() ) ) );
sb.append( " LTRNet ISW [" );
sb.append( mCRC.getAbbreviation() );
sb.append( "] " );
sb.append( getMessage() );
pad( sb, 100 );
sb.append( mMessage.toString() );
return sb.toString();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
switch( mMessageType )
{
case CA_STRT:
sb.append( "CALL LCN:" );
sb.append( format( getChannel(), 2 ) );
sb.append( " TG [ " );
sb.append( getTalkgroupID() );
sb.append( "/" );
sb.append( getTalkgroupIDAlias() );
sb.append( " ] FREE:" );
sb.append( format( getFree(), 2 ) );
break;
case CA_ENDD:
sb.append( "END* AREA:" );
sb.append( getArea() );
sb.append( " LCN:" );
sb.append( format( getChannel(), 2 ) );
sb.append( " TG [" );
sb.append( getTalkgroupID() );
sb.append( "/" );
sb.append( getTalkgroupIDAlias() );
sb.append( "] FREE:" );
sb.append( format( getFree(), 2 ) );
break;
case RQ_ACCE:
sb.append( "REQUEST ACCESS LCN:" );
sb.append( format( getChannel(), 2 ) );
sb.append( " TG [ " );
sb.append( getTalkgroupID() );
sb.append( "/" );
sb.append( getTalkgroupIDAlias() );
sb.append( " ] FREE:" );
sb.append( format( getFree(), 2 ) );
break;
case ID_ESNL:
sb.append( "ESN LOW " );
sb.append( getESN() );
sb.append( "/" );
sb.append( getESNAlias() );
break;
case ID_ESNH:
sb.append( "ESN HIGH " );
sb.append( getESN() );
sb.append( "/" );
sb.append( getESNAlias() );
break;
case ID_UNIQ:
sb.append( "RADIO UNIQUE ID " );
sb.append( getRadioUniqueID() );
if( getRadioUniqueIDAlias() != null )
{
sb.append( "/" );
sb.append( getRadioUniqueIDAlias().getName() );
}
break;
default:
sb.append( "UNKNOWN " );
sb.append( getArea() );
sb.append( " " );
sb.append( format( getChannel(), 2 ) );
sb.append( " " );
sb.append( format( getHomeRepeater(), 2 ) );
sb.append( " " );
sb.append( format( getGroup(), 3 ) );
sb.append( " " );
sb.append( format( getFree(), 2 ) );
sb.append( " CRC:" );
sb.append( CRCLTR.getTransmittedChecksum( mMessage ) );
sb.append( " CALC:" );
sb.append( CRCLTR.getCalculatedChecksum( mMessage ) );
}
return sb.toString();
}
public int getRadioUniqueID()
{
int retVal = INT_NULL_VALUE;
if( mMessageType == MessageType.ID_UNIQ )
{
retVal = getInt( sSIXTEEN_BITS );
}
return retVal;
}
public Alias getRadioUniqueIDAlias()
{
if( mAliasList != null )
{
return mAliasList.getUniqueID( getRadioUniqueID() );
}
return null;
}
public MessageType getMessageType()
{
MessageType retVal = MessageType.UN_KNWN;
int channel = getChannel();
if( channel == 31 )
{
retVal = MessageType.CA_ENDD;
}
else if( channel > 20 )
{
switch( channel )
{
case sCHIU_UNIQUE_ID:
retVal = MessageType.ID_UNIQ;
break;
case sCHIU_ESN_LOW:
retVal = MessageType.ID_ESNH;
break;
case sCHIU_ESN_HIGH:
retVal = MessageType.ID_ESNL;
break;
}
}
else if( channel > 0 )
{
int free = getFree();
switch( free )
{
case sFREE_CALL:
retVal = MessageType.CA_STRT;
break;
case sFREE_END_CALL:
retVal = MessageType.CA_ENDD;
break;
case sFREE_REQUEST_ACCESS:
retVal = MessageType.RQ_ACCE;
break;
}
}
return retVal;
}
@Override
public String getFromID()
{
return null;
}
@Override
public Alias getFromIDAlias()
{
return null;
}
@Override
public void setAuxiliaryMessage( LTRNetOSWMessage message )
{
// TODO Auto-generated method stub
}
@Override
public Plottable getPlottable()
{
// TODO Auto-generated method stub
return null;
}
/**
* Provides a listing of aliases contained in the message.
*/
public List<Alias> getAliases()
{
List<Alias> aliases = new ArrayList<Alias>();
Alias from = getFromIDAlias();
if( from != null )
{
aliases.add( from );
}
Alias to = getToIDAlias();
if( to != null )
{
aliases.add( to );
}
Alias esn = getESNAlias();
if( esn != null )
{
aliases.add( esn );
}
return aliases;
}
}

View File

@@ -0,0 +1,243 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrnet;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import message.Message;
import message.MessageDirection;
import message.MessageType;
import module.decode.DecoderType;
import alias.Alias;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
import edac.CRCLTR;
public abstract class LTRNetMessage extends Message
{
protected static final String sUNKNOWN = "**UNKNOWN**";
protected static final int INT_NULL_VALUE = -1;
protected static final double sDOUBLE_NULL_VALUE = -1.0D;
protected SimpleDateFormat mDatestampFormatter =
new SimpleDateFormat( "yyyyMMdd HHmmss" );
protected DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000");
protected static final int[] sSYNC = { 8,7,6,5,4,3,2,1,0 };
protected static final int[] sAREA = { 9 };
protected static final int[] sCHANNEL = { 14,13,12,11,10 };
protected static final int[] sHOME_REPEATER = { 19,18,17,16,15 };
protected static final int[] sGROUP = { 27,26,25,24,23,22,21,20 };
protected static final int[] sFREE = { 32,31,30,29,28 };
protected static final int[] sCRC = { 39,38,37,36,35,34,33 };
protected static final int[] sSIXTEEN_BITS = { 32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17 };
protected static final int[] sMANUFACTURER_ID = { 23,22,21,20,19,18,17 };
protected BinaryMessage mMessage;
protected MessageType mMessageType;
protected CRC mCRC;
protected AliasList mAliasList;
public LTRNetMessage( BinaryMessage message,
MessageDirection direction,
AliasList list )
{
mMessage = message;
mCRC = CRCLTR.check( message, direction );
mAliasList = list;
}
public MessageType getMessageType()
{
return mMessageType;
}
public abstract void setAuxiliaryMessage( LTRNetOSWMessage message );
public boolean isValid()
{
return mCRC != CRC.FAILED_CRC && mCRC != CRC.FAILED_PARITY;
}
/**
* Appends spaces to the end of the stringbuilder to make it length long
*/
protected void pad( StringBuilder sb, int length )
{
while( sb.length() < length )
{
sb.append( " " );
}
}
@Override
public String getBinaryMessage()
{
return mMessage.toString();
}
/**
* Returns a value formatted in hex notation.
*
* @param value - integer value
* @return - hex digits returned
*/
protected static String getHex( int value, int digits )
{
return String.format( "%0" + digits + "X", value );
}
protected int getInt( int[] bits )
{
int retVal = 0;
for( int x = 0; x < bits.length; x++ )
{
if( mMessage.get( bits[ x ] ) )
{
retVal += 1<<x;
}
}
return retVal;
}
/**
* Pads an integer value with additional zeroes to make it decimalPlaces long
*/
public String format( int number, int decimalPlaces )
{
StringBuilder sb = new StringBuilder();
int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() );
for( int x = 0; x < paddingRequired; x++)
{
sb.append( "0" );
}
sb.append( number );
return sb.toString();
}
public String format( String val, int places )
{
StringBuilder sb = new StringBuilder();
sb.append( val );
while( sb.length() < places )
{
sb.append( " " );
}
return sb.toString();
}
public CRC getCRC()
{
return mCRC;
}
public int getArea()
{
return getInt( sAREA );
}
public int getChannel()
{
return getInt( sCHANNEL );
}
public int getHomeRepeater()
{
return getInt( sHOME_REPEATER );
}
public int getGroup()
{
return getInt( sGROUP );
}
public String getTalkgroupID()
{
StringBuilder sb = new StringBuilder();
sb.append( getArea() );
sb.append( "-" );
sb.append( format( getHomeRepeater(), 2 ) );
sb.append( "-" );
sb.append( format( getGroup(), 3 ) );
return sb.toString();
}
public Alias getTalkgroupIDAlias()
{
return mAliasList.getTalkgroupAlias( getTalkgroupID() );
}
public int getFree()
{
return getInt( sFREE );
}
public int getCRCChecksum()
{
return getInt( sCRC );
}
@Override
public String getProtocol()
{
return DecoderType.LTR_NET.getDisplayString();
}
@Override
public String getEventType()
{
if( mMessageType != null )
{
return mMessageType.getDisplayText();
}
else
{
return MessageType.UN_KNWN.getDisplayText();
}
}
@Override
public String getToID()
{
return null;
}
@Override
public Alias getToIDAlias()
{
return null;
}
@Override
public String getErrorStatus()
{
return mCRC.getDisplayText();
}
}

View File

@@ -0,0 +1,80 @@
package module.decode.ltrnet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import message.Message;
import message.MessageType;
import filter.Filter;
import filter.FilterElement;
public class LTRNetMessageFilter extends Filter<Message>
{
private HashMap<MessageType, FilterElement<MessageType>> mElements =
new HashMap<MessageType, FilterElement<MessageType>>();
public LTRNetMessageFilter()
{
super( "LTR-Net Message Filter" );
mElements.put( MessageType.CA_STRT,
new FilterElement<MessageType>( MessageType.CA_STRT ) );
mElements.put( MessageType.CA_ENDD,
new FilterElement<MessageType>( MessageType.CA_ENDD ) );
mElements.put( MessageType.SY_IDLE,
new FilterElement<MessageType>( MessageType.SY_IDLE ) );
mElements.put( MessageType.ID_UNIQ,
new FilterElement<MessageType>( MessageType.ID_UNIQ ) );
mElements.put( MessageType.ID_ESNH,
new FilterElement<MessageType>( MessageType.ID_ESNH ) );
mElements.put( MessageType.ID_ESNL,
new FilterElement<MessageType>( MessageType.ID_ESNL ) );
mElements.put( MessageType.RQ_ACCE,
new FilterElement<MessageType>( MessageType.RQ_ACCE ) );
mElements.put( MessageType.ID_SITE,
new FilterElement<MessageType>( MessageType.ID_SITE ) );
mElements.put( MessageType.FQ_RXHI,
new FilterElement<MessageType>( MessageType.FQ_RXHI ) );
mElements.put( MessageType.FQ_RXLO,
new FilterElement<MessageType>( MessageType.FQ_RXLO ) );
mElements.put( MessageType.FQ_TXHI,
new FilterElement<MessageType>( MessageType.FQ_TXHI ) );
mElements.put( MessageType.FQ_TXLO,
new FilterElement<MessageType>( MessageType.FQ_TXLO ) );
mElements.put( MessageType.ID_NBOR,
new FilterElement<MessageType>( MessageType.ID_NBOR ) );
mElements.put( MessageType.MA_CHNH,
new FilterElement<MessageType>( MessageType.MA_CHNH ) );
mElements.put( MessageType.MA_CHNL,
new FilterElement<MessageType>( MessageType.MA_CHNL ) );
}
@Override
public boolean passes( Message message )
{
if( mEnabled && canProcess( message ) )
{
LTRNetMessage ltr = (LTRNetMessage)message;
if( mElements.containsKey( ltr.getMessageType() ) )
{
return mElements.get( ltr.getMessageType() ).isEnabled();
}
}
return false;
}
@Override
public boolean canProcess( Message message )
{
return message instanceof LTRNetMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return new ArrayList<FilterElement<?>>( mElements.values() );
}
}

View File

@@ -0,0 +1,167 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrnet;
import java.util.HashMap;
import message.Message;
import message.MessageDirection;
import message.MessageType;
import sample.Listener;
import alias.AliasList;
import bits.BinaryMessage;
public class LTRNetMessageProcessor implements Listener<BinaryMessage>
{
private Listener<Message> mMessageListener;
private HashMap<Integer,LTRNetOSWMessage> mReceiveHighAuxMessages =
new HashMap<Integer,LTRNetOSWMessage>();
private HashMap<Integer,LTRNetOSWMessage> mReceiveLowAuxMessages =
new HashMap<Integer,LTRNetOSWMessage>();
private HashMap<Integer,LTRNetOSWMessage> mTransmitHighAuxMessages =
new HashMap<Integer,LTRNetOSWMessage>();
private HashMap<Integer,LTRNetOSWMessage> mTransmitLowAuxMessages =
new HashMap<Integer,LTRNetOSWMessage>();
private LTRNetISWMessage mESNHighMessage;
private LTRNetISWMessage mESNLowMessage;
private MessageDirection mDirection;
private AliasList mAliasList;
public LTRNetMessageProcessor( MessageDirection direction,
AliasList list )
{
mDirection = direction;
mAliasList = list;
}
@Override
public void receive( BinaryMessage buffer )
{
if( mMessageListener != null )
{
LTRNetMessage message;
if( mDirection == MessageDirection.OSW )
{
message = new LTRNetOSWMessage( buffer, mAliasList );
if( message.isValid() )
{
if( message.getMessageType() == MessageType.FQ_TXHI )
{
mTransmitHighAuxMessages.put( message.getHomeRepeater(),
(LTRNetOSWMessage)message );
message.setAuxiliaryMessage( mTransmitLowAuxMessages.get(
message.getHomeRepeater() ) );
}
else if( message.getMessageType() == MessageType.FQ_TXLO )
{
mTransmitLowAuxMessages.put( message.getHomeRepeater(),
(LTRNetOSWMessage)message );
message.setAuxiliaryMessage( mTransmitHighAuxMessages.get(
message.getHomeRepeater() ) );
}
else if( message.getMessageType() == MessageType.FQ_RXHI )
{
mReceiveHighAuxMessages.put( message.getHomeRepeater(),
(LTRNetOSWMessage)message );
message.setAuxiliaryMessage( mReceiveLowAuxMessages.get(
message.getHomeRepeater() ) );
}
else if( message.getMessageType() == MessageType.FQ_RXLO )
{
mReceiveLowAuxMessages.put( message.getHomeRepeater(),
(LTRNetOSWMessage)message );
message.setAuxiliaryMessage( mReceiveHighAuxMessages.get(
message.getHomeRepeater() ) );
}
}
}
else
{
buffer.flip( 0, 40 );
message = new LTRNetISWMessage( buffer, mAliasList );
if( message.isValid() )
{
/**
* Catch ESN High messages to marry up with ESN Low messages. Purge
* the ESN high message after 2 messages, so that it doesn't get
* paired with the wrong esn low message
*/
if( message.getMessageType() == MessageType.ID_ESNH )
{
mESNHighMessage = (LTRNetISWMessage)message;
if( mESNLowMessage != null )
{
/* Check that the stored message is not older than
* 5 seconds */
if( System.currentTimeMillis() -
mESNLowMessage.getTimeReceived() < 5000 )
{
((LTRNetISWMessage)message).setAuxiliaryMessage( mESNLowMessage );
}
else
{
mESNLowMessage = null;
}
}
}
else if( message.getMessageType() == MessageType.ID_ESNL )
{
mESNLowMessage = (LTRNetISWMessage)message;
if( mESNHighMessage != null )
{
/* Check that the stored message is not older than 5 seconds */
if( System.currentTimeMillis() - mESNHighMessage.getTimeReceived() < 5000 )
{
((LTRNetISWMessage)message).setAuxiliaryMessage( mESNHighMessage );
}
else
{
mESNHighMessage = null;
}
}
}
}
}
mMessageListener.receive( message );
}
}
public void setMessageListener( Listener<Message> listener )
{
mMessageListener = listener;
}
public void removeMessageListener()
{
mMessageListener = null;
}
}

View File

@@ -0,0 +1,644 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrnet;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import map.Plottable;
import message.MessageDirection;
import message.MessageType;
import alias.Alias;
import alias.AliasList;
import bits.BinaryMessage;
public class LTRNetOSWMessage extends LTRNetMessage
{
private static final int CHANNEL_FREQUENCY_MESSAGE_TYPE_BIT = 20;
private static final int CHANNEL_MAP_MESSAGE_TYPE_BIT = 17;
private static final int CHIU_UNIQUE_ID = 17;
private static final int CHIU_SITE_ID = 18;
private static final int CHIU_TRANSMIT_FREQUENCY = 24;
private static final int CHIU_RECEIVE_FREQUENCY = 25;
private static final int CHIU_NEIGHBOR = 26;
private static final int CHIU_MAP = 28;
private static final int CHIU_CALL_END = 31;
private static final int HOME_DIRECTED_GROUP_CALL = 29;
private static final int HOME_CHANNEL_IN_USE_MUTE_AUDIO = 30;
private static final int GROUP_DO_NOTHING = 253;
private static final int GROUP_CWID = 254;
private static final int GROUP_CHANNEL_IDLE = 255;
private static final int FREE_ALL_CHANNELS_BUSY = 0;
private static final int FREE_LTR_GROUP_CALL = 30;
private static final int FREE_LTRNET_GROUP_CALL = 31;
private LTRNetOSWMessage mAuxMessage;
public LTRNetOSWMessage( BinaryMessage message, AliasList list )
{
super( message, MessageDirection.OSW, list );
mMessageType = getType();
}
public void setAuxiliaryMessage( LTRNetOSWMessage message )
{
if( message != null )
{
mAuxMessage = message;
}
}
public String getRadioUniqueIDText()
{
StringBuilder sb = new StringBuilder();
int radioId = getRadioUniqueID();
if( radioId != INT_NULL_VALUE )
{
sb.append( " RADIO UNIQUE ID: " );
sb.append( radioId );
}
return sb.toString();
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( mDatestampFormatter.format(
new Date( System.currentTimeMillis() ) ) );
sb.append( " LTRNet OSW [" );
sb.append( mCRC.getAbbreviation() );
sb.append( "] " );
sb.append( getMessage() );
pad( sb, 100 );
sb.append( mMessage.toString() );
return sb.toString();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
switch( mMessageType )
{
case SY_IDLE:
sb.append( "IDLE AREA:" );
sb.append( getArea() );
sb.append( " LCN:" );
sb.append( format( getChannel(), 2 ) );
sb.append( " HOME:" );
sb.append( format( getHomeRepeater(), 2 ) );
sb.append( " GRP:" );
sb.append( format( getGroup(), 3 ) );
sb.append( " FREE:" );
sb.append( format( getFree(), 2 ) );
break;
case CA_STRT:
sb.append( "CALL LCN:" );
sb.append( format( getChannel(), 2 ) );
sb.append( " TG [ " );
sb.append( getTalkgroupID() );
sb.append( "/" );
sb.append( getTalkgroupIDAlias() );
sb.append( " ] FREE:" );
sb.append( format( getFree(), 2 ) );
break;
case CA_ENDD:
sb.append( "END* AREA:" );
sb.append( getArea() );
sb.append( " LCN:" );
sb.append( format( getChannel(), 2 ) );
sb.append( " TG [" );
sb.append( getTalkgroupID() );
sb.append( "/" );
sb.append( getTalkgroupIDAlias() );
sb.append( "] FREE:" );
sb.append( format( getFree(), 2 ) );
break;
case MA_CHNL:
sb.append( "CHAN MAP LOW LCN [" );
sb.append( getChannelMapAsString( getChannelMapLow() ) );
sb.append( "]" );
break;
case MA_CHNH:
sb.append( "CHAN MAP HIGH LCN [" );
sb.append( getChannelMapAsString( getChannelMapHigh() ) );
sb.append( "]" );
break;
case FQ_RXLO:
sb.append( "CHAN RX LOW LCN:" );
sb.append( format( getHomeRepeater(), 2 ) );
sb.append( " = " );
long rxFreqLo = getFrequency();
if( rxFreqLo > 0 )
{
sb.append( mDecimalFormatter.format( (double)rxFreqLo / 1E6d ) );
}
else
{
sb.append( "000.00000" );
}
sb.append( " MHz" );
break;
case FQ_RXHI:
sb.append( "CHAN RX HIGH LCN:" );
sb.append( format( getHomeRepeater(), 2 ) );
sb.append( " = " );
long rxfreqhi = getFrequency();
if( rxfreqhi > 0 )
{
sb.append( mDecimalFormatter.format( (double)rxfreqhi / 1E6d ) );
}
else
{
sb.append( "000.00000" );
}
sb.append( " MHz" );
break;
case FQ_TXHI:
sb.append( "CHAN TX LOW LCN:" );
sb.append( format( getHomeRepeater(), 2 ) );
sb.append( " = " );
long txfreq = getFrequency();
if( txfreq > 0 )
{
sb.append( mDecimalFormatter.format( (double)txfreq / 1E6d ) );
}
else
{
sb.append( "000.00000" );
}
sb.append( " MHz" );
break;
case FQ_TXLO:
sb.append( "CHAN TX HIGH LCN:" );
sb.append( format( getHomeRepeater(), 2 ) );
sb.append( " = " );
long txFreqLo = getFrequency();
if( txFreqLo > 0 )
{
sb.append( mDecimalFormatter.format( (double)txFreqLo / 1E6d ) );
}
else
{
sb.append( "000.00000" );
}
sb.append( " MHz" );
break;
case ID_SITE:
String siteId = getSiteID();
if( siteId != null )
{
sb.append( "SITE ID: " );
sb.append( siteId );
sb.append( " " );
sb.append( getSiteIDAlias() );
}
break;
case ID_NBOR:
int neighborRank = getNeighborRank();
String neighborId = getNeighborID();
if( neighborRank != INT_NULL_VALUE &&
neighborId != null )
{
sb.append( "NEIGHBOR SITE " );
sb.append( neighborRank );
sb.append( ": " );
sb.append( neighborId );
sb.append( " " );
sb.append( getNeighborIDAlias() );
}
break;
case ID_UNIQ:
sb.append( "REGISTER RADIO UNIQUE ID:" );
sb.append( getRadioUniqueID() );
break;
default:
sb.append( "UNKNOWN " );
sb.append( getArea() );
sb.append( " " + format( getChannel(), 2 ) );
sb.append( " " + format( getHomeRepeater(), 2 ) );
sb.append( " " + format( getGroup(), 3 ) );
sb.append( " " + format( getFree(), 2 ) );
sb.append( " A:" );
sb.append( getArea() );
sb.append( " C:" );
sb.append( format( getChannel(), 2 ) );
sb.append( " H:" );
sb.append( format( getHomeRepeater(), 2 ) );
sb.append( " G:" );
sb.append( format( getGroup(), 3 ) );
sb.append( " F:" );
sb.append( format( getFree(), 2 ) );
}
return sb.toString();
}
public String getTalkgroupID()
{
StringBuilder sb = new StringBuilder();
sb.append( getArea() );
sb.append( "-" );
sb.append( format( getHomeRepeater(), 2 ) );
sb.append( "-" );
sb.append( format( getGroup(), 3 ) );
return sb.toString();
}
public Alias getTalkgroupIDAlias()
{
if( mAliasList != null )
{
return mAliasList.getTalkgroupAlias( getTalkgroupID() );
}
return null;
}
public ArrayList<Integer> getChannelMapLow()
{
ArrayList<Integer> retVal = new ArrayList<Integer>();
if( mMessageType == MessageType.MA_CHNL )
{
for( int x = 27; x >= 18; x-- )
{
if( mMessage.get( x ) )
{
retVal.add( 28 - x );
}
}
}
return retVal;
}
public ArrayList<Integer> getChannelMapHigh()
{
ArrayList<Integer> retVal = new ArrayList<Integer>();
if( mMessageType == MessageType.MA_CHNH )
{
for( int x = 27; x >= 18; x-- )
{
if( mMessage.get( x ) )
{
retVal.add( 38 - x );
}
}
}
return retVal;
}
public String getChannelMapAsString( ArrayList<Integer> channels )
{
StringBuilder sb = new StringBuilder();
for( Integer channel: channels )
{
sb.append( channel );
sb.append( " " );
}
return sb.toString();
}
public int getRadioUniqueID()
{
int retVal = INT_NULL_VALUE;
if( mMessageType == MessageType.ID_UNIQ )
{
retVal = mMessage.getInt( 17, 32 );
}
return retVal;
}
public Alias getRadioUniqueIDAlias()
{
if( mAliasList != null )
{
return mAliasList.getUniqueID( getRadioUniqueID() );
}
return null;
}
public String getSiteID()
{
if( mMessageType == MessageType.ID_SITE )
{
return String.valueOf( mMessage.getInt( 23, 32 ) );
}
return null;
}
public Alias getSiteIDAlias()
{
if( mAliasList != null )
{
return mAliasList.getSiteID( getSiteID() );
}
return null;
}
public String getNeighborID()
{
if( mMessageType == MessageType.ID_NBOR )
{
int id = mMessage.getInt( 23, 32 );
if( id > 0 )
{
return String.valueOf( id );
}
}
return null;
}
private Alias getNeighborIDAlias()
{
if( mAliasList != null )
{
return mAliasList.getSiteID( getNeighborID() );
}
return null;
}
public int getNeighborRank()
{
int retVal = INT_NULL_VALUE;
if( mMessageType == MessageType.ID_NBOR )
{
retVal = mMessage.getInt( 15, 18 ) + 1;
}
return retVal;
}
public long getFrequency()
{
long retVal = 0;
if( mAuxMessage != null )
{
int high;
int low;
MessageType type = getMessageType();
if( type == MessageType.FQ_RXHI || type == MessageType.FQ_TXHI )
{
high = getHighChannelUnits();
low = mAuxMessage.getLowChannelUnits();
}
else
{
high = mAuxMessage.getHighChannelUnits();
low = getLowChannelUnits();
}
if( high != INT_NULL_VALUE && low != INT_NULL_VALUE )
{
retVal = ( high + low ) * 1250 + 150000000;
}
}
return retVal;
}
/**
* Returns the value of the high order (15 - 12) frequency bits which are
* the lower 4 bits of the Free field, left shifted by 12, representing the
* frequency value in units of .00125 MHz
*/
public int getHighChannelUnits()
{
int retVal = INT_NULL_VALUE;
if( mAuxMessage != null &&
( mMessageType == MessageType.FQ_RXHI ||
mMessageType == MessageType.FQ_TXHI ) )
{
retVal = mMessage.getInt( 29, 32 );
retVal = Integer.rotateLeft( retVal, 12 );
}
return retVal;
}
/**
* Returns the value of the low order (11 - 0) frequency bits which are
* contained in the Group and Free fields representing the frequency value
* units of .00125 MHz
*/
public int getLowChannelUnits()
{
int retVal = INT_NULL_VALUE;
if( mMessageType == MessageType.FQ_RXLO ||
mMessageType == MessageType.FQ_TXLO )
{
retVal = mMessage.getInt( 21, 32 );
}
return retVal;
}
public MessageType getType()
{
MessageType retVal = MessageType.UN_KNWN;
int channel = getChannel();
int home = getHomeRepeater();
//LTR-Net messages
if( home != 31 && ( channel > 20 || home > 20 ) )
{
switch( channel )
{
case CHIU_UNIQUE_ID:
retVal = MessageType.ID_UNIQ;
break;
case CHIU_SITE_ID:
retVal = MessageType.ID_SITE;
break;
case CHIU_TRANSMIT_FREQUENCY:
if( getHomeRepeater() == 24 )
{
retVal = MessageType.ID_UNIQ;
}
else
{
if( mMessage.get( CHANNEL_FREQUENCY_MESSAGE_TYPE_BIT ) )
{
retVal = MessageType.FQ_TXHI;
}
else
{
retVal = MessageType.FQ_TXLO;
}
}
break;
case CHIU_RECEIVE_FREQUENCY:
if( mMessage.get( CHANNEL_FREQUENCY_MESSAGE_TYPE_BIT ) )
{
retVal = MessageType.FQ_RXHI;
}
else
{
retVal = MessageType.FQ_RXLO;
}
break;
case CHIU_NEIGHBOR:
retVal = MessageType.ID_NBOR;
break;
case CHIU_MAP:
if( mMessage.get( CHANNEL_MAP_MESSAGE_TYPE_BIT ) )
{
retVal = MessageType.MA_CHNH;
}
else
{
retVal = MessageType.MA_CHNL;
}
break;
case CHIU_CALL_END:
retVal = MessageType.CA_ENDD;
}
}
else
{
int group = getGroup();
if( group == GROUP_CHANNEL_IDLE )
{
retVal = MessageType.SY_IDLE;
}
else
{
retVal = MessageType.CA_STRT;
}
}
return retVal;
}
@Override
public String getFromID()
{
if( mMessageType == MessageType.CA_STRT ||
mMessageType == MessageType.CA_ENDD )
{
return getTalkgroupID();
}
else
{
return null;
}
}
@Override
public Alias getFromIDAlias()
{
if( mMessageType == MessageType.CA_STRT ||
mMessageType == MessageType.CA_ENDD )
{
return getTalkgroupIDAlias();
}
else
{
return null;
}
}
@Override
public Plottable getPlottable()
{
// TODO Auto-generated method stub
return null;
}
/**
* Provides a listing of aliases contained in the message.
*/
public List<Alias> getAliases()
{
List<Alias> aliases = new ArrayList<Alias>();
Alias from = getFromIDAlias();
if( from != null )
{
aliases.add( from );
}
Alias to = getToIDAlias();
if( to != null )
{
aliases.add( to );
}
Alias neighbor = getNeighborIDAlias();
if( neighbor != null )
{
aliases.add( neighbor );
}
Alias uid = getRadioUniqueIDAlias();
if( uid != null )
{
aliases.add( uid );
}
Alias site = getSiteIDAlias();
if( site != null )
{
aliases.add( site );
}
return aliases;
}
}

View File

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

View File

@@ -0,0 +1,78 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrstandard;
import sample.Listener;
import sample.real.IRealBufferListener;
import sample.real.RealBuffer;
import message.MessageDirection;
import module.decode.Decoder;
import module.decode.DecoderType;
import module.decode.config.DecodeConfiguration;
import alias.AliasList;
import bits.MessageFramer;
import bits.SyncPattern;
import dsp.fsk.LTRFSKDecoder;
public class LTRStandardDecoder extends Decoder implements IRealBufferListener
{
public static final int LTR_STANDARD_MESSAGE_LENGTH = 40;
private LTRFSKDecoder mLTRFSKDecoder;
private MessageFramer mLTRMessageFramer;
private LTRStandardMessageProcessor mLTRMessageProcessor;
public LTRStandardDecoder( AliasList aliasList,
MessageDirection direction )
{
super( new LTRStandardDecoderState( aliasList ) );
mLTRFSKDecoder = new LTRFSKDecoder();
if( direction == MessageDirection.OSW )
{
mLTRMessageFramer =
new MessageFramer( SyncPattern.LTR_STANDARD_OSW.getPattern(),
LTR_STANDARD_MESSAGE_LENGTH );
}
else
{
mLTRMessageFramer =
new MessageFramer( SyncPattern.LTR_STANDARD_ISW.getPattern(),
LTR_STANDARD_MESSAGE_LENGTH );
}
mLTRFSKDecoder.addListener( mLTRMessageFramer );
mLTRMessageProcessor = new LTRStandardMessageProcessor( direction, aliasList );
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
mLTRMessageProcessor.setMessageListener( mMessageBroadcaster );
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.LTR_STANDARD;
}
@Override
public Listener<RealBuffer> getRealBufferListener()
{
return mLTRFSKDecoder;
}
}

View File

@@ -0,0 +1,157 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrstandard;
import java.awt.EventQueue;
import javax.swing.JLabel;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderPanel;
import settings.ColorSetting;
import settings.Setting;
import settings.SettingsManager;
import alias.Alias;
public class LTRStandardDecoderPanel extends DecoderPanel
{
private static final long serialVersionUID = 1L;
private JLabel mProtocol = new JLabel( "LTR Standard" );
private JLabel mTalkgroup = new JLabel();
private JLabel mTalkgroupAlias = new JLabel();
private JLabel mLCN = new JLabel( "LCN:" );
public LTRStandardDecoderPanel( SettingsManager settingsManager, LTRStandardDecoder decoder )
{
super( settingsManager, decoder );
init();
}
public void dispose()
{
super.dispose();
}
public void init()
{
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
mTalkgroup.setFont( mFontDecoder );
mTalkgroup.setForeground( mColorLabelDecoder );
mTalkgroupAlias.setFont( mFontDecoder );
mTalkgroupAlias.setForeground( mColorLabelDecoder );
mLCN.setFont( mFontDecoder );
mLCN.setForeground( mColorLabelDecoder );
add( mProtocol );
add( mTalkgroup );
add( mTalkgroupAlias, "wrap" );
add( mLCN, "wrap" );
}
private LTRStandardDecoderState getDecoderState()
{
return (LTRStandardDecoderState)getDecoder().getDecoderState();
}
@Override
public void receive( final ChangedAttribute attribute )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
LTRStandardDecoderState state = getDecoderState();
switch( attribute )
{
case CHANNEL_NUMBER:
if( state.getChannelNumber() != 0 )
{
mLCN.setText( "LCN: " + state.getChannelNumber() );
}
break;
case TO_TALKGROUP:
mTalkgroup.setText( state.getTalkgroup() );
/* fall-through */
case TO_TALKGROUP_ALIAS:
Alias alias = state.getTalkgroupAlias();
if( alias != null )
{
mTalkgroupAlias.setText( alias.getName() );
mTalkgroupAlias.setIcon( mSettingsManager
.getImageIcon( alias.getIconName(), 12 ) );
}
else
{
mTalkgroupAlias.setText( null );
mTalkgroupAlias.setIcon( null );
}
default:
break;
}
repaint();
}
});
}
@Override
public void settingChanged( Setting setting )
{
super.settingChanged( setting );
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case CHANNEL_STATE_LABEL_DECODER:
if( mProtocol != null )
{
mProtocol.setForeground( mColorLabelDecoder );
}
if( mTalkgroup != null )
{
mTalkgroup.setForeground( mColorLabelDecoder );
}
if( mTalkgroupAlias != null )
{
mTalkgroupAlias.setForeground( mColorLabelDecoder );
}
if( mLCN != null )
{
mLCN.setForeground( mColorLabelDecoder );
}
break;
default:
break;
}
}
}
}

View File

@@ -0,0 +1,398 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrstandard;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.TreeSet;
import message.Message;
import module.decode.event.CallEvent.CallEventType;
import module.decode.ltrnet.LTRCallEvent;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderState;
import module.decode.state.DecoderStateEvent;
import module.decode.state.DecoderStateEvent.Event;
import module.decode.state.State;
import alias.Alias;
import alias.AliasList;
public class LTRStandardDecoderState extends DecoderState
{
private static final int sINT_NULL_VALUE = -1;
private DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000");
private HashMap<Integer,String> mActiveCalls = new HashMap<Integer,String>();
private HashSet<String> mTalkgroupsFirstHeard = new HashSet<String>();
private TreeSet<String> mTalkgroups = new TreeSet<String>();
private TreeSet<Integer> mActiveLCNs = new TreeSet<Integer>();
private int mMonitoredLCN;
private String mTalkgroup;
private Alias mTalkgroupAlias;
private int mChannelNumber;
private long mFrequency;
public LTRStandardDecoderState( AliasList aliasList )
{
super( aliasList );
}
private LTRCallEvent getCurrentCallEvent()
{
return (LTRCallEvent)mCurrentCallEvent;
}
@Override
public void receive( Message message )
{
if( message.isValid() )
{
if( message instanceof LTRStandardOSWMessage )
{
LTRStandardOSWMessage ltr = ((LTRStandardOSWMessage)message);
switch( ltr.getMessageType() )
{
case CA_STRT:
if( 0 < ltr.getChannel() && ltr.getChannel() <=20 )
{
/* If we haven't captured the LCN yet ... */
if( mChannelNumber == 0 )
{
mChannelNumber = ltr.getChannel();
broadcast( ChangedAttribute.CHANNEL_NUMBER );
}
/* If this call event is for this channel, then update
* talkgroup and alias */
if( ltr.getChannel() == mChannelNumber )
{
mTalkgroup = ltr.getTalkgroupID();
broadcast( ChangedAttribute.TO_TALKGROUP );
mTalkgroupAlias = ltr.getTalkgroupIDAlias();
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
LTRCallEvent current = getCurrentCallEvent();
/* If we have an ongoing call, then add this message
* to the current event - if addMessage returns
* false, then we have a different talkgroup and a
* new call event, so close out the old one. */
if( current != null && !current.addMessage( ltr ) )
{
mCurrentCallEvent.setEnd( System.currentTimeMillis() );
broadcast( mCurrentCallEvent );
mCurrentCallEvent = null;
}
if( current == null )
{
mCurrentCallEvent =
new LTRCallEvent.Builder( CallEventType.CALL )
.to( ltr.getTalkgroupID() )
.aliasList( getAliasList() )
.channel( String.valueOf( ltr.getChannel() ) )
.frequency( mFrequency )
.build();
broadcast( mCurrentCallEvent );
broadcast( new DecoderStateEvent( this, Event.START, State.CALL ) );
}
else
{
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CALL ) );
}
}
}
break;
case CA_ENDD:
mTalkgroup = ltr.getTalkgroupID();
broadcast( ChangedAttribute.FROM_TALKGROUP );
mTalkgroupAlias = ltr.getTalkgroupIDAlias();
broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS );
if( mCurrentCallEvent != null )
{
mCurrentCallEvent.setEnd( System.currentTimeMillis() );
broadcast( mCurrentCallEvent );
broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
mCurrentCallEvent = null;
}
int home = ltr.getHomeRepeater();
if( 0 < home && home <= 20 )
{
String talkgroup = ltr.getTalkgroupID( false );
if( mTalkgroupsFirstHeard.contains( talkgroup ) )
{
mTalkgroups.add( talkgroup );
}
else
{
mTalkgroupsFirstHeard.add( talkgroup );
}
}
int channel = ltr.getChannel();
if( 0 < channel && channel <= 20 )
{
if( channel == home )
{
if( mMonitoredLCN != channel )
{
mMonitoredLCN = channel;
broadcast( ChangedAttribute.CHANNEL_NUMBER );
}
mActiveLCNs.add( channel );
}
int free = ltr.getFree();
if( 0 < free && free <= 20 )
{
mActiveLCNs.add( free );
}
}
break;
case SY_IDLE:
int idleChannel = ltr.getChannel();
if( idleChannel == ltr.getHomeRepeater() &&
idleChannel == ltr.getFree() )
{
if( mMonitoredLCN != idleChannel )
{
mMonitoredLCN = idleChannel;
broadcast( ChangedAttribute.CHANNEL_NUMBER );
}
mActiveLCNs.add( idleChannel );
}
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.IDLE ) );
break;
case UN_KNWN:
default:
break;
}
}
}
}
// /**
// * Intercept the fade event so that we can generate a call end event
// */
// @Override
// public void fade( final CallEventType type )
// {
// /* We can receive multiple call tear-down messages -- only respond to
// * the message that can change the state to fade */
// if( getState().canChangeTo( State.FADE ) )
// {
// mActiveCalls.clear();
//
// LTRCallEvent current = getCurrentLTRCallEvent();
//
// if( current != null )
// {
// /* Close out the call event. If we only received 1 call
// * message, then flag it as noise and remove the call event */
// if( current.isValid() )
// {
// mCallEventModel.setEnd( current );
// }
// else
// {
// mCallEventModel.remove( current );
// }
// }
//
// setCurrentCallEvent( null );
// }
//
// super.fade( type );
// }
public void reset()
{
resetState();
if( mCurrentCallEvent != null )
{
mCurrentCallEvent.setEnd( System.currentTimeMillis() );
broadcast( mCurrentCallEvent );
mCurrentCallEvent = null;
}
}
private void resetState()
{
mTalkgroup = null;
broadcast( ChangedAttribute.FROM_TALKGROUP );
mTalkgroupAlias = null;
broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS );
}
public String getTalkgroup()
{
return mTalkgroup;
}
public Alias getTalkgroupAlias()
{
return mTalkgroupAlias;
}
public boolean hasChannelNumber()
{
return mChannelNumber != 0;
}
public int getChannelNumber()
{
return mChannelNumber;
}
@Override
public void init()
{
}
@Override
public void receiveDecoderStateEvent( DecoderStateEvent event )
{
switch( event.getEvent() )
{
case RESET:
resetState();
break;
case SOURCE_FREQUENCY:
mFrequency = event.getFrequency();
break;
default:
break;
}
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "Activity Summary\n\n" );
sb.append( "Decoder:\tLTR-Standard\n" );
sb.append( "Monitored LCN: " );
if( mMonitoredLCN > 0 )
{
sb.append( mMonitoredLCN );
}
else
{
sb.append( "unknown" );
}
sb.append( "\n" );
Integer[] lcns = mActiveLCNs.toArray( new Integer[0] );
sb.append( "Active LCNs:\t" );
if( lcns.length > 0 )
{
sb.append( Arrays.toString( lcns ) );
}
else
{
sb.append( "none" );
}
sb.append( "\n\n" );
sb.append( "Talkgroups\n" );
if( mTalkgroups.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mTalkgroups.iterator();
while( it.hasNext() )
{
String tgid = it.next();
sb.append( " " );
sb.append( formatTalkgroup( tgid ) );
sb.append( " " );
if( hasAliasList() )
{
Alias alias = getAliasList().getTalkgroupAlias( tgid );
if( alias != null )
{
sb.append( alias.getName() );
}
}
sb.append( "\n" );
}
}
return sb.toString();
}
public static String formatTalkgroup( String talkgroup )
{
StringBuilder sb = new StringBuilder();
if( talkgroup.length() == 6 )
{
sb.append( talkgroup.substring( 0, 1 ) );
sb.append( "-" );
sb.append( talkgroup.substring( 1, 3 ) );
sb.append( "-" );
sb.append( talkgroup.substring( 3, 6 ) );
return sb.toString();
}
else
{
return talkgroup;
}
}
}

View File

@@ -0,0 +1,71 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrstandard;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import message.MessageDirection;
import module.decode.DecodeEditor;
import module.decode.config.DecodeConfiguration;
public class LTRStandardEditor extends DecodeEditor
{
private static final long serialVersionUID = 1L;
private JComboBox<MessageDirection> mComboDirection;
public LTRStandardEditor( DecodeConfiguration config )
{
super( config );
initGUI();
}
private void initGUI()
{
mComboDirection = new JComboBox<MessageDirection>();
mComboDirection.setModel(
new DefaultComboBoxModel<MessageDirection>( MessageDirection.values() ) );
mComboDirection.setSelectedItem( ((DecodeConfigLTRStandard)mConfig).getMessageDirection() );
mComboDirection.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
MessageDirection selected = mComboDirection
.getItemAt( mComboDirection.getSelectedIndex() );
if( selected != null )
{
((DecodeConfigLTRStandard)mConfig).setMessageDirection( selected );
}
}
} );
add( new JLabel( "Format:" ) );
add( mComboDirection, "wrap" );
}
}

View File

@@ -0,0 +1,238 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrstandard;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import map.Plottable;
import message.MessageDirection;
import message.MessageType;
import alias.Alias;
import alias.AliasList;
import bits.BinaryMessage;
public class LTRStandardISWMessage extends LTRStandardMessage
{
private static final int sCALL_END = 31;
private static final int sIDLE = 255;
public LTRStandardISWMessage( BinaryMessage message, AliasList list )
{
super( message, MessageDirection.OSW, list );
mMessageType = getType();
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( mDatestampFormatter.format(
new Date( System.currentTimeMillis() ) ) );
sb.append( " LTR OSW [" );
sb.append( mCRC.getAbbreviation() );
sb.append( "] " );
sb.append( getMessage() );
pad( sb, 100 );
sb.append( mMessage.toString() );
return sb.toString();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
switch( mMessageType )
{
case SY_IDLE:
sb.append( "IDLE AREA:" );
sb.append( getArea() );
sb.append( " LCN:" );
sb.append( format( getChannel(), 2 ) );
sb.append( " HOME:" );
sb.append( format( getHomeRepeater(), 2 ) );
sb.append( " GRP:" );
sb.append( format( getGroup(), 3 ) );
sb.append( " FREE:" );
sb.append( format( getFree(), 2 ) );
break;
case CA_STRT:
sb.append( "CALL LCN:" );
sb.append( format( getChannel(), 2 ) );
sb.append( " TG [ " );
sb.append( getTalkgroupID() );
sb.append( "/" );
sb.append( getTalkgroupIDAlias() );
sb.append( " ] FREE:" );
sb.append( format( getFree(), 2 ) );
break;
case CA_ENDD:
sb.append( "END* AREA:" );
sb.append( getArea() );
sb.append( " LCN:" );
sb.append( format( getChannel(), 2 ) );
sb.append( " TG [" );
sb.append( getTalkgroupID() );
sb.append( "/" );
sb.append( getTalkgroupIDAlias() );
sb.append( "] FREE:" );
sb.append( format( getFree(), 2 ) );
break;
default:
sb.append( "UNKNOWN A:" );
sb.append( getArea() );
sb.append( " C:" );
sb.append( format( getChannel(), 2 ) );
sb.append( " H:" );
sb.append( format( getHomeRepeater(), 2 ) );
sb.append( " G:" );
sb.append( format( getGroup(), 3 ) );
sb.append( " F:" );
sb.append( format( getFree(), 2 ) );
}
return sb.toString();
}
public String getTalkgroupID()
{
return getTalkgroupID( true );
}
public String getTalkgroupID( boolean format )
{
StringBuilder sb = new StringBuilder();
sb.append( getArea() );
if( format )
{
sb.append( "-" );
}
sb.append( format( getHomeRepeater(), 2 ) );
if( format )
{
sb.append( "-" );
}
sb.append( format( getGroup(), 3 ) );
return sb.toString();
}
public Alias getTalkgroupIDAlias()
{
StringBuilder sb = new StringBuilder();
sb.append( getArea() );
sb.append( format( getHomeRepeater(), 2 ) );
sb.append( format( getGroup(), 3 ) );
return mAliasList.getTalkgroupAlias( sb.toString() );
}
public MessageType getType()
{
MessageType retVal = MessageType.UN_KNWN;
int group = getGroup();
if( group == sIDLE )
{
retVal = MessageType.SY_IDLE;
}
else
{
int channel = getChannel();
if( channel == sCALL_END )
{
retVal = MessageType.CA_ENDD;
}
else
{
retVal = MessageType.CA_STRT;
}
}
return retVal;
}
@Override
public String getFromID()
{
if( mMessageType == MessageType.CA_STRT ||
mMessageType == MessageType.CA_ENDD )
{
return getTalkgroupID();
}
else
{
return null;
}
}
@Override
public Alias getFromIDAlias()
{
if( mMessageType == MessageType.CA_STRT ||
mMessageType == MessageType.CA_ENDD )
{
return getTalkgroupIDAlias();
}
else
{
return null;
}
}
@Override
public Plottable getPlottable()
{
// TODO Auto-generated method stub
return null;
}
/**
* Provides a listing of aliases contained in the message.
*/
public List<Alias> getAliases()
{
List<Alias> aliases = new ArrayList<Alias>();
Alias from = getFromIDAlias();
if( from != null )
{
aliases.add( from );
}
Alias to = getToIDAlias();
if( to != null )
{
aliases.add( to );
}
return aliases;
}
}

View File

@@ -0,0 +1,254 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrstandard;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import message.Message;
import message.MessageDirection;
import message.MessageType;
import module.decode.DecoderType;
import alias.Alias;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
import edac.CRCLTR;
public abstract class LTRStandardMessage extends Message
{
protected static final String sUNKNOWN = "**UNKNOWN**";
protected static final int sINT_NULL_VALUE = -1;
protected static final double sDOUBLE_NULL_VALUE = -1.0D;
protected SimpleDateFormat mDatestampFormatter =
new SimpleDateFormat( "yyyyMMdd HHmmss" );
protected DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000");
protected static final int[] sSYNC = { 8,7,6,5,4,3,2,1,0 };
protected static final int[] sAREA = { 9 };
protected static final int[] sCHANNEL = { 14,13,12,11,10 };
protected static final int[] sHOME_REPEATER = { 19,18,17,16,15 };
protected static final int[] sGROUP = { 27,26,25,24,23,22,21,20 };
protected static final int[] sFREE = { 32,31,30,29,28 };
protected static final int[] sCRC = { 39,38,37,36,35,34,33 };
protected BinaryMessage mMessage;
protected MessageType mMessageType;
protected CRC mCRC;
protected AliasList mAliasList;
public LTRStandardMessage( BinaryMessage message,
MessageDirection direction,
AliasList list )
{
mMessage = message;
mCRC = CRCLTR.check( message, direction );
mAliasList = list;
}
public MessageType getMessageType()
{
return mMessageType;
}
public boolean isValid()
{
return mCRC != CRC.FAILED_CRC && mCRC != CRC.FAILED_PARITY;
}
/**
* Appends spaces to the end of the stringbuilder to make it length long
*/
protected void pad( StringBuilder sb, int length )
{
while( sb.length() < length )
{
sb.append( " " );
}
}
@Override
public String getBinaryMessage()
{
return mMessage.toString();
}
/**
* Returns a value formatted in hex notation.
*
* @param value - integer value
* @return - hex digits returned
*/
protected static String getHex( int value, int digits )
{
return String.format( "%0" + digits + "X", value );
}
protected int getInt( int[] bits )
{
int retVal = 0;
for( int x = 0; x < bits.length; x++ )
{
if( mMessage.get( bits[ x ] ) )
{
retVal += 1<<x;
}
}
return retVal;
}
/**
* Pads an integer value with additional zeroes to make it decimalPlaces long
*/
public String format( int number, int decimalPlaces )
{
StringBuilder sb = new StringBuilder();
int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() );
for( int x = 0; x < paddingRequired; x++)
{
sb.append( "0" );
}
sb.append( number );
return sb.toString();
}
public String format( String val, int places )
{
StringBuilder sb = new StringBuilder();
sb.append( val );
while( sb.length() < places )
{
sb.append( " " );
}
return sb.toString();
}
public CRC getCRC()
{
return mCRC;
}
public int getArea()
{
return getInt( sAREA );
}
public int getChannel()
{
return getInt( sCHANNEL );
}
public int getHomeRepeater()
{
return getInt( sHOME_REPEATER );
}
public int getGroup()
{
return getInt( sGROUP );
}
public String getTalkgroupID()
{
return getTalkgroupID( true );
}
public String getTalkgroupID( boolean format )
{
StringBuilder sb = new StringBuilder();
sb.append( getArea() );
if( format )
{
sb.append( "-" );
}
sb.append( format( getHomeRepeater(), 2 ) );
if( format )
{
sb.append( "-" );
}
sb.append( format( getGroup(), 3 ) );
return sb.toString();
}
public Alias getTalkgroupIDAlias()
{
return mAliasList.getTalkgroupAlias( getTalkgroupID() );
}
public int getFree()
{
return getInt( sFREE );
}
public int getCRCChecksum()
{
return getInt( sCRC );
}
@Override
public String getProtocol()
{
return DecoderType.LTR_STANDARD.getDisplayString();
}
@Override
public String getEventType()
{
if( mMessageType != null )
{
return mMessageType.getDisplayText();
}
else
{
return MessageType.UN_KNWN.getDisplayText();
}
}
@Override
public String getToID()
{
return null;
}
@Override
public Alias getToIDAlias()
{
return null;
}
@Override
public String getErrorStatus()
{
return mCRC.getDisplayText();
}
}

View File

@@ -0,0 +1,56 @@
package module.decode.ltrstandard;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import message.Message;
import message.MessageType;
import filter.Filter;
import filter.FilterElement;
public class LTRStandardMessageFilter extends Filter<Message>
{
private HashMap<MessageType, FilterElement<MessageType>> mElements =
new HashMap<MessageType, FilterElement<MessageType>>();
public LTRStandardMessageFilter()
{
super( "LTR Message Filter" );
mElements.put( MessageType.CA_STRT,
new FilterElement<MessageType>( MessageType.CA_STRT ) );
mElements.put( MessageType.CA_ENDD,
new FilterElement<MessageType>( MessageType.CA_ENDD ) );
mElements.put( MessageType.SY_IDLE,
new FilterElement<MessageType>( MessageType.SY_IDLE ) );
}
@Override
public boolean passes( Message message )
{
if( mEnabled && canProcess( message ) )
{
LTRStandardMessage ltr = (LTRStandardMessage)message;
if( mElements.containsKey( ltr.getMessageType() ) )
{
return mElements.get( ltr.getMessageType() ).isEnabled();
}
}
return false;
}
@Override
public boolean canProcess( Message message )
{
return message instanceof LTRStandardMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return new ArrayList<FilterElement<?>>( mElements.values() );
}
}

View File

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

View File

@@ -0,0 +1,248 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.ltrstandard;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import map.Plottable;
import message.MessageDirection;
import message.MessageType;
import alias.Alias;
import alias.AliasList;
import bits.BinaryMessage;
public class LTRStandardOSWMessage extends LTRStandardMessage
{
private static final int sCALL_END = 31;
private static final int sIDLE = 255;
public LTRStandardOSWMessage( BinaryMessage message, AliasList list )
{
super( message, MessageDirection.OSW, list );
mMessageType = getType();
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( mDatestampFormatter.format(
new Date( System.currentTimeMillis() ) ) );
sb.append( " LTR OSW [" );
sb.append( mCRC.getAbbreviation() );
sb.append( "] " );
sb.append( getMessage() );
pad( sb, 100 );
sb.append( mMessage.toString() );
return sb.toString();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
switch( mMessageType )
{
case SY_IDLE:
sb.append( "IDLE AREA:" );
sb.append( getArea() );
sb.append( " LCN:" );
sb.append( format( getChannel(), 2 ) );
sb.append( " HOME:" );
sb.append( format( getHomeRepeater(), 2 ) );
sb.append( " GRP:" );
sb.append( format( getGroup(), 3 ) );
sb.append( " FREE:" );
sb.append( format( getFree(), 2 ) );
break;
case CA_STRT:
sb.append( "CALL LCN:" );
sb.append( format( getChannel(), 2 ) );
sb.append( " TG [ " );
sb.append( getTalkgroupID() );
sb.append( "/" );
Alias alias = getTalkgroupIDAlias();
if( alias != null )
{
sb.append( getTalkgroupIDAlias().getName() );
}
else
{
sb.append( "UNKNOWN" );
}
sb.append( " ] FREE:" );
sb.append( format( getFree(), 2 ) );
break;
case CA_ENDD:
sb.append( "END* AREA:" );
sb.append( getArea() );
sb.append( " LCN:" );
sb.append( format( getChannel(), 2 ) );
sb.append( " TG [" );
sb.append( getTalkgroupID() );
sb.append( "/" );
Alias endAlias = getTalkgroupIDAlias();
if( endAlias != null )
{
sb.append( getTalkgroupIDAlias().getName() );
}
else
{
sb.append( "UNKNOWN" );
}
sb.append( "] FREE:" );
sb.append( format( getFree(), 2 ) );
break;
default:
sb.append( "UNKNOWN A:" );
sb.append( getArea() );
sb.append( " C:" );
sb.append( format( getChannel(), 2 ) );
sb.append( " H:" );
sb.append( format( getHomeRepeater(), 2 ) );
sb.append( " G:" );
sb.append( format( getGroup(), 3 ) );
sb.append( " F:" );
sb.append( format( getFree(), 2 ) );
}
return sb.toString();
}
public String getTalkgroupID()
{
return getTalkgroupID( true );
}
public String getTalkgroupID( boolean format )
{
StringBuilder sb = new StringBuilder();
sb.append( getArea() );
if( format )
{
sb.append( "-" );
}
sb.append( format( getHomeRepeater(), 2 ) );
if( format )
{
sb.append( "-" );
}
sb.append( format( getGroup(), 3 ) );
return sb.toString();
}
public Alias getTalkgroupIDAlias()
{
return mAliasList.getTalkgroupAlias( getTalkgroupID() );
}
public MessageType getType()
{
MessageType retVal = MessageType.UN_KNWN;
/* Group 255 when the channel equals the free is the IDLE message */
if( getGroup() == sIDLE && getChannel() == getFree() )
{
retVal = MessageType.SY_IDLE;
}
else
{
int channel = getChannel();
if( channel == sCALL_END )
{
retVal = MessageType.CA_ENDD;
}
else
{
retVal = MessageType.CA_STRT;
}
}
return retVal;
}
@Override
public String getFromID()
{
if( mMessageType == MessageType.CA_STRT ||
mMessageType == MessageType.CA_ENDD )
{
return getTalkgroupID();
}
else
{
return null;
}
}
@Override
public Alias getFromIDAlias()
{
if( mMessageType == MessageType.CA_STRT ||
mMessageType == MessageType.CA_ENDD )
{
return getTalkgroupIDAlias();
}
else
{
return null;
}
}
@Override
public Plottable getPlottable()
{
// TODO Auto-generated method stub
return null;
}
/**
* Provides a listing of aliases contained in the message.
*/
public List<Alias> getAliases()
{
List<Alias> aliases = new ArrayList<Alias>();
Alias from = getFromIDAlias();
if( from != null )
{
aliases.add( from );
}
Alias to = getToIDAlias();
if( to != null )
{
aliases.add( to );
}
return aliases;
}
}

View File

@@ -0,0 +1,159 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.mdc1200;
import module.decode.DecoderType;
import module.decode.event.CallEvent;
import alias.Alias;
import alias.AliasList;
public class MDCCallEvent extends CallEvent
{
public MDCCallEvent( CallEventType callEventType,
AliasList aliasList,
String fromID,
String toID,
String details )
{
super( DecoderType.MDC1200, callEventType,
aliasList, fromID, toID, details );
}
private Alias getAlias( String ident )
{
if( hasAliasList() )
{
return getAliasList().getMDC1200Alias( ident );
}
return null;
}
@Override
public Alias getFromIDAlias()
{
return getAlias( getFromID() );
}
@Override
public Alias getToIDAlias()
{
return getAlias( getToID() );
}
@Override
public String getChannel()
{
return null;
}
@Override
public long getFrequency()
{
return 0;
}
public static class Builder
{
/* Required parameters */
private CallEventType mCallEventType;
/* Optional parameters */
private AliasList mAliasList;
private String mFromID;
private String mToID;
private String mDetails;
public Builder( CallEventType callEventType )
{
mCallEventType = callEventType;
}
public Builder aliasList( AliasList aliasList )
{
mAliasList = aliasList;
return this;
}
public Builder from( String val )
{
mFromID = val;
return this;
}
public Builder details( String details )
{
mDetails = details;
return this;
}
public Builder to( String toID )
{
mToID = toID;
return this;
}
public MDCCallEvent build()
{
return new MDCCallEvent( this );
}
}
/**
* Private constructor for the builder
*/
private MDCCallEvent( Builder builder )
{
this( builder.mCallEventType,
builder.mAliasList,
builder.mFromID,
builder.mToID,
builder.mDetails );
}
public static MDCCallEvent
getMDCCallEvent( MDCMessage message )
{
CallEventType type = CallEventType.UNKNOWN;
StringBuilder sbDetails = new StringBuilder();
sbDetails.append( "MDC-1200 OPCODE:" );
sbDetails.append( String.valueOf( message.getOpcode() ) );
switch( message.getMessageType() )
{
case ACKNOWLEDGE:
type = CallEventType.RESPONSE;
break;
case ANI:
type = CallEventType.ID_ANI;
break;
case EMERGENCY:
type = CallEventType.EMERGENCY;
break;
case PAGING:
type = CallEventType.PAGE;
break;
}
return new MDCCallEvent.Builder( type )
.details( sbDetails.toString() )
.from( message.getFromID() )
.to( message.getToID() )
.build();
}
}

View File

@@ -0,0 +1,201 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.mdc1200;
import instrument.Instrumentable;
import instrument.tap.Tap;
import instrument.tap.stream.BinaryTap;
import instrument.tap.stream.FloatBufferTap;
import instrument.tap.stream.FloatTap;
import java.util.ArrayList;
import java.util.List;
import module.decode.Decoder;
import module.decode.DecoderType;
import sample.Listener;
import sample.real.IRealBufferListener;
import sample.real.RealBuffer;
import alias.AliasList;
import bits.MessageFramer;
import bits.SyncPattern;
import dsp.NRZDecoder;
import dsp.filter.Filters;
import dsp.filter.fir.real.RealFIRFilter_RB_RB;
import dsp.filter.halfband.real.HalfBandFilter_RB_RB;
import dsp.fsk.FSK2Decoder;
import dsp.fsk.FSK2Decoder.Output;
/**
* MDC1200 Decoder - 1200 baud 2FSK decoder that can process 48k sample rate
* complex or floating point samples and output fully framed MDC1200 messages
*/
public class MDCDecoder extends Decoder implements IRealBufferListener,
Instrumentable
{
/* Decimated sample rate ( 48,000 / 2 = 24,000 ) feeding the decoder */
private static final int sDECIMATED_SAMPLE_RATE = 24000;
/* Baud or Symbol Rate */
private static final int sSYMBOL_RATE = 1200;
/* Message length */
private static final int sMESSAGE_LENGTH = 304;
/* Instrumentation Taps */
private ArrayList<Tap> mAvailableTaps;
private static final String INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD =
"Tap Point: Bandpass Filter > < FSK2 Decoder";
private static final String INSTRUMENT_FSK2_DECODER_TO_NRZ_DECODER =
"Tap Point: FSK2 Decoder > < NRZI Decoder";
private static final String INSTRUMENT_NRZI_DECODER_TO_MESSAGE_FRAMER =
"Tap Point: NRZ Decoder > < Message Framer";
private FSK2Decoder mFSKDecoder;
private HalfBandFilter_RB_RB mDecimationFilter;
private RealFIRFilter_RB_RB mBandPassFilter;
private NRZDecoder mNRZDecoder;
private MessageFramer mMessageFramer;
private MDCMessageProcessor mMessageProcessor;
public MDCDecoder( AliasList aliasList )
{
super( new MDCDecoderState( aliasList ) );
/* Decimation filter: 48000 / 2 = 24000 */
mDecimationFilter = new HalfBandFilter_RB_RB(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0f, true );
/* Bandpass filter - predefined */
mBandPassFilter = new RealFIRFilter_RB_RB(
Filters.FIRBP_1200FSK_24000FS.getCoefficients(), 1.02f );
mDecimationFilter.setListener( mBandPassFilter );
/* 2FSK Decoder */
mFSKDecoder = new FSK2Decoder( sDECIMATED_SAMPLE_RATE,
sSYMBOL_RATE, Output.NORMAL );
mBandPassFilter.setListener( mFSKDecoder );
/* NRZ Decoder */
mNRZDecoder = new NRZDecoder( NRZDecoder.MODE_INVERTED );
mFSKDecoder.setListener( mNRZDecoder );
/* Message Framer */
mMessageFramer = new MessageFramer( SyncPattern.MDC1200.getPattern(),
sMESSAGE_LENGTH );
mNRZDecoder.setListener( mMessageFramer );
/* Message Processor */
mMessageProcessor = new MDCMessageProcessor( aliasList );
mMessageFramer.addMessageListener( mMessageProcessor );
mMessageProcessor.addMessageListener( mMessageBroadcaster );
}
public void dispose()
{
super.dispose();
mDecimationFilter.dispose();
mBandPassFilter.dispose();
mFSKDecoder.dispose();
mNRZDecoder.dispose();
mMessageFramer.dispose();
mMessageProcessor.dispose();
}
/* Instrumentation */
@Override
public List<Tap> getTaps()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
mAvailableTaps.add(
new FloatTap( INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD, 0, 0.5f ) );
mAvailableTaps.addAll( mFSKDecoder.getTaps() );
mAvailableTaps.add(
new BinaryTap( INSTRUMENT_FSK2_DECODER_TO_NRZ_DECODER, 0, 0.025f ) );
mAvailableTaps.add(
new BinaryTap( INSTRUMENT_NRZI_DECODER_TO_MESSAGE_FRAMER, 0, 0.025f ) );
}
return mAvailableTaps;
}
@Override
public void addTap( Tap tap )
{
mFSKDecoder.addTap( tap );
switch( tap.getName() )
{
case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
/* wire the tap between the bandpass filter and the demod */
FloatBufferTap floatTap = (FloatBufferTap)tap;
mBandPassFilter.setListener( floatTap );
floatTap.setListener( mFSKDecoder );
break;
case INSTRUMENT_FSK2_DECODER_TO_NRZ_DECODER:
/* wire between FSK2 decoder and NRZ decoder */
BinaryTap binTap = (BinaryTap)tap;
mFSKDecoder.setListener( binTap );
binTap.setListener( mNRZDecoder );
break;
case INSTRUMENT_NRZI_DECODER_TO_MESSAGE_FRAMER:
/* wire between demod and framer */
BinaryTap binInvTap = (BinaryTap)tap;
mNRZDecoder.setListener( binInvTap );
binInvTap.setListener( mMessageFramer );
break;
default:
}
}
@Override
public void removeTap( Tap tap )
{
mFSKDecoder.removeTap( tap );
switch( tap.getName() )
{
case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
mBandPassFilter.setListener( mFSKDecoder );
break;
case INSTRUMENT_FSK2_DECODER_TO_NRZ_DECODER:
mFSKDecoder.setListener( mNRZDecoder );
break;
case INSTRUMENT_NRZI_DECODER_TO_MESSAGE_FRAMER:
mNRZDecoder.setListener( mMessageFramer );
break;
default:
}
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.MDC1200;
}
@Override
public Listener<RealBuffer> getRealBufferListener()
{
return mDecimationFilter;
}
}

View File

@@ -0,0 +1,288 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.mdc1200;
import java.util.Iterator;
import java.util.TreeSet;
import message.Message;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderState;
import module.decode.state.DecoderStateEvent;
import module.decode.state.State;
import module.decode.state.DecoderStateEvent.Event;
import alias.Alias;
import alias.AliasList;
public class MDCDecoderState extends DecoderState
{
private TreeSet<String> mIdents = new TreeSet<String>();
private TreeSet<String> mEmergencyIdents = new TreeSet<String>();
private String mFrom;
private Alias mFromAlias;
private String mTo;
private Alias mToAlias;
private String mMessage;
private String mMessageType;
public MDCDecoderState( AliasList aliasList )
{
super( aliasList );
}
@Override
public void reset()
{
mIdents.clear();
mEmergencyIdents.clear();
resetState();
}
private void resetState()
{
setFrom( null );
setFromAlias( null );
setTo( null);
setToAlias( null );
setMessage( null );
setMessageType( null );
}
@Override
public void receive( Message message )
{
if( message instanceof MDCMessage )
{
MDCMessage mdc = (MDCMessage)message;
mIdents.add( mdc.getUnitID() );
if( mdc.isEmergency() )
{
mEmergencyIdents.add( mdc.getUnitID() );
}
setFrom( mdc.getFromID() );
setFromAlias( mdc.getFromIDAlias() );
MDCMessageType type = mdc.getMessageType();
setMessageType( type.getLabel() );
StringBuilder sb = new StringBuilder();
sb.append( "OPCODE " );
sb.append( String.valueOf( mdc.getOpcode() ) );
if( mdc.isBOT() )
{
sb.append( " TYPE:BOT" );
}
if( mdc.isEOT() )
{
sb.append( " TYPE:EOT" );
}
setMessage( sb.toString() );
MDCCallEvent event = MDCCallEvent.getMDCCallEvent( mdc );
event.setAliasList( getAliasList() );
broadcast( event );
switch( type )
{
case ANI:
broadcast( new DecoderStateEvent( this, Event.DECODE, State.CALL ) );
break;
case ACKNOWLEDGE:
case EMERGENCY:
case PAGING:
case STATUS:
default:
broadcast( new DecoderStateEvent( this, Event.DECODE, State.DATA ) );
break;
}
}
}
public String getFrom()
{
return mFrom;
}
public void setFrom( String from )
{
mFrom = from;
broadcast( ChangedAttribute.FROM_TALKGROUP );
}
public Alias getFromAlias()
{
return mFromAlias;
}
public void setFromAlias( Alias alias )
{
mFromAlias = alias;
broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS );
}
public String getTo()
{
return mTo;
}
public void setTo( String to )
{
mTo = to;
broadcast( ChangedAttribute.TO_TALKGROUP );
}
public Alias getToAlias()
{
return mToAlias;
}
public void setToAlias( Alias alias )
{
mToAlias = alias;
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
}
public String getMessage()
{
return mMessage;
}
public void setMessage( String message )
{
mMessage = message;
broadcast( ChangedAttribute.MESSAGE );
}
public String getMessageType()
{
return mMessageType;
}
public void setMessageType( String type )
{
mMessageType = type;
broadcast( ChangedAttribute.MESSAGE_TYPE );
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "=============================\n" );
sb.append( "Decoder:\tMDC-1200\n\n" );
sb.append( "MDC-1200 Idents\n" );
if( mIdents.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mIdents.iterator();
while( it.hasNext() )
{
String ident = it.next();
sb.append( " " );
sb.append( ident );
sb.append( " " );
if( hasAliasList() )
{
Alias alias = getAliasList().getMDC1200Alias( ident );
if( alias != null )
{
sb.append( alias.getName() );
}
}
sb.append( "\n" );
}
}
sb.append( "MDC-1200 Emergency Idents\n" );
if( mEmergencyIdents.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mEmergencyIdents.iterator();
while( it.hasNext() )
{
String ident = it.next();
sb.append( " " );
sb.append( ident );
sb.append( " " );
if( hasAliasList() )
{
Alias alias = getAliasList().getMDC1200Alias( ident );
if( alias != null )
{
sb.append( alias.getName() );
}
}
sb.append( "\n" );
}
}
return sb.toString();
}
@Override
public void init()
{
/* No initialization required */
}
/**
* Responds to reset events issued by the channel state
*/
@Override
public void receiveDecoderStateEvent( DecoderStateEvent event )
{
switch( event.getEvent() )
{
case RESET:
resetState();
break;
default:
break;
}
}
}

View File

@@ -0,0 +1,345 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.mdc1200;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import map.Plottable;
import message.Message;
import alias.Alias;
import alias.AliasList;
import bits.BinaryMessage;
public class MDCMessage extends Message
{
private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" );
private static int[] sSYNC1 = { 0,1,2,3,4,5,6,7,8,9,
10,11,12,13,14,15,16,17,18,19,
20,21,22,23,24,25,26,27,28,29,
30,31,32,33,34,35,36,37,38,39 };
private static int[] sOPCODE = { 47,46,45,44,43,42,41,40 };
private static int sANI_FLAG = 40;
private static int sDIRECTION_FLAG = 45;
private static int sACKNOWLEDGE_REQUIRED_FLAG = 46;
private static int sPACKET_TYPE_FLAG = 47;
private static int sEMERGENCY_FLAG = 48;
private static int[] sARGUMENT = { 49,50,51,52,53,54 };
private static int sBOT_EOT_FLAG = 55;
private static int[] sDIGIT_2 = { 59,58,57,56 };
private static int[] sDIGIT_1 = { 63,62,61,60 };
private static int[] sDIGIT_4 = { 67,66,65,64 };
private static int[] sDIGIT_3 = { 71,70,69,68 };
private BinaryMessage mMessage;
private AliasList mAliasList;
public MDCMessage( BinaryMessage message, AliasList list )
{
mMessage = message;
mAliasList = list;
}
public boolean isValid()
{
//TODO: add CRC and/or convolution decoding/repair
return true;
}
public PacketType getPacketType()
{
if( mMessage.get( sPACKET_TYPE_FLAG ) )
{
return PacketType.DATA;
}
else
{
return PacketType.CMND;
}
}
public Acknowledge getResponse()
{
if( mMessage.get( sACKNOWLEDGE_REQUIRED_FLAG ) )
{
return Acknowledge.YES;
}
else
{
return Acknowledge.NO;
}
}
public Direction getDirection()
{
if( mMessage.get( sDIRECTION_FLAG ) )
{
return Direction.OUT;
}
else
{
return Direction.IN;
}
}
public int getOpcode()
{
return mMessage.getInt( sOPCODE );
}
public int getArgument()
{
return mMessage.getInt( sARGUMENT );
}
public boolean isEmergency()
{
return mMessage.get( sEMERGENCY_FLAG );
}
public boolean isANI()
{
return mMessage.get( sANI_FLAG );
}
public boolean isBOT()
{
return mMessage.get( sBOT_EOT_FLAG );
}
public boolean isEOT()
{
return !mMessage.get( sBOT_EOT_FLAG );
}
public String getUnitID()
{
StringBuilder sb = new StringBuilder();
sb.append( Integer.toHexString( mMessage.getInt( sDIGIT_1 ) ).toUpperCase() );
sb.append( Integer.toHexString( mMessage.getInt( sDIGIT_2 ) ).toUpperCase() );
sb.append( Integer.toHexString( mMessage.getInt( sDIGIT_3 ) ).toUpperCase() );
sb.append( Integer.toHexString( mMessage.getInt( sDIGIT_4 ) ).toUpperCase() );
return sb.toString();
}
public MDCMessageType getMessageType()
{
switch( getOpcode() )
{
case 0:
if( isEmergency() )
{
return MDCMessageType.EMERGENCY;
}
case 1:
return MDCMessageType.ANI;
default:
return MDCMessageType.UNKNOWN;
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( mSDF.format( new Date( System.currentTimeMillis() ) ) );
sb.append( getMessage() );
sb.append( " [" + mMessage.toString() + "]" );
return sb.toString();
}
/**
* Pads spaces onto the end of the value to make it 'places' long
*/
public String pad( String value, int places, String padCharacter )
{
StringBuilder sb = new StringBuilder();
sb.append( value );
while( sb.length() < places )
{
sb.append( padCharacter );
}
return sb.toString();
}
/**
* Pads an integer value with additional zeroes to make it decimalPlaces long
*/
public String format( int number, int decimalPlaces )
{
StringBuilder sb = new StringBuilder();
int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() );
for( int x = 0; x < paddingRequired; x++)
{
sb.append( "0" );
}
sb.append( number );
return sb.toString();
}
@SuppressWarnings( "unused" )
private int getInt( int[] bits )
{
int retVal = 0;
for( int x = 0; x < bits.length; x++ )
{
if( mMessage.get( bits[ x ] ) )
{
retVal += 1<<x;
}
}
return retVal;
}
@Override
public String getBinaryMessage()
{
return mMessage.toString();
}
@Override
public String getProtocol()
{
return "MDC-1200";
}
@Override
public String getEventType()
{
// TODO Auto-generated method stub
return null;
}
@Override
public String getFromID()
{
return getUnitID();
}
@Override
public Alias getFromIDAlias()
{
return mAliasList.getMDC1200Alias( getFromID() );
}
@Override
public String getToID()
{
// TODO Auto-generated method stub
return null;
}
@Override
public Alias getToIDAlias()
{
// TODO Auto-generated method stub
return null;
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "MDC1200 UNIT:" + getUnitID() );
if( isEmergency() )
{
sb.append( " **EMERGENCY**" );
}
if( isBOT() )
{
sb.append( " BOT" );
}
if( isEOT() )
{
sb.append( " EOT" );
}
sb.append( " OPCODE:" + format( getOpcode(), 2 ) );
sb.append( " ARG:" + format( getArgument(), 3 ) );
sb.append( " TYPE:" + getPacketType().toString() );
sb.append( " ACK:" + getResponse().toString() );
sb.append( " DIR:" + pad( getDirection().toString(), 3, " " ) );
return sb.toString();
}
@Override
public String getErrorStatus()
{
// TODO Auto-generated method stub
return null;
}
@Override
public Plottable getPlottable()
{
// TODO Auto-generated method stub
return null;
}
/**
* Provides a listing of aliases contained in the message.
*/
public List<Alias> getAliases()
{
List<Alias> aliases = new ArrayList<Alias>();
Alias from = getFromIDAlias();
if( from != null )
{
aliases.add( from );
}
Alias to = getToIDAlias();
if( to != null )
{
aliases.add( to );
}
return aliases;
}
private enum PacketType { CMND, DATA };
private enum Acknowledge { YES, NO };
private enum Direction { IN, OUT };
}

View File

@@ -0,0 +1,56 @@
package module.decode.mdc1200;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import message.Message;
import filter.Filter;
import filter.FilterElement;
public class MDCMessageFilter extends Filter<Message>
{
private HashMap<MDCMessageType, FilterElement<MDCMessageType>> mElements =
new HashMap<MDCMessageType, FilterElement<MDCMessageType>>();
public MDCMessageFilter()
{
super( "MDC-1200 Message Filter" );
for( MDCMessageType type: MDCMessageType.values() )
{
if( type != MDCMessageType.UNKNOWN )
{
mElements.put( type, new FilterElement<MDCMessageType>( type ) );
}
}
}
@Override
public boolean passes( Message message )
{
if( mEnabled && canProcess( message ) )
{
MDCMessage mdc = (MDCMessage)message;
if( mElements.containsKey( mdc.getMessageType() ) )
{
return mElements.get( mdc.getMessageType() ).isEnabled();
}
}
return false;
}
@Override
public boolean canProcess( Message message )
{
return message instanceof MDCMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return new ArrayList<FilterElement<?>>( mElements.values() );
}
}

View File

@@ -0,0 +1,155 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.mdc1200;
import message.Message;
import sample.Broadcaster;
import sample.Listener;
import alias.AliasList;
import bits.BinaryMessage;
public class MDCMessageProcessor implements Listener<BinaryMessage>
{
private static int sMESSAGE_LENGTH = 112;
private Broadcaster<Message> mBroadcaster = new Broadcaster<Message>();
private AliasList mAliasList;
public MDCMessageProcessor( AliasList aliasList )
{
mAliasList = aliasList;
}
public void dispose()
{
mBroadcaster.dispose();
}
@Override
public void receive( BinaryMessage buffer )
{
/**
* De-interleave the 112 bits of message one, starting after the sync
* pattern
*/
deinterleave( buffer, 40 );
/**
* For simplicity, we also deinterleave message 2, which may or may not
* be there, starting after the initial sync ( 0 - 39 ) and the initial
* message ( 40 - 151 ) and the second sync ( 152 - 191 ).
*/
deinterleave( buffer, 192 );
/**
* Wrap the buffer in a message along with the designated alias list
* and send it on its merry way
*/
MDCMessage message = new MDCMessage( buffer, mAliasList );
mBroadcaster.receive( message );
}
public void addMessageListener( Listener<Message> listener )
{
mBroadcaster.addListener( listener );
}
public void removeMessageListener( Listener<Message> listener )
{
mBroadcaster.removeListener( listener );
}
/**
* Removes NRZ-I encoding from the entire buffer. Assumes that the initial
* bit will be a 0 after decode, despite what is currently in the buffer,
* in order to maintain correct polarity of the decoded message.
*/
private void removeNRZIEncoding( BinaryMessage buffer )
{
/**
* Clear bit position 0, the first bit of the sync pattern, to start
* the decode with the correct polarity
*/
buffer.clear( 0 );
/**
* Set each bit according to the state of the previous bit using XOR
* as follows:
*
* 0 = same as previous state
* 1 = inverse of previous state
*/
for( int x = 1; x < buffer.size(); x++ )
{
if( buffer.get( x - 1 ) ^ buffer.get( x ) )
{
buffer.set( x );
}
else
{
buffer.clear( x );
}
}
}
/**
* Deinterleaves a 112-bit packet, starting at the offset into the buffer
*/
private void deinterleave( BinaryMessage buffer, int offset )
{
if( buffer.size() < sMESSAGE_LENGTH + offset )
{
throw new IllegalArgumentException( "MDCMessageProcessor - "
+ "cannot deinterleave message - message buffer too short" );
}
BinaryMessage deinterleaved = new BinaryMessage( 112 );
int deinterleavedPointer = 0;
for( int column = 0; column < 16; column++ )
{
for( int row = 0; row < 7; row++ )
{
if( buffer.get( ( row * 16 ) + column + offset ) )
{
deinterleaved.set( deinterleavedPointer );
}
deinterleavedPointer++;
}
}
//Overlay the deinterleaved bits back onto the source message and
//return it
buffer.clear( offset, offset + sMESSAGE_LENGTH );
for( int x = 0; x < sMESSAGE_LENGTH; x++ )
{
if( deinterleaved.get( x ) )
{
buffer.set( x + offset );
}
else
{
buffer.clear( x + offset );
}
}
}
}

View File

@@ -0,0 +1,40 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.mdc1200;
public enum MDCMessageType
{
ACKNOWLEDGE( "Acknowledge" ),
ANI( "ANI" ),
EMERGENCY( "Emergency" ),
PAGING( "Paging" ),
STATUS( "Status" ),
UNKNOWN( "Unk" );
private String mLabel;
private MDCMessageType( String label )
{
mLabel = label;
}
public String getLabel()
{
return mLabel;
}
}

View File

@@ -0,0 +1,182 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.mdc1200;
import java.awt.EventQueue;
import javax.swing.JLabel;
import alias.Alias;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderPanel;
import net.miginfocom.swing.MigLayout;
import settings.ColorSetting;
import settings.ColorSetting.ColorSettingName;
import settings.Setting;
import settings.SettingsManager;
public class MDCPanel extends DecoderPanel
{
private static final long serialVersionUID = 1L;
private static final String sPROTOCOL = "MDC-1200";
private JLabel mFromLabel = new JLabel( " " );
private JLabel mFrom = new JLabel();
private JLabel mFromAlias = new JLabel();
private JLabel mToLabel = new JLabel( " " );
private JLabel mTo = new JLabel();
private JLabel mToAlias = new JLabel();
private JLabel mProtocol = new JLabel( sPROTOCOL );
private JLabel mMessage = new JLabel();
private JLabel mMessageType = new JLabel();
public MDCPanel( SettingsManager settingsManager, MDCDecoder decoder )
{
super( settingsManager, decoder );
init();
}
public MDCDecoderState getState()
{
return (MDCDecoderState)getDecoder().getDecoderState();
}
protected void init()
{
/* Calling super init will get and broadcast color settings to properly
* setup the jlabel colors */
super.init();
setLayout( new MigLayout( "insets 1 0 0 0", "[grow,fill]", "[]0[]0[]") );
add( mFromLabel );
add( mFrom );
add( mFromAlias, "wrap" );
add( mToLabel );
add( mTo );
add( mToAlias, "wrap" );
add( mProtocol );
add( mMessageType );
add( mMessage, "wrap" );
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
switch( changedAttribute )
{
case FROM_TALKGROUP:
String from = getState().getFrom();
if( from != null )
{
mFromLabel.setText( "FM:" );
}
else
{
mFromLabel.setText( " " );
}
mFrom.setText( from );
break;
case FROM_TALKGROUP_ALIAS:
Alias fromAlias = getState().getFromAlias();
if( fromAlias != null )
{
mFromAlias.setText( fromAlias.getName() );
}
else
{
mFromAlias.setText( null );
}
break;
case TO_TALKGROUP:
String to = getState().getTo();
if( to != null )
{
mToLabel.setText( "TO:" );
}
else
{
mToLabel.setText( " " );
}
mTo.setText( to );
break;
case TO_TALKGROUP_ALIAS:
Alias toAlias = getState().getToAlias();
if( toAlias != null )
{
mToAlias.setText( toAlias.getName() );
}
else
{
mToAlias.setText( null );
}
break;
case MESSAGE:
mMessage.setText( getState().getMessage() );
break;
case MESSAGE_TYPE:
mMessageType.setText( getState().getMessageType() );
break;
default:
break;
}
repaint();
}
} );
}
@Override
public void settingChanged( Setting setting )
{
super.settingChanged( setting );
if( setting instanceof ColorSetting )
{
ColorSetting color = (ColorSetting)setting;
if( color.getColorSettingName() ==
ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER )
{
mFromLabel.setForeground( color.getColor() );
mFrom.setForeground( color.getColor() );
mFromAlias.setForeground( color.getColor() );
mToLabel.setForeground( color.getColor() );
mTo.setForeground( color.getColor() );
mToAlias.setForeground( color.getColor() );
mProtocol.setForeground( color.getColor() );
mMessageType.setForeground( color.getColor() );
mMessage.setForeground( color.getColor() );
}
}
}
}

View File

@@ -0,0 +1,102 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.mpt1327;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import module.decode.DecoderType;
import module.decode.config.DecodeConfiguration;
import module.decode.mpt1327.MPT1327Decoder.Sync;
public class DecodeConfigMPT1327 extends DecodeConfiguration
{
private String mChannelMapName;
private Sync mSync = Sync.NORMAL;
private int mCallTimeout = DEFAULT_CALL_TIMEOUT_SECONDS;
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
public DecodeConfigMPT1327()
{
super( DecoderType.MPT1327 );
}
@XmlElement
public String getChannelMapName()
{
return mChannelMapName;
}
public void setChannelMapName( String name )
{
mChannelMapName = name;
}
@XmlAttribute( name="sync" )
public Sync getSync()
{
return mSync;
}
public void setSync( Sync sync )
{
mSync = sync;
}
@XmlAttribute( name="call_timeout" )
public int getCallTimeout()
{
return mCallTimeout;
}
/**
* Sets the call timeout value in seconds ( 10 - 600 );
* @param timeout
*/
public void setCallTimeout( int timeout )
{
if( CALL_TIMEOUT_MINIMUM <= timeout && timeout <= CALL_TIMEOUT_MAXIMUM )
{
mCallTimeout = timeout;
}
else
{
mCallTimeout = DEFAULT_CALL_TIMEOUT_SECONDS;
}
}
@XmlAttribute( name="traffic_channel_pool_size" )
public int getTrafficChannelPoolSize()
{
return mTrafficChannelPoolSize;
}
/**
* Sets the traffic channel pool size which is the maximum number of
* simultaneous traffic channels that can be allocated.
*
* This limits the maximum calls so that busy systems won't cause more
* traffic channels to be allocated than the decoder/software/host computer
* can support.
*/
public void setTrafficChannelPoolSize( int size )
{
mTrafficChannelPoolSize = size;
}
}

View File

@@ -0,0 +1,154 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.mpt1327;
import module.decode.DecoderType;
import module.decode.event.CallEvent;
import alias.Alias;
import alias.AliasList;
public class MPT1327CallEvent extends CallEvent
{
private String mChannel;
private long mFrequency;
public MPT1327CallEvent( CallEventType callEventType,
AliasList aliasList,
String fromID,
String toID,
String channel,
long frequency,
String details )
{
super( DecoderType.MPT1327, callEventType,
aliasList, fromID, toID, details );
mChannel = channel;
mFrequency = frequency;
}
private Alias getAlias( String mpt1327Talkgroup )
{
if( hasAliasList() )
{
return getAliasList().getMPT1327Alias( mpt1327Talkgroup );
}
return null;
}
@Override
public Alias getFromIDAlias()
{
return getAlias( getFromID() );
}
@Override
public Alias getToIDAlias()
{
return getAlias( getToID() );
}
@Override
public String getChannel()
{
return mChannel;
}
@Override
public long getFrequency()
{
return mFrequency;
}
public static class Builder
{
/* Required parameters */
private CallEventType mCallEventType;
/* Optional parameters */
private AliasList mAliasList;
private String mFromID;
private String mToID;
private String mDetails;
private String mChannel;
private long mFrequency;
public Builder( CallEventType callEventType )
{
mCallEventType = callEventType;
}
public Builder aliasList( AliasList aliasList )
{
mAliasList = aliasList;
return this;
}
public Builder from( String val )
{
mFromID = val;
return this;
}
public Builder channel( String channel )
{
mChannel = channel;
return this;
}
public Builder details( String details )
{
mDetails = details;
return this;
}
public Builder frequency( long frequency )
{
mFrequency = frequency;
return this;
}
public Builder to( String toID )
{
mToID = toID;
return this;
}
public MPT1327CallEvent build()
{
return new MPT1327CallEvent( this );
}
}
/**
* Private constructor for the builder
*/
private MPT1327CallEvent( Builder builder )
{
this( builder.mCallEventType,
builder.mAliasList,
builder.mFromID,
builder.mToID,
builder.mChannel,
builder.mFrequency,
builder.mDetails );
}
}

View File

@@ -0,0 +1,195 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.mpt1327;
import java.util.ArrayList;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import module.decode.DecodeEditor;
import module.decode.config.DecodeConfiguration;
import module.decode.mpt1327.MPT1327Decoder.Sync;
import controller.channel.ChannelNode;
import controller.channel.map.ChannelMap;
public class MPT1327ConfigEditor extends DecodeEditor
{
private static final long serialVersionUID = 1L;
private JComboBox<ChannelMap> mComboChannelMaps;
private JComboBox<Sync> mComboSync;
private JLabel mCallTimeoutLabel;
private JSlider mCallTimeout;
private JLabel mTrafficChannelPoolSizeLabel;
private JSlider mTrafficChannelPoolSize;
private ChannelNode mChannelNode;
public MPT1327ConfigEditor( DecodeConfiguration config, ChannelNode channelNode )
{
super( config );
mChannelNode = channelNode;
initGUI();
}
private DecodeConfigMPT1327 getMPTConfig()
{
return (DecodeConfigMPT1327)mConfig;
}
private void initGUI()
{
/**
* ComboBox: Alias Lists
*/
ArrayList<ChannelMap> maps = mChannelNode.getModel().getResourceManager()
.getPlaylistManager().getPlayist().getChannelMapList().getChannelMap();
ChannelMap[] mapArray = maps.toArray( new ChannelMap[ maps.size() ] );
mComboChannelMaps = new JComboBox<ChannelMap>();
mComboChannelMaps.setModel(
new DefaultComboBoxModel<ChannelMap>( mapArray ) );
add( new JLabel( "Channel Map:" ) );
add( mComboChannelMaps, "wrap" );
mComboSync = new JComboBox<Sync>();
mComboSync.setModel( new DefaultComboBoxModel<Sync>( Sync.values() ) );
add( new JLabel( "Sync:" ) );
add( mComboSync, "wrap" );
mCallTimeout = new JSlider( JSlider.HORIZONTAL,
DecodeConfiguration.CALL_TIMEOUT_MINIMUM,
DecodeConfiguration.CALL_TIMEOUT_MAXIMUM,
DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS );
mCallTimeout.setMajorTickSpacing( 90 );
mCallTimeout.setMinorTickSpacing( 10 );
mCallTimeout.setPaintTicks( true );
mCallTimeout.setLabelTable( mCallTimeout.createStandardLabels( 100, 100 ) );
mCallTimeout.setPaintLabels( true );
mCallTimeoutLabel = new JLabel( "Call Timeout: " + mCallTimeout.getValue() + " " );
mCallTimeout.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent e )
{
mCallTimeoutLabel.setText( "Call Timeout: " + mCallTimeout.getValue() );
}
} );
add( mCallTimeoutLabel );
add( mCallTimeout, "wrap,grow" );
mTrafficChannelPoolSize = new JSlider( JSlider.HORIZONTAL,
DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_MINIMUM,
DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_MAXIMUM,
DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT );
mTrafficChannelPoolSize.setMajorTickSpacing( 10 );
mTrafficChannelPoolSize.setMinorTickSpacing( 1 );
mTrafficChannelPoolSize.setPaintTicks( true );
mTrafficChannelPoolSize.setLabelTable( mTrafficChannelPoolSize.createStandardLabels( 10, 10 ) );
mTrafficChannelPoolSize.setPaintLabels( true );
mTrafficChannelPoolSizeLabel = new JLabel( "Traffic Channel Pool Size: " + mTrafficChannelPoolSize.getValue() + " " );
mTrafficChannelPoolSize.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent e )
{
mTrafficChannelPoolSizeLabel.setText( "Traffic Channel Pool Size: " + mTrafficChannelPoolSize.getValue() );
}
} );
add( mTrafficChannelPoolSizeLabel );
add( mTrafficChannelPoolSize, "wrap,grow" );
reset();
}
@Override
public void save()
{
DecodeConfigMPT1327 config = getMPTConfig();
ChannelMap selected = (ChannelMap)mComboChannelMaps.getSelectedItem();
if( selected != null )
{
config.setChannelMapName( selected.getName() );
}
Sync selectedSync = (Sync)mComboSync.getSelectedItem();
if( selectedSync != null )
{
config.setSync( selectedSync );
}
config.setCallTimeout( mCallTimeout.getValue() );
config.setTrafficChannelPoolSize( mTrafficChannelPoolSize.getValue() );
}
@Override
public void reset()
{
DecodeConfigMPT1327 config = (DecodeConfigMPT1327)mConfig;
String name = config.getChannelMapName();
ChannelMap selected = null;
if( name != null )
{
for( ChannelMap map: mChannelNode.getModel().getResourceManager()
.getPlaylistManager().getPlayist().getChannelMapList()
.getChannelMap() )
{
if( map.getName().equalsIgnoreCase( name ) )
{
selected = map;
}
}
}
mComboChannelMaps.setSelectedItem( selected );
mComboChannelMaps.requestFocus();
mComboChannelMaps.requestFocusInWindow();
mComboSync.setSelectedItem( config.getSync() );
mCallTimeout.setValue( config.getCallTimeout() );
mTrafficChannelPoolSize.setValue( config.getTrafficChannelPoolSize() );
}
}

View File

@@ -0,0 +1,295 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.mpt1327;
import instrument.Instrumentable;
import instrument.tap.Tap;
import instrument.tap.stream.BinaryTap;
import instrument.tap.stream.FloatBufferTap;
import instrument.tap.stream.FloatTap;
import java.util.ArrayList;
import java.util.List;
import module.decode.Decoder;
import module.decode.DecoderType;
import sample.Broadcaster;
import sample.Listener;
import sample.real.IRealBufferListener;
import sample.real.RealBuffer;
import alias.AliasList;
import bits.MessageFramer;
import bits.SyncPattern;
import controller.channel.Channel.ChannelType;
import controller.channel.map.ChannelMap;
import dsp.filter.FilterFactory;
import dsp.filter.Filters;
import dsp.filter.Window.WindowType;
import dsp.filter.dc.DCRemovalFilterLyons;
import dsp.filter.fir.real.RealFIRFilter_RB_RB;
import dsp.filter.halfband.real.HalfBandFilter_RB_RB;
import dsp.fsk.FSK2Decoder;
import dsp.fsk.FSK2Decoder.Output;
/**
* MPT1327 Decoder - 1200 baud 2FSK decoder that can process 48k sample rate
* complex or floating point samples and output fully framed MPT1327 control and
* traffic messages.
*/
public class MPT1327Decoder extends Decoder
implements IRealBufferListener, Instrumentable
{
/* Decimated sample rate ( 48,000 / 2 = 24,000 ) feeding the decoder */
private static final int sDECIMATED_SAMPLE_RATE = 24000;
/* Baud or Symbol Rate */
private static final int sSYMBOL_RATE = 1200;
/* Message length -- longest possible message is:
* 4xREVS + 16xSYNC + 64xADD1 + 64xDCW1 + 64xDCW2 + 64xDCW3 + 64xDCW4 */
private static final int sMESSAGE_LENGTH = 350;
/* Instrumentation Taps */
private static final String INSTRUMENT_HB1_FILTER_TO_LOW_PASS =
"Tap Point: Half-band Decimation Filter > < Low Pass Filter";
private static final String INSTRUMENT_LOW_PASS_TO_DECODER =
"Tap Point: Low Pass Filter > < Decoder";
private static final String INSTRUMENT_DECODER_TO_FRAMER =
"Tap Point: Decoder > < Sync Detect/Message Framer";
private List<Tap> mAvailableTaps;
private HalfBandFilter_RB_RB mDecimationFilter;
private RealFIRFilter_RB_RB mLowPassFilter;
private DCRemovalFilterLyons mDCRemovalFilter;
private FSK2Decoder mFSKDecoder;
private Broadcaster<Boolean> mSymbolBroadcaster;
private MessageFramer mControlMessageFramer;
private MessageFramer mTrafficMessageFramer;
private MPT1327MessageProcessor mMessageProcessor;
public MPT1327Decoder( ChannelMap channelMap,
ChannelType channelType,
AliasList aliasList,
Sync sync )
{
super( new MPT1327DecoderState( aliasList, channelMap, channelType ) );
/* Decimation filter - 48000 / 2 = 24000 output */
mDecimationFilter = new HalfBandFilter_RB_RB(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0002f, true );
/* Low pass filter: 2kHz to pass the 1200 & 1800 Hz FSK */
mLowPassFilter = new RealFIRFilter_RB_RB(
FilterFactory.getLowPass( 24000, //sampleRate,
2000, //passFrequency,
4000, //stopFrequency,
60, //attenuation,
WindowType.HANNING, //windowType,
true ) //forceOddLength
, 2.0f ); //Gain
mDecimationFilter.setListener( mLowPassFilter );
/**
* Normal: 2FSK Decoder with inverted output
* French: 2FSK Decoder with normal output
*/
if( sync == Sync.NORMAL )
{
mFSKDecoder = new FSK2Decoder( 24000, 1200, Output.INVERTED );
}
else if( sync == Sync.FRENCH )
{
mFSKDecoder = new FSK2Decoder( 24000, 1200, Output.NORMAL );
}
else
{
throw new IllegalArgumentException( "MPT1327 Decoder - unrecognized Sync type" );
}
mLowPassFilter.setListener( mFSKDecoder );
mSymbolBroadcaster = new Broadcaster<Boolean>();
mFSKDecoder.setListener( mSymbolBroadcaster );
/* Message framer for control channel messages */
mControlMessageFramer =
new MessageFramer( sync.getControlSyncPattern().getPattern(),
sMESSAGE_LENGTH );
mSymbolBroadcaster.addListener( mControlMessageFramer );
/* Message framer for traffic channel massages */
mTrafficMessageFramer =
new MessageFramer( sync.getTrafficSyncPattern().getPattern(),
sMESSAGE_LENGTH );
mSymbolBroadcaster.addListener( mTrafficMessageFramer );
/* Fully decoded and framed messages processor */
mMessageProcessor = new MPT1327MessageProcessor( aliasList );
mMessageProcessor.setMessageListener( mMessageBroadcaster );
mControlMessageFramer.addMessageListener( mMessageProcessor );
mTrafficMessageFramer.addMessageListener( mMessageProcessor );
}
@Override
public Listener<RealBuffer> getRealBufferListener()
{
return mDecimationFilter;
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.MPT1327;
}
/**
* Cleanup method
*/
public void dispose()
{
super.dispose();
mSymbolBroadcaster.dispose();
mControlMessageFramer.dispose();
if( mDCRemovalFilter != null )
{
mDCRemovalFilter.dispose();
}
mFSKDecoder.dispose();
mDecimationFilter.dispose();
mLowPassFilter.dispose();
mMessageProcessor.dispose();
mTrafficMessageFramer.dispose();
}
/* Instrumentation Taps */
@Override
public List<Tap> getTaps()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
mAvailableTaps.add(
new FloatTap( INSTRUMENT_HB1_FILTER_TO_LOW_PASS, 0, 0.5f ) );
mAvailableTaps.add(
new FloatTap( INSTRUMENT_LOW_PASS_TO_DECODER, 0, 0.5f ) );
mAvailableTaps.add(
new BinaryTap( INSTRUMENT_DECODER_TO_FRAMER, 0, 0.025f ) );
/* Add the taps from the FSK decoder */
mAvailableTaps.addAll( mFSKDecoder.getTaps() );
}
return mAvailableTaps;
}
@Override
public void addTap( Tap tap )
{
/* Send request to decoder */
mFSKDecoder.addTap( tap );
switch( tap.getName() )
{
case INSTRUMENT_HB1_FILTER_TO_LOW_PASS:
FloatBufferTap hb1Tap = (FloatBufferTap)tap;
mDecimationFilter.setListener( hb1Tap );
hb1Tap.setListener( mLowPassFilter );
break;
case INSTRUMENT_LOW_PASS_TO_DECODER:
FloatBufferTap lowTap = (FloatBufferTap)tap;
mLowPassFilter.setListener( lowTap );
lowTap.setListener( mFSKDecoder );
break;
case INSTRUMENT_DECODER_TO_FRAMER:
BinaryTap decoderTap = (BinaryTap)tap;
mFSKDecoder.setListener( decoderTap );
decoderTap.setListener( mSymbolBroadcaster );
break;
}
}
@Override
public void removeTap( Tap tap )
{
mFSKDecoder.removeTap( tap );
switch( tap.getName() )
{
case INSTRUMENT_HB1_FILTER_TO_LOW_PASS:
mDecimationFilter.setListener( mLowPassFilter );
break;
case INSTRUMENT_LOW_PASS_TO_DECODER:
mLowPassFilter.setListener( mFSKDecoder );
break;
case INSTRUMENT_DECODER_TO_FRAMER:
mFSKDecoder.setListener( mSymbolBroadcaster );
break;
}
}
public enum Sync
{
NORMAL( "Normal",
SyncPattern.MPT1327_CONTROL,
SyncPattern.MPT1327_TRAFFIC ),
FRENCH( "French",
SyncPattern.MPT1327_CONTROL_FRENCH,
SyncPattern.MPT1327_TRAFFIC_FRENCH );
private String mLabel;
private SyncPattern mControlSyncPattern;
private SyncPattern mTrafficSyncPattern;
private Sync( String label,
SyncPattern controlPattern,
SyncPattern trafficPattern )
{
mLabel = label;
mControlSyncPattern = controlPattern;
mTrafficSyncPattern = trafficPattern;
}
public String getLabel()
{
return mLabel;
}
public SyncPattern getControlSyncPattern()
{
return mControlSyncPattern;
}
public SyncPattern getTrafficSyncPattern()
{
return mTrafficSyncPattern;
}
public String toString()
{
return getLabel();
}
}
}

View File

@@ -0,0 +1,222 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.mpt1327;
import java.awt.EventQueue;
import javax.swing.JLabel;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderPanel;
import net.miginfocom.swing.MigLayout;
import settings.ColorSetting;
import settings.Setting;
import settings.SettingsManager;
import alias.Alias;
import controller.channel.Channel.ChannelType;
public class MPT1327DecoderPanel extends DecoderPanel
{
private static final long serialVersionUID = 1L;
private JLabel mProtocol = new JLabel( "" );
private JLabel mSiteOrFromTalkgroup = new JLabel( "" );
private JLabel mSiteOrFromTalkgroupAlias = new JLabel( "" );
private JLabel mToTalkgroupLabel = new JLabel( "" );
private JLabel mToTalkgroup = new JLabel( "" );
private JLabel mToTalkgroupAliasLabel = new JLabel( "" );
public MPT1327DecoderPanel( SettingsManager settingsManager, MPT1327Decoder decoder )
{
super( settingsManager, decoder );
init();
}
private MPT1327DecoderState getDecoderState()
{
return (MPT1327DecoderState)getDecoder().getDecoderState();
}
public void dispose()
{
super.dispose();
}
public void init()
{
super.init();
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]0[]") );
mProtocol.setText( "MPT1327" );
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
MPT1327DecoderState mptState = getDecoderState();
mSiteOrFromTalkgroup.setFont( mFontDecoder );
mSiteOrFromTalkgroup.setForeground( mColorLabelDecoder );
mSiteOrFromTalkgroupAlias.setFont( mFontDecoder );
mSiteOrFromTalkgroupAlias.setForeground( mColorLabelDecoder );
mToTalkgroupLabel.setFont( mFontDetails );
mToTalkgroupLabel.setForeground( mColorLabelDetails );
mToTalkgroup.setFont( mFontDecoder );
mToTalkgroup.setForeground( mColorLabelDecoder );
mToTalkgroupAliasLabel.setFont( mFontDecoder );
mToTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
switch( mptState.getChannelType() )
{
case STANDARD:
mSiteOrFromTalkgroup.setText( mptState.getSite() );
setAliasLabel( mSiteOrFromTalkgroupAlias, mptState.getSiteAlias() );
break;
case TRAFFIC:
mSiteOrFromTalkgroup.setText( mptState.getFromTalkgroup() );
setAliasLabel( mSiteOrFromTalkgroupAlias, mptState.getFromTalkgroupAlias() );
mToTalkgroupLabel.setText( "To:" );
mToTalkgroup.setText( getDecoderState().getToTalkgroup() );
setAliasLabel( mToTalkgroupAliasLabel, mptState.getToTalkgroupAlias() );
break;
default:
throw new IllegalArgumentException( "Unrecognized channel "
+ "type in MPT1327 Decoder [" + getDecoderState()
.getChannelType().name() + "]" );
}
add( mProtocol );
add( mSiteOrFromTalkgroup );
add( mSiteOrFromTalkgroupAlias, "wrap" );
if( mptState.getChannelType() == ChannelType.TRAFFIC )
{
add( mToTalkgroupLabel );
add( mToTalkgroup );
add( mToTalkgroupAliasLabel, "wrap" );
}
}
private void setAliasLabel( final JLabel label, final Alias alias )
{
if( alias != null )
{
label.setText( alias.getName() );
String iconName = alias.getIconName();
if( iconName != null )
{
label.setIcon( mSettingsManager.getImageIcon( iconName, 12 ) );
}
else
{
label.setIcon( null );
}
}
else
{
label.setText( "" );
label.setIcon( null );
}
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
final MPT1327DecoderState state = getDecoderState();
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
switch( changedAttribute )
{
case CHANNEL_SITE_NUMBER:
mSiteOrFromTalkgroup.setText( String.valueOf( state.getSite() ) );
setAliasLabel( mSiteOrFromTalkgroupAlias, state.getSiteAlias() );
repaint();
break;
case FROM_TALKGROUP:
mSiteOrFromTalkgroup.setText( state.getFromTalkgroup() );
setAliasLabel( mSiteOrFromTalkgroupAlias, state.getFromTalkgroupAlias() );
repaint();
break;
case TO_TALKGROUP:
mToTalkgroup.setText( state.getToTalkgroup() );
setAliasLabel( mToTalkgroupAliasLabel, state.getToTalkgroupAlias() );
repaint();
break;
default:
break;
}
}
} );
}
@Override
public void settingChanged( Setting setting )
{
super.settingChanged( setting );
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case CHANNEL_STATE_LABEL_DECODER:
if( mProtocol != null )
{
mProtocol.setForeground( mColorLabelDecoder );
}
if( mSiteOrFromTalkgroup != null )
{
mSiteOrFromTalkgroup.setForeground( mColorLabelDecoder );
}
if( mSiteOrFromTalkgroupAlias != null )
{
mSiteOrFromTalkgroupAlias.setForeground( mColorLabelDecoder );
}
if( mToTalkgroup != null )
{
mToTalkgroup.setForeground( mColorLabelDecoder );
}
if( mToTalkgroupAliasLabel != null )
{
mToTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
}
break;
case CHANNEL_STATE_LABEL_DETAILS:
if( mToTalkgroupLabel != null )
{
mToTalkgroupLabel.setForeground( mColorLabelDetails );
}
break;
}
}
}
}

View File

@@ -0,0 +1,572 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.mpt1327;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import message.Message;
import module.decode.event.CallEvent;
import module.decode.event.CallEvent.CallEventType;
import module.decode.mpt1327.MPT1327Message.IdentType;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderState;
import module.decode.state.DecoderStateEvent;
import module.decode.state.DecoderStateEvent.Event;
import module.decode.state.State;
import module.decode.state.TrafficChannelAllocationEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.Alias;
import alias.AliasList;
import controller.channel.Channel.ChannelType;
import controller.channel.map.ChannelMap;
public class MPT1327DecoderState extends DecoderState
{
private final static Logger mLog = LoggerFactory.getLogger( MPT1327DecoderState.class );
private TreeSet<String> mIdents = new TreeSet<String>();
private HashMap<String,ArrayList<String>> mGroups = new HashMap<String,ArrayList<String>>();
private String mSite;
private String mFromTalkgroup;
private String mToTalkgroup;
private int mChannelNumber;
private ChannelType mChannelType;
private ChannelMap mChannelMap;
private long mFrequency = 0;
public MPT1327DecoderState( AliasList aliasList, ChannelMap channelMap,
ChannelType channelType )
{
super( aliasList );
mChannelMap = channelMap;
mChannelType = channelType;
}
public ChannelType getChannelType()
{
return mChannelType;
}
public void dispose()
{
super.dispose();
}
@Override
public void receive( Message message )
{
if( message.isValid() )
{
if( message instanceof MPT1327Message )
{
MPT1327Message mpt = ((MPT1327Message)message);
switch( mpt.getMessageType() )
{
case ACK:
mIdents.add( mpt.getFromID() );
IdentType identType = mpt.getIdent1Type();
if( identType == IdentType.REGI )
{
broadcast( new MPT1327CallEvent.Builder( CallEventType.REGISTER )
.aliasList( getAliasList() )
.channel( String.valueOf( mChannelNumber ) )
.details( "REGISTERED ON NETWORK" )
.frequency( mFrequency )
.from( mpt.getToID() )
.to( mpt.getFromID() )
.build() );
}
else
{
broadcast( new MPT1327CallEvent.Builder( CallEventType.RESPONSE )
.aliasList( getAliasList() )
.channel( String.valueOf( mChannelNumber ) )
.details( "ACK " + identType.getLabel() )
.frequency( mFrequency )
.from( mpt.getFromID() )
.to( mpt.getToID() )
.build() );
}
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
break;
case ACKI:
mIdents.add( mpt.getFromID() );
mIdents.add( mpt.getToID() );
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
break;
case AHYC:
mIdents.add( mpt.getToID() );
broadcast( new MPT1327CallEvent.Builder( CallEventType.COMMAND )
.aliasList( getAliasList() )
.channel( String.valueOf( mChannelNumber ) )
.details( ( (MPT1327Message) message ).getRequestString() )
.frequency( mFrequency )
.from( mpt.getFromID() )
.to( mpt.getToID() )
.build() );
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
break;
case AHYQ:
broadcast( new MPT1327CallEvent.Builder( CallEventType.STATUS )
.aliasList( getAliasList() )
.channel( String.valueOf( mChannelNumber ) )
.details( mpt.getStatusMessage() )
.frequency( mFrequency )
.from( mpt.getFromID() )
.to( mpt.getToID() )
.build() );
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
break;
case ALH:
String site = mpt.getSiteID();
if( site != null && ( mSite == null || !site.contentEquals( mSite ) ) )
{
mSite = site;
broadcast( ChangedAttribute.CHANNEL_SITE_NUMBER );
}
mSite = mpt.getSiteID();
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
break;
case GTC:
String from = mpt.getFromID();
String to = mpt.getToID();
if( from != null )
{
mIdents.add( from );
}
if( to != null )
{
mIdents.add( to );
}
/* Capture the idents that talk to each group */
if( from != null && to != null )
{
if( mGroups.containsKey( to ) )
{
ArrayList<String> talkgroups = mGroups.get( to );
if( !talkgroups.contains( from ) )
{
talkgroups.add( from );
}
}
else
{
ArrayList<String> talkgroups = new ArrayList<String>();
talkgroups.add( from );
mGroups.put( to, talkgroups );
}
}
int channel = mpt.getChannel();
long frequency = 0;
if( getChannelMap() != null )
{
frequency = getChannelMap().getFrequency( channel );
}
CallEvent event = new MPT1327CallEvent.Builder( CallEventType.CALL )
.aliasList( getAliasList() )
.channel( String.valueOf( channel ) )
.details( "GTC" )
.frequency( frequency )
.from( mpt.getFromID() )
.to( mpt.getToID() )
.build();
broadcast( new TrafficChannelAllocationEvent( this, event ) );
break;
case HEAD_PLUS1:
case HEAD_PLUS2:
case HEAD_PLUS3:
case HEAD_PLUS4:
broadcast( new MPT1327CallEvent.Builder( CallEventType.SDM )
.aliasList( getAliasList() )
.details( mpt.getMessage() )
.from( mpt.getFromID() )
.to( mpt.getToID() )
.build() );
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
break;
/* Traffic Channel Events */
case CLEAR:
case MAINT:
broadcast( new DecoderStateEvent( this, Event.END, State.END ) );
break;
}
}
}
}
// /**
// * Intercept the fade event so that we can generate a call end event
// */
// @Override
// public void fade( final CallEventType type )
// {
// /*
// * We can receive multiple call tear-down messages -- only post a call
// * end event for the one that can change the state to fade
// */
// if( getState().canChangeTo( State.FADE ) )
// {
// CallEvent current = getCurrentCallEvent();
//
// if( current != null )
// {
// mCallEventModel.setEnd( current );
// }
// else
// {
// mCallEventModel.add(
// new MPT1327CallEvent.Builder( type )
// .aliasList( getAliasList() )
// .channel( String.valueOf( mChannelNumber ) )
// .frequency( mChannelMap.getFrequency( mChannelNumber ) )
// .from( mFromTalkgroup )
// .to( mToTalkgroup )
// .build() );
// }
//
// setCurrentCall( null );
// }
//
// super.fade( type );
// }
public void reset()
{
mIdents.clear();
resetState();
}
private void resetState()
{
mFromTalkgroup = null;
broadcast( ChangedAttribute.FROM_TALKGROUP );
mToTalkgroup = null;
broadcast( ChangedAttribute.TO_TALKGROUP );
}
public String getSite()
{
return mSite;
}
public Alias getSiteAlias()
{
if( hasAliasList() )
{
return getAliasList().getSiteID( mSite );
}
return null;
}
public ChannelMap getChannelMap()
{
return mChannelMap;
}
public void setChannelMap( ChannelMap channelMap )
{
mChannelMap = channelMap;
}
public String getFromTalkgroup()
{
return mFromTalkgroup;
}
/**
* Set the talkgroup. This is used primarily for traffic channels since
* the talkgroup will already have been identified prior to the traffic
* channel being created.
*/
public void setFromTalkgroup( String fromTalkgroup )
{
mFromTalkgroup = fromTalkgroup;
broadcast( ChangedAttribute.FROM_TALKGROUP );
}
public Alias getFromTalkgroupAlias()
{
if( hasAliasList() )
{
return getAliasList().getTalkgroupAlias( mFromTalkgroup );
}
return null;
}
public String getToTalkgroup()
{
return mToTalkgroup;
}
/**
* Set the talkgroup. This is used primarily for traffic channels since
* the talkgroup will already have been identified prior to the traffic
* channel being created.
*/
public void setToTalkgroup( String toTalkgroup )
{
mToTalkgroup = toTalkgroup;
broadcast( ChangedAttribute.TO_TALKGROUP );
}
public Alias getToTalkgroupAlias()
{
if( hasAliasList() )
{
return getAliasList().getTalkgroupAlias( mToTalkgroup );
}
return null;
}
public int getChannelNumber()
{
return mChannelNumber;
}
/**
* Set the channel number. This is used primarily for traffic channels since
* the channel will already have been identified prior to the traffic
* channel being created.
*/
public void setChannelNumber( int channel )
{
mChannelNumber = channel;
broadcast( ChangedAttribute.CHANNEL_NUMBER );
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "Activity Summary\n" );
sb.append( "Decoder:\tMPT-1327\n" );
sb.append( "Site:\t" );
if( mSite != null )
{
sb.append( mSite );
if( hasAliasList() )
{
Alias siteAlias = getAliasList().getSiteID( mSite );
if( siteAlias != null )
{
sb.append( " " );
sb.append( siteAlias.getName() );
sb.append( "\n" );
}
}
}
else
{
sb.append( "Unknown\n" );
}
sb.append( "\n\n" );
sb.append( "Talkgroups\n\n" );
if( mGroups.isEmpty() )
{
sb.append( " None\n" );
}
else
{
List<String> talkgroups = new ArrayList<String>( mGroups.keySet() );
Collections.sort( talkgroups );
for( String talkgroup: talkgroups )
{
sb.append( " " );
sb.append( talkgroup );
if( hasAliasList() )
{
Alias alias = getAliasList().getMPT1327Alias( talkgroup );
if( alias != null )
{
sb.append( " " );
sb.append( alias.getName() );
}
}
sb.append( "\n" );
ArrayList<String> members = mGroups.get( talkgroup );
Collections.sort( members );
for( String member: members )
{
sb.append( " >" );
sb.append( member );
if( hasAliasList() )
{
Alias alias = getAliasList().getMPT1327Alias( member );
if( alias != null )
{
sb.append( " " );
sb.append( alias.getName() );
}
}
sb.append( "\n" );
}
sb.append( "\n" );
}
}
sb.append( "\n" );
sb.append( "Individual Idents\n" );
if( mIdents.isEmpty() )
{
sb.append( " None\n" );
}
else
{
Iterator<String> it = mIdents.iterator();
while( it.hasNext() )
{
String ident = it.next();
sb.append( " " );
sb.append( ident );
sb.append( " " );
if( hasAliasList() )
{
Alias alias = getAliasList().getMPT1327Alias( ident );
if( alias != null )
{
sb.append( alias.getName() );
}
}
sb.append( "\n" );
}
}
return sb.toString();
}
@Override
public void init()
{
/* No initialization required */
}
@Override
public void receiveDecoderStateEvent( DecoderStateEvent event )
{
switch( event.getEvent() )
{
case RESET:
resetState();
break;
case SOURCE_FREQUENCY:
mFrequency = event.getFrequency();
break;
case TRAFFIC_CHANNEL_ALLOCATION:
if( event.getSource() != MPT1327DecoderState.this )
{
if( event instanceof TrafficChannelAllocationEvent )
{
TrafficChannelAllocationEvent allocationEvent =
(TrafficChannelAllocationEvent)event;
String channel = allocationEvent.getCallEvent().getChannel();
if( channel != null )
{
try
{
mChannelNumber = Integer.valueOf( channel );
broadcast( ChangedAttribute.CHANNEL_NUMBER );
}
catch( Exception e )
{
//Do nothing, we couldn't parse the channel number
}
}
mFrequency = allocationEvent.getCallEvent().getFrequency();
broadcast( ChangedAttribute.SOURCE );
mFromTalkgroup = allocationEvent.getCallEvent().getFromID();
broadcast( ChangedAttribute.FROM_TALKGROUP );
mToTalkgroup = allocationEvent.getCallEvent().getToID();
broadcast( ChangedAttribute.TO_TALKGROUP );
}
}
break;
default:
break;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
package module.decode.mpt1327;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import message.Message;
import module.decode.mpt1327.MPT1327Message.MPTMessageType;
import filter.Filter;
import filter.FilterElement;
public class MPT1327MessageFilter extends Filter<Message>
{
private HashMap<MPTMessageType,FilterElement<MPTMessageType>> mFilterElements =
new HashMap<MPTMessageType,FilterElement<MPTMessageType>>();
public MPT1327MessageFilter()
{
super( "MPT1327 Message Type Filter" );
for( MPTMessageType type: MPT1327Message.MPTMessageType.values() )
{
if( type != MPTMessageType.UNKN )
{
mFilterElements.put( type,
new FilterElement<MPTMessageType>( type ) );
}
}
}
@Override
public boolean passes( Message message )
{
if( mEnabled && canProcess( message ) )
{
MPT1327Message mpt = (MPT1327Message)message;
FilterElement<MPTMessageType> element =
mFilterElements.get( mpt.getMessageType() );
if( element != null )
{
return element.isEnabled();
}
}
return false;
}
@Override
public boolean canProcess( Message message )
{
return message instanceof MPT1327Message;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return new ArrayList<FilterElement<?>>( mFilterElements.values() );
}
}

View File

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

View File

@@ -0,0 +1,177 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.mpt1327;
import java.awt.EventQueue;
import javax.swing.JLabel;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderPanel;
import settings.ColorSetting;
import settings.Setting;
import settings.SettingsManager;
import alias.Alias;
public class MPT1327TrafficPanel extends DecoderPanel
{
private static final long serialVersionUID = 1L;
private JLabel mProtocolLabel;
private JLabel mFromTalkgroupLabel;
private JLabel mFromTalkgroupAliasLabel;
private JLabel mToLabel;
private JLabel mToTalkgroupLabel;
private JLabel mToTalkgroupAliasLabel;
public MPT1327TrafficPanel( SettingsManager settingsManager, MPT1327Decoder decoder )
{
super( settingsManager, decoder );
init();
}
private MPT1327DecoderState getDecoderState()
{
return (MPT1327DecoderState)getDecoder().getDecoderState();
}
public void dispose()
{
super.dispose();
}
public void init()
{
mProtocolLabel = new JLabel( "MPT1327" );
mProtocolLabel.setFont( mFontDecoder );
mProtocolLabel.setForeground( mColorLabelDecoder );
mFromTalkgroupLabel = new JLabel();
mFromTalkgroupLabel.setFont( mFontDecoder );
mFromTalkgroupLabel.setForeground( mColorLabelDecoder );
mFromTalkgroupAliasLabel = new JLabel();
mFromTalkgroupAliasLabel.setFont( mFontDecoder );
mFromTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
mToLabel = new JLabel( "TO:" );
mToLabel.setFont( mFontDetails );
mToLabel.setForeground( mColorLabelDetails );
mToTalkgroupLabel = new JLabel( getDecoderState().getFromTalkgroup() );
mToTalkgroupLabel.setFont( mFontDecoder );
mToTalkgroupLabel.setForeground( mColorLabelDecoder );
mToTalkgroupAliasLabel = new JLabel();
mToTalkgroupAliasLabel.setFont( mFontDecoder );
mToTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
add( mProtocolLabel );
add( mFromTalkgroupLabel );
add( mFromTalkgroupAliasLabel, "wrap" );
add( mToLabel );
add( mToTalkgroupLabel );
add( mToTalkgroupAliasLabel, "wrap" );
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
final MPT1327DecoderState state = getDecoderState();
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
switch( changedAttribute )
{
case FROM_TALKGROUP:
String talkgroup = state.getFromTalkgroup();
mFromTalkgroupLabel.setText( talkgroup );
Alias alias = state.getFromTalkgroupAlias();
if( alias != null )
{
mFromTalkgroupAliasLabel.setText( alias.getName() );
String icon = alias.getIconName();
mFromTalkgroupAliasLabel.setIcon( mSettingsManager
.getImageIcon( icon, 12 ) );
}
break;
case TO_TALKGROUP:
String toTalkgroup = state.getToTalkgroup();
mToTalkgroupLabel.setText( toTalkgroup );
Alias toAlias = state.getToTalkgroupAlias();
if( toAlias != null )
{
mToTalkgroupAliasLabel.setText( toAlias.getName() );
String icon = toAlias.getIconName();
if( icon != null )
{
mToTalkgroupAliasLabel.setIcon( mSettingsManager
.getImageIcon( icon, 12 ) );
}
}
break;
default:
break;
}
repaint();
}
} );
}
@Override
public void settingChanged( Setting setting )
{
super.settingChanged( setting );
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case CHANNEL_STATE_LABEL_DECODER:
mProtocolLabel.setForeground( mColorLabelDecoder );
mFromTalkgroupLabel.setForeground( mColorLabelDecoder );
mFromTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
mToTalkgroupLabel.setForeground( mColorLabelDecoder );
mToTalkgroupAliasLabel.setForeground( mColorLabelDecoder );
break;
case CHANNEL_STATE_LABEL_DETAILS:
mToLabel.setForeground( mColorLabelDetails );
break;
}
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,78 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.nbfm;
import javax.swing.JLabel;
import net.miginfocom.swing.MigLayout;
import module.decode.Decoder;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderPanel;
import settings.ColorSetting;
import settings.Setting;
import settings.SettingsManager;
import settings.ColorSetting.ColorSettingName;
public class NBFMDecoderPanel extends DecoderPanel
{
private static final long serialVersionUID = 1L;
private JLabel mProtocol = new JLabel( "NBFM" );
public NBFMDecoderPanel( SettingsManager settingsManager, Decoder decoder )
{
super( settingsManager, decoder );
init();
}
protected void init()
{
super.init();
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]0[]") );
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
add( mProtocol, "wrap" );
}
@Override
public void receive( ChangedAttribute t )
{
/* Not implemented */
}
@Override
public void settingChanged( Setting setting )
{
super.settingChanged( setting );
if( setting instanceof ColorSetting )
{
ColorSetting color = (ColorSetting)setting;
if( color.getColorSettingName() ==
ColorSettingName.CHANNEL_STATE_LABEL_DECODER )
{
mProtocol.setForeground( color.getColor() );
}
}
}
}

View File

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

View File

@@ -0,0 +1,15 @@
package module.decode.p25;
import bits.BinaryMessage;
public interface C4FMFrameListener
{
/**
* Listener interface to receive the output of the C4FMMessageFramer.
*
* @param buffer - framed message without the sync pattern
* @param inverted - flag indicating if the message was received inverted
*/
public void receive( BinaryMessage buffer, boolean inverted );
}

View File

@@ -0,0 +1,70 @@
package module.decode.p25;
import dsp.symbol.Dibit;
import sample.Broadcaster;
import sample.Listener;
import sample.real.RealSampleListener;
/**
* C4FM slicer to convert the output stream of the C4FMSymbolFilter into a
* stream of C4FM symbols.
*
* Supports registering listener(s) to receive normal and/or inverted symbol
* output streams.
*/
public class C4FMSlicer implements RealSampleListener
{
private static final float THRESHOLD = 2.0f;
private Broadcaster<Dibit> mBroadcaster = new Broadcaster<Dibit>();
/**
* Primary method for receiving output from the C4FMSymbolFilter. Slices
* (converts) the filtered sample value into a C4FMSymbol decision.
*/
@Override
public void receive( float sample )
{
if( sample > THRESHOLD )
{
dispatch( Dibit.D01_PLUS_3 );
}
else if( sample > 0 )
{
dispatch( Dibit.D00_PLUS_1 );
}
else if( sample > -THRESHOLD )
{
dispatch( Dibit.D10_MINUS_1 );
}
else
{
dispatch( Dibit.D11_MINUS_3 );
}
}
/**
* Dispatches the symbol decision to any registered listeners
*/
private void dispatch( Dibit symbol )
{
mBroadcaster.receive( symbol );
}
/**
* Registers the listener to receive the normal (non-inverted) C4FM symbol
* stream.
*/
public void addListener( Listener<Dibit> listener )
{
mBroadcaster.addListener( listener );
}
/**
* Removes the listener
*/
public void removeListener( Listener<Dibit> listener )
{
mBroadcaster.removeListener( listener );
}
}

View File

@@ -0,0 +1,431 @@
package module.decode.p25;
import instrument.Instrumentable;
import instrument.tap.Tap;
import instrument.tap.stream.AdditiveFloatTap;
import instrument.tap.stream.FloatTap;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.real.RealSampleListener;
import sample.real.RealSampleProvider;
import source.tuner.frequency.FrequencyCorrectionControl;
import source.tuner.frequency.FrequencyCorrectionControl.FrequencyCorrectionResetListener;
import dsp.gain.GainController;
public class C4FMSymbolFilter implements FrequencyCorrectionResetListener,
RealSampleListener,
RealSampleProvider,
Instrumentable
{
private final static Logger mLog =
LoggerFactory.getLogger( C4FMSymbolFilter.class );
private static final float TAPS[][] =
{
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 0/128
{ -1.54700e-04f, 8.53777e-04f, -2.76968e-03f, 7.89295e-03f, 9.98534e-01f, -5.41054e-03f, 1.24642e-03f, -1.98993e-04f }, // 1/128
{ -3.09412e-04f, 1.70888e-03f, -5.55134e-03f, 1.58840e-02f, 9.96891e-01f, -1.07209e-02f, 2.47942e-03f, -3.96391e-04f }, // 2/128
{ -4.64053e-04f, 2.56486e-03f, -8.34364e-03f, 2.39714e-02f, 9.95074e-01f, -1.59305e-02f, 3.69852e-03f, -5.92100e-04f }, // 3/128
{ -6.18544e-04f, 3.42130e-03f, -1.11453e-02f, 3.21531e-02f, 9.93082e-01f, -2.10389e-02f, 4.90322e-03f, -7.86031e-04f }, // 4/128
{ -7.72802e-04f, 4.27773e-03f, -1.39548e-02f, 4.04274e-02f, 9.90917e-01f, -2.60456e-02f, 6.09305e-03f, -9.78093e-04f }, // 5/128
{ -9.26747e-04f, 5.13372e-03f, -1.67710e-02f, 4.87921e-02f, 9.88580e-01f, -3.09503e-02f, 7.26755e-03f, -1.16820e-03f }, // 6/128
{ -1.08030e-03f, 5.98883e-03f, -1.95925e-02f, 5.72454e-02f, 9.86071e-01f, -3.57525e-02f, 8.42626e-03f, -1.35627e-03f }, // 7/128
{ -1.23337e-03f, 6.84261e-03f, -2.24178e-02f, 6.57852e-02f, 9.83392e-01f, -4.04519e-02f, 9.56876e-03f, -1.54221e-03f }, // 8/128
{ -1.38589e-03f, 7.69462e-03f, -2.52457e-02f, 7.44095e-02f, 9.80543e-01f, -4.50483e-02f, 1.06946e-02f, -1.72594e-03f }, // 9/128
{ -1.53777e-03f, 8.54441e-03f, -2.80746e-02f, 8.31162e-02f, 9.77526e-01f, -4.95412e-02f, 1.18034e-02f, -1.90738e-03f }, // 10/128
{ -1.68894e-03f, 9.39154e-03f, -3.09033e-02f, 9.19033e-02f, 9.74342e-01f, -5.39305e-02f, 1.28947e-02f, -2.08645e-03f }, // 11/128
{ -1.83931e-03f, 1.02356e-02f, -3.37303e-02f, 1.00769e-01f, 9.70992e-01f, -5.82159e-02f, 1.39681e-02f, -2.26307e-03f }, // 12/128
{ -1.98880e-03f, 1.10760e-02f, -3.65541e-02f, 1.09710e-01f, 9.67477e-01f, -6.23972e-02f, 1.50233e-02f, -2.43718e-03f }, // 13/128
{ -2.13733e-03f, 1.19125e-02f, -3.93735e-02f, 1.18725e-01f, 9.63798e-01f, -6.64743e-02f, 1.60599e-02f, -2.60868e-03f }, // 14/128
{ -2.28483e-03f, 1.27445e-02f, -4.21869e-02f, 1.27812e-01f, 9.59958e-01f, -7.04471e-02f, 1.70776e-02f, -2.77751e-03f }, // 15/128
{ -2.43121e-03f, 1.35716e-02f, -4.49929e-02f, 1.36968e-01f, 9.55956e-01f, -7.43154e-02f, 1.80759e-02f, -2.94361e-03f }, // 16/128
{ -2.57640e-03f, 1.43934e-02f, -4.77900e-02f, 1.46192e-01f, 9.51795e-01f, -7.80792e-02f, 1.90545e-02f, -3.10689e-03f }, // 17/128
{ -2.72032e-03f, 1.52095e-02f, -5.05770e-02f, 1.55480e-01f, 9.47477e-01f, -8.17385e-02f, 2.00132e-02f, -3.26730e-03f }, // 18/128
{ -2.86289e-03f, 1.60193e-02f, -5.33522e-02f, 1.64831e-01f, 9.43001e-01f, -8.52933e-02f, 2.09516e-02f, -3.42477e-03f }, // 19/128
{ -3.00403e-03f, 1.68225e-02f, -5.61142e-02f, 1.74242e-01f, 9.38371e-01f, -8.87435e-02f, 2.18695e-02f, -3.57923e-03f }, // 20/128
{ -3.14367e-03f, 1.76185e-02f, -5.88617e-02f, 1.83711e-01f, 9.33586e-01f, -9.20893e-02f, 2.27664e-02f, -3.73062e-03f }, // 21/128
{ -3.28174e-03f, 1.84071e-02f, -6.15931e-02f, 1.93236e-01f, 9.28650e-01f, -9.53307e-02f, 2.36423e-02f, -3.87888e-03f }, // 22/128
{ -3.41815e-03f, 1.91877e-02f, -6.43069e-02f, 2.02814e-01f, 9.23564e-01f, -9.84679e-02f, 2.44967e-02f, -4.02397e-03f }, // 23/128
{ -3.55283e-03f, 1.99599e-02f, -6.70018e-02f, 2.12443e-01f, 9.18329e-01f, -1.01501e-01f, 2.53295e-02f, -4.16581e-03f }, // 24/128
{ -3.68570e-03f, 2.07233e-02f, -6.96762e-02f, 2.22120e-01f, 9.12947e-01f, -1.04430e-01f, 2.61404e-02f, -4.30435e-03f }, // 25/128
{ -3.81671e-03f, 2.14774e-02f, -7.23286e-02f, 2.31843e-01f, 9.07420e-01f, -1.07256e-01f, 2.69293e-02f, -4.43955e-03f }, // 26/128
{ -3.94576e-03f, 2.22218e-02f, -7.49577e-02f, 2.41609e-01f, 9.01749e-01f, -1.09978e-01f, 2.76957e-02f, -4.57135e-03f }, // 27/128
{ -4.07279e-03f, 2.29562e-02f, -7.75620e-02f, 2.51417e-01f, 8.95936e-01f, -1.12597e-01f, 2.84397e-02f, -4.69970e-03f }, // 28/128
{ -4.19774e-03f, 2.36801e-02f, -8.01399e-02f, 2.61263e-01f, 8.89984e-01f, -1.15113e-01f, 2.91609e-02f, -4.82456e-03f }, // 29/128
{ -4.32052e-03f, 2.43930e-02f, -8.26900e-02f, 2.71144e-01f, 8.83893e-01f, -1.17526e-01f, 2.98593e-02f, -4.94589e-03f }, // 30/128
{ -4.44107e-03f, 2.50946e-02f, -8.52109e-02f, 2.81060e-01f, 8.77666e-01f, -1.19837e-01f, 3.05345e-02f, -5.06363e-03f }, // 31/128
{ -4.55932e-03f, 2.57844e-02f, -8.77011e-02f, 2.91006e-01f, 8.71305e-01f, -1.22047e-01f, 3.11866e-02f, -5.17776e-03f }, // 32/128
{ -4.67520e-03f, 2.64621e-02f, -9.01591e-02f, 3.00980e-01f, 8.64812e-01f, -1.24154e-01f, 3.18153e-02f, -5.28823e-03f }, // 33/128
{ -4.78866e-03f, 2.71272e-02f, -9.25834e-02f, 3.10980e-01f, 8.58189e-01f, -1.26161e-01f, 3.24205e-02f, -5.39500e-03f }, // 34/128
{ -4.89961e-03f, 2.77794e-02f, -9.49727e-02f, 3.21004e-01f, 8.51437e-01f, -1.28068e-01f, 3.30021e-02f, -5.49804e-03f }, // 35/128
{ -5.00800e-03f, 2.84182e-02f, -9.73254e-02f, 3.31048e-01f, 8.44559e-01f, -1.29874e-01f, 3.35600e-02f, -5.59731e-03f }, // 36/128
{ -5.11376e-03f, 2.90433e-02f, -9.96402e-02f, 3.41109e-01f, 8.37557e-01f, -1.31581e-01f, 3.40940e-02f, -5.69280e-03f }, // 37/128
{ -5.21683e-03f, 2.96543e-02f, -1.01915e-01f, 3.51186e-01f, 8.30432e-01f, -1.33189e-01f, 3.46042e-02f, -5.78446e-03f }, // 38/128
{ -5.31716e-03f, 3.02507e-02f, -1.04150e-01f, 3.61276e-01f, 8.23188e-01f, -1.34699e-01f, 3.50903e-02f, -5.87227e-03f }, // 39/128
{ -5.41467e-03f, 3.08323e-02f, -1.06342e-01f, 3.71376e-01f, 8.15826e-01f, -1.36111e-01f, 3.55525e-02f, -5.95620e-03f }, // 40/128
{ -5.50931e-03f, 3.13987e-02f, -1.08490e-01f, 3.81484e-01f, 8.08348e-01f, -1.37426e-01f, 3.59905e-02f, -6.03624e-03f }, // 41/128
{ -5.60103e-03f, 3.19495e-02f, -1.10593e-01f, 3.91596e-01f, 8.00757e-01f, -1.38644e-01f, 3.64044e-02f, -6.11236e-03f }, // 42/128
{ -5.68976e-03f, 3.24843e-02f, -1.12650e-01f, 4.01710e-01f, 7.93055e-01f, -1.39767e-01f, 3.67941e-02f, -6.18454e-03f }, // 43/128
{ -5.77544e-03f, 3.30027e-02f, -1.14659e-01f, 4.11823e-01f, 7.85244e-01f, -1.40794e-01f, 3.71596e-02f, -6.25277e-03f }, // 44/128
{ -5.85804e-03f, 3.35046e-02f, -1.16618e-01f, 4.21934e-01f, 7.77327e-01f, -1.41727e-01f, 3.75010e-02f, -6.31703e-03f }, // 45/128
{ -5.93749e-03f, 3.39894e-02f, -1.18526e-01f, 4.32038e-01f, 7.69305e-01f, -1.42566e-01f, 3.78182e-02f, -6.37730e-03f }, // 46/128
{ -6.01374e-03f, 3.44568e-02f, -1.20382e-01f, 4.42134e-01f, 7.61181e-01f, -1.43313e-01f, 3.81111e-02f, -6.43358e-03f }, // 47/128
{ -6.08674e-03f, 3.49066e-02f, -1.22185e-01f, 4.52218e-01f, 7.52958e-01f, -1.43968e-01f, 3.83800e-02f, -6.48585e-03f }, // 48/128
{ -6.15644e-03f, 3.53384e-02f, -1.23933e-01f, 4.62289e-01f, 7.44637e-01f, -1.44531e-01f, 3.86247e-02f, -6.53412e-03f }, // 49/128
{ -6.22280e-03f, 3.57519e-02f, -1.25624e-01f, 4.72342e-01f, 7.36222e-01f, -1.45004e-01f, 3.88454e-02f, -6.57836e-03f }, // 50/128
{ -6.28577e-03f, 3.61468e-02f, -1.27258e-01f, 4.82377e-01f, 7.27714e-01f, -1.45387e-01f, 3.90420e-02f, -6.61859e-03f }, // 51/128
{ -6.34530e-03f, 3.65227e-02f, -1.28832e-01f, 4.92389e-01f, 7.19116e-01f, -1.45682e-01f, 3.92147e-02f, -6.65479e-03f }, // 52/128
{ -6.40135e-03f, 3.68795e-02f, -1.30347e-01f, 5.02377e-01f, 7.10431e-01f, -1.45889e-01f, 3.93636e-02f, -6.68698e-03f }, // 53/128
{ -6.45388e-03f, 3.72167e-02f, -1.31800e-01f, 5.12337e-01f, 7.01661e-01f, -1.46009e-01f, 3.94886e-02f, -6.71514e-03f }, // 54/128
{ -6.50285e-03f, 3.75341e-02f, -1.33190e-01f, 5.22267e-01f, 6.92808e-01f, -1.46043e-01f, 3.95900e-02f, -6.73929e-03f }, // 55/128
{ -6.54823e-03f, 3.78315e-02f, -1.34515e-01f, 5.32164e-01f, 6.83875e-01f, -1.45993e-01f, 3.96678e-02f, -6.75943e-03f }, // 56/128
{ -6.58996e-03f, 3.81085e-02f, -1.35775e-01f, 5.42025e-01f, 6.74865e-01f, -1.45859e-01f, 3.97222e-02f, -6.77557e-03f }, // 57/128
{ -6.62802e-03f, 3.83650e-02f, -1.36969e-01f, 5.51849e-01f, 6.65779e-01f, -1.45641e-01f, 3.97532e-02f, -6.78771e-03f }, // 58/128
{ -6.66238e-03f, 3.86006e-02f, -1.38094e-01f, 5.61631e-01f, 6.56621e-01f, -1.45343e-01f, 3.97610e-02f, -6.79588e-03f }, // 59/128
{ -6.69300e-03f, 3.88151e-02f, -1.39150e-01f, 5.71370e-01f, 6.47394e-01f, -1.44963e-01f, 3.97458e-02f, -6.80007e-03f }, // 60/128
{ -6.71985e-03f, 3.90083e-02f, -1.40136e-01f, 5.81063e-01f, 6.38099e-01f, -1.44503e-01f, 3.97077e-02f, -6.80032e-03f }, // 61/128
{ -6.74291e-03f, 3.91800e-02f, -1.41050e-01f, 5.90706e-01f, 6.28739e-01f, -1.43965e-01f, 3.96469e-02f, -6.79662e-03f }, // 62/128
{ -6.76214e-03f, 3.93299e-02f, -1.41891e-01f, 6.00298e-01f, 6.19318e-01f, -1.43350e-01f, 3.95635e-02f, -6.78902e-03f }, // 63/128
{ -6.77751e-03f, 3.94578e-02f, -1.42658e-01f, 6.09836e-01f, 6.09836e-01f, -1.42658e-01f, 3.94578e-02f, -6.77751e-03f }, // 64/128
{ -6.78902e-03f, 3.95635e-02f, -1.43350e-01f, 6.19318e-01f, 6.00298e-01f, -1.41891e-01f, 3.93299e-02f, -6.76214e-03f }, // 65/128
{ -6.79662e-03f, 3.96469e-02f, -1.43965e-01f, 6.28739e-01f, 5.90706e-01f, -1.41050e-01f, 3.91800e-02f, -6.74291e-03f }, // 66/128
{ -6.80032e-03f, 3.97077e-02f, -1.44503e-01f, 6.38099e-01f, 5.81063e-01f, -1.40136e-01f, 3.90083e-02f, -6.71985e-03f }, // 67/128
{ -6.80007e-03f, 3.97458e-02f, -1.44963e-01f, 6.47394e-01f, 5.71370e-01f, -1.39150e-01f, 3.88151e-02f, -6.69300e-03f }, // 68/128
{ -6.79588e-03f, 3.97610e-02f, -1.45343e-01f, 6.56621e-01f, 5.61631e-01f, -1.38094e-01f, 3.86006e-02f, -6.66238e-03f }, // 69/128
{ -6.78771e-03f, 3.97532e-02f, -1.45641e-01f, 6.65779e-01f, 5.51849e-01f, -1.36969e-01f, 3.83650e-02f, -6.62802e-03f }, // 70/128
{ -6.77557e-03f, 3.97222e-02f, -1.45859e-01f, 6.74865e-01f, 5.42025e-01f, -1.35775e-01f, 3.81085e-02f, -6.58996e-03f }, // 71/128
{ -6.75943e-03f, 3.96678e-02f, -1.45993e-01f, 6.83875e-01f, 5.32164e-01f, -1.34515e-01f, 3.78315e-02f, -6.54823e-03f }, // 72/128
{ -6.73929e-03f, 3.95900e-02f, -1.46043e-01f, 6.92808e-01f, 5.22267e-01f, -1.33190e-01f, 3.75341e-02f, -6.50285e-03f }, // 73/128
{ -6.71514e-03f, 3.94886e-02f, -1.46009e-01f, 7.01661e-01f, 5.12337e-01f, -1.31800e-01f, 3.72167e-02f, -6.45388e-03f }, // 74/128
{ -6.68698e-03f, 3.93636e-02f, -1.45889e-01f, 7.10431e-01f, 5.02377e-01f, -1.30347e-01f, 3.68795e-02f, -6.40135e-03f }, // 75/128
{ -6.65479e-03f, 3.92147e-02f, -1.45682e-01f, 7.19116e-01f, 4.92389e-01f, -1.28832e-01f, 3.65227e-02f, -6.34530e-03f }, // 76/128
{ -6.61859e-03f, 3.90420e-02f, -1.45387e-01f, 7.27714e-01f, 4.82377e-01f, -1.27258e-01f, 3.61468e-02f, -6.28577e-03f }, // 77/128
{ -6.57836e-03f, 3.88454e-02f, -1.45004e-01f, 7.36222e-01f, 4.72342e-01f, -1.25624e-01f, 3.57519e-02f, -6.22280e-03f }, // 78/128
{ -6.53412e-03f, 3.86247e-02f, -1.44531e-01f, 7.44637e-01f, 4.62289e-01f, -1.23933e-01f, 3.53384e-02f, -6.15644e-03f }, // 79/128
{ -6.48585e-03f, 3.83800e-02f, -1.43968e-01f, 7.52958e-01f, 4.52218e-01f, -1.22185e-01f, 3.49066e-02f, -6.08674e-03f }, // 80/128
{ -6.43358e-03f, 3.81111e-02f, -1.43313e-01f, 7.61181e-01f, 4.42134e-01f, -1.20382e-01f, 3.44568e-02f, -6.01374e-03f }, // 81/128
{ -6.37730e-03f, 3.78182e-02f, -1.42566e-01f, 7.69305e-01f, 4.32038e-01f, -1.18526e-01f, 3.39894e-02f, -5.93749e-03f }, // 82/128
{ -6.31703e-03f, 3.75010e-02f, -1.41727e-01f, 7.77327e-01f, 4.21934e-01f, -1.16618e-01f, 3.35046e-02f, -5.85804e-03f }, // 83/128
{ -6.25277e-03f, 3.71596e-02f, -1.40794e-01f, 7.85244e-01f, 4.11823e-01f, -1.14659e-01f, 3.30027e-02f, -5.77544e-03f }, // 84/128
{ -6.18454e-03f, 3.67941e-02f, -1.39767e-01f, 7.93055e-01f, 4.01710e-01f, -1.12650e-01f, 3.24843e-02f, -5.68976e-03f }, // 85/128
{ -6.11236e-03f, 3.64044e-02f, -1.38644e-01f, 8.00757e-01f, 3.91596e-01f, -1.10593e-01f, 3.19495e-02f, -5.60103e-03f }, // 86/128
{ -6.03624e-03f, 3.59905e-02f, -1.37426e-01f, 8.08348e-01f, 3.81484e-01f, -1.08490e-01f, 3.13987e-02f, -5.50931e-03f }, // 87/128
{ -5.95620e-03f, 3.55525e-02f, -1.36111e-01f, 8.15826e-01f, 3.71376e-01f, -1.06342e-01f, 3.08323e-02f, -5.41467e-03f }, // 88/128
{ -5.87227e-03f, 3.50903e-02f, -1.34699e-01f, 8.23188e-01f, 3.61276e-01f, -1.04150e-01f, 3.02507e-02f, -5.31716e-03f }, // 89/128
{ -5.78446e-03f, 3.46042e-02f, -1.33189e-01f, 8.30432e-01f, 3.51186e-01f, -1.01915e-01f, 2.96543e-02f, -5.21683e-03f }, // 90/128
{ -5.69280e-03f, 3.40940e-02f, -1.31581e-01f, 8.37557e-01f, 3.41109e-01f, -9.96402e-02f, 2.90433e-02f, -5.11376e-03f }, // 91/128
{ -5.59731e-03f, 3.35600e-02f, -1.29874e-01f, 8.44559e-01f, 3.31048e-01f, -9.73254e-02f, 2.84182e-02f, -5.00800e-03f }, // 92/128
{ -5.49804e-03f, 3.30021e-02f, -1.28068e-01f, 8.51437e-01f, 3.21004e-01f, -9.49727e-02f, 2.77794e-02f, -4.89961e-03f }, // 93/128
{ -5.39500e-03f, 3.24205e-02f, -1.26161e-01f, 8.58189e-01f, 3.10980e-01f, -9.25834e-02f, 2.71272e-02f, -4.78866e-03f }, // 94/128
{ -5.28823e-03f, 3.18153e-02f, -1.24154e-01f, 8.64812e-01f, 3.00980e-01f, -9.01591e-02f, 2.64621e-02f, -4.67520e-03f }, // 95/128
{ -5.17776e-03f, 3.11866e-02f, -1.22047e-01f, 8.71305e-01f, 2.91006e-01f, -8.77011e-02f, 2.57844e-02f, -4.55932e-03f }, // 96/128
{ -5.06363e-03f, 3.05345e-02f, -1.19837e-01f, 8.77666e-01f, 2.81060e-01f, -8.52109e-02f, 2.50946e-02f, -4.44107e-03f }, // 97/128
{ -4.94589e-03f, 2.98593e-02f, -1.17526e-01f, 8.83893e-01f, 2.71144e-01f, -8.26900e-02f, 2.43930e-02f, -4.32052e-03f }, // 98/128
{ -4.82456e-03f, 2.91609e-02f, -1.15113e-01f, 8.89984e-01f, 2.61263e-01f, -8.01399e-02f, 2.36801e-02f, -4.19774e-03f }, // 99/128
{ -4.69970e-03f, 2.84397e-02f, -1.12597e-01f, 8.95936e-01f, 2.51417e-01f, -7.75620e-02f, 2.29562e-02f, -4.07279e-03f }, // 100/128
{ -4.57135e-03f, 2.76957e-02f, -1.09978e-01f, 9.01749e-01f, 2.41609e-01f, -7.49577e-02f, 2.22218e-02f, -3.94576e-03f }, // 101/128
{ -4.43955e-03f, 2.69293e-02f, -1.07256e-01f, 9.07420e-01f, 2.31843e-01f, -7.23286e-02f, 2.14774e-02f, -3.81671e-03f }, // 102/128
{ -4.30435e-03f, 2.61404e-02f, -1.04430e-01f, 9.12947e-01f, 2.22120e-01f, -6.96762e-02f, 2.07233e-02f, -3.68570e-03f }, // 103/128
{ -4.16581e-03f, 2.53295e-02f, -1.01501e-01f, 9.18329e-01f, 2.12443e-01f, -6.70018e-02f, 1.99599e-02f, -3.55283e-03f }, // 104/128
{ -4.02397e-03f, 2.44967e-02f, -9.84679e-02f, 9.23564e-01f, 2.02814e-01f, -6.43069e-02f, 1.91877e-02f, -3.41815e-03f }, // 105/128
{ -3.87888e-03f, 2.36423e-02f, -9.53307e-02f, 9.28650e-01f, 1.93236e-01f, -6.15931e-02f, 1.84071e-02f, -3.28174e-03f }, // 106/128
{ -3.73062e-03f, 2.27664e-02f, -9.20893e-02f, 9.33586e-01f, 1.83711e-01f, -5.88617e-02f, 1.76185e-02f, -3.14367e-03f }, // 107/128
{ -3.57923e-03f, 2.18695e-02f, -8.87435e-02f, 9.38371e-01f, 1.74242e-01f, -5.61142e-02f, 1.68225e-02f, -3.00403e-03f }, // 108/128
{ -3.42477e-03f, 2.09516e-02f, -8.52933e-02f, 9.43001e-01f, 1.64831e-01f, -5.33522e-02f, 1.60193e-02f, -2.86289e-03f }, // 109/128
{ -3.26730e-03f, 2.00132e-02f, -8.17385e-02f, 9.47477e-01f, 1.55480e-01f, -5.05770e-02f, 1.52095e-02f, -2.72032e-03f }, // 110/128
{ -3.10689e-03f, 1.90545e-02f, -7.80792e-02f, 9.51795e-01f, 1.46192e-01f, -4.77900e-02f, 1.43934e-02f, -2.57640e-03f }, // 111/128
{ -2.94361e-03f, 1.80759e-02f, -7.43154e-02f, 9.55956e-01f, 1.36968e-01f, -4.49929e-02f, 1.35716e-02f, -2.43121e-03f }, // 112/128
{ -2.77751e-03f, 1.70776e-02f, -7.04471e-02f, 9.59958e-01f, 1.27812e-01f, -4.21869e-02f, 1.27445e-02f, -2.28483e-03f }, // 113/128
{ -2.60868e-03f, 1.60599e-02f, -6.64743e-02f, 9.63798e-01f, 1.18725e-01f, -3.93735e-02f, 1.19125e-02f, -2.13733e-03f }, // 114/128
{ -2.43718e-03f, 1.50233e-02f, -6.23972e-02f, 9.67477e-01f, 1.09710e-01f, -3.65541e-02f, 1.10760e-02f, -1.98880e-03f }, // 115/128
{ -2.26307e-03f, 1.39681e-02f, -5.82159e-02f, 9.70992e-01f, 1.00769e-01f, -3.37303e-02f, 1.02356e-02f, -1.83931e-03f }, // 116/128
{ -2.08645e-03f, 1.28947e-02f, -5.39305e-02f, 9.74342e-01f, 9.19033e-02f, -3.09033e-02f, 9.39154e-03f, -1.68894e-03f }, // 117/128
{ -1.90738e-03f, 1.18034e-02f, -4.95412e-02f, 9.77526e-01f, 8.31162e-02f, -2.80746e-02f, 8.54441e-03f, -1.53777e-03f }, // 118/128
{ -1.72594e-03f, 1.06946e-02f, -4.50483e-02f, 9.80543e-01f, 7.44095e-02f, -2.52457e-02f, 7.69462e-03f, -1.38589e-03f }, // 119/128
{ -1.54221e-03f, 9.56876e-03f, -4.04519e-02f, 9.83392e-01f, 6.57852e-02f, -2.24178e-02f, 6.84261e-03f, -1.23337e-03f }, // 120/128
{ -1.35627e-03f, 8.42626e-03f, -3.57525e-02f, 9.86071e-01f, 5.72454e-02f, -1.95925e-02f, 5.98883e-03f, -1.08030e-03f }, // 121/128
{ -1.16820e-03f, 7.26755e-03f, -3.09503e-02f, 9.88580e-01f, 4.87921e-02f, -1.67710e-02f, 5.13372e-03f, -9.26747e-04f }, // 122/128
{ -9.78093e-04f, 6.09305e-03f, -2.60456e-02f, 9.90917e-01f, 4.04274e-02f, -1.39548e-02f, 4.27773e-03f, -7.72802e-04f }, // 123/128
{ -7.86031e-04f, 4.90322e-03f, -2.10389e-02f, 9.93082e-01f, 3.21531e-02f, -1.11453e-02f, 3.42130e-03f, -6.18544e-04f }, // 124/128
{ -5.92100e-04f, 3.69852e-03f, -1.59305e-02f, 9.95074e-01f, 2.39714e-02f, -8.34364e-03f, 2.56486e-03f, -4.64053e-04f }, // 125/128
{ -3.96391e-04f, 2.47942e-03f, -1.07209e-02f, 9.96891e-01f, 1.58840e-02f, -5.55134e-03f, 1.70888e-03f, -3.09412e-04f }, // 126/128
{ -1.98993e-04f, 1.24642e-03f, -5.41054e-03f, 9.98534e-01f, 7.89295e-03f, -2.76968e-03f, 8.53777e-04f, -1.54700e-04f }, // 127/128
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 128/128
};
/* Instrumentation Taps */
private static final String INSTRUMENT_SYMBOL_SPREAD = "Tap Point: Symbol Spread (Goal=2.0)";
private FloatTap mSymbolSpreadTap;
private List<Tap> mAvailableTaps;
private static final int NUMBER_FILTER_TAPS = 8;
private static final int NUMBER_FILTER_STEPS = 128;
private static final int SAMPLE_RATE = 48000;
private static final int SYMBOL_RATE = 4800;
/* Tracking loop gain constant */
private static final double K_SYMBOL_SPREAD = 0.0100;
/* Constraints on symbol spreading */
private static final float SYMBOL_SPREAD_MAX = 2.4f; // upper range limit: +20%
private static final float SYMBOL_SPREAD_MIN = 1.6f; // lower range limit: -20%
/* Symbol clock tracking loop gain */
private static final double K_SYMBOL_TIMING = 0.025;
/* Coarse and fine frequency tracking constants */
private static final double K_COARSE_FREQUENCY = 0.00125;
private static final double K_FINE_FREQUENCY = 0.125;
/* Frequency correction broadcast threshold */
// private static final double COARSE_FREQUENCY_DEADBAND = 1.66;
private static final double COARSE_FREQUENCY_DEADBAND = 1.20;
/* 2.0 symbol spread gives -3, -1, 1, 3 */
private float mSymbolSpread = 2.0f;
private float mSymbolClock = 0.0f;
private float mSymbolTime = (float)SYMBOL_RATE / (float)SAMPLE_RATE;
private float mFineFrequencyCorrection = 0.0f;
private float mCoarseFrequencyCorrection = 0.0f;
private float mHistory[] = new float[ NUMBER_FILTER_TAPS ];
private int mHistoryLast = 0;
private boolean mReset = false;
private RealSampleListener mListener;
private GainController mGainController;
private FrequencyCorrectionControl mFrequencyCorrectionControl;
/**
* C4FM Symbol Filter
*
* Sample Gain values - the gain of incoming sample values will critically
* impact the performance of this filter. Gain should be adjusted to
* optimize the value of mSymbolSpread toward a value of 2.0. If the value
* is less than 2.0, then increase gain. If the value is over 2.0, then
* decrease gain.
*
* When preceded by the RealAutomaticGainFilter class, an optimal gain
* setting for this filter is 15.4 and that yields a symbol spread that
* is centered on 2.0, ranging 1.92 to 2.08.
*/
public C4FMSymbolFilter( GainController gainController,
FrequencyCorrectionControl frequencyControl )
{
mGainController = gainController;
mFrequencyCorrectionControl = frequencyControl;
/* Register for reset events, so we can reset internal frequency tracking */
mFrequencyCorrectionControl.setListener( this );
}
@Override
public void receive( float sample )
{
//TODO: limit frequency corrections to once per buffer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!11
if( mReset )
{
mGainController.reset();
mCoarseFrequencyCorrection = 0.0f;
mFineFrequencyCorrection = 0.0f;
mSymbolSpread = 2.0f;
mSymbolClock = 0.0f;
mSymbolTime = (float)SYMBOL_RATE / (float)SAMPLE_RATE;
mReset = false;
}
mSymbolClock += mSymbolTime;
mHistory[ mHistoryLast++ ] = sample;
mHistoryLast %= NUMBER_FILTER_TAPS;
if( mSymbolClock > 1.0f )
{
mSymbolClock -= 1.0f;
int imu = (int)Math.floor( 0.5 +
( (float)NUMBER_FILTER_STEPS * ( mSymbolClock / mSymbolTime ) ) );
if( imu >= NUMBER_FILTER_STEPS )
{
imu = NUMBER_FILTER_STEPS - 1;
}
int imu_p1 = imu + 1;
int j = mHistoryLast;
double interp = 0.0;
double interp_p1 = 0.0;
for( int i = 0; i < NUMBER_FILTER_TAPS; i++ )
{
interp += TAPS[ imu][ i ] * mHistory[ j ];
interp_p1 += TAPS[ imu_p1 ][ i ] * mHistory[ j ];
j = ( j + 1 ) % NUMBER_FILTER_TAPS;
}
/* Output symbol will be interpolated value corrected for symbol
* spread and frequency offset */
interp -= mFineFrequencyCorrection;
interp_p1 -= mFineFrequencyCorrection;
/* Correct output for symbol deviation (spread) */
float output = (float)( 2.0 * interp / mSymbolSpread );
/* Detect received symbol error: basically use a hard decision and
* subtract off expected position nominal symbol level which will be
* +/- 0.5 * symbol spread and +/- 1.5 symbol spread. Remember that
* nominal symbol spread will be 2.0 */
double symbolError;
if( interp < -mSymbolSpread )
{
/* symbol is -3: Expected at -1.5 * symbol spread */
symbolError = interp + ( 1.5 * mSymbolSpread );
mSymbolSpread -= ( symbolError * 0.5 * K_SYMBOL_SPREAD );
}
else if( interp < 0.0 )
{
/* symbol is -1: Expected at -0.5 * symbol_spread */
symbolError = interp + (0.5 * mSymbolSpread );
mSymbolSpread -= ( symbolError * K_SYMBOL_SPREAD );
}
else if( interp < mSymbolSpread )
{
/* symbol is +1: Expected at +0.5 * symbol_spread */
symbolError = interp - ( 0.5 * mSymbolSpread );
mSymbolSpread += ( symbolError * K_SYMBOL_SPREAD );
}
else
{
/* symbol is +3: Expected at +1.5 * symbol_spread */
symbolError = interp - ( 1.5 * mSymbolSpread );
mSymbolSpread += ( symbolError * 0.5 * K_SYMBOL_SPREAD );
}
/* Symbol clock tracking loop adjustment */
if( interp_p1 < interp )
{
mSymbolClock += symbolError * K_SYMBOL_TIMING;
}
else
{
mSymbolClock -= symbolError * K_SYMBOL_TIMING;
}
if( mSymbolSpread < SYMBOL_SPREAD_MIN )
{
mGainController.increase();
mSymbolSpread = SYMBOL_SPREAD_MIN;
}
else if( mSymbolSpread > SYMBOL_SPREAD_MAX )
{
mGainController.decrease();
mSymbolSpread = SYMBOL_SPREAD_MAX;
}
mCoarseFrequencyCorrection += ( ( mFineFrequencyCorrection -
mCoarseFrequencyCorrection ) * K_COARSE_FREQUENCY );
mFineFrequencyCorrection += ( symbolError * K_FINE_FREQUENCY );
/* send frequency correction */
if( Math.abs( mCoarseFrequencyCorrection ) > COARSE_FREQUENCY_DEADBAND )
{
int adjustment = 500 * ( mCoarseFrequencyCorrection > 0 ? 1 : -1 );
mFrequencyCorrectionControl.adjust( adjustment );
/* reset internal frequency error tracker */
mCoarseFrequencyCorrection = 0;
}
if( mSymbolSpreadTap != null )
{
mSymbolSpreadTap.receive( mSymbolSpread );
}
/* dispatch the interpolated value to the listener */
if( mListener != null )
{
mListener.receive( output );
}
}
}
/**
* Listener interface for frequency correction reset events. We reset the
* coarse and fine frequency control values when the tuner channel source
* has changed frequency or frequency offset.
*/
@Override
public void resetFrequencyCorrection()
{
mReset = true;
}
@Override
public void setListener( RealSampleListener listener )
{
mListener = listener;
}
@Override
public void removeListener( RealSampleListener listener )
{
mListener = null;
}
@Override
public List<Tap> getTaps()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
/*
* Since the target is 2.0, we subtract 2.0 from the value so that
* a good value is plotted right on the zero center line
*/
mAvailableTaps.add(
new AdditiveFloatTap( INSTRUMENT_SYMBOL_SPREAD, 0, 0.1f, -2.0f ) );
}
return mAvailableTaps;
}
@Override
public void addTap( Tap tap )
{
if( tap.getName().contentEquals( INSTRUMENT_SYMBOL_SPREAD ) )
{
mSymbolSpreadTap = (FloatTap)tap;
}
}
@Override
public void removeTap( Tap tap )
{
if( tap.getName().contentEquals( INSTRUMENT_SYMBOL_SPREAD ) )
{
mSymbolSpreadTap = null;
}
}
}

View File

@@ -0,0 +1,97 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.p25;
import javax.xml.bind.annotation.XmlAttribute;
import module.decode.DecoderType;
import module.decode.config.DecodeConfiguration;
import module.decode.p25.P25Decoder.Modulation;
public class DecodeConfigP25Phase1 extends DecodeConfiguration
{
private P25_LSMDecoder.Modulation mModulation = Modulation.C4FM;
private int mCallTimeout = DEFAULT_CALL_TIMEOUT_SECONDS;
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
public DecodeConfigP25Phase1()
{
super( DecoderType.P25_PHASE1 );
setAFC( false );
}
public boolean supportsAFC()
{
return false;
}
@XmlAttribute( name = "modulation" )
public P25_LSMDecoder.Modulation getModulation()
{
return mModulation;
}
public void setModulation( P25_LSMDecoder.Modulation modulation )
{
mModulation = modulation;
}
@XmlAttribute( name="call_timeout" )
public int getCallTimeout()
{
return mCallTimeout;
}
/**
* Sets the call timeout value in seconds ( 10 - 600 );
* @param timeout
*/
public void setCallTimeout( int timeout )
{
if( CALL_TIMEOUT_MINIMUM <= timeout && timeout <= CALL_TIMEOUT_MAXIMUM )
{
mCallTimeout = timeout;
}
else
{
mCallTimeout = DEFAULT_CALL_TIMEOUT_SECONDS;
}
}
@XmlAttribute( name="traffic_channel_pool_size" )
public int getTrafficChannelPoolSize()
{
return mTrafficChannelPoolSize;
}
/**
* Sets the traffic channel pool size which is the maximum number of
* simultaneous traffic channels that can be allocated.
*
* This limits the maximum calls so that busy systems won't cause more
* traffic channels to be allocated than the decoder/software/host computer
* can support.
*/
public void setTrafficChannelPoolSize( int size )
{
mTrafficChannelPoolSize = size;
}
}

View File

@@ -0,0 +1,154 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.p25;
import module.decode.DecoderType;
import module.decode.event.CallEvent;
import alias.Alias;
import alias.AliasList;
public class P25CallEvent extends CallEvent
{
private String mChannel;
private long mFrequency;
public P25CallEvent( CallEventType callEventType,
AliasList aliasList,
String fromID,
String toID,
String channel,
long frequency,
String details )
{
super( DecoderType.P25_PHASE1, callEventType,
aliasList, fromID, toID, details );
mChannel = channel;
mFrequency = frequency;
}
private Alias getAlias( String talkgroup )
{
if( hasAliasList() )
{
return getAliasList().getTalkgroupAlias( talkgroup );
}
return null;
}
@Override
public Alias getFromIDAlias()
{
return getAlias( getFromID() );
}
@Override
public Alias getToIDAlias()
{
return getAlias( getToID() );
}
@Override
public String getChannel()
{
return mChannel;
}
@Override
public long getFrequency()
{
return mFrequency;
}
public static class Builder
{
/* Required parameters */
private CallEventType mCallEventType;
/* Optional parameters */
private AliasList mAliasList;
private String mFromID;
private String mToID;
private String mDetails;
private String mChannel;
private long mFrequency;
public Builder( CallEventType callEventType )
{
mCallEventType = callEventType;
}
public Builder aliasList( AliasList aliasList )
{
mAliasList = aliasList;
return this;
}
public Builder from( String val )
{
mFromID = val;
return this;
}
public Builder channel( String channel )
{
mChannel = channel;
return this;
}
public Builder details( String details )
{
mDetails = details;
return this;
}
public Builder frequency( long frequency )
{
mFrequency = frequency;
return this;
}
public Builder to( String toID )
{
mToID = toID;
return this;
}
public P25CallEvent build()
{
return new P25CallEvent( this );
}
}
/**
* Private constructor for the builder
*/
private P25CallEvent( Builder builder )
{
this( builder.mCallEventType,
builder.mAliasList,
builder.mFromID,
builder.mToID,
builder.mChannel,
builder.mFrequency,
builder.mDetails );
}
}

View File

@@ -0,0 +1,65 @@
package module.decode.p25;
import instrument.Instrumentable;
import module.decode.Decoder;
import module.decode.DecoderType;
import source.tuner.frequency.IFrequencyCorrectionController;
import alias.AliasList;
import controller.channel.Channel.ChannelType;
public abstract class P25Decoder extends Decoder
implements IFrequencyCorrectionController, Instrumentable
{
private P25MessageProcessor mMessageProcessor;
public P25Decoder( AliasList aliasList, ChannelType channelType )
{
super( new P25DecoderState( aliasList, channelType ) );
mMessageProcessor = new P25MessageProcessor( aliasList );
mMessageProcessor.setMessageListener( mMessageBroadcaster );
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.P25_PHASE1;
}
protected P25MessageProcessor getMessageProcessor()
{
return mMessageProcessor;
}
public abstract Modulation getModulation();
public enum Modulation
{
CQPSK( "Simulcast (LSM)", "LSM" ),
C4FM( "Normal (C4FM)", "C4FM" );
private String mLabel;
private String mShortLabel;
private Modulation( String label, String shortLabel )
{
mLabel = label;
mShortLabel = shortLabel;
}
public String getLabel()
{
return mLabel;
}
public String getShortLabel()
{
return mShortLabel;
}
public String toString()
{
return getLabel();
}
}
}

View File

@@ -0,0 +1,200 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.p25;
import java.awt.EventQueue;
import javax.swing.JLabel;
import net.miginfocom.swing.MigLayout;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderPanel;
import module.decode.state.State;
import settings.ColorSetting;
import settings.Setting;
import settings.SettingsManager;
import alias.Alias;
public class P25DecoderPanel extends DecoderPanel
{
private static final long serialVersionUID = 1L;
private JLabel mProtocol;
private JLabel mFrom = new JLabel( " " );
private JLabel mFromAlias = new JLabel( " " );
private JLabel mNAC = new JLabel( "NAC:" );
private JLabel mTo = new JLabel( " " );
private JLabel mToAlias = new JLabel( " " );
private JLabel mSystem = new JLabel( "SYS:" );
private JLabel mSite = new JLabel( "Site:" );
private JLabel mSiteAlias = new JLabel( "" );
public P25DecoderPanel( SettingsManager settingsManager, P25Decoder decoder )
{
super( settingsManager, decoder );
mProtocol = new JLabel( "P25-1 " + decoder.getModulation().getShortLabel() );
init();
}
public void init()
{
super.init();
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]0[]") );
mProtocol.setFont( mFontDecoder );
mProtocol.setForeground( mColorLabelDecoder );
mFrom.setFont( mFontDecoder );
mFrom.setForeground( mColorLabelDecoder );
mFromAlias.setFont( mFontDecoder );
mFromAlias.setForeground( mColorLabelDecoder );
mNAC.setFont( mFontDetails );
mNAC.setForeground( mColorLabelDecoder );
mTo.setFont( mFontDecoder );
mTo.setForeground( mColorLabelDecoder );
mToAlias.setFont( mFontDecoder );
mToAlias.setForeground( mColorLabelDecoder );
mSystem.setFont( mFontDetails );
mSystem.setForeground( mColorLabelDecoder );
mSiteAlias.setFont( mFontDecoder );
mSiteAlias.setForeground( mColorLabelDecoder );
mSite.setFont( mFontDetails );
mSite.setForeground( mColorLabelDecoder );
add( mProtocol );
add( mFrom );
add( mFromAlias, "wrap" );
add( mNAC );
add( mTo );
add( mToAlias, "wrap" );
add( mSystem );
add( mSite );
add( mSiteAlias, "wrap" );
}
private P25DecoderState getDecoderState()
{
return (P25DecoderState)getDecoder().getDecoderState();
}
@Override
public void receive( final ChangedAttribute changedAttribute )
{
final P25DecoderState state = getDecoderState();
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
switch( changedAttribute )
{
case NAC:
mNAC.setText( "NAC:" + state.getNAC() );
break;
case SYSTEM:
mSystem.setText( "SYS:" + state.getSystem() );
break;
case SITE:
mSite.setText( "SITE:" + state.getSite() );
break;
case SITE_ALIAS:
mSiteAlias.setText( state.getSiteAlias() );
break;
case FROM_TALKGROUP:
mFrom.setText( state.getFromTalkgroup() );
break;
case FROM_TALKGROUP_ALIAS:
Alias from = state.getFromAlias();
if( from != null )
{
mFromAlias.setText( from.getName() );
mFromAlias.setIcon( mSettingsManager
.getImageIcon( from.getIconName(), 12 ) );
}
else
{
mFromAlias.setText( null );
mFromAlias.setIcon( null );
}
break;
case TO_TALKGROUP:
mTo.setText( state.getToTalkgroup() );
break;
case TO_TALKGROUP_ALIAS:
Alias to = state.getToAlias();
if( to != null )
{
mToAlias.setText( to.getName() );
mToAlias.setIcon( mSettingsManager
.getImageIcon( to.getIconName(), 12 ) );
}
else
{
mToAlias.setText( null );
mToAlias.setIcon( null );
}
break;
}
repaint();
}
});
}
@Override
public void settingChanged( Setting setting )
{
super.settingChanged( setting );
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case CHANNEL_STATE_LABEL_DECODER:
if( mProtocol != null )
{
mProtocol.setForeground( mColorLabelDecoder );
}
break;
default:
break;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package module.decode.p25;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import module.decode.DecodeEditor;
import module.decode.config.DecodeConfiguration;
import module.decode.p25.P25Decoder.Modulation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import source.SourceEditor;
import source.tuner.TunerEditor;
import controller.Editor;
import controller.channel.ChannelValidationException;
public class P25Editor extends DecodeEditor
{
private final static Logger mLog = LoggerFactory.getLogger( P25Editor.class );
private static final long serialVersionUID = 1L;
private JComboBox<P25_LSMDecoder.Modulation> mComboModulation;
public P25Editor( DecodeConfiguration config )
{
super( config );
initGUI();
}
private void initGUI()
{
mComboModulation = new JComboBox<P25_LSMDecoder.Modulation>();
mComboModulation.setModel( new DefaultComboBoxModel<P25_LSMDecoder.Modulation>(
P25_LSMDecoder.Modulation.values() ) );
mComboModulation.setSelectedItem( ((DecodeConfigP25Phase1)mConfig).getModulation() );
mComboModulation.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
P25_LSMDecoder.Modulation selected = mComboModulation
.getItemAt( mComboModulation.getSelectedIndex() );
if( selected != null )
{
((DecodeConfigP25Phase1)mConfig).setModulation( selected );
}
}
} );
add( new JLabel( "Modulation:" ) );
add( mComboModulation, "wrap" );
}
/**
* Enforce source=tuner for CQPSK modulation
*/
@Override
public void validate( Editor editor ) throws ChannelValidationException
{
if( editor instanceof SourceEditor &&
((DecodeConfigP25Phase1)mConfig).getModulation() == Modulation.CQPSK )
{
if( !( editor instanceof TunerEditor ) )
{
throw new ChannelValidationException(
"<html><body width='175'><h1>LSM Simulcast</h1>"
+ "<p>P25 LSM Simulcast decoder can only be used with "
+ "a tuner source. Please change the Source to use a tuner"
+ " or change the P25 Decoder to C4FM modulation" );
}
}
}
}

View File

@@ -0,0 +1,161 @@
package module.decode.p25;
import java.util.BitSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import bits.BinaryMessage;
/**
* Utility class to process interleave of P25 Voice and Data messages.
*/
public class P25Interleave
{
private final static Logger mLog =
LoggerFactory.getLogger( P25Interleave.class );
private static int[] DATA_INTERLEAVE = new int[] { 0,1,2,3,52,53,54,55,100,
101,102,103,148,149,150,151,4,5,6,7,56,57,58,59,104,105,106,107,152,153,
154,155,8,9,10,11,60,61,62,63,108,109,110,111,156,157,158,159,12,13,14,
15,64,65,66,67,112,113,114,115,160,161,162,163,16,17,18,19,68,69,70,71,
116,117,118,119,164,165,166,167,20,21,22,23,72,73,74,75,120,121,122,123,
168,169,170,171,24,25,26,27,76,77,78,79,124,125,126,127,172,173,174,175,
28,29,30,31,80,81,82,83,128,129,130,131,176,177,178,179,32,33,34,35,84,
85,86,87,132,133,134,135,180,181,182,183,36,37,38,39,88,89,90,91,136,
137,138,139,184,185,186,187,40,41,42,43,92,93,94,95,140,141,142,143,188,
189,190,191,44,45,46,47,96,97,98,99,144,145,146,147,192,193,194,195,48,
49,50,51 };
private static int[] DATA_DEINTERLEAVE = new int[] { 0,1,2,3,16,17,18,19,32,
33,34,35,48,49,50,51,64,65,66,67,80,81,82,83,96,97,98,99,112,113,114,115,
128,129,130,131,144,145,146,147,160,161,162,163,176,177,178,179,192,193,
194,195,4,5,6,7,20,21,22,23,36,37,38,39,52,53,54,55,68,69,70,71,84,85,
86,87,100,101,102,103,116,117,118,119,132,133,134,135,148,149,150,151,
164,165,166,167,180,181,182,183,8,9,10,11,24,25,26,27,40,41,42,43,56,57,
58,59,72,73,74,75,88,89,90,91,104,105,106,107,120,121,122,123,136,137,
138,139,152,153,154,155,168,169,170,171,184,185,186,187,12,13,14,15,28,
29,30,31,44,45,46,47,60,61,62,63,76,77,78,79,92,93,94,95,108,109,110,
111,124,125,126,127,140,141,142,143,156,157,158,159,172,173,174,175,188,
189,190,191 };
private static int[] VOICE_INTERLEAVE = new int[] { 0,24,48,72,96,120,25,
1,73,49,121,97,2,26,50,74,98,122,27,3,75,51,123,99,4,28,52,76,100,124,
29,5,77,53,125,101,6,30,54,78,102,126,31,7,79,55,127,103,8,32,56,80,104,
128,33,9,81,57,129,105,10,34,58,82,106,130,35,11,83,59,131,107,12,36,60,
84,108,132,37,13,85,61,133,109,14,38,62,86,110,134,39,87,15,63,135,111,
16,40,64,88,112,136,41,17,89,65,137,113,18,42,66,90,114,138,43,19,91,
67,139,115,20,44,68,92,116,140,45,21,93,69,141,117,22,46,70,94,119,142,
47,23,95,71,143,118 };
private static int[] VOICE_DEINTERLEAVE = new int[] { 0,7,12,19,24,31,36,43,
48,55,60,67,72,79,84,91,96,103,108,115,120,127,132,139,1,6,13,18,25,30,
37,42,49,54,61,66,73,78,85,90,97,102,109,114,121,126,133,138,2,9,14,21,
26,33,38,45,50,57,62,69,74,81,86,93,98,105,110,117,122,129,134,141,3,8,
15,20,27,32,39,44,51,56,63,68,75,80,87,92,99,104,111,116,123,128,135,
140,4,11,16,23,28,35,40,47,52,59,64,71,76,83,88,95,100,107,112,119,124,
131,136,143,5,10,17,22,29,34,41,46,53,58,65,70,77,82,89,94,101,106,113,
118,125,130,137,142 };
/**
* Deinterleaves the 196-bit block in message, identified by start and end
* bit positions. Note: end index (exclusive) should be one more than the
* last bit in the block.
*
* @param message - source message to deinterleave
* @param start - starting bit index for the block
* @param end - ending bit index for the block, plus 1
*/
public static BinaryMessage deinterleaveData( BinaryMessage message,
int start, int end )
{
return deinterleave( DATA_DEINTERLEAVE, message, start, end );
}
public static BinaryMessage deinterleaveVoice( BinaryMessage message,
int start, int end )
{
return deinterleave( VOICE_DEINTERLEAVE, message, start, end );
}
public static BinaryMessage deinterleave( int[] pattern, BinaryMessage message,
int start, int end )
{
BitSet original = message.get( start, end );
/* Clear block bits in source message */
message.clear( start, end );
/* Iterate only the set bits in the original message and apply
* the deinterleave -- we don't have to evaluate the 0 bits */
for (int i = original.nextSetBit( 0 );
i >= 0 && i < pattern.length;
i = original.nextSetBit( i + 1 ) )
{
message.set( start + pattern[ i ] );
}
return message;
}
/**
* Interleaves the 196-bit block in message, identified by start and end
* bit positions. Note: end index (exclusive) should be one more than the
* last bit in the block.
*
* @param message - source message to interleave
* @param start - starting bit index for the block
* @param end - ending bit index for the block, plus 1
*/
public static BinaryMessage interleaveData( BinaryMessage message,
int start, int end )
{
return interleave( DATA_INTERLEAVE, message, start, end );
}
public static BinaryMessage interleaveVoice( BinaryMessage message,
int start, int end )
{
return interleave( VOICE_INTERLEAVE, message, start, end );
}
public static BinaryMessage interleave( int[] pattern, BinaryMessage message,
int start, int end )
{
BitSet original = message.get( start, end );
/* Clear block bits in source message */
message.clear( start, end );
/* Iterate only the set bits in the original message and apply
* the deinterleave -- we don't have to evaluate the 0 bits */
for (int i = original.nextSetBit( 0 );
i >= 0 && i < pattern.length;
i = original.nextSetBit( i + 1 ) )
{
message.set( start + pattern[ i ] );
}
return message;
}
public static void main( String[] args )
{
int start = 0;
int end = 144;
int set = 50;
String interleaved = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
BinaryMessage b = BinaryMessage.load( interleaved );
b.set( set );
mLog.debug( "INT:" + b.toString() );
P25Interleave.deinterleaveVoice( b, start, end );
mLog.debug( "DEI:" + b.toString() );
mLog.debug( "In: " + set + " Out:" + b.nextSetBit( 0 ) );
}
}

View File

@@ -0,0 +1,692 @@
package module.decode.p25;
import java.util.ArrayList;
import message.Message;
import module.decode.p25.message.P25Message;
import module.decode.p25.message.hdu.HDUMessage;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.message.ldu.LDU2Message;
import module.decode.p25.message.ldu.lc.LDULCMessageFactory;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.message.pdu.PDUMessageFactory;
import module.decode.p25.message.pdu.confirmed.PDUConfirmedMessage;
import module.decode.p25.message.pdu.confirmed.PDUConfirmedMessageFactory;
import module.decode.p25.message.tdu.TDUMessage;
import module.decode.p25.message.tdu.lc.TDULCMessageFactory;
import module.decode.p25.message.tdu.lc.TDULinkControlMessage;
import module.decode.p25.message.tsbk.TSBKMessage;
import module.decode.p25.message.tsbk.TSBKMessageFactory;
import module.decode.p25.reference.DataUnitID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import alias.AliasList;
import bits.BinaryMessage;
import bits.BitSetFullException;
import bits.ISyncDetectListener;
import bits.MultiSyncPatternMatcher;
import bits.SoftSyncDetector;
import bits.SyncDetector;
import dsp.psk.CQPSKDemodulator;
import dsp.psk.CQPSKDemodulator2;
import dsp.symbol.Dibit;
import dsp.symbol.FrameSync;
import edac.BCH_63_16_11;
import edac.CRC;
import edac.CRCP25;
public class P25MessageFramer implements Listener<Dibit>
{
private final static Logger mLog =
LoggerFactory.getLogger( P25MessageFramer.class );
/* Determines the threshold for sync pattern soft matching */
private static final int SYNC_MATCH_THRESHOLD = 2;
private static final int SYNC_IN_CALL_THRESHOLD = 4;
private SoftSyncDetector mPrimarySyncDetector = new SoftSyncDetector(
FrameSync.P25_PHASE1_NORMAL.getSync(), SYNC_MATCH_THRESHOLD );
/* Costas Loop phase lock error correction values. A phase lock error of
* 90 degrees requires a correction of 1/4 of the symbol rate (1200Hz). An
* error of 180 degrees requires a correction of 1/2 of the symbol rate */
public static final double SYMBOL_FREQUENCY = 2.0d * Math.PI * 4800.0d / 48000.0d;
public static final double PHASE_CORRECTION_90_DEGREES = SYMBOL_FREQUENCY / 4.0d;
public static final double PHASE_CORRECTION_180_DEGREES = SYMBOL_FREQUENCY / 2.0d;
private MultiSyncPatternMatcher mMatcher = new MultiSyncPatternMatcher( 48 );
private ArrayList<P25MessageAssembler> mAssemblers =
new ArrayList<P25MessageAssembler>();
public static final int TSBK_BEGIN = 64;
public static final int TSBK_CRC_START = 144;
public static final int TSBK_END = 260;
public static final int TSBK_DECODED_END = 160;
public static final int PDU0_BEGIN = 64;
public static final int PDU0_CRC_BEGIN = 144;
public static final int PDU0_END = 260;
public static final int PDU1_BEGIN = 160;
public static final int PDU1_END = 356;
public static final int PDU2_BEGIN = 256;
public static final int PDU2_END = 452;
public static final int PDU3_BEGIN = 352;
public static final int PDU3_END = 548;
public static final int PDU3_DECODED_END = 448;
private Listener<Message> mListener;
private AliasList mAliasList;
private Trellis_1_2_Rate mHalfRate = new Trellis_1_2_Rate();
private Trellis_3_4_Rate mThreeQuarterRate = new Trellis_3_4_Rate();
private BCH_63_16_11 mNIDDecoder = new BCH_63_16_11();
/**
* Constructs a P25 message framer to receive a stream of symbols and
* detect the sync pattern then capture the following stream of symbols up
* to the message length, and then broadcast that bit buffer to the registered
* listener.
*/
public P25MessageFramer( AliasList aliasList )
{
mAliasList = aliasList;
mPrimarySyncDetector.setListener( new ISyncDetectListener()
{
@Override
public void syncDetected()
{
for( P25MessageAssembler assembler: mAssemblers )
{
if( !assembler.isActive() )
{
assembler.setActive( true );
break;
}
}
}
} );
mMatcher.add( mPrimarySyncDetector );
/**
* We use two message assemblers to catch any sync detections, so that
* if we have a false trigger then we still have a chance to catch the
* valid message. If we have two false triggers before the arrival of
* the actual message sync, then the third or subsequent real or false
* sync pattern will produce a debug message indicating no assemblers
* are available..
*/
mAssemblers.add( new P25MessageAssembler() );
mAssemblers.add( new P25MessageAssembler() );
}
public P25MessageFramer( AliasList aliasList, CQPSKDemodulator demodulator )
{
this( aliasList );
if( demodulator != null )
{
/* For CQPSK, we include 3 additional sync detectors to watch for and
* correction +/-90 and 180 degree costas loop phase lock errors */
mMatcher.add( new CostasPhaseErrorDetector( FrameSync.P25_PHASE1_ERROR_90_CCW,
demodulator, PHASE_CORRECTION_90_DEGREES ) );
mMatcher.add( new CostasPhaseErrorDetector( FrameSync.P25_PHASE1_ERROR_90_CW,
demodulator, -PHASE_CORRECTION_90_DEGREES ) );
mMatcher.add( new CostasPhaseErrorDetector( FrameSync.P25_PHASE1_ERROR_180,
demodulator, PHASE_CORRECTION_180_DEGREES ) );
}
}
private void dispatch( Message message )
{
if( mListener != null )
{
mListener.receive( message );
}
}
@Override
public void receive( Dibit symbol )
{
for( P25MessageAssembler assembler: mAssemblers )
{
if( assembler.isActive() )
{
assembler.receive( symbol );
if( assembler.complete() )
{
assembler.reset();
}
}
}
mMatcher.receive( symbol.getBit1(), symbol.getBit2() );
}
public void setListener( Listener<Message> listener )
{
mListener = listener;
}
public void removeListener( Listener<Message> listener )
{
mListener = null;
}
private class P25MessageAssembler
{
/* Starting position of the status symbol counter is 24 symbols to
* account for the 48-bit sync pattern which is not included in message */
private int mStatusSymbolPointer = 24;
private BinaryMessage mMessage;
private int mMessageLength;
private boolean mComplete = false;
private boolean mActive = false;
private DataUnitID mDUID = DataUnitID.NID;
public P25MessageAssembler()
{
mMessageLength = mDUID.getMessageLength();
mMessage = new BinaryMessage( mMessageLength );
reset();
}
public void receive( Dibit dibit )
{
if( mActive )
{
if( mStatusSymbolPointer == 35 )
{
mStatusSymbolPointer = 0;
}
else
{
mStatusSymbolPointer++;
try
{
mMessage.add( dibit.getBit1() );
mMessage.add( dibit.getBit2() );
}
catch( BitSetFullException e )
{
mComplete = true;
}
/* Check the message for complete */
if( mMessage.isFull() )
{
checkComplete();
}
}
}
}
public void reset()
{
mDUID = DataUnitID.NID;
mMessage.setSize( mDUID.getMessageLength() );
mMessage.clear();
mStatusSymbolPointer = 24;
mComplete = false;
mActive = false;
}
public void setActive( boolean active )
{
mActive = active;
}
private void setDUID( DataUnitID id )
{
mDUID = id;
mMessageLength = id.getMessageLength();
mMessage.setSize( mMessageLength );
}
private void checkComplete()
{
switch( mDUID )
{
case NID:
mMessage = mNIDDecoder.correctNID( mMessage );
if( mMessage.getCRC() != CRC.FAILED_CRC )
{
int value = mMessage.getInt( P25Message.DUID );
DataUnitID duid = DataUnitID.fromValue( value );
if( duid != DataUnitID.UNKN )
{
setDUID( duid );
}
else
{
mComplete = true;
}
}
else
{
mComplete = true;
}
break;
case HDU:
mComplete = true;
dispatch( new HDUMessage( mMessage.copy(), mDUID, mAliasList ) );
/* We're in a call now, lower the sync match threshold */
mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD );
break;
case LDU1:
mComplete = true;
LDU1Message ldu1 = new LDU1Message( mMessage.copy(),
mDUID, mAliasList );
/* Convert the LDU1 message into a link control LDU1 message */
dispatch( LDULCMessageFactory.getMessage( ldu1 ) );
/* We're in a call now, lower the sync match threshold */
mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD );
break;
case LDU2:
mComplete = true;
dispatch( new LDU2Message( mMessage.copy(), mDUID, mAliasList ) );
/* We're in a call now, lower the sync match threshold */
mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD );
break;
case PDU0:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, PDU0_BEGIN, PDU0_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, PDU0_BEGIN, PDU0_END ) )
{
mMessage = CRCP25.correctCCITT80( mMessage,
PDU0_BEGIN, PDU0_CRC_BEGIN );
if( mMessage.getCRC() != CRC.FAILED_CRC )
{
boolean confirmed = mMessage.get(
PDUMessage.CONFIRMATION_REQUIRED_INDICATOR );
if( confirmed )
{
setDUID( DataUnitID.PDUC );
}
else
{
setDUID( DataUnitID.PDU1 );
}
mMessage.setPointer( PDU1_BEGIN );
}
else
{
mComplete = true;
}
}
else
{
mComplete = true;
}
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case PDU1:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, PDU1_BEGIN, PDU1_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, PDU1_BEGIN, PDU1_END ) )
{
if( mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW ) == 1 )
{
mMessage.setSize( PDU2_BEGIN );
PDUMessage pduMessage1 = PDUMessageFactory.getMessage(
mMessage.copy(), DataUnitID.PDU1, mAliasList );
dispatch( pduMessage1 );
mComplete = true;
}
else
{
setDUID( DataUnitID.PDU2 );
mMessage.setPointer( PDU2_BEGIN );
}
}
else
{
mComplete = true;
}
break;
case PDU2:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, PDU2_BEGIN, PDU2_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, PDU2_BEGIN, PDU2_END ) )
{
if( mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW ) == 2 )
{
mMessage.setSize( PDU3_BEGIN );
PDUMessage pduMessage2 = PDUMessageFactory.getMessage(
mMessage.copy(), DataUnitID.PDU2, mAliasList );
dispatch( pduMessage2 );
mComplete = true;
}
else
{
setDUID( DataUnitID.PDU3 );
mMessage.setPointer( PDU3_BEGIN );
}
}
else
{
mComplete = true;
}
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case PDU3:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, PDU3_BEGIN, PDU3_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, PDU3_BEGIN, PDU3_END ) )
{
mMessage.setSize( PDU3_DECODED_END );
PDUMessage pduMessage3 = PDUMessageFactory.getMessage(
mMessage.copy(), DataUnitID.PDU3, mAliasList );
dispatch( pduMessage3 );
}
mComplete = true;
break;
case PDUC:
/* De-interleave the latest block*/
P25Interleave.deinterleaveData( mMessage,
mMessage.size() - 196, mMessage.size() );
/* Decode 3/4 rate convolutional encoding from latest block */
if( mThreeQuarterRate.decode( mMessage,
mMessage.size() - 196, mMessage.size() ) )
{
/* Resize the message and adjust the message pointer
* to account for removing 48 + 4 parity bits */
mMessage.setSize( mMessage.size() - 52 );
mMessage.adjustPointer( -52 );
int blocks = mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW );
int current = ( mMessage.size() - 160 ) / 144;
if( current < blocks )
{
mMessage.setSize( mMessage.size() + 196 );
mMessageLength = mMessage.size();
}
else
{
PDUConfirmedMessage pducm =
new PDUConfirmedMessage( mMessage.copy(),
mAliasList );
/* Translate into correct subclass */
dispatch( PDUConfirmedMessageFactory.getMessage( pducm ) );
mComplete = true;
}
break;
}
else
{
PDUConfirmedMessage pducm =
new PDUConfirmedMessage( mMessage.copy(),
mAliasList );
/* Translate into correct subclass */
dispatch( PDUConfirmedMessageFactory.getMessage( pducm ) );
mComplete = true;
}
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case TDU:
dispatch( new TDUMessage( mMessage.copy(), mDUID, mAliasList ) );
mComplete = true;
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case TDULC:
TDULinkControlMessage tdulc = new TDULinkControlMessage(
mMessage.copy(), mDUID, mAliasList );
/* Convert to an appropriate link control message */
tdulc = TDULCMessageFactory.getMessage( tdulc );
dispatch( tdulc );
mComplete = true;
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case TSBK1:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, TSBK_BEGIN, TSBK_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
{
mMessage = CRCP25.correctCCITT80( mMessage,
TSBK_BEGIN, TSBK_CRC_START );
if( mMessage.getCRC() != CRC.FAILED_CRC )
{
BinaryMessage tsbkBuffer1 = mMessage.copy();
tsbkBuffer1.setSize( TSBK_DECODED_END );
TSBKMessage tsbkMessage1 = TSBKMessageFactory.getMessage(
tsbkBuffer1, DataUnitID.TSBK1, mAliasList );
if( tsbkMessage1.isLastBlock() )
{
mComplete = true;
}
else
{
setDUID( DataUnitID.TSBK2 );
mMessage.setPointer( TSBK_BEGIN );
}
dispatch( tsbkMessage1 );
}
else
{
mComplete = true;
}
}
else
{
mComplete = true;
}
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case TSBK2:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, TSBK_BEGIN, TSBK_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
{
mMessage = CRCP25.correctCCITT80( mMessage,
TSBK_BEGIN, TSBK_CRC_START );
if( mMessage.getCRC() != CRC.FAILED_CRC )
{
BinaryMessage tsbkBuffer2 = mMessage.copy();
tsbkBuffer2.setSize( TSBK_DECODED_END );
TSBKMessage tsbkMessage2 = TSBKMessageFactory.getMessage(
tsbkBuffer2, DataUnitID.TSBK2, mAliasList );
if( tsbkMessage2.isLastBlock() )
{
mComplete = true;
}
else
{
setDUID( DataUnitID.TSBK3 );
mMessage.setPointer( TSBK_BEGIN );
}
dispatch( tsbkMessage2 );
}
else
{
mComplete = true;
}
}
else
{
mComplete = true;
}
break;
case TSBK3:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, TSBK_BEGIN, TSBK_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
{
mMessage = CRCP25.correctCCITT80( mMessage,
TSBK_BEGIN, TSBK_CRC_START );
if( mMessage.getCRC() != CRC.FAILED_CRC )
{
BinaryMessage tsbkBuffer3 = mMessage.copy();
tsbkBuffer3.setSize( TSBK_DECODED_END );
TSBKMessage tsbkMessage3 = TSBKMessageFactory.getMessage(
tsbkBuffer3, DataUnitID.TSBK3, mAliasList );
dispatch( tsbkMessage3 );
}
}
mComplete = true;
break;
case UNKN:
mComplete = true;
dispatch( new P25Message( mMessage.copy(), mDUID, mAliasList ) );
break;
default:
mComplete = true;
break;
}
}
public void dispose()
{
mMessage = null;
mHalfRate.dispose();
}
/**
* Flag to indicate when this assembler has received all of the bits it
* is looking for (ie message length), and should then be removed from
* receiving any more bits
*/
public boolean complete()
{
return mComplete;
}
public boolean isActive()
{
return mActive;
}
}
/**
* Sync pattern detector to listen for costas loop phase lock errors and
* apply a phase correction to the costas loop so that we don't miss any
* messages.
*
* When the costas loop locks with a +/- 90 degree or 180 degree phase
* error, the slicer will incorrectly apply the symbol pattern rotated left
* or right by the phase error. However, we can detect these rotated sync
* patterns and apply immediate phase correction so that message processing
* can continue.
*/
public class CostasPhaseErrorDetector extends SyncDetector
{
private CQPSKDemodulator mDemodulator;
private double mCorrection;
public CostasPhaseErrorDetector( FrameSync frameSync,
CQPSKDemodulator demodulator, double correction )
{
super( frameSync.getSync() );
mDemodulator = demodulator;
mCorrection = correction;
setListener( new ISyncDetectListener()
{
@Override
public void syncDetected()
{
mDemodulator.correctPhaseError( mCorrection );
/* Since we detected a sync pattern, start a message assembler */
for( P25MessageAssembler assembler: mAssemblers )
{
if( !assembler.isActive() )
{
assembler.setActive( true );
break;
}
}
}
} );
}
}
}

View File

@@ -0,0 +1,98 @@
package module.decode.p25;
import java.util.HashMap;
import message.Message;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.ldu.LDUMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Broadcaster;
import sample.Listener;
import alias.AliasList;
public class P25MessageProcessor implements Listener<Message>
{
private final static Logger mLog =
LoggerFactory.getLogger( P25MessageProcessor.class );
private Listener<Message> mMessageListener;
/* Map of up to 16 band identifiers per RFSS. These identifier update
* messages are inserted into any message that conveys channel information
* so that the uplink/downlink frequencies can be calculated */
private HashMap<Integer,IBandIdentifier> mBandIdentifierMap =
new HashMap<Integer,IBandIdentifier>();
private AliasList mAliasList;
public P25MessageProcessor( AliasList aliasList )
{
mAliasList = aliasList;
}
@Override
public void receive( Message message )
{
/**
* Capture frequency band identifier messages and inject them into any
* messages that require band information in order to calculate the
* up-link and down-link frequencies for any numeric channel references
* contained within the message.
*/
if( message.isValid() )
{
/* Insert band identifier update messages into channel-type messages */
if( message instanceof IdentifierReceiver )
{
IdentifierReceiver receiver = (IdentifierReceiver)message;
int[] identifiers = receiver.getIdentifiers();
for( int identifier: identifiers )
{
receiver.setIdentifierMessage( identifier,
mBandIdentifierMap.get( identifier ) );
}
}
/* Store band identifiers so that they can be injected into channel
* type messages */
if( message instanceof IBandIdentifier )
{
IBandIdentifier bandIdentifier = (IBandIdentifier)message;
mBandIdentifierMap.put( bandIdentifier.getIdentifier(),
bandIdentifier );
}
}
/**
* Broadcast all valid messages and any LDU voice messages regardless if
* they are valid or not, so that we don't miss any voice frames
*/
if( mMessageListener != null && message.isValid() || message instanceof LDUMessage )
{
mMessageListener.receive( message );
}
}
public void dispose()
{
mBandIdentifierMap.clear();
mMessageListener = null;
}
public void setMessageListener( Listener<Message> listener )
{
mMessageListener = listener;
}
public void removeMessageListener()
{
mMessageListener = null;
}
}

Some files were not shown because too many files have changed in this diff Show More