From 5481fb7d4dc59625e6dd436dcfb4c49498dd3b7e Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Sun, 6 Jan 2019 06:59:04 -0500 Subject: [PATCH] 61 Rotating Control Channel Support - P25 Decoder (#475) * Initial work on add support for rotating control channels. * Creates multiple frequency editor control. * Updates channel source editor to allow user to select multi-frequency configuration and add multiple frequencies. * Adds full support for rotating control channels to the P25 channelizer. Users define multiple frequencies in the channel source configuration. The decoder monitors the state of the channel for a CONTROL state. If the state does not stay in the CONTROL state for a user-specified delay value, then the decoder rotates to the next frequency in the list. This behavior continues until the decoder state once again changes to CONTROL. This is NOT a 'scanner' feature. --- build.gradle | 21 + .../channel/metadata/ChannelMetadata.java | 81 ++- .../metadata/ChannelMetadataModel.java | 4 +- .../metadata/ChannelMetadataPanel.java | 8 +- .../dsheirer/channel/state/ChannelState.java | 90 +++- .../dsheirer/channel/state/DecoderState.java | 29 +- .../channel/ChannelProcessingManager.java | 11 +- .../java/io/github/dsheirer/gui/SDRTrunk.java | 4 +- .../gui/control/MultipleFrequencyEditor.java | 494 ++++++++++++++++++ .../preference/PreferenceEditorFactory.java | 7 +- .../gui/preference/PreferenceEditorType.java | 7 +- .../gui/preference/PreferencesEditor.java | 5 +- ...nnelMultipleFrequencyPreferenceEditor.java | 123 +++++ .../tuner/TunerPreferenceEditor.java | 6 +- .../IdentifierUpdateNotification.java | 6 +- .../MutableIdentifierCollection.java | 6 +- ...ifier.java => ChannelStateIdentifier.java} | 28 +- .../dsheirer/module/ProcessingChain.java | 8 +- .../module/decode/DecoderFactory.java | 23 +- .../module/decode/p25/P25DecoderEditor.java | 357 ++++++------- .../dsheirer/preference/PreferenceType.java | 3 +- .../dsheirer/preference/UserPreferences.java | 17 +- .../ChannelMultiFrequencyPreference.java | 96 ++++ .../{tuner => source}/ChannelizerType.java | 4 +- .../{tuner => source}/TunerPreference.java | 4 +- .../github/dsheirer/sample/Broadcaster.java | 70 ++- .../io/github/dsheirer/source/Source.java | 59 ++- .../source/SourceConfigurationEditor.java | 204 ++++---- .../github/dsheirer/source/SourceEvent.java | 55 +- .../github/dsheirer/source/SourceManager.java | 30 +- .../io/github/dsheirer/source/SourceType.java | 67 ++- .../source/config/SourceConfigFactory.java | 154 +++--- .../source/config/SourceConfigTuner.java | 37 +- .../SourceConfigTunerMultipleFrequency.java | 127 +++++ .../source/config/SourceConfiguration.java | 2 +- .../source/heartbeat/IHeartbeatProvider.java | 22 +- .../github/dsheirer/source/tuner/Tuner.java | 4 +- .../dsheirer/source/tuner/TunerModel.java | 107 ++-- .../TunerSourceMultipleFrequencyEditor.java | 184 +++++++ .../tuner/channel/ChannelRotationMonitor.java | 182 +++++++ .../MultiFrequencyTunerChannelSource.java | 315 +++++++++++ .../tuner/channel/TunerChannelSource.java | 15 +- .../tuner/frequency/FrequencyController.java | 30 +- .../spectrum/SpectralDisplayPanel.java | 30 +- 44 files changed, 2502 insertions(+), 634 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/gui/control/MultipleFrequencyEditor.java create mode 100644 src/main/java/io/github/dsheirer/gui/preference/tuner/ChannelMultipleFrequencyPreferenceEditor.java rename src/main/java/io/github/dsheirer/identifier/decoder/{DecoderStateIdentifier.java => ChannelStateIdentifier.java} (65%) create mode 100644 src/main/java/io/github/dsheirer/preference/source/ChannelMultiFrequencyPreference.java rename src/main/java/io/github/dsheirer/preference/{tuner => source}/ChannelizerType.java (93%) rename src/main/java/io/github/dsheirer/preference/{tuner => source}/TunerPreference.java (97%) create mode 100644 src/main/java/io/github/dsheirer/source/config/SourceConfigTunerMultipleFrequency.java create mode 100644 src/main/java/io/github/dsheirer/source/tuner/TunerSourceMultipleFrequencyEditor.java create mode 100644 src/main/java/io/github/dsheirer/source/tuner/channel/ChannelRotationMonitor.java create mode 100644 src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java diff --git a/build.gradle b/build.gradle index db9cbf67..462ea83e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,23 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 + * ***************************************************************************** + */ + buildscript { repositories { jcenter() @@ -55,6 +75,7 @@ dependencies { compile 'org.apache.axis:axis:1.4' compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.3' compile 'org.kordamp.ikonli:ikonli-javafx:2.1.1' + compile 'org.kordamp.ikonli:ikonli-swing:2.1.1' compile 'org.kordamp.ikonli:ikonli-fontawesome-pack:2.1.1' compile 'com.google.guava:guava:27.0-jre' } diff --git a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadata.java b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadata.java index aa031ec7..fdd8ca4e 100644 --- a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadata.java +++ b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadata.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -33,8 +33,8 @@ import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdent import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier; import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier; +import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier; import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier; -import io.github.dsheirer.identifier.decoder.DecoderStateIdentifier; import io.github.dsheirer.sample.Listener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,7 +54,7 @@ public class ChannelMetadata implements Listener, private SiteConfigurationIdentifier mSiteConfigurationIdentifier; private ChannelNameConfigurationIdentifier mChannelNameConfigurationIdentifier; private FrequencyConfigurationIdentifier mFrequencyConfigurationIdentifier; - private DecoderStateIdentifier mDecoderStateIdentifier = DecoderStateIdentifier.IDLE; + private ChannelStateIdentifier mChannelStateIdentifier = ChannelStateIdentifier.IDLE; private DecoderLogicalChannelNameIdentifier mDecoderLogicalChannelNameIdentifier; private DecoderTypeConfigurationIdentifier mDecoderTypeConfigurationIdentifier; private Identifier mFromIdentifier; @@ -145,14 +145,14 @@ public class ChannelMetadata implements Listener, /** * Decoder state identifier */ - public DecoderStateIdentifier getDecoderStateIdentifier() + public ChannelStateIdentifier getChannelStateIdentifier() { - return mDecoderStateIdentifier; + return mChannelStateIdentifier; } public boolean hasDecoderStateIdentifier() { - return mDecoderStateIdentifier != null; + return mChannelStateIdentifier != null; } /** @@ -250,7 +250,7 @@ public class ChannelMetadata implements Listener, switch(identifier.getForm()) { case ALIAS_LIST: - if(update.isAdded()) + if(update.isAdd()) { mAliasList = mAliasModel.getAliasList((AliasListConfigurationIdentifier)identifier); @@ -268,23 +268,58 @@ public class ChannelMetadata implements Listener, } break; case CHANNEL: - mChannelNameConfigurationIdentifier = update.isAdded() ? (ChannelNameConfigurationIdentifier)identifier : null; + if(identifier instanceof ChannelNameConfigurationIdentifier && (update.isAdd() || update.isSilentAdd())) + { + mChannelNameConfigurationIdentifier = (ChannelNameConfigurationIdentifier)identifier; + } + else + { + mChannelNameConfigurationIdentifier = null; + } broadcastUpdate(ChannelMetadataField.CONFIGURATION_CHANNEL); break; case CHANNEL_FREQUENCY: - mFrequencyConfigurationIdentifier = update.isAdded() ? (FrequencyConfigurationIdentifier)identifier : null; + if(identifier instanceof FrequencyConfigurationIdentifier && (update.isAdd() || update.isSilentAdd())) + { + mFrequencyConfigurationIdentifier = (FrequencyConfigurationIdentifier)identifier; + } + else + { + mFrequencyConfigurationIdentifier = null; + } broadcastUpdate(ChannelMetadataField.CONFIGURATION_FREQUENCY); break; case DECODER_TYPE: - mDecoderTypeConfigurationIdentifier = update.isAdded() ? (DecoderTypeConfigurationIdentifier)identifier : null; + if(identifier instanceof DecoderTypeConfigurationIdentifier && (update.isAdd() || update.isSilentAdd())) + { + mDecoderTypeConfigurationIdentifier = (DecoderTypeConfigurationIdentifier)identifier; + } + else + { + mDecoderTypeConfigurationIdentifier = null; + } broadcastUpdate(ChannelMetadataField.DECODER_TYPE); break; case SITE: - mSiteConfigurationIdentifier = update.isAdded() ? (SiteConfigurationIdentifier)identifier : null; + if(identifier instanceof SiteConfigurationIdentifier && (update.isAdd() || update.isSilentAdd())) + { + mSiteConfigurationIdentifier = (SiteConfigurationIdentifier)identifier; + } + else + { + mSiteConfigurationIdentifier = null; + } broadcastUpdate(ChannelMetadataField.CONFIGURATION_SITE); break; case SYSTEM: - mSystemConfigurationIdentifier = update.isAdded() ? (SystemConfigurationIdentifier)identifier : null; + if(identifier instanceof SystemConfigurationIdentifier && (update.isAdd() || update.isSilentAdd())) + { + mSystemConfigurationIdentifier = (SystemConfigurationIdentifier)identifier; + } + else + { + mSystemConfigurationIdentifier = null; + } broadcastUpdate(ChannelMetadataField.CONFIGURATION_SYSTEM); break; } @@ -293,11 +328,25 @@ public class ChannelMetadata implements Listener, switch(identifier.getForm()) { case CHANNEL_NAME: - mDecoderLogicalChannelNameIdentifier = update.isAdded() ? (DecoderLogicalChannelNameIdentifier)identifier : null; + if(identifier instanceof DecoderLogicalChannelNameIdentifier && (update.isAdd() || update.isSilentAdd())) + { + mDecoderLogicalChannelNameIdentifier = (DecoderLogicalChannelNameIdentifier)identifier; + } + else + { + mDecoderLogicalChannelNameIdentifier = null; + } broadcastUpdate(ChannelMetadataField.DECODER_CHANNEL_NAME); break; case STATE: - mDecoderStateIdentifier = update.isAdded() ? (DecoderStateIdentifier)identifier : null; + if(identifier instanceof ChannelStateIdentifier && (update.isAdd() || update.isSilentAdd())) + { + mChannelStateIdentifier = (ChannelStateIdentifier)identifier; + } + else + { + mChannelStateIdentifier = null; + } broadcastUpdate(ChannelMetadataField.DECODER_STATE); break; } @@ -305,7 +354,7 @@ public class ChannelMetadata implements Listener, case USER: if(identifier.getRole() == Role.FROM) { - mFromIdentifier = update.isAdded() ? identifier : null; + mFromIdentifier = update.isAdd() ? identifier : null; if(mAliasList != null && mFromIdentifier != null) { @@ -320,7 +369,7 @@ public class ChannelMetadata implements Listener, } else if(identifier.getRole() == Role.TO) { - mToIdentifier = update.isAdded() ? identifier : null; + mToIdentifier = update.isAdd() ? identifier : null; if(mAliasList != null && mToIdentifier != null) { diff --git a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataModel.java b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataModel.java index 3958352b..33a19efd 100644 --- a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataModel.java +++ b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataModel.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -176,7 +176,7 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel switch(columnIndex) { case COLUMN_DECODER_STATE: - return channelMetadata.getDecoderStateIdentifier(); + return channelMetadata.getChannelStateIdentifier(); case COLUMN_DECODER_TYPE: return channelMetadata.getDecoderTypeConfigurationIdentifier(); case COLUMN_DECODER_LOGICAL_CHANNEL_NAME: diff --git a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataPanel.java b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataPanel.java index 2e84fc6a..5c64d920 100644 --- a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataPanel.java +++ b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataPanel.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -29,7 +29,7 @@ import io.github.dsheirer.controller.channel.ChannelUtils; import io.github.dsheirer.icon.IconManager; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; -import io.github.dsheirer.identifier.decoder.DecoderStateIdentifier; +import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier; import io.github.dsheirer.module.ProcessingChain; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference; @@ -347,9 +347,9 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene Color background = table.getBackground(); Color foreground = table.getForeground(); - if(value instanceof DecoderStateIdentifier) + if(value instanceof ChannelStateIdentifier) { - State state = ((DecoderStateIdentifier)value).getValue(); + State state = ((ChannelStateIdentifier)value).getValue(); label.setText(state.getDisplayValue()); if(mBackgroundColors.containsKey(state)) diff --git a/src/main/java/io/github/dsheirer/channel/state/ChannelState.java b/src/main/java/io/github/dsheirer/channel/state/ChannelState.java index ee0c3fda..9fa25609 100644 --- a/src/main/java/io/github/dsheirer/channel/state/ChannelState.java +++ b/src/main/java/io/github/dsheirer/channel/state/ChannelState.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -14,8 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.channel.state; import io.github.dsheirer.alias.AliasModel; @@ -37,7 +38,7 @@ import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdent import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier; import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier; -import io.github.dsheirer.identifier.decoder.DecoderStateIdentifier; +import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier; import io.github.dsheirer.module.Module; import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.decode.event.IDecodeEvent; @@ -49,12 +50,14 @@ import io.github.dsheirer.source.ISourceEventProvider; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceType; import io.github.dsheirer.source.config.SourceConfigTuner; +import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency; import io.github.dsheirer.source.heartbeat.Heartbeat; import io.github.dsheirer.source.heartbeat.IHeartbeatListener; -import io.github.dsheirer.source.tuner.channel.TunerChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + public class ChannelState extends Module implements IChannelEventProvider, IDecodeEventProvider, IDecoderStateEventListener, IDecoderStateEventProvider, ISourceEventListener, ISourceEventProvider, IHeartbeatListener, ISquelchStateProvider, IOverflowListener, IdentifierUpdateListener, IdentifierUpdateProvider @@ -76,6 +79,7 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco private Channel mChannel; private SourceEventListener mInternalSourceEventListener; private ChannelMetadata mChannelMetadata; + private IdentifierUpdateNotificationProxy mIdentifierUpdateNotificationProxy = new IdentifierUpdateNotificationProxy(); private boolean mSquelchLocked = false; private boolean mSelected = false; @@ -108,6 +112,7 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco { mChannel = channel; mChannelMetadata = new ChannelMetadata(aliasModel); + mIdentifierCollection.setIdentifierUpdateListener(mIdentifierUpdateNotificationProxy); createConfigurationIdentifiers(channel); } @@ -136,8 +141,17 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco } if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER) { - TunerChannel tunerChannel = ((SourceConfigTuner)channel.getSourceConfiguration()).getTunerChannel(); - mIdentifierCollection.update(FrequencyConfigurationIdentifier.create(tunerChannel.getFrequency())); + long frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency(); + mIdentifierCollection.update(FrequencyConfigurationIdentifier.create(frequency)); + } + else if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER_MULTIPLE_FREQUENCIES) + { + List frequencies = ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencies(); + + if(frequencies.size() > 0) + { + mIdentifierCollection.update(FrequencyConfigurationIdentifier.create(frequencies.get(0))); + } } } @@ -164,13 +178,13 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco @Override public void setIdentifierUpdateListener(Listener listener) { - mIdentifierCollection.setIdentifierUpdateListener(listener); + mIdentifierUpdateNotificationProxy.setListener(listener); } @Override public void removeIdentifierUpdateListener() { - mIdentifierCollection.removeIdentifierUpdateListener(); + mIdentifierUpdateNotificationProxy.removeListener(); } /** @@ -189,7 +203,7 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco { mState = State.IDLE; broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE)); - mIdentifierCollection.update(DecoderStateIdentifier.IDLE); + mIdentifierCollection.update(ChannelStateIdentifier.IDLE); mSourceOverflow = false; } @@ -358,7 +372,7 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco broadcast(SquelchState.SQUELCH); updateFadeTimeout(); mState = state; - mIdentifierCollection.update(DecoderStateIdentifier.ACTIVE); + mIdentifierCollection.update(ChannelStateIdentifier.ACTIVE); break; case CONTROL: //Don't allow traffic channels to be control channels, otherwise they can't transition to teardown @@ -367,39 +381,41 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco broadcast(SquelchState.SQUELCH); updateFadeTimeout(); mState = state; - mIdentifierCollection.update(DecoderStateIdentifier.CONTROL); + mIdentifierCollection.update(ChannelStateIdentifier.CONTROL); } break; case DATA: broadcast(SquelchState.SQUELCH); updateFadeTimeout(); mState = state; - mIdentifierCollection.update(DecoderStateIdentifier.DATA); + mIdentifierCollection.update(ChannelStateIdentifier.DATA); break; case ENCRYPTED: broadcast(SquelchState.SQUELCH); updateFadeTimeout(); mState = state; - mIdentifierCollection.update(DecoderStateIdentifier.ENCRYPTED); + mIdentifierCollection.update(ChannelStateIdentifier.ENCRYPTED); break; case CALL: broadcast(SquelchState.UNSQUELCH); updateFadeTimeout(); mState = state; - mIdentifierCollection.update(DecoderStateIdentifier.CALL); + mIdentifierCollection.update(ChannelStateIdentifier.CALL); break; case FADE: processFadeState(); + mIdentifierCollection.update(ChannelStateIdentifier.FADE); break; case IDLE: processIdleState(); + mIdentifierCollection.update(ChannelStateIdentifier.IDLE); break; case TEARDOWN: processTeardownState(); break; case RESET: mState = State.IDLE; - mIdentifierCollection.update(DecoderStateIdentifier.IDLE); + mIdentifierCollection.update(ChannelStateIdentifier.IDLE); break; default: break; @@ -447,7 +463,7 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco { updateResetTimeout(); mState = State.FADE; - mIdentifierCollection.update(DecoderStateIdentifier.FADE); + mIdentifierCollection.update(ChannelStateIdentifier.FADE); broadcast(SquelchState.SQUELCH); } @@ -462,7 +478,7 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco } mState = State.IDLE; - mIdentifierCollection.update(DecoderStateIdentifier.IDLE); + mIdentifierCollection.update(ChannelStateIdentifier.IDLE); } private void processTeardownState() @@ -470,7 +486,7 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco broadcast(SquelchState.SQUELCH); mState = State.TEARDOWN; - mIdentifierCollection.update(DecoderStateIdentifier.TEARDOWN); + mIdentifierCollection.update(ChannelStateIdentifier.TEARDOWN); if(mChannel.isTrafficChannel()) { @@ -591,6 +607,12 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco //Rebroadcast source frequency change events for the decoder(s) to process long frequency = sourceEvent.getValue().longValue(); broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency)); + + //Create a new frequency configuration identifier so that downstream consumers receive the change + //via channel metadata and audio packet updates - this is a silent add that is sent as a notification + //to all identifier collections so that they don't rebroadcast the change and cause a feedback loop + mIdentifierUpdateNotificationProxy.receive(new IdentifierUpdateNotification( + FrequencyConfigurationIdentifier.create(frequency), IdentifierUpdateNotification.Operation.SILENT_ADD)); break; case NOTIFICATION_MEASURED_FREQUENCY_ERROR: //Rebroadcast frequency error measurements to external listener if we're currently @@ -707,4 +729,32 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco } } } + + /** + * Proxy between the identifier collection and the external update notification listener. This proxy enables + * access to internal components to broadcast silent identifier update notifications externally. + */ + public class IdentifierUpdateNotificationProxy implements Listener + { + private Listener mIdentifierUpdateNotificationListener; + + @Override + public void receive(IdentifierUpdateNotification identifierUpdateNotification) + { + if(mIdentifierUpdateNotificationListener != null) + { + mIdentifierUpdateNotificationListener.receive(identifierUpdateNotification); + } + } + + public void setListener(Listener listener) + { + mIdentifierUpdateNotificationListener = listener; + } + + public void removeListener() + { + mIdentifierUpdateNotificationListener = null; + } + } } diff --git a/src/main/java/io/github/dsheirer/channel/state/DecoderState.java b/src/main/java/io/github/dsheirer/channel/state/DecoderState.java index 8eead5af..3c1211f3 100644 --- a/src/main/java/io/github/dsheirer/channel/state/DecoderState.java +++ b/src/main/java/io/github/dsheirer/channel/state/DecoderState.java @@ -1,3 +1,23 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 io.github.dsheirer.channel.state; import io.github.dsheirer.channel.IChannelDescriptor; @@ -261,7 +281,14 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv identifierUpdateNotification.getIdentifier().getForm() != Form.DECODER_TYPE && identifierUpdateNotification.getIdentifier().getForm() != Form.CHANNEL_DESCRIPTOR) { - getIdentifierCollection().update(identifierUpdateNotification.getIdentifier()); + if(identifierUpdateNotification.isAdd()) + { + getIdentifierCollection().update(identifierUpdateNotification.getIdentifier()); + } + else if(identifierUpdateNotification.isSilentAdd()) + { + getIdentifierCollection().silentUpdate(identifierUpdateNotification.getIdentifier()); + } } if(identifierUpdateNotification.getOperation() == IdentifierUpdateNotification.Operation.ADD) diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java index 2b7a1809..3aa56899 100644 --- a/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java +++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -36,6 +36,7 @@ import io.github.dsheirer.module.decode.DecoderFactory; import io.github.dsheirer.module.decode.event.IDecodeEvent; import io.github.dsheirer.module.decode.event.MessageActivityModel; import io.github.dsheirer.module.log.EventLogManager; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.RecorderManager; import io.github.dsheirer.record.RecorderType; import io.github.dsheirer.record.binary.BinaryRecorder; @@ -76,6 +77,7 @@ public class ChannelProcessingManager implements Listener private RecorderManager mRecorderManager; private SourceManager mSourceManager; private AliasModel mAliasModel; + private UserPreferences mUserPreferences; /** * Constructs the channel processing manager @@ -85,15 +87,18 @@ public class ChannelProcessingManager implements Listener * @param recorderManager for receiving audio packets produced by the channel * @param sourceManager for obtaining a tuner channel source for the channel * @param aliasModel for aliasing of identifiers produced by the channel + * @param userPreferences for user defined behavior and settings */ public ChannelProcessingManager(ChannelMapModel channelMapModel, EventLogManager eventLogManager, - RecorderManager recorderManager, SourceManager sourceManager, AliasModel aliasModel) + RecorderManager recorderManager, SourceManager sourceManager, + AliasModel aliasModel, UserPreferences userPreferences) { mChannelMapModel = channelMapModel; mEventLogManager = eventLogManager; mRecorderManager = recorderManager; mSourceManager = sourceManager; mAliasModel = aliasModel; + mUserPreferences = userPreferences; mChannelMetadataModel = new ChannelMetadataModel(); } @@ -262,7 +267,7 @@ public class ChannelProcessingManager implements Listener processingChain.addFrequencyChangeListener(channel); /* Processing Modules */ - List modules = DecoderFactory.getModules(mChannelMapModel, channel, mAliasModel); + List modules = DecoderFactory.getModules(mChannelMapModel, channel, mAliasModel, mUserPreferences); processingChain.addModules(modules); /* Setup message activity model with filtering */ diff --git a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java index a033697f..d9407bf3 100644 --- a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java +++ b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -154,7 +154,7 @@ public class SDRTrunk implements Listener mSourceManager = new SourceManager(tunerModel, mSettingsManager, mUserPreferences); mChannelProcessingManager = new ChannelProcessingManager(channelMapModel, eventLogManager, recorderManager, - mSourceManager, aliasModel); + mSourceManager, aliasModel, mUserPreferences); mChannelModel.addListener(mChannelProcessingManager); mChannelProcessingManager.addChannelEventListener(mChannelModel); diff --git a/src/main/java/io/github/dsheirer/gui/control/MultipleFrequencyEditor.java b/src/main/java/io/github/dsheirer/gui/control/MultipleFrequencyEditor.java new file mode 100644 index 00000000..877c9aed --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/control/MultipleFrequencyEditor.java @@ -0,0 +1,494 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 io.github.dsheirer.gui.control; + +import net.miginfocom.swing.MigLayout; +import org.kordamp.ikonli.fontawesome.FontAwesome; +import org.kordamp.ikonli.swing.FontIcon; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.AbstractAction; +import javax.swing.DefaultCellEditor; +import javax.swing.JButton; +import javax.swing.JFormattedTextField; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.text.DefaultFormatterFactory; +import javax.swing.text.NumberFormatter; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.List; + +/** + * Multiple frequency editor control for editing an ordered list of frequencies. Frequency values are persisted as + * long values in hertz, but presented to the user as double values in megahertz. + * + */ +public class MultipleFrequencyEditor extends JPanel +{ + private final static Logger mLog = LoggerFactory.getLogger(MultipleFrequencyEditor.class); + private static final Long DEFAULT_FREQUENCY = 0l; + private DecimalFormat mDecimalFormat = new DecimalFormat("0.000000"); + private FrequencyModel mFrequencyModel = new FrequencyModel(); + private JTable mFrequencyTable; + private JButton mAddButton; + private JButton mRemoveButton; + private JButton mMoveUpButton; + private JButton mMoveDownButton; + + /** + * Constructs the editor + * @param minimum allowable frequency value + * @param maximum allowable frequency value + */ + public MultipleFrequencyEditor(double minimum, double maximum) + { + setLayout(new MigLayout("insets 0 0 0 0", "[]1[grow,fill]1[]", "[]1[]1[top,grow,fill]")); + + mMoveUpButton = new JButton(); + mMoveUpButton.setIcon(FontIcon.of(FontAwesome.ARROW_UP, 10)); + mMoveUpButton.setEnabled(false); + mMoveUpButton.addActionListener(e -> { + int selectedRow = mFrequencyTable.getSelectedRow(); + int index = mFrequencyTable.convertRowIndexToModel(selectedRow); + mFrequencyModel.moveUp(index); + mFrequencyTable.getSelectionModel().setLeadSelectionIndex(index - 1); + mFrequencyTable.scrollRectToVisible(new Rectangle(mFrequencyTable.getCellRect(selectedRow, 0, true))); + + updateButtons(); + }); + add(mMoveUpButton); + + mFrequencyTable = new JTable(mFrequencyModel); + //testing ... + mFrequencyTable.setPreferredScrollableViewportSize(new Dimension(100, 65)); + mFrequencyTable.setFillsViewportHeight(true); + + mFrequencyTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + mFrequencyTable.getSelectionModel().addListSelectionListener(new SelectionListener()); + mFrequencyTable.setDefaultEditor(Long.class, new FrequencyEditor(minimum, maximum)); + mFrequencyTable.setDefaultRenderer(Long.class, new FrequencyRenderer()); + JScrollPane listScrollPane = new JScrollPane(mFrequencyTable); + add(listScrollPane, "span 1 3"); + + mAddButton = new JButton(); + mAddButton.setIcon(FontIcon.of(FontAwesome.PLUS, 10)); + mAddButton.addActionListener(e -> { + int selectedRow = mFrequencyTable.getSelectedRow(); + if(selectedRow >= 0) + { + int index = mFrequencyTable.convertRowIndexToModel(selectedRow); + int nextIndex = index + 1; + mFrequencyModel.add(DEFAULT_FREQUENCY, nextIndex); + mFrequencyTable.requestFocus(); + mFrequencyTable.setRowSelectionInterval(nextIndex, nextIndex); + mFrequencyTable.scrollRectToVisible(new Rectangle(mFrequencyTable.getCellRect(nextIndex, 0, true))); + mFrequencyTable.editCellAt(nextIndex, 0); + } + else + { + mFrequencyModel.add(DEFAULT_FREQUENCY); + int lastIndex = mFrequencyModel.getRowCount() - 1; + mFrequencyTable.setRowSelectionInterval(lastIndex, lastIndex); + mFrequencyTable.editCellAt(lastIndex, 0); + } + + mFrequencyTable.setSurrendersFocusOnKeystroke(true); + mFrequencyTable.getEditorComponent().requestFocus(); + + updateButtons(); + }); + add(mAddButton, "wrap"); + + mMoveDownButton = new JButton(); + mMoveDownButton.setIcon(FontIcon.of(FontAwesome.ARROW_DOWN, 10)); + mMoveDownButton.setEnabled(false); + mMoveDownButton.addActionListener(e -> { + int selectedRow = mFrequencyTable.getSelectedRow(); + int index = mFrequencyTable.convertRowIndexToModel(selectedRow); + mFrequencyModel.moveDown(index); + mFrequencyTable.getSelectionModel().setLeadSelectionIndex(index + 1); + mFrequencyTable.scrollRectToVisible(new Rectangle(mFrequencyTable.getCellRect(selectedRow, 0, true))); + + updateButtons(); + }); + add(mMoveDownButton); + + mRemoveButton = new JButton(); + mRemoveButton.setIcon(FontIcon.of(FontAwesome.MINUS, 10)); + mRemoveButton.setEnabled(false); + mRemoveButton.addActionListener(e -> { + int selectedRow = mFrequencyTable.getSelectedRow(); + int selectedIndex = mFrequencyTable.convertRowIndexToModel(selectedRow); + mFrequencyModel.remove(selectedIndex); + + if(selectedIndex < mFrequencyModel.getRowCount()) + { + int reselectedRow = mFrequencyTable.convertRowIndexToView(selectedIndex); + mFrequencyTable.setRowSelectionInterval(reselectedRow, reselectedRow); + mFrequencyTable.scrollRectToVisible(new Rectangle(mFrequencyTable.getCellRect(reselectedRow, 0, true))); + } + + updateButtons(); + }); + add(mRemoveButton); + } + + public FrequencyModel getFrequencyModel() + { + return mFrequencyModel; + } + + private void updateButtons() + { + int index = mFrequencyTable.getSelectedRow(); + + if(index >= 0) + { + mMoveUpButton.setEnabled(index > 0); + mMoveDownButton.setEnabled(index < mFrequencyModel.getRowCount() - 1); + mRemoveButton.setEnabled(true); + } + else + { + mRemoveButton.setEnabled(false); + mMoveUpButton.setEnabled(false); + mMoveDownButton.setEnabled(false); + } + } + + /** + * Formats the frequency value as MHz + */ + private String format(long frequency) + { + return mDecimalFormat.format(frequency / 1e6d); + } + + public class FrequencyModel extends AbstractTableModel + { + private List mFrequencies = new ArrayList<>(); + + @Override + public String getColumnName(int column) + { + return "Frequencies (MHz)"; + } + + @Override + public int getRowCount() + { + return mFrequencies.size(); + } + + @Override + public int getColumnCount() + { + return 1; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) + { + return mFrequencies.get(rowIndex); + } + + public void setValueAt(int index, Long value) + { + mFrequencies.set(index, value); + fireTableRowsUpdated(index, index); + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) + { + return true; + } + + @Override + public Class getColumnClass(int columnIndex) + { + return Long.class; + } + + /** + * Adds a frequency to the model + */ + public void add(long frequency, int index) + { + mFrequencies.add(index, frequency); + fireTableRowsInserted(index, index); + } + + /** + * Adds a frequency to the model + */ + public void add(long frequency) + { + mFrequencies.add(frequency); + fireTableRowsInserted(mFrequencies.size() - 1, mFrequencies.size() - 1); + } + + /** + * Removes an indexed value from the model + */ + public Long remove(int index) + { + Long value = mFrequencies.remove(index); + fireTableRowsDeleted(index, index); + return value; + } + + /** + * Removes all frequencies from the model + */ + public void clear() + { + mFrequencies.clear(); + fireTableDataChanged(); + } + + /** + * Moves the frequency up in the list + */ + public void moveUp(int index) + { + if(index > 0 && index < mFrequencies.size()) + { + Long valueA = mFrequencies.get(index); + Long valueB = mFrequencies.get(index - 1); + mFrequencies.set(index, valueB); + mFrequencies.set(index - 1, valueA); + + fireTableRowsUpdated(index - 1, index); + } + } + + /** + * Moves the frequency down in the list + */ + public void moveDown(int index) + { + if(index >= 0 && index + 1 < mFrequencies.size()) + { + Long valueA = mFrequencies.get(index); + Long valueB = mFrequencies.get(index + 1); + mFrequencies.set(index, valueB); + mFrequencies.set(index + 1, valueA); + + fireTableRowsUpdated(index, index + 1); + } + } + + /** + * Sets the frequencies for the model + */ + public void setFrequencies(List frequencies) + { + mFrequencies.clear(); + mFrequencies.addAll(frequencies); + fireTableDataChanged(); + } + + /** + * List of long frequency values contained in the model + */ + public List getFrequencies() + { + List frequencies = new ArrayList<>(); + frequencies.addAll(mFrequencies); + return frequencies; + } + } + + public class SelectionListener implements ListSelectionListener + { + @Override + public void valueChanged(ListSelectionEvent event) + { + updateButtons(); + } + } + + public class FrequencyEditor extends DefaultCellEditor + { + private JFormattedTextField mTextField; + private NumberFormat mNumberFormat; + private Double mMinimum; + private Double mMaximum; + + public FrequencyEditor(double minValue, double maxValue) + { + super(new JFormattedTextField()); + mTextField = (JFormattedTextField)getComponent(); + mMinimum = minValue; + mMaximum = maxValue; + + mNumberFormat = NumberFormat.getNumberInstance(); + mNumberFormat.setMinimumFractionDigits(1); + mNumberFormat.setMaximumFractionDigits(6); + mNumberFormat.setGroupingUsed(false); + NumberFormatter numberFormatter = new NumberFormatter(mNumberFormat); + numberFormatter.setFormat(mNumberFormat); + numberFormatter.setMinimum(mMinimum); + numberFormatter.setMaximum(mMaximum); + numberFormatter.setAllowsInvalid(false); + + mTextField.setFormatterFactory(new DefaultFormatterFactory(numberFormatter)); + mTextField.setValue(mMinimum); + mTextField.setHorizontalAlignment(JTextField.TRAILING); + mTextField.setFocusLostBehavior(JFormattedTextField.PERSIST); + mTextField.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "check"); + mTextField.getActionMap().put("check", new AbstractAction() + { + @Override + public void actionPerformed(ActionEvent event) + { + //This should always be true since we use formatter.setAllowsInvalid(false) above. + if(mTextField.isEditValid()) + { + int selectedRow = mFrequencyTable.getSelectedRow(); + int selectedIndex = mFrequencyTable.convertRowIndexToModel(selectedRow); + + try + { + Double frequencyMHz = Double.parseDouble(mTextField.getText()); + + long frequency = (long)(frequencyMHz * 1e6d); + mFrequencyModel.setValueAt(selectedIndex, frequency); + } + catch(Exception err) + { + mLog.error("Error parsing frequency: " + mTextField.getText(), err); + } + + fireEditingStopped(); + } + } + }); + } + + //Override to invoke setValue on the formatted text field. + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) + { + JFormattedTextField ftf = (JFormattedTextField)super.getTableCellEditorComponent(table, value, isSelected, row, column); + + if(value instanceof Long) + { + ftf.setValue(((Long)value).doubleValue() / 1e6d); + } + else + { + ftf.setValue(value); + } + + ftf.requestFocus(); + ftf.selectAll(); + ftf.setCaretPosition(0); + return ftf; + } + + //Override to ensure that the value remains a Double. + public Object getCellEditorValue() + { + JFormattedTextField ftf = (JFormattedTextField)getComponent(); + Object o = ftf.getValue(); + if(o instanceof Double) + { + return o; + } + else if(o instanceof Number) + { + return ((Number)o).doubleValue(); + } + else + { + try + { + return Double.parseDouble(o.toString()); + } + catch(Exception exc) + { + return new Double(0); + } + } + } + } + + /** + * Table cell renderer to display long hertz values as double megahertz values + */ + public class FrequencyRenderer extends DefaultTableCellRenderer + { + + public FrequencyRenderer() + { + super.setHorizontalAlignment(JLabel.RIGHT); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel cell = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + if(value instanceof Long) + { + cell.setText(format((long)value)); + } + else + { + cell.setText("??"); + } + + return cell; + } + } + + public static void main(String[] args) + { + EventQueue.invokeLater(() -> { + final JFrame frame = new JFrame(); + frame.setSize(new Dimension(400, 400)); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.add(new MultipleFrequencyEditor(1.0, 6000.0)); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + }); + } +} diff --git a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java index 1b149851..0ac1afe1 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java +++ b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -21,6 +21,7 @@ package io.github.dsheirer.gui.preference; import io.github.dsheirer.gui.preference.directory.DirectoryPreferenceEditor; +import io.github.dsheirer.gui.preference.tuner.ChannelMultipleFrequencyPreferenceEditor; import io.github.dsheirer.gui.preference.tuner.TunerPreferenceEditor; import io.github.dsheirer.preference.UserPreferences; import javafx.scene.Node; @@ -38,9 +39,11 @@ public class PreferenceEditorFactory return new DecodeEventViewPreferenceEditor(userPreferences); case DIRECTORY: return new DirectoryPreferenceEditor(userPreferences); + case SOURCE_CHANNEL_MULTIPLE_FREQUENCY: + return new ChannelMultipleFrequencyPreferenceEditor(userPreferences); case TALKGROUP_FORMAT: return new TalkgroupFormatPreferenceEditor(userPreferences); - case TUNER: + case SOURCE_TUNER_CHANNELIZER: return new TunerPreferenceEditor(userPreferences); } diff --git a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java index b076b7da..652df137 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java +++ b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -27,8 +27,9 @@ public enum PreferenceEditorType { CHANNEL_EVENT("Channel Events"), DIRECTORY("Directories"), - TALKGROUP_FORMAT("Talkgroups"), - TUNER("Tuners"); + SOURCE_CHANNEL_MULTIPLE_FREQUENCY("Channel - Multiple Frequency"), + SOURCE_TUNER_CHANNELIZER("Tuner Channelizer"), + TALKGROUP_FORMAT("Talkgroups"); private String mLabel; diff --git a/src/main/java/io/github/dsheirer/gui/preference/PreferencesEditor.java b/src/main/java/io/github/dsheirer/gui/preference/PreferencesEditor.java index babc7061..e00105a2 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/PreferencesEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/PreferencesEditor.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -201,7 +201,8 @@ public class PreferencesEditor extends Application storageItem.setExpanded(true); TreeItem sourceItem = new TreeItem<>("Source"); - sourceItem.getChildren().add(new TreeItem(PreferenceEditorType.TUNER)); + sourceItem.getChildren().add(new TreeItem(PreferenceEditorType.SOURCE_CHANNEL_MULTIPLE_FREQUENCY)); + sourceItem.getChildren().add(new TreeItem(PreferenceEditorType.SOURCE_TUNER_CHANNELIZER)); treeRoot.getChildren().add(sourceItem); sourceItem.setExpanded(true); diff --git a/src/main/java/io/github/dsheirer/gui/preference/tuner/ChannelMultipleFrequencyPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/tuner/ChannelMultipleFrequencyPreferenceEditor.java new file mode 100644 index 00000000..8350f209 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/preference/tuner/ChannelMultipleFrequencyPreferenceEditor.java @@ -0,0 +1,123 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 io.github.dsheirer.gui.preference.tuner; + +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.preference.source.ChannelMultiFrequencyPreference; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.Spinner; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; + + +/** + * Preference settings for channel source configurations containing multiple frequencies + */ +public class ChannelMultipleFrequencyPreferenceEditor extends HBox +{ + private static final String HELP_TEXT = "Rotation delay determines how long a decoder will wait before rotating " + + "to the next frequency in a list for a rotating control channel configuration."; + + private static final Insets MARGINS = new Insets(5,5,5,5); + private ChannelMultiFrequencyPreference mChannelMultiFrequencyPreference; + private GridPane mEditorPane; + private Label mRotationDelayLabel; + private Spinner mRotationDelaySpinner; + private Button mResetButton; + private Label mHelpText; + + public ChannelMultipleFrequencyPreferenceEditor(UserPreferences userPreferences) + { + mChannelMultiFrequencyPreference = userPreferences.getChannelMultiFrequencyPreference(); + getChildren().add(getEditorPane()); + } + + private GridPane getEditorPane() + { + if(mEditorPane == null) + { + mEditorPane = new GridPane(); + GridPane.setMargin(getRotationDelaySpinner(), new Insets(5, 5, 5, 5)); + mEditorPane.add(getRotationDelaySpinner(), 0, 0); + GridPane.setMargin(getRotationDelayLabel(), new Insets(5, 5, 5, 5)); + mEditorPane.add(getRotationDelayLabel(), 1, 0); + GridPane.setMargin(getResetButton(), MARGINS); + mEditorPane.add(getResetButton(), 2, 0); + GridPane.setMargin(getHelpText(), MARGINS); + mEditorPane.add(getHelpText(), 0, 1, 3, 1); + } + + return mEditorPane; + } + + public Spinner getRotationDelaySpinner() + { + if(mRotationDelaySpinner == null) + { + int currentDelay = (int)(mChannelMultiFrequencyPreference.getRotationDelay() / 1000); + mRotationDelaySpinner = new Spinner<>(1, 60, currentDelay, 1); + mRotationDelaySpinner.setEditable(true); + mRotationDelaySpinner.valueProperty().addListener((observable, oldValue, newValue) -> { + mChannelMultiFrequencyPreference.setRotationDelay(newValue * 1000); + }); + } + + return mRotationDelaySpinner; + } + + public Label getRotationDelayLabel() + { + if(mRotationDelayLabel == null) + { + mRotationDelayLabel = new Label("Rotation Delay (seconds)"); + } + + return mRotationDelayLabel; + } + + public Button getResetButton() + { + if(mResetButton == null) + { + mResetButton = new Button("Reset"); + mResetButton.setOnAction(event -> { + mChannelMultiFrequencyPreference.resetRotationDelay(); + int currentDelay = (int)(mChannelMultiFrequencyPreference.getRotationDelay() / 1000); + getRotationDelaySpinner().getValueFactory().setValue(currentDelay); + }); + } + + return mResetButton; + } + + public Label getHelpText() + { + if(mHelpText == null) + { + mHelpText = new Label(HELP_TEXT); + mHelpText.setWrapText(true); + } + + return mHelpText; + } +} diff --git a/src/main/java/io/github/dsheirer/gui/preference/tuner/TunerPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/tuner/TunerPreferenceEditor.java index 9e4a04e6..184ff343 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/tuner/TunerPreferenceEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/tuner/TunerPreferenceEditor.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -21,8 +21,8 @@ package io.github.dsheirer.gui.preference.tuner; import io.github.dsheirer.preference.UserPreferences; -import io.github.dsheirer.preference.tuner.ChannelizerType; -import io.github.dsheirer.preference.tuner.TunerPreference; +import io.github.dsheirer.preference.source.ChannelizerType; +import io.github.dsheirer.preference.source.TunerPreference; import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.Orientation; diff --git a/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateNotification.java b/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateNotification.java index a948bfb2..6b18222e 100644 --- a/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateNotification.java +++ b/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateNotification.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -48,12 +48,12 @@ public class IdentifierUpdateNotification return mOperation; } - public boolean isAdded() + public boolean isAdd() { return mOperation == Operation.ADD; } - public boolean isRemoved() + public boolean isRemove() { return mOperation == Operation.REMOVE; } diff --git a/src/main/java/io/github/dsheirer/identifier/MutableIdentifierCollection.java b/src/main/java/io/github/dsheirer/identifier/MutableIdentifierCollection.java index c18e6cb7..c9456c1c 100644 --- a/src/main/java/io/github/dsheirer/identifier/MutableIdentifierCollection.java +++ b/src/main/java/io/github/dsheirer/identifier/MutableIdentifierCollection.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -367,11 +367,11 @@ public class MutableIdentifierCollection extends IdentifierCollection implements @Override public void receive(IdentifierUpdateNotification identifierUpdateNotification) { - if(identifierUpdateNotification.isAdded()) + if(identifierUpdateNotification.isAdd()) { update(identifierUpdateNotification.getIdentifier()); } - else if(identifierUpdateNotification.isRemoved()) + else if(identifierUpdateNotification.isRemove()) { remove(identifierUpdateNotification.getIdentifier()); } diff --git a/src/main/java/io/github/dsheirer/identifier/decoder/DecoderStateIdentifier.java b/src/main/java/io/github/dsheirer/identifier/decoder/ChannelStateIdentifier.java similarity index 65% rename from src/main/java/io/github/dsheirer/identifier/decoder/DecoderStateIdentifier.java rename to src/main/java/io/github/dsheirer/identifier/decoder/ChannelStateIdentifier.java index 41ccf413..981d7071 100644 --- a/src/main/java/io/github/dsheirer/identifier/decoder/DecoderStateIdentifier.java +++ b/src/main/java/io/github/dsheirer/identifier/decoder/ChannelStateIdentifier.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -27,19 +27,19 @@ import io.github.dsheirer.identifier.IdentifierClass; import io.github.dsheirer.identifier.Role; import io.github.dsheirer.protocol.Protocol; -public class DecoderStateIdentifier extends Identifier +public class ChannelStateIdentifier extends Identifier { - public static final DecoderStateIdentifier ACTIVE = DecoderStateIdentifier.create(State.ACTIVE); - public static final DecoderStateIdentifier CALL = DecoderStateIdentifier.create(State.CALL); - public static final DecoderStateIdentifier CONTROL = DecoderStateIdentifier.create(State.CONTROL); - public static final DecoderStateIdentifier DATA = DecoderStateIdentifier.create(State.DATA); - public static final DecoderStateIdentifier ENCRYPTED = DecoderStateIdentifier.create(State.ENCRYPTED); - public static final DecoderStateIdentifier FADE = DecoderStateIdentifier.create(State.FADE); - public static final DecoderStateIdentifier IDLE = DecoderStateIdentifier.create(State.IDLE); - public static final DecoderStateIdentifier TEARDOWN = DecoderStateIdentifier.create(State.TEARDOWN); - public static final DecoderStateIdentifier RESET = DecoderStateIdentifier.create(State.RESET); + public static final ChannelStateIdentifier ACTIVE = ChannelStateIdentifier.create(State.ACTIVE); + public static final ChannelStateIdentifier CALL = ChannelStateIdentifier.create(State.CALL); + public static final ChannelStateIdentifier CONTROL = ChannelStateIdentifier.create(State.CONTROL); + public static final ChannelStateIdentifier DATA = ChannelStateIdentifier.create(State.DATA); + public static final ChannelStateIdentifier ENCRYPTED = ChannelStateIdentifier.create(State.ENCRYPTED); + public static final ChannelStateIdentifier FADE = ChannelStateIdentifier.create(State.FADE); + public static final ChannelStateIdentifier IDLE = ChannelStateIdentifier.create(State.IDLE); + public static final ChannelStateIdentifier TEARDOWN = ChannelStateIdentifier.create(State.TEARDOWN); + public static final ChannelStateIdentifier RESET = ChannelStateIdentifier.create(State.RESET); - public DecoderStateIdentifier(State value) + public ChannelStateIdentifier(State value) { super(value, IdentifierClass.DECODER, Form.STATE, Role.ANY); } @@ -53,9 +53,9 @@ public class DecoderStateIdentifier extends Identifier /** * Creates a decoder state identifier from the decoder state value */ - public static DecoderStateIdentifier create(State state) + public static ChannelStateIdentifier create(State state) { - return new DecoderStateIdentifier(state); + return new ChannelStateIdentifier(state); } @Override diff --git a/src/main/java/io/github/dsheirer/module/ProcessingChain.java b/src/main/java/io/github/dsheirer/module/ProcessingChain.java index 661cbea7..9b404b25 100644 --- a/src/main/java/io/github/dsheirer/module/ProcessingChain.java +++ b/src/main/java/io/github/dsheirer/module/ProcessingChain.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -456,7 +456,7 @@ public class ProcessingChain implements Listener if(module instanceof IHeartbeatProvider) { - ((IHeartbeatProvider)module).getHeartbeatManager().addHeartbeatListener(mHeartbeatBroadcaster); + ((IHeartbeatProvider)module).addHeartbeatListener(mHeartbeatBroadcaster); } if(module instanceof IMessageProvider) @@ -528,7 +528,7 @@ public class ProcessingChain implements Listener if(module instanceof IHeartbeatProvider) { - ((IHeartbeatProvider)module).getHeartbeatManager().removeHeartbeatListener(mHeartbeatBroadcaster); + ((IHeartbeatProvider)module).removeHeartbeatListener(mHeartbeatBroadcaster); } if(module instanceof IMessageProvider) @@ -571,7 +571,7 @@ public class ProcessingChain implements Listener mSourceEventBroadcaster.broadcast(SourceEvent.sampleRateChange(mSource.getSampleRate(), "Processing Chain Startup")); //Broadcast the source center frequency / sample rate so that each of the modules can self-configure - mSourceEventBroadcaster.broadcast(SourceEvent.frequencyChange(mSource.getFrequency(), "Processing Chain Startup")); + mSourceEventBroadcaster.broadcast(SourceEvent.frequencyChange(mSource, mSource.getFrequency(), "Processing Chain Startup")); //Setup the channel state to monitor source overflow conditions mSource.setOverflowListener(mChannelState); diff --git a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java index a5694007..a6f88e9f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -24,6 +24,7 @@ import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.alias.action.AliasActionManager; import io.github.dsheirer.audio.AudioModule; import io.github.dsheirer.channel.state.AlwaysUnsquelchedDecoderState; +import io.github.dsheirer.channel.state.State; import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.controller.channel.Channel.ChannelType; import io.github.dsheirer.controller.channel.map.ChannelMap; @@ -87,7 +88,10 @@ import io.github.dsheirer.module.decode.tait.Tait1200Decoder; import io.github.dsheirer.module.decode.tait.Tait1200DecoderState; import io.github.dsheirer.module.demodulate.am.AMDemodulatorModule; import io.github.dsheirer.module.demodulate.fm.FMDemodulatorModule; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.source.SourceType; +import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency; +import io.github.dsheirer.source.tuner.channel.ChannelRotationMonitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -109,9 +113,10 @@ public class DecoderFactory * * @return list of configured decoders */ - public static List getModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel) + public static List getModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel, + UserPreferences userPreferences) { - List modules = getPrimaryModules(channelMapModel, channel, aliasModel); + List modules = getPrimaryModules(channelMapModel, channel, aliasModel, userPreferences); modules.addAll(getAuxiliaryDecoders(channel.getAuxDecodeConfiguration())); return modules; } @@ -119,7 +124,8 @@ public class DecoderFactory /** * Constructs a primary decoder as specified in the decode configuration */ - public static List getPrimaryModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel) + public static List getPrimaryModules(ChannelMapModel channelMapModel, Channel channel, + AliasModel aliasModel, UserPreferences userPreferences) { List modules = new ArrayList(); @@ -239,6 +245,15 @@ public class DecoderFactory } modules.add(new P25AudioModule()); + + //Add a channel rotation monitor when we have multiple control channel frequencies specified + if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency && + ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).hasMultipleFrequencies()) + { + List activeStates = new ArrayList<>(); + activeStates.add(State.CONTROL); + modules.add(new ChannelRotationMonitor(activeStates, userPreferences)); + } break; default: throw new IllegalArgumentException("Unknown decoder type [" + decodeConfig.getDecoderType().toString() + "]"); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderEditor.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderEditor.java index d6d4df83..967a0b79 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderEditor.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderEditor.java @@ -1,20 +1,22 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 io.github.dsheirer.module.decode.p25; import io.github.dsheirer.controller.channel.Channel; @@ -26,6 +28,7 @@ import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration; import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.source.SourceConfigurationEditor; import io.github.dsheirer.source.config.SourceConfigTuner; +import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency; import io.github.dsheirer.source.config.SourceConfiguration; import net.miginfocom.swing.MigLayout; import org.slf4j.Logger; @@ -43,178 +46,176 @@ import java.awt.event.ActionListener; public class P25DecoderEditor extends ValidatingEditor { - private final static Logger mLog = LoggerFactory.getLogger( P25DecoderEditor.class ); + private final static Logger mLog = LoggerFactory.getLogger(P25DecoderEditor.class); + + private static final long serialVersionUID = 1L; - private static final long serialVersionUID = 1L; - private JComboBox mComboModulation; private JCheckBox mIgnoreDataCalls; private JLabel mTrafficChannelPoolSizeLabel; private JSlider mTrafficChannelPoolSize; public P25DecoderEditor() - { - init(); - } - - private void init() - { - setLayout( new MigLayout( "insets 0 0 0 0,wrap 3", "[right][grow,fill][]", "" ) ); - - mComboModulation = new JComboBox(); - mComboModulation.setModel( new DefaultComboBoxModel( - P25DecoderLSM.Modulation.values() ) ); - mComboModulation.setEnabled( false ); - mComboModulation.addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent e ) - { - setModified( true ); - } - } ); - - add( new JLabel( "Modulation:" ) ); - add( mComboModulation ); - - mIgnoreDataCalls = new JCheckBox( "Ignore Data Calls" ); - mIgnoreDataCalls.setEnabled( false ); - mIgnoreDataCalls.addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent e ) - { - setModified( true ); - } - } ); - - add( mIgnoreDataCalls ); - - mTrafficChannelPoolSize = new JSlider( JSlider.HORIZONTAL, DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_MINIMUM, - DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_MAXIMUM, DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT ); - mTrafficChannelPoolSize.setEnabled( false ); - mTrafficChannelPoolSize.setMajorTickSpacing( 10 ); - mTrafficChannelPoolSize.setMinorTickSpacing( 5 ); - mTrafficChannelPoolSize.setPaintTicks( true ); - mTrafficChannelPoolSize.addChangeListener( new ChangeListener() - { - @Override - public void stateChanged( ChangeEvent e ) - { - mTrafficChannelPoolSizeLabel.setText( "Traffic Pool: " + mTrafficChannelPoolSize.getValue() ); - setModified( true ); - } - } ); - - mTrafficChannelPoolSizeLabel = new JLabel( "Traffic Pool: " + mTrafficChannelPoolSize.getValue() + " " ); - - add( mTrafficChannelPoolSizeLabel ); - add( mTrafficChannelPoolSize ); - } - - @Override - public void save() - { - if( hasItem() && isModified() ) - { - DecodeConfigP25Phase1 p25 = new DecodeConfigP25Phase1(); - - p25.setModulation( (P25Decoder.Modulation)mComboModulation.getSelectedItem() ); - p25.setIgnoreDataCalls( mIgnoreDataCalls.isSelected() ); - p25.setTrafficChannelPoolSize( mTrafficChannelPoolSize.getValue() ); - - getItem().setDecodeConfiguration( p25 ); - } - - setModified( false ); + { + init(); } - @Override - public void validate( Editor editor ) throws EditorValidationException - { - /** - * Enforce CQPSK modulation uses a Tuner Source for I/Q sample data - */ - if( editor instanceof SourceConfigurationEditor && - ((P25Decoder.Modulation)mComboModulation.getSelectedItem()) == P25Decoder.Modulation.CQPSK ) - { - SourceConfiguration config = ((SourceConfigurationEditor)editor).getSourceConfiguration(); + private void init() + { + setLayout(new MigLayout("insets 0 0 0 0,wrap 3", "[right][grow,fill][]", "")); - if( config != null && !( config instanceof SourceConfigTuner ) ) - { - throw new EditorValidationException( editor, - "

LSM Simulcast

" - + "

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

" ); - } - } - - if( editor instanceof AuxDecodeConfigurationEditor) - { - AuxDecodeConfiguration config = ((AuxDecodeConfigurationEditor)editor).getConfiguration(); - - if( config != null && !config.getAuxDecoders().isEmpty() ) - { - throw new EditorValidationException( editor, - "

Auxilary Decoders

" - + "

The auxiliary decoders work with analog audio and are not " - + "compatible with the P25 Decoder. Please uncheck any auxiliary " - + "decoders that you have selected for this channel

" ); - } - } - } + mComboModulation = new JComboBox(); + mComboModulation.setModel(new DefaultComboBoxModel( + P25DecoderLSM.Modulation.values())); + mComboModulation.setEnabled(false); + mComboModulation.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + setModified(true); + } + }); - private void setControlsEnabled( boolean enabled ) - { - if( mComboModulation.isEnabled() != enabled ) - { - mComboModulation.setEnabled( enabled ); - } - - if( mTrafficChannelPoolSize.isEnabled() != enabled ) - { - mTrafficChannelPoolSize.setEnabled( enabled ); - } + add(new JLabel("Modulation:")); + add(mComboModulation); - if( mIgnoreDataCalls.isEnabled() != enabled ) - { - mIgnoreDataCalls.setEnabled( enabled ); - } - } + mIgnoreDataCalls = new JCheckBox("Ignore Data Calls"); + mIgnoreDataCalls.setEnabled(false); + mIgnoreDataCalls.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + setModified(true); + } + }); - @Override - public void setItem( Channel item ) - { - super.setItem( item ); - - if( hasItem() ) - { - setControlsEnabled( true ); - - DecodeConfiguration config = getItem().getDecodeConfiguration(); - - if( config instanceof DecodeConfigP25Phase1 ) - { - DecodeConfigP25Phase1 p25 = (DecodeConfigP25Phase1)config; - - mComboModulation.setSelectedItem( p25.getModulation() ); - mIgnoreDataCalls.setSelected( p25.getIgnoreDataCalls() ); - mTrafficChannelPoolSize.setValue( p25.getTrafficChannelPoolSize() ); - setModified( false ); - } - else - { - mComboModulation.setSelectedItem( P25Decoder.Modulation.C4FM ); - mIgnoreDataCalls.setSelected( false ); - mTrafficChannelPoolSize.setValue( DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT ); - setModified( true ); - } - } - else - { - setControlsEnabled( false ); - setModified( false ); - } - } + add(mIgnoreDataCalls); + + mTrafficChannelPoolSize = new JSlider(JSlider.HORIZONTAL, DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_MINIMUM, + DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_MAXIMUM, DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT); + mTrafficChannelPoolSize.setEnabled(false); + mTrafficChannelPoolSize.setMajorTickSpacing(10); + mTrafficChannelPoolSize.setMinorTickSpacing(5); + mTrafficChannelPoolSize.setPaintTicks(true); + mTrafficChannelPoolSize.addChangeListener(new ChangeListener() + { + @Override + public void stateChanged(ChangeEvent e) + { + mTrafficChannelPoolSizeLabel.setText("Traffic Pool: " + mTrafficChannelPoolSize.getValue()); + setModified(true); + } + }); + + mTrafficChannelPoolSizeLabel = new JLabel("Traffic Pool: " + mTrafficChannelPoolSize.getValue() + " "); + + add(mTrafficChannelPoolSizeLabel); + add(mTrafficChannelPoolSize); + } + + @Override + public void save() + { + if(hasItem() && isModified()) + { + DecodeConfigP25Phase1 p25 = new DecodeConfigP25Phase1(); + + p25.setModulation((P25Decoder.Modulation)mComboModulation.getSelectedItem()); + p25.setIgnoreDataCalls(mIgnoreDataCalls.isSelected()); + p25.setTrafficChannelPoolSize(mTrafficChannelPoolSize.getValue()); + + getItem().setDecodeConfiguration(p25); + } + + setModified(false); + } + + @Override + public void validate(Editor editor) throws EditorValidationException + { + /** + * Enforce CQPSK modulation uses a Tuner Source for I/Q sample data + */ + if(editor instanceof SourceConfigurationEditor) + { + SourceConfiguration config = ((SourceConfigurationEditor)editor).getSourceConfiguration(); + + if(!(config instanceof SourceConfigTuner || config instanceof SourceConfigTunerMultipleFrequency)) + { + throw new EditorValidationException(editor, + "

LSM Simulcast

" + + "

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

"); + } + } + else if(editor instanceof AuxDecodeConfigurationEditor) + { + AuxDecodeConfiguration config = ((AuxDecodeConfigurationEditor)editor).getConfiguration(); + + if(config != null && !config.getAuxDecoders().isEmpty()) + { + throw new EditorValidationException(editor, + "

Auxilary Decoders

" + + "

The auxiliary decoders work with analog audio and are not " + + "compatible with the P25 Decoder. Please uncheck any auxiliary " + + "decoders that you have selected for this channel

"); + } + } + } + + private void setControlsEnabled(boolean enabled) + { + if(mComboModulation.isEnabled() != enabled) + { + mComboModulation.setEnabled(enabled); + } + + if(mTrafficChannelPoolSize.isEnabled() != enabled) + { + mTrafficChannelPoolSize.setEnabled(enabled); + } + + if(mIgnoreDataCalls.isEnabled() != enabled) + { + mIgnoreDataCalls.setEnabled(enabled); + } + } + + @Override + public void setItem(Channel item) + { + super.setItem(item); + + if(hasItem()) + { + setControlsEnabled(true); + + DecodeConfiguration config = getItem().getDecodeConfiguration(); + + if(config instanceof DecodeConfigP25Phase1) + { + DecodeConfigP25Phase1 p25 = (DecodeConfigP25Phase1)config; + + mComboModulation.setSelectedItem(p25.getModulation()); + mIgnoreDataCalls.setSelected(p25.getIgnoreDataCalls()); + mTrafficChannelPoolSize.setValue(p25.getTrafficChannelPoolSize()); + setModified(false); + } + else + { + mComboModulation.setSelectedItem(P25Decoder.Modulation.C4FM); + mIgnoreDataCalls.setSelected(false); + mTrafficChannelPoolSize.setValue(DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT); + setModified(true); + } + } + else + { + setControlsEnabled(false); + setModified(false); + } + } } diff --git a/src/main/java/io/github/dsheirer/preference/PreferenceType.java b/src/main/java/io/github/dsheirer/preference/PreferenceType.java index 5f00a600..dd5d5bc1 100644 --- a/src/main/java/io/github/dsheirer/preference/PreferenceType.java +++ b/src/main/java/io/github/dsheirer/preference/PreferenceType.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -28,5 +28,6 @@ public enum PreferenceType DECODE_EVENT, DIRECTORY, IDENTIFIER, + MULTI_FREQUENCY, TUNER; } diff --git a/src/main/java/io/github/dsheirer/preference/UserPreferences.java b/src/main/java/io/github/dsheirer/preference/UserPreferences.java index bef3682a..e206cb23 100644 --- a/src/main/java/io/github/dsheirer/preference/UserPreferences.java +++ b/src/main/java/io/github/dsheirer/preference/UserPreferences.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -25,7 +25,8 @@ import io.github.dsheirer.preference.directory.DirectoryPreference; import io.github.dsheirer.preference.event.DecodeEventPreference; import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference; import io.github.dsheirer.preference.playlist.PlaylistPreference; -import io.github.dsheirer.preference.tuner.TunerPreference; +import io.github.dsheirer.preference.source.ChannelMultiFrequencyPreference; +import io.github.dsheirer.preference.source.TunerPreference; import io.github.dsheirer.sample.Listener; /** @@ -48,6 +49,7 @@ public class UserPreferences implements Listener { private DecodeEventPreference mDecodeEventPreference; private DirectoryPreference mDirectoryPreference; + private ChannelMultiFrequencyPreference mChannelMultiFrequencyPreference; private PlaylistPreference mPlaylistPreference; private TalkgroupFormatPreference mTalkgroupFormatPreference; private TunerPreference mTunerPreference; @@ -76,6 +78,14 @@ public class UserPreferences implements Listener return mDirectoryPreference; } + /** + * Multiple frequency channel source preferences + */ + public ChannelMultiFrequencyPreference getChannelMultiFrequencyPreference() + { + return mChannelMultiFrequencyPreference; + } + /** * Playlist preferences */ @@ -106,7 +116,8 @@ public class UserPreferences implements Listener private void loadPreferenceTypes() { mDecodeEventPreference = new DecodeEventPreference(this); - mDirectoryPreference = new DirectoryPreference(this::receive); + mDirectoryPreference = new DirectoryPreference(this); + mChannelMultiFrequencyPreference = new ChannelMultiFrequencyPreference(this); mPlaylistPreference = new PlaylistPreference(this, mDirectoryPreference); mTalkgroupFormatPreference = new TalkgroupFormatPreference(this); mTunerPreference = new TunerPreference(this); diff --git a/src/main/java/io/github/dsheirer/preference/source/ChannelMultiFrequencyPreference.java b/src/main/java/io/github/dsheirer/preference/source/ChannelMultiFrequencyPreference.java new file mode 100644 index 00000000..04888b29 --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/source/ChannelMultiFrequencyPreference.java @@ -0,0 +1,96 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 io.github.dsheirer.preference.source; + +import io.github.dsheirer.preference.Preference; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.sample.Listener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.prefs.Preferences; + +/** + * Preferences for multiple frequency tuner channel sources. + */ +public class ChannelMultiFrequencyPreference extends Preference +{ + private final static Logger mLog = LoggerFactory.getLogger(ChannelMultiFrequencyPreference.class); + private Preferences mPreferences = Preferences.userNodeForPackage(ChannelMultiFrequencyPreference.class); + private static final String PREFERENCE_KEY_ROTATION_DELAY = "rotation.delay"; + private static final long DEFAULT_ROTATION_DELAY = 5000; //5 seconds + private Long mRotationDelay = DEFAULT_ROTATION_DELAY; + + /** + * Constructs a tuner preference with the update listener + * + * @param updateListener + */ + public ChannelMultiFrequencyPreference(Listener updateListener) + { + super(updateListener); + } + + @Override + public PreferenceType getPreferenceType() + { + return PreferenceType.MULTI_FREQUENCY; + } + + /** + * Rotation delay value in milliseconds. This value is used for channel configurations where there source + * defines multiple frequencies for a system that employs rotating control channels. + */ + public long getRotationDelay() + { + if(mRotationDelay == null) + { + mRotationDelay = mPreferences.getLong(PREFERENCE_KEY_ROTATION_DELAY, DEFAULT_ROTATION_DELAY); + } + + return mRotationDelay; + } + + /** + * Sets the rotation delay value in milliseconds. + * @param rotationDelay in milliseconds (1,000 - 60,000) + */ + public void setRotationDelay(long rotationDelay) + { + if(1000 <= rotationDelay && rotationDelay <= 60000) + { + mRotationDelay = rotationDelay; + mPreferences.putLong(PREFERENCE_KEY_ROTATION_DELAY, mRotationDelay); + notifyPreferenceUpdated(); + } + } + + /** + * Resets the rotation delay and removes it from the preferences store, so that it can be recreated with + * a default value. + */ + public void resetRotationDelay() + { + mPreferences.remove(PREFERENCE_KEY_ROTATION_DELAY); + mRotationDelay = null; + notifyPreferenceUpdated(); + } +} diff --git a/src/main/java/io/github/dsheirer/preference/tuner/ChannelizerType.java b/src/main/java/io/github/dsheirer/preference/source/ChannelizerType.java similarity index 93% rename from src/main/java/io/github/dsheirer/preference/tuner/ChannelizerType.java rename to src/main/java/io/github/dsheirer/preference/source/ChannelizerType.java index 610f1b4d..69d5a11d 100644 --- a/src/main/java/io/github/dsheirer/preference/tuner/ChannelizerType.java +++ b/src/main/java/io/github/dsheirer/preference/source/ChannelizerType.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -18,7 +18,7 @@ * ***************************************************************************** */ -package io.github.dsheirer.preference.tuner; +package io.github.dsheirer.preference.source; /** * Channelizer Type - identifies the type of channelizer that the tuner will use to provide DDR channels diff --git a/src/main/java/io/github/dsheirer/preference/tuner/TunerPreference.java b/src/main/java/io/github/dsheirer/preference/source/TunerPreference.java similarity index 97% rename from src/main/java/io/github/dsheirer/preference/tuner/TunerPreference.java rename to src/main/java/io/github/dsheirer/preference/source/TunerPreference.java index a45de274..fcd13d93 100644 --- a/src/main/java/io/github/dsheirer/preference/tuner/TunerPreference.java +++ b/src/main/java/io/github/dsheirer/preference/source/TunerPreference.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -18,7 +18,7 @@ * ***************************************************************************** */ -package io.github.dsheirer.preference.tuner; +package io.github.dsheirer.preference.source; import io.github.dsheirer.preference.Preference; import io.github.dsheirer.preference.PreferenceType; diff --git a/src/main/java/io/github/dsheirer/sample/Broadcaster.java b/src/main/java/io/github/dsheirer/sample/Broadcaster.java index c705bf1d..5605a00f 100644 --- a/src/main/java/io/github/dsheirer/sample/Broadcaster.java +++ b/src/main/java/io/github/dsheirer/sample/Broadcaster.java @@ -1,22 +1,27 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 io.github.dsheirer.sample; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -25,8 +30,23 @@ import java.util.concurrent.CopyOnWriteArrayList; */ public class Broadcaster implements Listener { + private final static Logger mLog = LoggerFactory.getLogger(Broadcaster.class); + private boolean mDebug; + protected List> mListeners = new CopyOnWriteArrayList<>(); + public Broadcaster() + { + } + + /** + * Turns on/off debugging to troubleshoot and log which listeners are receiving an item. + */ + public void setDebug(boolean debug) + { + mDebug = debug; + } + /** * Implements the Listener interface to receive an element and broadcast that element to all registered * listeners. @@ -116,9 +136,21 @@ public class Broadcaster implements Listener */ public void broadcast(T t) { - for(Listener listener : mListeners) + if(mDebug) { - listener.receive(t); + for(Listener listener : mListeners) + { + mLog.debug("Sending [" + t + "] to listener [" + listener.getClass() + "]"); + listener.receive(t); + mLog.debug("Finished sending to listener [" + listener.getClass() + "]"); + } + } + else + { + for(Listener listener : mListeners) + { + listener.receive(t); + } } } } diff --git a/src/main/java/io/github/dsheirer/source/Source.java b/src/main/java/io/github/dsheirer/source/Source.java index 5e378606..948f17cc 100644 --- a/src/main/java/io/github/dsheirer/source/Source.java +++ b/src/main/java/io/github/dsheirer/source/Source.java @@ -1,25 +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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 io.github.dsheirer.source; import io.github.dsheirer.module.Module; import io.github.dsheirer.sample.IOverflowListener; +import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.SampleType; +import io.github.dsheirer.source.heartbeat.Heartbeat; import io.github.dsheirer.source.heartbeat.HeartbeatManager; import io.github.dsheirer.source.heartbeat.IHeartbeatProvider; @@ -39,8 +43,29 @@ public abstract class Source extends Module implements ISourceEventListener, ISo } + /** + * Buffer overflow listener + */ protected IOverflowListener mOverflowListener; + /** + * Adds the listener to receive heartbeats from this source + */ + @Override + public void addHeartbeatListener(Listener listener) + { + getHeartbeatManager().addHeartbeatListener(listener); + } + + /** + * Removes the listener from receiving heartbeats from this source + */ + @Override + public void removeHeartbeatListener(Listener listener) + { + getHeartbeatManager().removeHeartbeatListener(listener); + } + /** * Indicates the type of samples provided by this source: real or complex */ diff --git a/src/main/java/io/github/dsheirer/source/SourceConfigurationEditor.java b/src/main/java/io/github/dsheirer/source/SourceConfigurationEditor.java index 8a903f3e..4a0c28e2 100644 --- a/src/main/java/io/github/dsheirer/source/SourceConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/source/SourceConfigurationEditor.java @@ -1,20 +1,22 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 io.github.dsheirer.source; import io.github.dsheirer.controller.channel.Channel; @@ -23,6 +25,7 @@ import io.github.dsheirer.gui.editor.EmptyEditor; import io.github.dsheirer.source.config.SourceConfiguration; import io.github.dsheirer.source.mixer.MixerSourceEditor; import io.github.dsheirer.source.tuner.TunerSourceEditor; +import io.github.dsheirer.source.tuner.TunerSourceMultipleFrequencyEditor; import net.miginfocom.swing.MigLayout; import javax.swing.DefaultComboBoxModel; @@ -37,99 +40,106 @@ public class SourceConfigurationEditor extends Editor private JComboBox mComboSources; private MixerSourceEditor mMixerSourceEditor; private TunerSourceEditor mTunerSourceEditor; + private TunerSourceMultipleFrequencyEditor mTunerSourceMultipleFrequencyEditor; private Editor mCurrentEditor; - - public SourceConfigurationEditor( SourceManager sourceManager ) - { - mMixerSourceEditor = new MixerSourceEditor( sourceManager ); - mMixerSourceEditor.setSaveRequestListener( this ); - - mTunerSourceEditor = new TunerSourceEditor(sourceManager.getTunerModel()); - mTunerSourceEditor.setSaveRequestListener( this ); - - init(); - } - + + public SourceConfigurationEditor(SourceManager sourceManager) + { + mMixerSourceEditor = new MixerSourceEditor(sourceManager); + mMixerSourceEditor.setSaveRequestListener(this); + + mTunerSourceEditor = new TunerSourceEditor(sourceManager.getTunerModel()); + mTunerSourceEditor.setSaveRequestListener(this); + + mTunerSourceMultipleFrequencyEditor = new TunerSourceMultipleFrequencyEditor(sourceManager.getTunerModel()); + mTunerSourceMultipleFrequencyEditor.setSaveRequestListener(this); + + init(); + } + public SourceConfiguration getSourceConfiguration() { - return hasItem() ? getItem().getSourceConfiguration() : null; + return hasItem() ? getItem().getSourceConfiguration() : null; } private void init() { - setLayout( new MigLayout( "wrap 2", "[][grow,fill]", "[align top][grow]" ) ); + setLayout(new MigLayout("wrap 2", "[][grow,fill]", "[align top][grow,fill]")); - mComboSources = new JComboBox(); - mComboSources.setModel( new DefaultComboBoxModel( SourceType.getTypes() ) ); - mComboSources.addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent e ) - { - SourceType selected = (SourceType)mComboSources.getSelectedItem(); - - switch( selected ) - { - case MIXER: - setEditor( mMixerSourceEditor ); - break; - case TUNER: - setEditor( mTunerSourceEditor ); - break; - default: - setEditor( new EmptyEditor() ); - break; - } - } - }); - - add( mComboSources ); - - mCurrentEditor = mTunerSourceEditor; - add( mCurrentEditor ); + mComboSources = new JComboBox(); + mComboSources.setModel(new DefaultComboBoxModel(SourceType.getTypes())); + mComboSources.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + SourceType selected = (SourceType) mComboSources.getSelectedItem(); + + switch(selected) + { + case MIXER: + setEditor(mMixerSourceEditor); + break; + case TUNER: + setEditor(mTunerSourceEditor); + break; + case TUNER_MULTIPLE_FREQUENCIES: + setEditor(mTunerSourceMultipleFrequencyEditor); + break; + default: + setEditor(new EmptyEditor()); + break; + } + } + }); + + add(mComboSources); + + mCurrentEditor = mTunerSourceEditor; + add(mCurrentEditor, "span 1 2"); } - - private void setEditor( Editor editor ) + + private void setEditor(Editor editor) { - if( mCurrentEditor != editor ) - { - //Set channel to null to force a save prompt as required - mCurrentEditor.setItem( null ); - - remove( mCurrentEditor ); - mCurrentEditor = editor; - mCurrentEditor.setSaveRequestListener( this ); - add( mCurrentEditor ); - - mCurrentEditor.setItem( getItem() ); - - revalidate(); - } + if(mCurrentEditor != editor) + { + //Set channel to null to force a save prompt as required + mCurrentEditor.setItem(null); + + remove(mCurrentEditor); + mCurrentEditor = editor; + mCurrentEditor.setSaveRequestListener(this); + add(mCurrentEditor, "span 1 2"); + + mCurrentEditor.setItem(getItem()); + + revalidate(); + } } - - @Override + + @Override public void save() { - if( hasItem() ) - { - mCurrentEditor.save(); - } + if(hasItem()) + { + mCurrentEditor.save(); + } } - /** - * Sets the channel configuration. Note: this method must be invoked from - * the swing event dispatch thread. - */ - @Override - public void setItem( Channel channel ) - { - super.setItem( channel ); - - if( hasItem() ) - { - mComboSources.setSelectedItem( getItem().getSourceConfiguration().getSourceType() ); - repaint(); - mCurrentEditor.setItem( channel ); - } - } + /** + * Sets the channel configuration. Note: this method must be invoked from + * the swing event dispatch thread. + */ + @Override + public void setItem(Channel channel) + { + super.setItem(channel); + + if(hasItem()) + { + mComboSources.setSelectedItem(getItem().getSourceConfiguration().getSourceType()); + repaint(); + mCurrentEditor.setItem(channel); + } + } } diff --git a/src/main/java/io/github/dsheirer/source/SourceEvent.java b/src/main/java/io/github/dsheirer/source/SourceEvent.java index d3cc2890..fe489237 100644 --- a/src/main/java/io/github/dsheirer/source/SourceEvent.java +++ b/src/main/java/io/github/dsheirer/source/SourceEvent.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -14,8 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.source; import java.util.EnumSet; @@ -31,6 +32,8 @@ public class SourceEvent NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED, NOTIFICATION_FREQUENCY_CHANGE, NOTIFICATION_FREQUENCY_CORRECTION_CHANGE, + NOTIFICATION_FREQUENCY_ROTATION_SUCCESS, + NOTIFICATION_FREQUENCY_ROTATION_FAILURE, NOTIFICATION_MEASURED_FREQUENCY_ERROR, NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED, NOTIFICATION_SAMPLE_RATE_CHANGE, @@ -38,6 +41,7 @@ public class SourceEvent REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, REQUEST_FREQUENCY_CHANGE, + REQUEST_FREQUENCY_ROTATION, REQUEST_SOURCE_DISPOSE, REQUEST_START_SAMPLE_STREAM, REQUEST_STOP_SAMPLE_STREAM; @@ -64,6 +68,14 @@ public class SourceEvent mEventDescription = eventDescription; } + /** + * Private constructor. Use the static constructor methods to create an event. + */ + private SourceEvent(Event event) + { + this(event, null, null, null); + } + /** * Private constructor. Use the static constructor methods to create an event. */ @@ -175,9 +187,9 @@ public class SourceEvent * * @param frequency in hertz */ - public static SourceEvent frequencyChange(long frequency) + public static SourceEvent frequencyChange(Source source, long frequency) { - return new SourceEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, frequency); + return new SourceEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, source, frequency); } /** @@ -186,9 +198,9 @@ public class SourceEvent * @param frequency in hertz * @param eventDescription to add a tag to the event */ - public static SourceEvent frequencyChange(long frequency, String eventDescription) + public static SourceEvent frequencyChange(Source source, long frequency, String eventDescription) { - return new SourceEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, frequency, eventDescription); + return new SourceEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, source, frequency, eventDescription); } /** @@ -338,6 +350,33 @@ public class SourceEvent return new SourceEvent(Event.REQUEST_SOURCE_DISPOSE, source); } + /** + * Creates a request to cycle to the next source frequency. This is normally used for decoders to request the + * next frequency in a list when a multiple-frequency source configuration is defined. + */ + public static SourceEvent frequencyRotationRequest() + { + return new SourceEvent(Event.REQUEST_FREQUENCY_ROTATION); + } + + /** + * Creates a notification that the request to rotate the source frequency to the next frequency in the list + * was successful. + */ + public static SourceEvent frequencyRotationSuccessNotification(Source source, long frequency) + { + return new SourceEvent(Event.NOTIFICATION_FREQUENCY_ROTATION_SUCCESS, source, frequency); + } + + /** + * Creates a notification that the request to rotate the source frequency to the next frequency in the list + * was unsuccessful. + */ + public static SourceEvent frequencyRotationFailureNotification(Source source, long frequency) + { + return new SourceEvent(Event.NOTIFICATION_FREQUENCY_ROTATION_FAILURE, source, frequency); + } + @Override public String toString() { diff --git a/src/main/java/io/github/dsheirer/source/SourceManager.java b/src/main/java/io/github/dsheirer/source/SourceManager.java index 5166f82f..2ea29479 100644 --- a/src/main/java/io/github/dsheirer/source/SourceManager.java +++ b/src/main/java/io/github/dsheirer/source/SourceManager.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -22,12 +22,16 @@ package io.github.dsheirer.source; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.settings.SettingsManager; import io.github.dsheirer.source.config.SourceConfigTuner; +import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency; import io.github.dsheirer.source.config.SourceConfiguration; import io.github.dsheirer.source.mixer.MixerManager; import io.github.dsheirer.source.recording.RecordingSourceManager; import io.github.dsheirer.source.tuner.TunerManager; import io.github.dsheirer.source.tuner.TunerModel; import io.github.dsheirer.source.tuner.channel.ChannelSpecification; +import io.github.dsheirer.source.tuner.channel.MultiFrequencyTunerChannelSource; +import io.github.dsheirer.source.tuner.channel.TunerChannel; +import io.github.dsheirer.source.tuner.channel.TunerChannelSource; public class SourceManager { @@ -86,7 +90,29 @@ public class SourceManager retVal = mMixerManager.getSource(config); break; case TUNER: - retVal = mTunerModel.getSource((SourceConfigTuner) config, channelSpecification); + if(config instanceof SourceConfigTuner) + { + SourceConfigTuner sourceConfigTuner = (SourceConfigTuner)config; + TunerChannel tunerChannel = sourceConfigTuner.getTunerChannel(channelSpecification.getBandwidth()); + String preferredTuner = sourceConfigTuner.getPreferredTuner(); + retVal = mTunerModel.getSource(tunerChannel, channelSpecification, preferredTuner); + } + break; + case TUNER_MULTIPLE_FREQUENCIES: + if(config instanceof SourceConfigTunerMultipleFrequency) + { + SourceConfigTunerMultipleFrequency sourceConfigTuner = (SourceConfigTunerMultipleFrequency)config; + TunerChannel tunerChannel = sourceConfigTuner.getFirstTunerChannel(channelSpecification.getBandwidth()); + String preferredTuner = sourceConfigTuner.getPreferredTuner(); + + Source source = mTunerModel.getSource(tunerChannel, channelSpecification, preferredTuner); + + if(source instanceof TunerChannelSource) + { + retVal = new MultiFrequencyTunerChannelSource(getTunerModel(), (TunerChannelSource)source, + sourceConfigTuner.getFrequencies(), channelSpecification, sourceConfigTuner.getPreferredTuner()); + } + } break; case RECORDING: retVal = mRecordingSourceManager.getSource(config, channelSpecification); diff --git a/src/main/java/io/github/dsheirer/source/SourceType.java b/src/main/java/io/github/dsheirer/source/SourceType.java index 1145bcf7..fb1173ae 100644 --- a/src/main/java/io/github/dsheirer/source/SourceType.java +++ b/src/main/java/io/github/dsheirer/source/SourceType.java @@ -1,54 +1,51 @@ /******************************************************************************* - * 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 + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.source; - +/** + * Source Configuration Types enumeration. + */ public enum SourceType { - NONE( "No Source" ), - MIXER( "Sound Card" ), - TUNER( "Tuner" ), - RECORDING( "IQ Recording" ); - + NONE("No Source"), + MIXER("Sound Card"), + TUNER("Tuner"), + TUNER_MULTIPLE_FREQUENCIES("Tuner - Multiple Frequencies"), + RECORDING("IQ Recording"); + private String mDisplayString; - - SourceType( String displayString ) + + SourceType(String displayString) { mDisplayString = displayString; } - + public static SourceType[] getTypes() { - SourceType[] types = new SourceType[ 2 ]; - types[ 0 ] = SourceType.TUNER; - types[ 1 ] = SourceType.MIXER; + SourceType[] types = new SourceType[3]; + types[0] = SourceType.TUNER; + types[1] = SourceType.TUNER_MULTIPLE_FREQUENCIES; + types[2] = SourceType.MIXER; - return types; + return types; } - - public String getDisplayString() - { - return mDisplayString; - } - + @Override public String toString() { - return mDisplayString; + return mDisplayString; } } diff --git a/src/main/java/io/github/dsheirer/source/config/SourceConfigFactory.java b/src/main/java/io/github/dsheirer/source/config/SourceConfigFactory.java index 851f3bff..2dfb2ead 100644 --- a/src/main/java/io/github/dsheirer/source/config/SourceConfigFactory.java +++ b/src/main/java/io/github/dsheirer/source/config/SourceConfigFactory.java @@ -1,19 +1,17 @@ /******************************************************************************* - * 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 + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.source.config; @@ -21,64 +19,74 @@ import io.github.dsheirer.source.SourceType; public class SourceConfigFactory { - public static SourceConfiguration getDefaultSourceConfiguration() - { - return getSourceConfiguration( SourceType.TUNER ); - } - - public static SourceConfiguration getSourceConfiguration( SourceType source ) - { - SourceConfiguration retVal; + public static SourceConfiguration getDefaultSourceConfiguration() + { + return getSourceConfiguration(SourceType.TUNER); + } - switch( source ) - { - case TUNER: - retVal = new SourceConfigTuner(); - break; - case MIXER: - retVal = new SourceConfigMixer(); - break; - case NONE: - default: - retVal = new SourceConfigNone(); - break; - } - - return retVal; - } + public static SourceConfiguration getSourceConfiguration(SourceType source) + { + SourceConfiguration retVal; - /** - * Creates a copy of the configuration - */ - public static SourceConfiguration copy( SourceConfiguration config ) - { - if( config != null ) - { - switch( config.getSourceType() ) - { - case MIXER: - SourceConfigMixer originalMixer = (SourceConfigMixer)config; - SourceConfigMixer copyMixer = new SourceConfigMixer(); - copyMixer.setChannel( originalMixer.getChannel() ); - copyMixer.setMixer( originalMixer.getMixer() ); - return copyMixer; - case RECORDING: - SourceConfigRecording originalRec = (SourceConfigRecording)config; - SourceConfigRecording copyRec = new SourceConfigRecording(); - copyRec.setFrequency( originalRec.getFrequency() ); - copyRec.setRecordingAlias( originalRec.getRecordingAlias() ); - return copyRec; - case TUNER: - SourceConfigTuner originalTuner = (SourceConfigTuner)config; - SourceConfigTuner copyTuner = new SourceConfigTuner(); - copyTuner.setFrequency( originalTuner.getFrequency() ); - return copyTuner; - case NONE: - default: - return new SourceConfigNone(); - } - } - - return null; - } + switch(source) + { + case TUNER: + retVal = new SourceConfigTuner(); + break; + case TUNER_MULTIPLE_FREQUENCIES: + retVal = new SourceConfigTunerMultipleFrequency(); + break; + case MIXER: + retVal = new SourceConfigMixer(); + break; + case NONE: + default: + retVal = new SourceConfigNone(); + break; + } + + return retVal; + } + + /** + * Creates a copy of the configuration + */ + public static SourceConfiguration copy(SourceConfiguration config) + { + if(config != null) + { + switch(config.getSourceType()) + { + case MIXER: + SourceConfigMixer originalMixer = (SourceConfigMixer) config; + SourceConfigMixer copyMixer = new SourceConfigMixer(); + copyMixer.setChannel(originalMixer.getChannel()); + copyMixer.setMixer(originalMixer.getMixer()); + return copyMixer; + case RECORDING: + SourceConfigRecording originalRec = (SourceConfigRecording) config; + SourceConfigRecording copyRec = new SourceConfigRecording(); + copyRec.setFrequency(originalRec.getFrequency()); + copyRec.setRecordingAlias(originalRec.getRecordingAlias()); + return copyRec; + case TUNER: + SourceConfigTuner originalTuner = (SourceConfigTuner) config; + SourceConfigTuner copyTuner = new SourceConfigTuner(); + copyTuner.setPreferredTuner(originalTuner.getPreferredTuner()); + copyTuner.setFrequency(originalTuner.getFrequency()); + return copyTuner; + case TUNER_MULTIPLE_FREQUENCIES: + SourceConfigTunerMultipleFrequency originalMulti = (SourceConfigTunerMultipleFrequency) config; + SourceConfigTunerMultipleFrequency copyMulti = new SourceConfigTunerMultipleFrequency(); + copyMulti.setPreferredTuner(originalMulti.getPreferredTuner()); + copyMulti.setFrequencies(originalMulti.getFrequencies()); + return copyMulti; + case NONE: + default: + return new SourceConfigNone(); + } + } + + return null; + } } diff --git a/src/main/java/io/github/dsheirer/source/config/SourceConfigTuner.java b/src/main/java/io/github/dsheirer/source/config/SourceConfigTuner.java index 3c467c6e..9c397320 100644 --- a/src/main/java/io/github/dsheirer/source/config/SourceConfigTuner.java +++ b/src/main/java/io/github/dsheirer/source/config/SourceConfigTuner.java @@ -1,20 +1,22 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 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. + * 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 - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.source.config; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -31,7 +33,6 @@ public class SourceConfigTuner extends SourceConfiguration private static DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000"); private long mFrequency = 0; - private int mBandwidth = 12500; private String mPreferredTuner; public SourceConfigTuner() @@ -42,9 +43,7 @@ public class SourceConfigTuner extends SourceConfiguration public SourceConfigTuner(TunerChannel tunerChannel) { this(); - mFrequency = tunerChannel.getFrequency(); - mBandwidth = tunerChannel.getBandwidth(); } @JacksonXmlProperty(isAttribute = true, localName = "frequency") @@ -94,8 +93,8 @@ public class SourceConfigTuner extends SourceConfiguration } @JsonIgnore - public TunerChannel getTunerChannel() + public TunerChannel getTunerChannel(int bandwidth) { - return new TunerChannel(mFrequency, mBandwidth); + return new TunerChannel(mFrequency, bandwidth); } } diff --git a/src/main/java/io/github/dsheirer/source/config/SourceConfigTunerMultipleFrequency.java b/src/main/java/io/github/dsheirer/source/config/SourceConfigTunerMultipleFrequency.java new file mode 100644 index 00000000..e0f34549 --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/config/SourceConfigTunerMultipleFrequency.java @@ -0,0 +1,127 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 io.github.dsheirer.source.config; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import io.github.dsheirer.source.SourceType; +import io.github.dsheirer.source.tuner.channel.TunerChannel; + +import java.util.ArrayList; +import java.util.List; + +/** + * Multiple frequency tuner source configuration is used for a system with a rolling/rotating control + * channel where the control channel rotates across a pre-defined set of channel frequencies. + */ +@JsonSubTypes.Type(value = SourceConfigTunerMultipleFrequency.class, name = "sourceConfigTunerMultipleFrequency") +public class SourceConfigTunerMultipleFrequency extends SourceConfiguration +{ + private List mFrequencies = new ArrayList<>(); + private String mPreferredTuner; + + public SourceConfigTunerMultipleFrequency() + { + super(SourceType.TUNER_MULTIPLE_FREQUENCIES); + } + + /** + * List of frequencies for this configuration + */ + @JacksonXmlProperty(isAttribute = false, localName = "frequency") + public List getFrequencies() + { + return mFrequencies; + } + + /** + * Sets the list of frequencies for this configuration + */ + public void setFrequencies(List frequencies) + { + mFrequencies = frequencies; + } + + /** + * Indicates if this configuration has more than one frequency specified + */ + @JsonIgnore + public boolean hasMultipleFrequencies() + { + return mFrequencies.size() > 1; + } + + /** + * Adds a frequency to the list + */ + public void addFrequency(long frequency) + { + mFrequencies.add(frequency); + } + + /** + * Preferred tuner to use for this configuration. + * @return tuner name or null + */ + @JacksonXmlProperty(isAttribute = true, localName = "preferred_tuner") + public String getPreferredTuner() + { + return mPreferredTuner; + } + + /** + * Indicates if this configuration has a specified preferred tuner + */ + @JsonIgnore + public boolean hasPreferredTuner() + { + return mPreferredTuner != null; + } + + /** + * Specifies the preferred tuner to use for this configuration + * @param preferredTuner to use, if available + */ + public void setPreferredTuner(String preferredTuner) + { + mPreferredTuner = preferredTuner; + } + + @JsonIgnore + @Override + public String getDescription() + { + return "Frequencies [" + mFrequencies.size() + "]"; + } + + @JsonIgnore + public TunerChannel getFirstTunerChannel(int bandwidth) + { + if(getFrequencies().size() > 0) + { + return new TunerChannel(getFrequencies().get(0), bandwidth); + } + else + { + return new TunerChannel(0l, bandwidth); + } + } +} diff --git a/src/main/java/io/github/dsheirer/source/config/SourceConfiguration.java b/src/main/java/io/github/dsheirer/source/config/SourceConfiguration.java index 7d4adb66..821f2243 100644 --- a/src/main/java/io/github/dsheirer/source/config/SourceConfiguration.java +++ b/src/main/java/io/github/dsheirer/source/config/SourceConfiguration.java @@ -30,7 +30,7 @@ import io.github.dsheirer.source.SourceType; @JsonSubTypes.Type(value = SourceConfigNone.class, name = "sourceConfigNone"), @JsonSubTypes.Type(value = SourceConfigRecording.class, name = "sourceConfigRecording"), @JsonSubTypes.Type(value = SourceConfigTuner.class, name = "sourceConfigTuner"), -}) + @JsonSubTypes.Type(value = SourceConfigTunerMultipleFrequency.class, name = "sourceConfigTunerMultipleFrequency")}) @JacksonXmlRootElement(localName = "source_configuration") public class SourceConfiguration extends Configuration { diff --git a/src/main/java/io/github/dsheirer/source/heartbeat/IHeartbeatProvider.java b/src/main/java/io/github/dsheirer/source/heartbeat/IHeartbeatProvider.java index 076b50a2..b045579f 100644 --- a/src/main/java/io/github/dsheirer/source/heartbeat/IHeartbeatProvider.java +++ b/src/main/java/io/github/dsheirer/source/heartbeat/IHeartbeatProvider.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -14,11 +15,22 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.source.heartbeat; +import io.github.dsheirer.sample.Listener; + public interface IHeartbeatProvider { - public HeartbeatManager getHeartbeatManager(); + /** + * Adds the listener to receive heartbeats + */ + void addHeartbeatListener(Listener listener); + + /** + * Removes the listener from receiving heartbeats + * @param listener + */ + void removeHeartbeatListener(Listener listener); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/Tuner.java b/src/main/java/io/github/dsheirer/source/tuner/Tuner.java index 8e3ae629..f94f11dc 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/Tuner.java +++ b/src/main/java/io/github/dsheirer/source/tuner/Tuner.java @@ -1,7 +1,7 @@ /* * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -20,7 +20,7 @@ package io.github.dsheirer.source.tuner; import io.github.dsheirer.preference.UserPreferences; -import io.github.dsheirer.preference.tuner.ChannelizerType; +import io.github.dsheirer.preference.source.ChannelizerType; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.source.ISourceEventProcessor; diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java b/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java index a4a12ea1..948c45c4 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java @@ -1,25 +1,28 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.source.tuner; import io.github.dsheirer.properties.SystemProperties; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.source.Source; import io.github.dsheirer.source.SourceException; -import io.github.dsheirer.source.config.SourceConfigTuner; import io.github.dsheirer.source.tuner.TunerEvent.Event; import io.github.dsheirer.source.tuner.channel.ChannelSpecification; import io.github.dsheirer.source.tuner.channel.TunerChannel; @@ -335,62 +338,60 @@ public class TunerModel extends AbstractTableModel implements Listener it = mTuners.iterator(); - - Tuner tuner; - - if(config.hasPreferredTuner()) + if(tunerChannel != null && channelSpecification != null) { - tuner = getTuner(config.getPreferredTuner()); + Iterator it = mTuners.iterator(); - if(tuner != null) + Tuner tuner; + + if(preferredTuner != null) { + tuner = getTuner(preferredTuner); + + if(tuner != null) + { + try + { + source = tuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification); + + if(source != null) + { + return source; + } + } + catch(Exception e) + { + //Fall through to logger below + } + } + + mLog.info("Unable to source channel [" + tunerChannel.getFrequency() + "] from preferred tuner [" + + preferredTuner + "] - searching for another tuner"); + } + + while(it.hasNext() && source == null) + { + tuner = it.next(); + try { - retVal = tuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification); - - if(retVal != null) - { - return retVal; - } + source = tuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification); } catch(Exception e) { - //Fall through to logger below + mLog.error("Error obtaining channel from tuner [" + tuner.getName() + "]", e); } } - - mLog.info("Unable to source channel [" + config.getFrequency() + "] from preferred tuner [" + - config.getPreferredTuner() + "] - searching for another tuner"); } - while(it.hasNext() && retVal == null) - { - tuner = it.next(); - - try - { - retVal = tuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification); - } - catch(Exception e) - { - mLog.error("error obtaining channel from tuner [" + tuner.getName() + "]", e); - } - } - - return retVal; + return source; } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerSourceMultipleFrequencyEditor.java b/src/main/java/io/github/dsheirer/source/tuner/TunerSourceMultipleFrequencyEditor.java new file mode 100644 index 00000000..068b3a84 --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerSourceMultipleFrequencyEditor.java @@ -0,0 +1,184 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 io.github.dsheirer.source.tuner; + +import io.github.dsheirer.controller.channel.Channel; +import io.github.dsheirer.gui.control.MultipleFrequencyEditor; +import io.github.dsheirer.gui.editor.Editor; +import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency; +import io.github.dsheirer.source.config.SourceConfiguration; +import net.miginfocom.swing.MigLayout; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class TunerSourceMultipleFrequencyEditor extends Editor +{ + private static final long serialVersionUID = 1L; + private static final String NO_PREFERRED_TUNER = "(none)"; + private static final String UNAVAILABLE_TUNER = " (unavailable)"; + private MultipleFrequencyEditor mFrequencyEditor = new MultipleFrequencyEditor(0.0, 6000.0); + private JComboBox mTunerNameComboBox; + private TunerModel mTunerModel; + private List mCurrentTunerNames; + + public TunerSourceMultipleFrequencyEditor(TunerModel tunerModel) + { + mTunerModel = tunerModel; + init(); + } + + private void init() + { + loadTunerNames(); + + setLayout(new MigLayout("insets 0 0 0 0", + "[grow,fill][][right][grow,fill,left]", "[][grow,fill]")); + + mFrequencyEditor.getFrequencyModel().addTableModelListener(new TableModelListener() + { + @Override + public void tableChanged(TableModelEvent e) + { + setModified(true); + } + }); +// mFrequencyEditor.setSize(new Dimension(100, 30)); + add(mFrequencyEditor, "span 2 2"); + + mTunerNameComboBox = new JComboBox<>(mCurrentTunerNames.toArray(new String[mCurrentTunerNames.size()])); + mTunerNameComboBox.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + setModified(true); + } + }); + add(new JLabel("Preferred Tuner:")); + add(mTunerNameComboBox); + } + + private void loadTunerNames() + { + mCurrentTunerNames = new ArrayList<>(); + + for(Tuner tuner: mTunerModel.getTuners()) + { + mCurrentTunerNames.add(tuner.getName()); + } + + Collections.sort(mCurrentTunerNames); + + mCurrentTunerNames.add(0, NO_PREFERRED_TUNER); + } + + private void updateTunerNameCombo(String preferredTuner) + { + if(preferredTuner != null && !mCurrentTunerNames.contains(preferredTuner)) + { + List updatedTunerNameList = new ArrayList<>(mCurrentTunerNames); + String unavailableTuner = preferredTuner + UNAVAILABLE_TUNER; + updatedTunerNameList.add(unavailableTuner); + String[] tunerNameArray = updatedTunerNameList.toArray(new String[updatedTunerNameList.size()]); + mTunerNameComboBox.setModel(new DefaultComboBoxModel<>(tunerNameArray)); + mTunerNameComboBox.setSelectedItem(unavailableTuner); + } + else + { + String[] tunerNameArray = mCurrentTunerNames.toArray(new String[mCurrentTunerNames.size()]); + mTunerNameComboBox.setModel(new DefaultComboBoxModel<>(tunerNameArray)); + + if(preferredTuner == null) + { + mTunerNameComboBox.setSelectedItem(NO_PREFERRED_TUNER); + } + else + { + mTunerNameComboBox.setSelectedItem(preferredTuner); + } + } + } + + public void save() + { + if(hasItem() && isModified()) + { + SourceConfigTunerMultipleFrequency config = new SourceConfigTunerMultipleFrequency(); + + String preferredTuner = (String)mTunerNameComboBox.getSelectedItem(); + + if(preferredTuner != null) + { + if(preferredTuner.equalsIgnoreCase(NO_PREFERRED_TUNER)) + { + config.setPreferredTuner(null); + } + else if(preferredTuner.contains(UNAVAILABLE_TUNER)) + { + //don't do anything - the config already contains this unavailable tuner name + } + else + { + config.setPreferredTuner(preferredTuner); + } + } + + config.setFrequencies(mFrequencyEditor.getFrequencyModel().getFrequencies()); + + getItem().setSourceConfiguration(config); + } + + setModified(false); + } + + @Override + public void setItem(Channel item) + { + super.setItem(item); + + mFrequencyEditor.getFrequencyModel().clear(); + + if(hasItem()) + { + SourceConfiguration config = getItem().getSourceConfiguration(); + + if(config instanceof SourceConfigTunerMultipleFrequency) + { + SourceConfigTunerMultipleFrequency tunerConfig = (SourceConfigTunerMultipleFrequency) config; + updateTunerNameCombo(tunerConfig.getPreferredTuner()); + mFrequencyEditor.getFrequencyModel().setFrequencies(tunerConfig.getFrequencies()); + setModified(false); + } + } + else + { + setModified(false); + } + } +} diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/ChannelRotationMonitor.java b/src/main/java/io/github/dsheirer/source/tuner/channel/ChannelRotationMonitor.java new file mode 100644 index 00000000..90b715b0 --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/ChannelRotationMonitor.java @@ -0,0 +1,182 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 io.github.dsheirer.source.tuner.channel; + +import com.google.common.eventbus.Subscribe; +import io.github.dsheirer.channel.state.State; +import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierUpdateListener; +import io.github.dsheirer.identifier.IdentifierUpdateNotification; +import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier; +import io.github.dsheirer.module.Module; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.source.ISourceEventProvider; +import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.util.ThreadPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * Monitors channel state to detect when a channel is not in an identified active state and issues a request to rotate + * the next channel frequency in the list. + */ +public class ChannelRotationMonitor extends Module implements ISourceEventProvider, IdentifierUpdateListener, + Listener +{ + private final static Logger mLog = LoggerFactory.getLogger(ChannelRotationMonitor.class); + + private UserPreferences mUserPreferences; + private Collection mActiveStates; + private ScheduledFuture mScheduledFuture; + private Listener mSourceEventListener; + private long mRotationDelay; + private boolean mActive; + private long mInactiveTimestamp = System.currentTimeMillis(); + + /** + * Constructs a channel rotation monitor. + * + * @param activeStates to monitor + * @param userPreferences to receive updates for rotation delay value from user preferences + */ + public ChannelRotationMonitor(Collection activeStates, UserPreferences userPreferences) + { + mActiveStates = activeStates; + mUserPreferences = userPreferences; + mRotationDelay = mUserPreferences.getChannelMultiFrequencyPreference().getRotationDelay(); + + //Register to receive user preference updates + MyEventBus.getEventBus().register(this); + } + + /** + * Registers the external listener to receive frequency rotation requests from this module + */ + @Override + public void setSourceEventListener(Listener listener) + { + mSourceEventListener = listener; + } + + /** + * Unregisters the external listener from receiving frequency rotation requests. + */ + @Override + public void removeSourceEventListener() + { + mSourceEventListener = null; + } + + @Override + public Listener getIdentifierUpdateListener() + { + return this; + } + + /** + * Primary method for receiving channel state identifier notifications, enabling this module to track the + * current channel state being provided by the Channel State module. + */ + @Override + public void receive(IdentifierUpdateNotification identifierUpdateNotification) + { + Identifier identifier = identifierUpdateNotification.getIdentifier(); + + if(identifier instanceof ChannelStateIdentifier) + { + State currentState = ((ChannelStateIdentifier)identifier).getValue(); + + if(mActiveStates.contains(currentState)) + { + mActive = true; + } + else if(mActive) + { + mActive = false; + mInactiveTimestamp = System.currentTimeMillis(); + } + } + } + + /** + * Checks the current active state and when inactive for longer than the specified delay, issues a + * channel frequency rotation request + */ + private void checkState() + { + if(mSourceEventListener != null && !mActive && + ((mInactiveTimestamp + mRotationDelay) < System.currentTimeMillis())) + { + mSourceEventListener.receive(SourceEvent.frequencyRotationRequest()); + mInactiveTimestamp = System.currentTimeMillis(); + } + } + + /** + * Guava event bus method to receive notifications when user preferences are updated. This allows us to + * dynamically adjust rotation delay if/when the user changes the rotation delay value. + */ + @Subscribe + public void preferenceUpdated(PreferenceType preferenceType) + { + if(preferenceType == PreferenceType.MULTI_FREQUENCY) + { + mRotationDelay = mUserPreferences.getChannelMultiFrequencyPreference().getRotationDelay(); + } + } + + @Override + public void reset() + { + + } + + @Override + public void start() + { + if(mScheduledFuture == null) + { + mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(() -> checkState(), 1, 1, TimeUnit.SECONDS); + } + } + + @Override + public void stop() + { + if(mScheduledFuture != null) + { + mScheduledFuture.cancel(true); + } + } + + @Override + public void dispose() + { + } +} + diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java new file mode 100644 index 00000000..eab2d87e --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java @@ -0,0 +1,315 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 io.github.dsheirer.source.tuner.channel; + +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.source.Source; +import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.source.SourceException; +import io.github.dsheirer.source.heartbeat.Heartbeat; +import io.github.dsheirer.source.tuner.TunerModel; +import io.github.dsheirer.util.ThreadPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Multiple-frequency tuner channel source. Provides a wrapper around a tuner channel source and listens for external + * source events requests to change frequency. Maintains an ordered list of frequencies and automatically tears down + * an existing tuner channel source and obtains a new one with the next frequency from the list, on request. + */ +public class MultiFrequencyTunerChannelSource extends TunerChannelSource +{ + private final static Logger mLog = LoggerFactory.getLogger(MultiFrequencyTunerChannelSource.class); + + private TunerModel mTunerModel; + private TunerChannelSource mTunerChannelSource; + private List mFrequencies; + private int mFrequencyListPointer = 0; + private ChannelSpecification mChannelSpecification; + private Listener mReusableComplexBufferListener; + private Listener mHeartbeatListener; + private String mPreferredTuner; + private AtomicBoolean mChangingChannels = new AtomicBoolean(); + private boolean mStarted; + private ConsumerSourceEventAdapter mConsumerSourceEventAdapter = new ConsumerSourceEventAdapter(); + + public MultiFrequencyTunerChannelSource(TunerModel tunerModel, TunerChannelSource tunerChannelSource, + List frequencies, ChannelSpecification channelSpecification, + String preferredTuner) + { + super(null, tunerChannelSource.getTunerChannel()); + mTunerModel = tunerModel; + mTunerChannelSource = tunerChannelSource; + mTunerChannelSource.setSourceEventListener(mConsumerSourceEventAdapter); + mFrequencies = frequencies; + mChannelSpecification = channelSpecification; + mPreferredTuner = preferredTuner; + } + + /** + * Cycles this source to use the next frequency in the list + */ + private void nextFrequency() + { + if(mChangingChannels.compareAndSet(false, true)) + { + //Only change if we have more than one frequency specified + if(mFrequencies.size() > 0) + { + if(mTunerChannelSource != null) + { + //Shutdown the existing tuner channel source + mTunerChannelSource.stop(); + mTunerChannelSource.removeListener(mReusableComplexBufferListener); + mTunerChannelSource.removeSourceEventListener(); + mTunerChannelSource.removeHeartbeatListener(mHeartbeatListener); + mTunerChannelSource.dispose(); + mTunerChannelSource = null; + } + + //Request the next tuner channel source + getNextSource(); + } + } + } + + /** + * Persistently attempts to get the next tuner channel source using the next frequency in the list. This method + * should only be invoked by the nextFrequency() method that protects access via the mChangingChannels flag. + * + * Note: if this channel source is shutdown by the external consumer, the mStarted flag will be set to false and + * the persistent attempts will stop. + */ + private void getNextSource() + { + if(mStarted) + { + TunerChannel nextChannel = getNextChannel(); + Source source = mTunerModel.getSource(nextChannel, mChannelSpecification, mPreferredTuner); + + if(source instanceof TunerChannelSource) + { + mTunerChannelSource = (TunerChannelSource)source; + mTunerChannelSource.setSourceEventListener(mConsumerSourceEventAdapter); + mTunerChannelSource.setListener(mReusableComplexBufferListener); + mTunerChannelSource.addHeartbeatListener(mHeartbeatListener); + mTunerChannelSource.start(); + mChangingChannels.set(false); + getSourceEventListener().receive(SourceEvent.frequencyRotationSuccessNotification(this, nextChannel.getFrequency())); + } + + //If we don't get a channel source because a tuner is not available or none of the available tuners can + //support the frequency, then persistently attempt to get a source by iterating the frequency list + if(mTunerChannelSource == null) + { + getSourceEventListener().receive(SourceEvent.frequencyRotationFailureNotification(this, nextChannel.getFrequency())); + ThreadPool.SCHEDULED.schedule(() -> getNextSource(), 1, TimeUnit.SECONDS); + } + } + } + + @Override + public void start() + { + //The initial source should not be null + if(mTunerChannelSource != null) + { + mTunerChannelSource.start(); + mStarted = true; + } + } + + @Override + public void stop() + { + mStarted = false; + + if(mTunerChannelSource != null) + { + mTunerChannelSource.stop(); + mTunerChannelSource.removeSourceEventListener(); + } + } + + /** + * Provides the next tuner channel from the frequency list. + */ + private TunerChannel getNextChannel() + { + mFrequencyListPointer++; + + if(mFrequencyListPointer >= mFrequencies.size()) + { + mFrequencyListPointer = 0; + } + + long frequency = mFrequencies.get(mFrequencyListPointer); + + return new TunerChannel(frequency, mChannelSpecification.getBandwidth()); + } + + /** + * Adds the heartbeat listener to receive heartbeats from the enclosed tuner channel source. + */ + @Override + public void addHeartbeatListener(Listener listener) + { + mHeartbeatListener = listener; + + if(mTunerChannelSource != null) + { + mTunerChannelSource.addHeartbeatListener(listener); + } + } + + /** + * Removes the heartbeat listener from receiving heartbeats from the enclosed tuner channel source + */ + @Override + public void removeHeartbeatListener(Listener listener) + { + mHeartbeatListener = null; + + if(mTunerChannelSource != null) + { + mTunerChannelSource.removeHeartbeatListener(listener); + } + } + + @Override + public void setFrequency(long frequency) + { + if(mTunerChannelSource != null) + { + mTunerChannelSource.setFrequency(frequency); + } + } + + @Override + protected void setSampleRate(double sampleRate) + { + if(mTunerChannelSource != null) + { + mTunerChannelSource.setSampleRate(sampleRate); + } + } + + @Override + protected void setChannelFrequencyCorrection(long correction) + { + if(mTunerChannelSource != null) + { + mTunerChannelSource.setChannelFrequencyCorrection(correction); + } + } + + @Override + public long getChannelFrequencyCorrection() + { + if(mTunerChannelSource != null) + { + return mTunerChannelSource.getChannelFrequencyCorrection(); + } + + return 0; + } + + @Override + protected void processSamples() + { + if(mTunerChannelSource != null) + { + mTunerChannelSource.processSamples(); + } + } + + @Override + public void setListener(Listener complexBufferListener) + { + mReusableComplexBufferListener = complexBufferListener; + + if(mTunerChannelSource != null) + { + mTunerChannelSource.setListener(complexBufferListener); + } + } + + @Override + public void removeListener(Listener listener) + { + if(mTunerChannelSource != null) + { + mTunerChannelSource.removeListener(listener); + } + + mReusableComplexBufferListener = null; + } + + @Override + public double getSampleRate() + { + if(mTunerChannelSource != null) + { + return mTunerChannelSource.getSampleRate(); + } + + return 0; + } + + /** + * Overrides the parent method so that we can intercept external requests to change to the next frequency in the + * frequency list. + * + * @param sourceEvent to process + * @throws SourceException + */ + @Override + public void process(SourceEvent sourceEvent) throws SourceException + { + if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_FREQUENCY_ROTATION) + { + nextFrequency(); + } + else if(mTunerChannelSource != null) + { + mTunerChannelSource.process(sourceEvent); + } + } + + /** + * Listener for source events produced by the enclosed tuner channel source. This adapter receives events from + * the wrapped tuner channel source and rebroadcasts them as consumer source events from this enclosing multi-frequency + * tuner channel source and to the registered external consumer source event listener. + */ + public class ConsumerSourceEventAdapter implements Listener + { + @Override + public void receive(SourceEvent sourceEvent) + { + broadcastConsumerSourceEvent(sourceEvent); + } + } +} diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java index 0f429aed..e5c23226 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2019 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 @@ -14,8 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.source.tuner.channel; import io.github.dsheirer.sample.Listener; @@ -117,7 +118,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc public void start() { //Broadcast current frequency and sample rate so consumer can configure correctly - broadcastConsumerSourceEvent(SourceEvent.frequencyChange(getFrequency())); + broadcastConsumerSourceEvent(SourceEvent.frequencyChange(this, getFrequency(), "Startup")); broadcastProducerSourceEvent(SourceEvent.startSampleStreamRequest(this)); mScheduledIntervalProcessor.start(); } @@ -186,6 +187,10 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE: setChannelFrequencyCorrection(sourceEvent.getValue().longValue()); break; + case NOTIFICATION_FREQUENCY_ROTATION_FAILURE: + case NOTIFICATION_FREQUENCY_ROTATION_SUCCESS: + //Ignore + break; default: mLog.error("Ignoring unrecognized source event: " + sourceEvent.getEvent() + " from [" + (sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "") + "]"); diff --git a/src/main/java/io/github/dsheirer/source/tuner/frequency/FrequencyController.java b/src/main/java/io/github/dsheirer/source/tuner/frequency/FrequencyController.java index 9631c710..9da7037b 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/frequency/FrequencyController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/frequency/FrequencyController.java @@ -1,18 +1,22 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.source.tuner.frequency; import io.github.dsheirer.source.ISourceEventProcessor; @@ -267,7 +271,7 @@ public class FrequencyController */ protected void broadcastFrequencyChange() throws SourceException { - broadcast(SourceEvent.frequencyChange(mFrequency)); + broadcast(SourceEvent.frequencyChange(null, mFrequency)); } /** diff --git a/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java b/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java index 963db996..20b73a8e 100644 --- a/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java +++ b/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java @@ -1,18 +1,22 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2017 Dennis Sheirer +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2019 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.spectrum; import com.jidesoft.swing.JideSplitPane; @@ -445,7 +449,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener