diff --git a/src/controller/ThreadPoolManager.java b/src/controller/ThreadPoolManager.java index 158a5b19..ba8234bb 100644 --- a/src/controller/ThreadPoolManager.java +++ b/src/controller/ThreadPoolManager.java @@ -26,7 +26,7 @@ import java.util.concurrent.TimeUnit; public class ThreadPoolManager { - private int THREAD_POOL_SIZE = 10; + private int THREAD_POOL_SIZE = 2; private int MAX_TASK_COUNT = 24; private HashMap,ThreadType> mTasks = diff --git a/src/controller/channel/ProcessingChain.java b/src/controller/channel/ProcessingChain.java index 2a7c7b93..fc583d90 100644 --- a/src/controller/channel/ProcessingChain.java +++ b/src/controller/channel/ProcessingChain.java @@ -682,26 +682,33 @@ public class ProcessingChain implements Listener @Override public void run() { - List sampleBuffers = new ArrayList(); - - mRealQueue.drainTo( sampleBuffers, 4 ); - - for( RealBuffer sampleBuffer: sampleBuffers ) + try { - if( mDecoder != null ) + List sampleBuffers = new ArrayList(); + + mRealQueue.drainTo( sampleBuffers, 4 ); + + for( RealBuffer sampleBuffer: sampleBuffers ) { - float[] samples = sampleBuffer.getSamples(); - - for( float sample: samples ) + if( mDecoder != null ) { - mDecoder.getRealReceiver().receive( sample ); + float[] samples = sampleBuffer.getSamples(); + + for( float sample: samples ) + { + mDecoder.getRealReceiver().receive( sample ); + } } + + sampleBuffer.dispose(); } - sampleBuffer.dispose(); + sampleBuffers.clear(); + } + catch( Exception e ) + { + mLog.error( "error during processing chain real processor run", e ); } - - sampleBuffers.clear(); } } @@ -713,25 +720,32 @@ public class ProcessingChain implements Listener @Override public void run() { - List> sampleSets = - new ArrayList>(); - - mComplexQueue.drainTo( sampleSets, 16 ); - - for( List samples: sampleSets ) + try { - for( ComplexSample sample: samples ) + List> sampleSets = + new ArrayList>(); + + mComplexQueue.drainTo( sampleSets, 16 ); + + for( List samples: sampleSets ) { - if( sample != null && mDecoder != null ) + for( ComplexSample sample: samples ) { - mDecoder.getComplexReceiver().receive( sample ); + if( sample != null && mDecoder != null ) + { + mDecoder.getComplexReceiver().receive( sample ); + } } + + samples.clear(); } - samples.clear(); + sampleSets.clear(); + } + catch( Exception e ) + { + mLog.error( "error during processing chain complex processor run", e ); } - - sampleSets.clear(); } } diff --git a/src/decode/DecodeComponentEditor.java b/src/decode/DecodeComponentEditor.java index ece532ab..ee9e754f 100644 --- a/src/decode/DecodeComponentEditor.java +++ b/src/decode/DecodeComponentEditor.java @@ -124,7 +124,10 @@ public class DecodeComponentEditor extends AbstractChannelEditor @Override public void save() { - mEditor.save(); + if( mEditor != null ) + { + mEditor.save(); + } //The component that calls save will invoke the change broadcast mChannelNode.getChannel() diff --git a/src/decode/p25/P25ChannelState.java b/src/decode/p25/P25ChannelState.java index b3942971..3883e61b 100644 --- a/src/decode/p25/P25ChannelState.java +++ b/src/decode/p25/P25ChannelState.java @@ -17,6 +17,10 @@ ******************************************************************************/ package decode.p25; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + import message.Message; import alias.Alias; import alias.AliasList; @@ -35,11 +39,19 @@ import decode.p25.message.tsbk.osp.control.UnitDeregistrationAcknowledge; import decode.p25.message.tsbk.osp.voice.GroupVoiceChannelGrant; import decode.p25.message.tsbk.osp.voice.GroupVoiceChannelGrantUpdate; import decode.p25.message.tsbk.osp.voice.GroupVoiceChannelGrantUpdateExplicit; +import decode.p25.message.tsbk.osp.voice.TelephoneInterconnectVoiceChannelGrant; +import decode.p25.message.tsbk.osp.voice.TelephoneInterconnectVoiceChannelGrantUpdate; import decode.p25.message.tsbk.osp.voice.UnitToUnitAnswerRequest; +import decode.p25.message.tsbk.osp.voice.UnitToUnitVoiceChannelGrant; +import decode.p25.message.tsbk.osp.voice.UnitToUnitVoiceChannelGrantUpdate; +import decode.p25.reference.Opcode; import decode.p25.reference.Vendor; public class P25ChannelState extends ChannelState { + private HashMap mRegistrations = new HashMap(); + private String mLastDeRegistration; + private P25ActivitySummary mActivitySummary; private String mNAC; private String mSystem; @@ -154,99 +166,261 @@ public class P25ChannelState extends ChannelState switch( tsbk.getOpcode() ) { case GROUP_VOICE_CHANNEL_GRANT: - GroupVoiceChannelGrant gvcg = (GroupVoiceChannelGrant)tsbk; - - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.CALL ) - .aliasList( mAliasList ) - .channel( gvcg.getChannelIdentifier() + "-" + gvcg.getChannel() ) - .details( ( gvcg.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gvcg.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gvcg.getDownlinkFrequency() ) - .from( gvcg.getSourceAddress() ) - .to( gvcg.getGroupAddress() ) - .build() ); - break; case GROUP_VOICE_CHANNEL_GRANT_UPDATE: - GroupVoiceChannelGrantUpdate gvcgu = - (GroupVoiceChannelGrantUpdate)tsbk; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.CALL ) - .aliasList( mAliasList ) - .channel( gvcgu.getChannelID1() + "-" + gvcgu.getChannelNumber1() ) - .details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gvcgu.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gvcgu.getDownlinkFrequency() ) - .to( gvcgu.getGroupAddress1() ) - .build() ); - if( gvcgu.getChannelNumber2() != 0 ) - { - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.CALL ) - .aliasList( mAliasList ) - .channel( gvcgu.getChannelID2() + "-" + gvcgu.getChannelNumber2() ) - .details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gvcgu.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gvcgu.getDownlinkFrequency() ) - .to( gvcgu.getGroupAddress2() ) - .build() ); - } - break; case GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: - GroupVoiceChannelGrantUpdateExplicit gvcgue = - (GroupVoiceChannelGrantUpdateExplicit)tsbk; - - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.CALL ) - .aliasList( mAliasList ) - .channel( gvcgue.getTransmitChannelID() + "-" + - gvcgue.getTransmitChannelNumber() ) - .details( ( gvcgue.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gvcgue.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gvcgue.getDownlinkFrequency() ) - .to( gvcgue.getGroupAddress() ) - .build() ); + case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: + case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: + case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: + case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + processCall( tsbk ); break; + case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: case UNIT_TO_UNIT_ANSWER_REQUEST: - UnitToUnitAnswerRequest utuar = - (UnitToUnitAnswerRequest)tsbk; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( mAliasList ) - .details( ( utuar.isEmergency() ? "EMERGENCY" : "" ) ) - .from( utuar.getSourceAddress() ) - .to( utuar.getTargetAddress() ) - .build() ); + processPage( tsbk ); break; case LOCATION_REGISTRATION_RESPONSE: - LocationRegistrationResponse lrr = - (LocationRegistrationResponse)tsbk; - - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.REGISTER ) - .aliasList( mAliasList ) - .details( "REG:" + lrr.getResponse().name() + - " SITE: " + lrr.getRFSSID() + "-" + - lrr.getSiteID() ) - .from( lrr.getTargetAddress() ) - .to( lrr.getGroupAddress() ) - .build() ); - break; case UNIT_DEREGISTRATION_ACKNOWLEDGE: - UnitDeregistrationAcknowledge udr = - (UnitDeregistrationAcknowledge)tsbk; - - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.DEREGISTER ) - .aliasList( mAliasList ) - .from( udr.getSourceID() ) - .build() ); + processRegistration( tsbk ); + default: break; } } } } + + /** + * Process a call event message + */ + private void processCall( TSBKMessage message ) + { + switch( message.getOpcode() ) + { + case GROUP_VOICE_CHANNEL_GRANT: + GroupVoiceChannelGrant gvcg = (GroupVoiceChannelGrant)message; + mCallEventModel.add( + new P25CallEvent.Builder( CallEventType.CALL ) + .aliasList( mAliasList ) + .channel( gvcg.getChannelIdentifier() + "-" + gvcg.getChannel() ) + .details( ( gvcg.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gvcg.isEmergency() ? " EMERGENCY" : "") ) + .frequency( gvcg.getDownlinkFrequency() ) + .from( gvcg.getSourceAddress() ) + .to( gvcg.getGroupAddress() ) + .build() ); + break; + case GROUP_VOICE_CHANNEL_GRANT_UPDATE: + GroupVoiceChannelGrantUpdate gvcgu = + (GroupVoiceChannelGrantUpdate)message; + mCallEventModel.add( + new P25CallEvent.Builder( CallEventType.CALL ) + .aliasList( mAliasList ) + .channel( gvcgu.getChannelID1() + "-" + gvcgu.getChannelNumber1() ) + .details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gvcgu.isEmergency() ? " EMERGENCY" : "") ) + .frequency( gvcgu.getDownlinkFrequency1() ) + .to( gvcgu.getGroupAddress1() ) + .build() ); + if( gvcgu.hasChannelNumber2() ) + { + mCallEventModel.add( + new P25CallEvent.Builder( CallEventType.CALL ) + .aliasList( mAliasList ) + .channel( gvcgu.getChannelID2() + "-" + gvcgu.getChannelNumber2() ) + .details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gvcgu.isEmergency() ? " EMERGENCY" : "") ) + .frequency( gvcgu.getDownlinkFrequency2() ) + .to( gvcgu.getGroupAddress2() ) + .build() ); + } + break; + case GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + GroupVoiceChannelGrantUpdateExplicit gvcgue = + (GroupVoiceChannelGrantUpdateExplicit)message; + + mCallEventModel.add( + new P25CallEvent.Builder( CallEventType.CALL ) + .aliasList( mAliasList ) + .channel( gvcgue.getTransmitChannelID() + "-" + + gvcgue.getTransmitChannelNumber() ) + .details( ( gvcgue.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gvcgue.isEmergency() ? " EMERGENCY" : "") ) + .frequency( gvcgue.getDownlinkFrequency() ) + .to( gvcgue.getGroupAddress() ) + .build() ); + break; + case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: + TelephoneInterconnectVoiceChannelGrant tivcg = + (TelephoneInterconnectVoiceChannelGrant)message; + + mCallEventModel.add( + new P25CallEvent.Builder( CallEventType.CALL ) + .aliasList( mAliasList ) + .channel( tivcg.getChannelIdentifier() + "-" + + tivcg.getChannel() ) + .details( ( tivcg.isEncrypted() ? "ENCRYPTED" : "" ) + + ( tivcg.isEmergency() ? " EMERGENCY" : "") ) + .frequency( tivcg.getDownlinkFrequency() ) + .from( tivcg.getSourceAddress() ) + .to( tivcg.getTargetAddress() ) + .build() ); + break; + case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: + TelephoneInterconnectVoiceChannelGrantUpdate tivcgu = + (TelephoneInterconnectVoiceChannelGrantUpdate)message; + + mCallEventModel.add( + new P25CallEvent.Builder( CallEventType.CALL ) + .aliasList( mAliasList ) + .channel( tivcgu.getChannelIdentifier() + "-" + + tivcgu.getChannel() ) + .details( ( tivcgu.isEncrypted() ? "ENCRYPTED" : "" ) + + ( tivcgu.isEmergency() ? " EMERGENCY" : "") ) + .frequency( tivcgu.getDownlinkFrequency() ) + .from( tivcgu.getSourceAddress() ) + .to( tivcgu.getTargetAddress() ) + .build() ); + break; + case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: + UnitToUnitVoiceChannelGrant uuvcg = + (UnitToUnitVoiceChannelGrant)message; + + mCallEventModel.add( + new P25CallEvent.Builder( CallEventType.CALL ) + .aliasList( mAliasList ) + .channel( uuvcg.getChannelIdentifier() + "-" + + uuvcg.getChannel() ) + .details( ( uuvcg.isEncrypted() ? "ENCRYPTED" : "" ) + + ( uuvcg.isEmergency() ? " EMERGENCY" : "") ) + .frequency( uuvcg.getDownlinkFrequency() ) + .from( uuvcg.getSourceAddress() ) + .to( uuvcg.getTargetAddress() ) + .build() ); + break; + case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + UnitToUnitVoiceChannelGrantUpdate uuvcgu = + (UnitToUnitVoiceChannelGrantUpdate)message; + + mCallEventModel.add( + new P25CallEvent.Builder( CallEventType.CALL ) + .aliasList( mAliasList ) + .channel( uuvcgu.getChannelIdentifier() + "-" + + uuvcgu.getChannel() ) + .details( ( uuvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + + ( uuvcgu.isEmergency() ? " EMERGENCY" : "") ) + .frequency( uuvcgu.getDownlinkFrequency() ) + .from( uuvcgu.getSourceAddress() ) + .to( uuvcgu.getTargetAddress() ) + .build() ); + break; + default: + break; + } + } + + /** + * Process a unit paging event message + */ + private void processPage( TSBKMessage message ) + { + if( message.getOpcode() == Opcode.UNIT_TO_UNIT_ANSWER_REQUEST ) + { + UnitToUnitAnswerRequest utuar = (UnitToUnitAnswerRequest)message; + mCallEventModel.add( + new P25CallEvent.Builder( CallEventType.PAGE ) + .aliasList( mAliasList ) + .details( ( utuar.isEmergency() ? "EMERGENCY" : "" ) ) + .from( utuar.getSourceAddress() ) + .to( utuar.getTargetAddress() ) + .build() ); + } + } + + /** + * Process a unit registration or deregistration message + */ + private void processRegistration( TSBKMessage message ) + { + switch( message.getOpcode() ) + { + case LOCATION_REGISTRATION_RESPONSE: + LocationRegistrationResponse lrr = + (LocationRegistrationResponse)message; + + String addressPlusGroup = lrr.getTargetAddress() + + lrr.getGroupAddress(); + + boolean updateEvent = false; + + if( mRegistrations.containsKey( addressPlusGroup ) ) + { + long timestamp = mRegistrations.get( addressPlusGroup ); + + updateEvent = ( System.currentTimeMillis() - + timestamp ) > 300000; //5 minutes + } + else + { + updateEvent = true; + } + + mRegistrations.put( addressPlusGroup, System.currentTimeMillis() ); + + if( updateEvent ) + { + mCallEventModel.add( + new P25CallEvent.Builder( CallEventType.REGISTER ) + .aliasList( mAliasList ) + .details( "RESPONSE:" + lrr.getResponse().name() + + " SITE: " + lrr.getRFSSID() + "-" + + lrr.getSiteID() ) + .from( lrr.getTargetAddress() ) + .to( lrr.getGroupAddress() ) + .build() ); + } + + /* Reset the deregistration variable */ + mLastDeRegistration = null; + break; + case UNIT_DEREGISTRATION_ACKNOWLEDGE: + UnitDeregistrationAcknowledge udr = + (UnitDeregistrationAcknowledge)message; + + if( mLastDeRegistration != null && + mLastDeRegistration.contentEquals( udr.getSourceID() ) ) + { + break; //Suppress duplicate event + } + + mLastDeRegistration = udr.getSourceID(); + + mCallEventModel.add( + new P25CallEvent.Builder( CallEventType.DEREGISTER ) + .aliasList( mAliasList ) + .from( udr.getSourceID() ) + .build() ); + + List keysToRemove = new ArrayList(); + + /* Remove this radio from the registrations set */ + for( String key: mRegistrations.keySet() ) + { + if( key.startsWith( udr.getSourceID() ) ) + { + keysToRemove.add( key ); + } + } + + for( String key: keysToRemove ) + { + mRegistrations.remove( key ); + } + + break; + default: + break; + } + } @Override public void setSquelchState( SquelchState state ) diff --git a/src/decode/p25/P25Decoder.java b/src/decode/p25/P25Decoder.java index 457a3fa1..be1fb72d 100644 --- a/src/decode/p25/P25Decoder.java +++ b/src/decode/p25/P25Decoder.java @@ -19,6 +19,7 @@ package decode.p25; import instrument.Instrumentable; import instrument.tap.Tap; +import instrument.tap.stream.FloatTap; import java.util.ArrayList; import java.util.List; @@ -31,6 +32,7 @@ import source.Source.SampleType; import alias.AliasList; import decode.Decoder; import decode.DecoderType; +import dsp.agc.RealAutomaticGainControl; import dsp.filter.ComplexFIRFilter; import dsp.filter.FilterFactory; import dsp.filter.Window.WindowType; @@ -43,10 +45,17 @@ public class P25Decoder extends Decoder implements Instrumentable { private final static Logger mLog = LoggerFactory.getLogger( P25Decoder.class ); + /* Instrumentation Taps */ + private static final String INSTRUMENT_INPUT_TO_AGC = "Tap Point: Input to AGC"; + private static final String INSTRUMENT_AGC_TO_SYMBOL_FILTER = "Tap Point: AGC to Symbol Filter"; + private static final String INSTRUMENT_SYMBOL_FILTER_TO_SLICER = "Tap Point: Symbol Filter to Slicer"; + private List mAvailableTaps; + private ComplexFIRFilter mBasebandFilter = new ComplexFIRFilter( FilterFactory.getLowPass( 48000, 5000, 31, WindowType.HAMMING ), 1.0 ); private FMDiscriminator mDemodulator = new FMDiscriminator( 1 ); + private RealAutomaticGainControl mAGC = new RealAutomaticGainControl(); private C4FMSymbolFilter mSymbolFilter = new C4FMSymbolFilter(); private C4FMSlicer mSlicer = new C4FMSlicer(); private P25MessageFramer mNormalFramer; @@ -80,7 +89,8 @@ public class P25Decoder extends Decoder implements Instrumentable mDemodulator.setListener( getRealReceiver() ); } - addRealSampleListener( mSymbolFilter ); + addRealSampleListener( mAGC ); + mAGC.setListener( mSymbolFilter ); mSymbolFilter.setListener( mSlicer ); @@ -108,17 +118,65 @@ public class P25Decoder extends Decoder implements Instrumentable @Override public List getTaps() { - return new ArrayList(); + if( mAvailableTaps == null ) + { + mAvailableTaps = new ArrayList(); + + mAvailableTaps.add( new FloatTap( INSTRUMENT_INPUT_TO_AGC, 0, 1.0f ) ); + mAvailableTaps.add( new FloatTap( INSTRUMENT_AGC_TO_SYMBOL_FILTER, 0, 1.0f ) ); + mAvailableTaps.add( new FloatTap( INSTRUMENT_SYMBOL_FILTER_TO_SLICER, 0, 0.1f ) ); + + mAvailableTaps.addAll( mSymbolFilter.getTaps() ); + } + + return mAvailableTaps; } @Override public void addTap( Tap tap ) { + mSymbolFilter.addTap( tap ); + + switch( tap.getName() ) + { + case INSTRUMENT_INPUT_TO_AGC: + FloatTap inputAGC = (FloatTap)tap; + removeRealListener( mAGC ); + addRealSampleListener( inputAGC ); + inputAGC.setListener( mAGC ); + break; + case INSTRUMENT_AGC_TO_SYMBOL_FILTER: + FloatTap agcSymbol = (FloatTap)tap; + mAGC.setListener( agcSymbol ); + agcSymbol.setListener( mSymbolFilter ); + break; + case INSTRUMENT_SYMBOL_FILTER_TO_SLICER: + FloatTap symbolSlicer = (FloatTap)tap; + mSymbolFilter.setListener( symbolSlicer ); + symbolSlicer.setListener( mSlicer ); + break; + } } @Override public void removeTap( Tap tap ) { + mSymbolFilter.removeTap( tap ); + + switch( tap.getName() ) + { + case INSTRUMENT_INPUT_TO_AGC: + FloatTap inputAGC = (FloatTap)tap; + removeRealListener( inputAGC ); + addRealSampleListener( mAGC ); + break; + case INSTRUMENT_AGC_TO_SYMBOL_FILTER: + mAGC.setListener( mSymbolFilter ); + break; + case INSTRUMENT_SYMBOL_FILTER_TO_SLICER: + mSymbolFilter.setListener( mSlicer ); + break; + } } @Override diff --git a/src/decode/p25/message/tsbk/GroupMultiChannelGrant.java b/src/decode/p25/message/tsbk/GroupMultiChannelGrant.java index 7703ab08..b8084b04 100644 --- a/src/decode/p25/message/tsbk/GroupMultiChannelGrant.java +++ b/src/decode/p25/message/tsbk/GroupMultiChannelGrant.java @@ -1,11 +1,13 @@ package decode.p25.message.tsbk; -import alias.Alias; import alias.AliasList; import bits.BitSetBuffer; +import decode.p25.message.tsbk.osp.control.IdentifierUpdate; +import decode.p25.message.tsbk.osp.control.IdentifierUpdateReceiver; import decode.p25.reference.DataUnitID; public abstract class GroupMultiChannelGrant extends ChannelGrant + implements IdentifierUpdateReceiver { public static final int[] CHANNEL_ID_1 = { 80,81,82,83 }; public static final int[] CHANNEL_NUMBER_1 = { 84,85,86,87, @@ -17,6 +19,9 @@ public abstract class GroupMultiChannelGrant extends ChannelGrant 112,113,114,115,116,117,118,119 }; public static final int[] GROUP_ADDRESS_2 = { 120,121,122,123,124,125,126, 127,128,129,130,131,132,133,134,135 }; + + private IdentifierUpdate mIdentifierUpdate1; + private IdentifierUpdate mIdentifierUpdate2; public GroupMultiChannelGrant( BitSetBuffer message, DataUnitID duid, @@ -37,11 +42,14 @@ public abstract class GroupMultiChannelGrant extends ChannelGrant sb.append( " GRP1:" ); sb.append( getGroupAddress1() ); - sb.append( " CHAN2:" ); - sb.append( getChannelID2() + "/" + getChannelNumber2() ); - - sb.append( " GRP2:" ); - sb.append( getGroupAddress2() ); + if( hasChannelNumber2() ) + { + sb.append( " CHAN2:" ); + sb.append( getChannelID2() + "/" + getChannelNumber2() ); + + sb.append( " GRP2:" ); + sb.append( getGroupAddress2() ); + } return sb.toString(); } @@ -76,6 +84,11 @@ public abstract class GroupMultiChannelGrant extends ChannelGrant return mMessage.getHex( GROUP_ADDRESS_2, 4 ); } + public boolean hasChannelNumber2() + { + return mMessage.getInt( GROUP_ADDRESS_2 ) > 0; + } + @Override public String getFromID() { @@ -87,4 +100,59 @@ public abstract class GroupMultiChannelGrant extends ChannelGrant { return getGroupAddress2(); } + + @Override + public void setIdentifierMessage( int identifier, IdentifierUpdate message ) + { + if( identifier == getChannelID1() ) + { + mIdentifierUpdate1 = message; + } + + if( hasChannelNumber2() && identifier == getChannelID2() ) + { + mIdentifierUpdate2 = message; + } + } + + @Override + public int[] getIdentifiers() + { + int[] identifiers; + + if( hasChannelNumber2() ) + { + identifiers = new int[ 2 ]; + identifiers[ 0 ] = getChannelID1(); + identifiers[ 1 ] = getChannelID2(); + } + else + { + identifiers = new int[ 1 ]; + identifiers[ 0 ] = getChannelID1(); + } + + return identifiers; + } + + public long getDownlinkFrequency1() + { + return calculateDownlink( mIdentifierUpdate1, getChannel() ); + } + + public long getUplinkFrequency1() + { + return calculateUplink( mIdentifierUpdate1, getChannel() ); + } + + public long getDownlinkFrequency2() + { + return calculateDownlink( mIdentifierUpdate2, getChannel() ); + } + + public long getUplinkFrequency2() + { + return calculateUplink( mIdentifierUpdate2, getChannel() ); + } + } diff --git a/src/decode/p25/message/tsbk/TSBKMessage.java b/src/decode/p25/message/tsbk/TSBKMessage.java index 3f326c4a..18f67861 100644 --- a/src/decode/p25/message/tsbk/TSBKMessage.java +++ b/src/decode/p25/message/tsbk/TSBKMessage.java @@ -36,15 +36,10 @@ public class TSBKMessage extends P25Message public TSBKMessage( BitSetBuffer message, DataUnitID duid, AliasList aliasList ) { super( message, duid, aliasList ); - - checkCRC(); - } - - protected void checkCRC() - { - mCRC = new CRC[ 1 ]; - mCRC[ 0 ] = CRCP25.correctCCITT80( mMessage, MESSAGE_START, CRC_START ); + /* Since CRC is checked before construction, mark it as passed */ + mCRC = new CRC[ 1 ]; + mCRC[ 0 ] = CRC.PASSED; } public boolean isLastBlock() diff --git a/src/decode/p25/message/tsbk/osp/voice/GroupVoiceChannelGrantUpdate.java b/src/decode/p25/message/tsbk/osp/voice/GroupVoiceChannelGrantUpdate.java index 0d9f4999..7a7c3b45 100644 --- a/src/decode/p25/message/tsbk/osp/voice/GroupVoiceChannelGrantUpdate.java +++ b/src/decode/p25/message/tsbk/osp/voice/GroupVoiceChannelGrantUpdate.java @@ -18,6 +18,6 @@ public class GroupVoiceChannelGrantUpdate extends GroupMultiChannelGrant @Override public String getEventType() { - return Opcode.GROUP_VOICE_CHANNEL_GRANT.getDescription(); + return Opcode.GROUP_VOICE_CHANNEL_GRANT_UPDATE.getDescription(); } } diff --git a/src/decode/p25/message/tsbk/osp/voice/TelephoneInterconnectAnswerRequest.java b/src/decode/p25/message/tsbk/osp/voice/TelephoneInterconnectAnswerRequest.java index 248a47cd..bc532999 100644 --- a/src/decode/p25/message/tsbk/osp/voice/TelephoneInterconnectAnswerRequest.java +++ b/src/decode/p25/message/tsbk/osp/voice/TelephoneInterconnectAnswerRequest.java @@ -3,7 +3,6 @@ package decode.p25.message.tsbk.osp.voice; import java.util.ArrayList; import java.util.List; -import alias.Alias; import alias.AliasList; import bits.BitSetBuffer; import decode.p25.message.tsbk.TSBKMessage; @@ -34,6 +33,12 @@ public class TelephoneInterconnectAnswerRequest extends TSBKMessage super( message, duid, aliasList ); } + @Override + public String getEventType() + { + return Opcode.TELEPHONE_INTERCONNECT_ANSWER_REQUEST.getDescription(); + } + public String getMessage() { StringBuilder sb = new StringBuilder(); @@ -72,12 +77,6 @@ public class TelephoneInterconnectAnswerRequest extends TSBKMessage return mMessage.getHex( TARGET_ADDRESS, 6 ); } - @Override - public String getEventType() - { - return Opcode.TELEPHONE_INTERCONNECT_ANSWER_REQUEST.getDescription(); - } - @Override public String getToID() { diff --git a/src/dsp/fsk/C4FMSymbolFilter.java b/src/dsp/fsk/C4FMSymbolFilter.java index adbf3c5d..ec9af0b4 100644 --- a/src/dsp/fsk/C4FMSymbolFilter.java +++ b/src/dsp/fsk/C4FMSymbolFilter.java @@ -1,5 +1,13 @@ package dsp.fsk; +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; @@ -7,7 +15,8 @@ import sample.real.RealSampleListener; import sample.real.RealSampleProvider; public class C4FMSymbolFilter implements RealSampleListener, - RealSampleProvider + RealSampleProvider, + Instrumentable { private final static Logger mLog = LoggerFactory.getLogger( C4FMSymbolFilter.class ); @@ -145,6 +154,11 @@ public class C4FMSymbolFilter implements RealSampleListener, { 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 mAvailableTaps; + private static final int NUMBER_FILTER_TAPS = 8; private static final int NUMBER_FILTER_STEPS = 128; @@ -181,6 +195,8 @@ public class C4FMSymbolFilter implements RealSampleListener, private RealSampleListener mListener; + private float mGain = 15.4f; + /** * C4FM Symbol Filter * @@ -190,10 +206,15 @@ public class C4FMSymbolFilter implements RealSampleListener, * is less than 2.0, then increase gain. If the value is over 2.0, then * decrease gain. * - * During testing, the incoming samples were within the range of -0.5 <> 0.5 - * and applying a gain of 11.0 to those samples achieved the best symbol - * spread, centered about 2.0 with +/- 0.5% deviations on a strong signal. + * 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( float gain ) + { + mGain = gain; + } + public C4FMSymbolFilter() { } @@ -203,8 +224,7 @@ public class C4FMSymbolFilter implements RealSampleListener, { mSymbolClock += mSymbolTime; -// mHistory[ mHistoryLast++ ] = sample; - mHistory[ mHistoryLast++ ] = sample * 10.5f; + mHistory[ mHistoryLast++ ] = sample * mGain; mHistoryLast %= NUMBER_FILTER_TAPS; if( mSymbolClock > 1.0f ) @@ -298,6 +318,11 @@ public class C4FMSymbolFilter implements RealSampleListener, ( mCoarseFrequencyCorrection > 0 ? "-" : "+" ) + mCoarseFrequencyCorrection + "]" ); } + + if( mSymbolSpreadTap != null ) + { + mSymbolSpreadTap.receive( mSymbolSpread ); + } /* dispatch the interpolated value to the listener */ if( mListener != null ) @@ -318,4 +343,40 @@ public class C4FMSymbolFilter implements RealSampleListener, { mListener = null; } + + @Override + public List getTaps() + { + if( mAvailableTaps == null ) + { + mAvailableTaps = new ArrayList(); + + /* + * 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; + } + } } diff --git a/src/dsp/fsk/P25MessageFramer.java b/src/dsp/fsk/P25MessageFramer.java index b76b6124..3629a894 100644 --- a/src/dsp/fsk/P25MessageFramer.java +++ b/src/dsp/fsk/P25MessageFramer.java @@ -33,6 +33,7 @@ public class P25MessageFramer implements Listener new ArrayList(); 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; @@ -378,23 +379,33 @@ public class P25MessageFramer implements Listener * unsuccessful decode due to excessive errors */ if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) ) { - BitSetBuffer tsbkBuffer1 = mMessage.copy(); - tsbkBuffer1.setSize( TSBK_DECODED_END ); - - TSBKMessage tsbkMessage1 = TSBKMessageFactory.getMessage( - tsbkBuffer1, DataUnitID.TSBK1, mAliasList ); + CRC tsbkCRC = CRCP25.correctCCITT80( mMessage, + TSBK_BEGIN, TSBK_CRC_START ); - if( tsbkMessage1.isLastBlock() ) + if( tsbkCRC != CRC.FAILED_CRC ) { - mComplete = true; + BitSetBuffer 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 { - setDUID( DataUnitID.TSBK2 ); - mMessage.setPointer( TSBK_BEGIN ); + mComplete = true; } - - dispatch( tsbkMessage1 ); } else { @@ -409,23 +420,33 @@ public class P25MessageFramer implements Listener * unsuccessful decode due to excessive errors */ if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) ) { - BitSetBuffer tsbkBuffer2 = mMessage.copy(); - tsbkBuffer2.setSize( TSBK_DECODED_END ); + CRC tsbkCRC = CRCP25.correctCCITT80( mMessage, + TSBK_BEGIN, TSBK_CRC_START ); - TSBKMessage tsbkMessage2 = TSBKMessageFactory.getMessage( - tsbkBuffer2, DataUnitID.TSBK2, mAliasList ); - - if( tsbkMessage2.isLastBlock() ) + if( tsbkCRC != CRC.FAILED_CRC ) { - mComplete = true; + BitSetBuffer 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 { - setDUID( DataUnitID.TSBK3 ); - mMessage.setPointer( TSBK_BEGIN ); + mComplete = true; } - - dispatch( tsbkMessage2 ); } else { @@ -440,13 +461,19 @@ public class P25MessageFramer implements Listener * unsuccessful decode due to excessive errors */ if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) ) { - BitSetBuffer tsbkBuffer3 = mMessage.copy(); - tsbkBuffer3.setSize( TSBK_DECODED_END ); - - TSBKMessage tsbkMessage3 = TSBKMessageFactory.getMessage( - tsbkBuffer3, DataUnitID.TSBK3, mAliasList ); + CRC tsbkCRC = CRCP25.correctCCITT80( mMessage, + TSBK_BEGIN, TSBK_CRC_START ); + + if( tsbkCRC != CRC.FAILED_CRC ) + { + BitSetBuffer tsbkBuffer3 = mMessage.copy(); + tsbkBuffer3.setSize( TSBK_DECODED_END ); + + TSBKMessage tsbkMessage3 = TSBKMessageFactory.getMessage( + tsbkBuffer3, DataUnitID.TSBK3, mAliasList ); - dispatch( tsbkMessage3 ); + dispatch( tsbkMessage3 ); + } } mComplete = true; diff --git a/src/gui/SDRTrunk.java b/src/gui/SDRTrunk.java index 7a5b1cb7..22498f7d 100644 --- a/src/gui/SDRTrunk.java +++ b/src/gui/SDRTrunk.java @@ -18,12 +18,14 @@ package gui; import java.awt.AWTException; +import java.awt.Desktop; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Robot; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; +import java.io.File; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; @@ -34,6 +36,7 @@ import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; +import javax.swing.JOptionPane; import javax.swing.JSeparator; import net.miginfocom.swing.MigLayout; @@ -188,6 +191,30 @@ public class SDRTrunk JMenu fileMenu = new JMenu( "File" ); menuBar.add( fileMenu ); + + JMenuItem logFilesMenu = new JMenuItem( "Logs & Recordings" ); + logFilesMenu.addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent arg0 ) + { + try + { + Desktop.getDesktop().open( getHomePath().toFile() ); + } + catch( Exception e ) + { + mLog.error( "Couldn't open file explorer" ); + + JOptionPane.showMessageDialog( mMainGui, + "Can't launch file explorer - files are located at: " + + getHomePath().toString(), + "Can't launch file explorer", + JOptionPane.ERROR_MESSAGE ); + } + } + } ); + fileMenu.add( logFilesMenu ); JMenuItem settingsMenu = new JMenuItem( "Icon Manager" ); settingsMenu.addActionListener( new ActionListener() diff --git a/src/instrument/tap/stream/AdditiveFloatTap.java b/src/instrument/tap/stream/AdditiveFloatTap.java new file mode 100644 index 00000000..31ff2d23 --- /dev/null +++ b/src/instrument/tap/stream/AdditiveFloatTap.java @@ -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 + ******************************************************************************/ +package instrument.tap.stream; + + +public class AdditiveFloatTap extends FloatTap +{ + private float mValue; + + public AdditiveFloatTap( String name, + int delay, + float sampleRateRatio, + float addValue ) + { + super( name, delay, sampleRateRatio ); + + mValue = addValue; + } + + @Override + public void receive( float sample ) + { + super.receive( sample + mValue ); + } +} diff --git a/src/instrument/tap/stream/GainFloatTap.java b/src/instrument/tap/stream/GainFloatTap.java new file mode 100644 index 00000000..3a064a00 --- /dev/null +++ b/src/instrument/tap/stream/GainFloatTap.java @@ -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 + ******************************************************************************/ +package instrument.tap.stream; + + +public class GainFloatTap extends FloatTap +{ + private float mGain; + + public GainFloatTap( String name, + int delay, + float sampleRateRatio, + float gain ) + { + super( name, delay, sampleRateRatio ); + + mGain = gain; + } + + @Override + public void receive( float sample ) + { + super.receive( sample * mGain ); + } +} diff --git a/src/source/tuner/TunerChannelSource.java b/src/source/tuner/TunerChannelSource.java index bf6a96da..8ff69422 100644 --- a/src/source/tuner/TunerChannelSource.java +++ b/src/source/tuner/TunerChannelSource.java @@ -24,6 +24,9 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import sample.Listener; import sample.complex.ComplexBuffer; import sample.complex.ComplexSample; @@ -34,6 +37,7 @@ import source.tuner.FrequencyChangeEvent.Attribute; import util.Oscillator; import controller.ThreadPoolManager; import controller.ThreadPoolManager.ThreadType; +import decode.p25.P25Decoder; import dsp.filter.ComplexFilter; import dsp.filter.FilterFactory; import dsp.filter.Window.WindowType; @@ -42,6 +46,9 @@ public class TunerChannelSource extends ComplexSource implements FrequencyChangeListener, Listener { + private final static Logger mLog = + LoggerFactory.getLogger( TunerChannelSource.class ); + private static int sCHANNEL_RATE = 48000; private static int sCHANNEL_PASS_FREQUENCY = 12500; @@ -87,9 +94,9 @@ public class TunerChannelSource extends ComplexSource frequencyChanged( new FrequencyChangeEvent( Attribute.SAMPLE_RATE, mTuner.getSampleRate() ) ); - /* Schedule the decimation task to run 20 times a second */ + /* Schedule the decimation task to run 50 times a second */ mTaskHandle = mThreadPoolManager.schedule( ThreadType.DECIMATION, - new DecimationProcessor(), 50, TimeUnit.MILLISECONDS ); + new DecimationProcessor(), 20, TimeUnit.MILLISECONDS ); /* Finally, register to receive samples from the tuner */ mTuner.addListener( (Listener)this ); @@ -243,41 +250,51 @@ public class TunerChannelSource extends ComplexSource @Override public void run() { - List sampleBuffers = - new ArrayList(); - - if( mBuffer != null ) + /* General exception handler so that any errors won't kill the + * decimation thread and cause the input buffers to fill up and + * run the program out of memory */ + try { - /* Limit to 10, so that after a garbage collect we don't - * induce wild swings as it tries to take tons of samples - * and process them, causing delays and buffer refill */ - mBuffer.drainTo( sampleBuffers, 10 ); - - for( ComplexBuffer sampleArray: sampleBuffers ) + List sampleBuffers = + new ArrayList(); + + if( mBuffer != null ) { - float[] samples = sampleArray.getSamples(); - - for( int x = 0; x < samples.length; x += 2 ) + /* Limit to 4, so that after a garbage collect we don't + * induce wild swings as it tries to take tons of samples + * and process them, causing delays and buffer refill */ + mBuffer.drainTo( sampleBuffers, 4 ); + + for( ComplexBuffer sampleArray: sampleBuffers ) { - Float left = samples[ x ]; - Float right = samples[ x + 1 ]; + float[] samples = sampleArray.getSamples(); - if( left != null && right != null ) + for( int x = 0; x < samples.length; x += 2 ) { - /* perform frequency translation */ - ComplexSample translated = ComplexSample.multiply( - mSineWaveGenerator.nextComplex(), left, right ); - - /* decimate the sample rate */ - if( mDecimationFilters[ 0 ] != null ) - { - mDecimationFilters[ 0 ].receive( translated ); - } + Float left = samples[ x ]; + Float right = samples[ x + 1 ]; + + if( left != null && right != null ) + { + /* perform frequency translation */ + ComplexSample translated = ComplexSample.multiply( + mSineWaveGenerator.nextComplex(), left, right ); + + /* decimate the sample rate */ + if( mDecimationFilters[ 0 ] != null ) + { + mDecimationFilters[ 0 ].receive( translated ); + } + } } } + + sampleBuffers.clear(); } - - sampleBuffers.clear(); + } + catch( Exception e ) + { + mLog.error( "encountered an error during decimation process", e ); } } } diff --git a/src/source/tuner/hackrf/HackRFTunerController.java b/src/source/tuner/hackrf/HackRFTunerController.java index aadb63ed..66461a43 100644 --- a/src/source/tuner/hackrf/HackRFTunerController.java +++ b/src/source/tuner/hackrf/HackRFTunerController.java @@ -1115,15 +1115,22 @@ public class HackRFTunerController extends TunerController @Override public void run() { - ArrayList buffers = new ArrayList(); - - mFilledBuffers.drainTo( buffers ); - - for( byte[] buffer: buffers ) + try { - float[] samples = mSampleAdapter.convert( buffer ); + ArrayList buffers = new ArrayList(); - broadcast( new ComplexBuffer( samples ) ); + mFilledBuffers.drainTo( buffers ); + + for( byte[] buffer: buffers ) + { + float[] samples = mSampleAdapter.convert( buffer ); + + broadcast( new ComplexBuffer( samples ) ); + } + } + catch( Exception e ) + { + mLog.error( "error duruing HackRF buffer dispatching", e ); } } } diff --git a/src/source/tuner/rtl/RTL2832TunerController.java b/src/source/tuner/rtl/RTL2832TunerController.java index 8995043a..91db8979 100644 --- a/src/source/tuner/rtl/RTL2832TunerController.java +++ b/src/source/tuner/rtl/RTL2832TunerController.java @@ -1611,15 +1611,22 @@ public abstract class RTL2832TunerController extends TunerController @Override public void run() { - ArrayList buffers = new ArrayList(); - - mFilledBuffers.drainTo( buffers ); - - for( byte[] buffer: buffers ) + try { - float[] samples = mSampleAdapter.convert( buffer ); + ArrayList buffers = new ArrayList(); - broadcast( new ComplexBuffer( samples ) ); + mFilledBuffers.drainTo( buffers ); + + for( byte[] buffer: buffers ) + { + float[] samples = mSampleAdapter.convert( buffer ); + + broadcast( new ComplexBuffer( samples ) ); + } + } + catch( Exception e ) + { + mLog.error( "error duing rtl2832 buffer dispatcher run", e ); } } } diff --git a/src/spectrum/DFTProcessor.java b/src/spectrum/DFTProcessor.java index 98a9bd55..d445dc57 100644 --- a/src/spectrum/DFTProcessor.java +++ b/src/spectrum/DFTProcessor.java @@ -400,14 +400,21 @@ public class DFTProcessor implements Listener, @Override public void run() { - /* Only run if we're not currently running */ - if( mRunning.compareAndSet( false, true ) ) + try { - checkFFTSize(); + /* Only run if we're not currently running */ + if( mRunning.compareAndSet( false, true ) ) + { + checkFFTSize(); - calculate(); - - mRunning.set( false ); + calculate(); + + mRunning.set( false ); + } + } + catch( Exception e ) + { + mLog.error( "error during dft processor calculation task", e ); } } }