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.
This commit is contained in:
Denny Sheirer
2019-01-06 06:59:04 -05:00
committed by GitHub
parent 3a39ae76ba
commit 5481fb7d4d
44 changed files with 2502 additions and 634 deletions

View File

@@ -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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
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'
}

View File

@@ -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<IdentifierUpdateNotification>,
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<IdentifierUpdateNotification>,
/**
* 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<IdentifierUpdateNotification>,
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<IdentifierUpdateNotification>,
}
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<IdentifierUpdateNotification>,
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<IdentifierUpdateNotification>,
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<IdentifierUpdateNotification>,
}
else if(identifier.getRole() == Role.TO)
{
mToIdentifier = update.isAdded() ? identifier : null;
mToIdentifier = update.isAdd() ? identifier : null;
if(mAliasList != null && mToIdentifier != null)
{

View File

@@ -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:

View File

@@ -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))

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
* *****************************************************************************
*/
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<Long> 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<IdentifierUpdateNotification> 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<IdentifierUpdateNotification>
{
private Listener<IdentifierUpdateNotification> mIdentifierUpdateNotificationListener;
@Override
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
{
if(mIdentifierUpdateNotificationListener != null)
{
mIdentifierUpdateNotificationListener.receive(identifierUpdateNotification);
}
}
public void setListener(Listener<IdentifierUpdateNotification> listener)
{
mIdentifierUpdateNotificationListener = listener;
}
public void removeListener()
{
mIdentifierUpdateNotificationListener = null;
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
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)

View File

@@ -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<ChannelEvent>
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<ChannelEvent>
* @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<ChannelEvent>
processingChain.addFrequencyChangeListener(channel);
/* Processing Modules */
List<Module> modules = DecoderFactory.getModules(mChannelMapModel, channel, mAliasModel);
List<Module> modules = DecoderFactory.getModules(mChannelMapModel, channel, mAliasModel, mUserPreferences);
processingChain.addModules(modules);
/* Setup message activity model with filtering */

View File

@@ -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<TunerEvent>
mSourceManager = new SourceManager(tunerModel, mSettingsManager, mUserPreferences);
mChannelProcessingManager = new ChannelProcessingManager(channelMapModel, eventLogManager, recorderManager,
mSourceManager, aliasModel);
mSourceManager, aliasModel, mUserPreferences);
mChannelModel.addListener(mChannelProcessingManager);
mChannelProcessingManager.addChannelEventListener(mChannelModel);

View File

@@ -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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
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<Long> 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<Long> frequencies)
{
mFrequencies.clear();
mFrequencies.addAll(frequencies);
fireTableDataChanged();
}
/**
* List of long frequency values contained in the model
*/
public List<Long> getFrequencies()
{
List<Long> 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);
});
}
}

View File

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

View File

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

View File

@@ -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<String> 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);

View File

@@ -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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
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<Integer> 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<Integer> 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;
}
}

View File

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

View File

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

View File

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

View File

@@ -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<State>
public class ChannelStateIdentifier extends Identifier<State>
{
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<State>
/**
* 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

View File

@@ -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<ChannelEvent>
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<ChannelEvent>
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<ChannelEvent>
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);

View File

@@ -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<Module> getModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel)
public static List<Module> getModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel,
UserPreferences userPreferences)
{
List<Module> modules = getPrimaryModules(channelMapModel, channel, aliasModel);
List<Module> 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<Module> getPrimaryModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel)
public static List<Module> getPrimaryModules(ChannelMapModel channelMapModel, Channel channel,
AliasModel aliasModel, UserPreferences userPreferences)
{
List<Module> modules = new ArrayList<Module>();
@@ -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<State> activeStates = new ArrayList<>();
activeStates.add(State.CONTROL);
modules.add(new ChannelRotationMonitor(activeStates, userPreferences));
}
break;
default:
throw new IllegalArgumentException("Unknown decoder type [" + decodeConfig.getDecoderType().toString() + "]");

View File

@@ -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 <http://www.gnu.org/licenses/>
******************************************************************************/
/*
* ******************************************************************************
* 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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
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<Channel>
{
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<P25DecoderLSM.Modulation> 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<P25DecoderLSM.Modulation>();
mComboModulation.setModel( new DefaultComboBoxModel<P25DecoderLSM.Modulation>(
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<Channel> 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,
"<html><body width='175'><h3>LSM Simulcast</h3>"
+ "<p>P25 LSM Simulcast decoder can only be used with "
+ "a tuner source. Please change the Source to use a tuner"
+ " or change the P25 Decoder to C4FM modulation</p>" );
}
}
if( editor instanceof AuxDecodeConfigurationEditor)
{
AuxDecodeConfiguration config = ((AuxDecodeConfigurationEditor)editor).getConfiguration();
if( config != null && !config.getAuxDecoders().isEmpty() )
{
throw new EditorValidationException( editor,
"<html><body width='175'><h3>Auxilary Decoders</h3>"
+ "<p>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</p>" );
}
}
}
mComboModulation = new JComboBox<P25DecoderLSM.Modulation>();
mComboModulation.setModel(new DefaultComboBoxModel<P25DecoderLSM.Modulation>(
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<Channel> 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,
"<html><body width='175'><h3>LSM Simulcast</h3>"
+ "<p>P25 LSM Simulcast decoder can only be used with "
+ "a tuner source. Please change the Source to use a tuner"
+ " or change the P25 Decoder to C4FM modulation</p>");
}
}
else if(editor instanceof AuxDecodeConfigurationEditor)
{
AuxDecodeConfiguration config = ((AuxDecodeConfigurationEditor)editor).getConfiguration();
if(config != null && !config.getAuxDecoders().isEmpty())
{
throw new EditorValidationException(editor,
"<html><body width='175'><h3>Auxilary Decoders</h3>"
+ "<p>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</p>");
}
}
}
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);
}
}
}

View File

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

View File

@@ -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<PreferenceType>
{
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<PreferenceType>
return mDirectoryPreference;
}
/**
* Multiple frequency channel source preferences
*/
public ChannelMultiFrequencyPreference getChannelMultiFrequencyPreference()
{
return mChannelMultiFrequencyPreference;
}
/**
* Playlist preferences
*/
@@ -106,7 +116,8 @@ public class UserPreferences implements Listener<PreferenceType>
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);

View File

@@ -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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
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<PreferenceType> 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();
}
}

View File

@@ -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

View File

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

View File

@@ -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 <http://www.gnu.org/licenses/>
******************************************************************************/
/*
* ******************************************************************************
* 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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
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<T> implements Listener<T>
{
private final static Logger mLog = LoggerFactory.getLogger(Broadcaster.class);
private boolean mDebug;
protected List<Listener<T>> 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<T> interface to receive an element and broadcast that element to all registered
* listeners.
@@ -116,9 +136,21 @@ public class Broadcaster<T> implements Listener<T>
*/
public void broadcast(T t)
{
for(Listener<T> listener : mListeners)
if(mDebug)
{
listener.receive(t);
for(Listener<T> listener : mListeners)
{
mLog.debug("Sending [" + t + "] to listener [" + listener.getClass() + "]");
listener.receive(t);
mLog.debug("Finished sending to listener [" + listener.getClass() + "]");
}
}
else
{
for(Listener<T> listener : mListeners)
{
listener.receive(t);
}
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
******************************************************************************/
/*
* ******************************************************************************
* 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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
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<Heartbeat> listener)
{
getHeartbeatManager().addHeartbeatListener(listener);
}
/**
* Removes the listener from receiving heartbeats from this source
*/
@Override
public void removeHeartbeatListener(Listener<Heartbeat> listener)
{
getHeartbeatManager().removeHeartbeatListener(listener);
}
/**
* Indicates the type of samples provided by this source: real or complex
*/

View File

@@ -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 <http://www.gnu.org/licenses/>
******************************************************************************/
/*
* ******************************************************************************
* 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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
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<Channel>
private JComboBox<SourceType> mComboSources;
private MixerSourceEditor mMixerSourceEditor;
private TunerSourceEditor mTunerSourceEditor;
private TunerSourceMultipleFrequencyEditor mTunerSourceMultipleFrequencyEditor;
private Editor<Channel> 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<SourceType>();
mComboSources.setModel( new DefaultComboBoxModel<SourceType>( 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<Channel>() );
break;
}
}
});
add( mComboSources );
mCurrentEditor = mTunerSourceEditor;
add( mCurrentEditor );
mComboSources = new JComboBox<SourceType>();
mComboSources.setModel(new DefaultComboBoxModel<SourceType>(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<Channel>());
break;
}
}
});
add(mComboSources);
mCurrentEditor = mTunerSourceEditor;
add(mCurrentEditor, "span 1 2");
}
private void setEditor( Editor<Channel> editor )
private void setEditor(Editor<Channel> 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);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
* *****************************************************************************
*/
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()
{

View File

@@ -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);

View File

@@ -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 <http://www.gnu.org/licenses/>
* 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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* 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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
******************************************************************************/
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
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<Long> 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<Long> getFrequencies()
{
return mFrequencies;
}
/**
* Sets the list of frequencies for this configuration
*/
public void setFrequencies(List<Long> 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);
}
}
}

View File

@@ -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
{

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
* *****************************************************************************
*/
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<Heartbeat> listener);
/**
* Removes the listener from receiving heartbeats
* @param listener
*/
void removeHeartbeatListener(Listener<Heartbeat> listener);
}

View File

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

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.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<TunerEven
}
/**
* Iterates current tuners to get a tuner channel source for the frequency
* specified in the channel config's source config object.
* Iterates current tuners to get a tuner channel source for the specified frequency and bandwidth
*
* Returns null if no tuner can source the channel
*/
public Source getSource(SourceConfigTuner config, ChannelSpecification channelSpecification)
public Source getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification, String preferredTuner)
{
TunerChannelSource retVal = null;
TunerChannelSource source = null;
TunerChannel tunerChannel = config.getTunerChannel();
tunerChannel.setBandwidth(channelSpecification.getBandwidth());
Iterator<Tuner> it = mTuners.iterator();
Tuner tuner;
if(config.hasPreferredTuner())
if(tunerChannel != null && channelSpecification != null)
{
tuner = getTuner(config.getPreferredTuner());
Iterator<Tuner> 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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
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<Channel>
{
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<String> mTunerNameComboBox;
private TunerModel mTunerModel;
private List<String> 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<String> 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);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
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<IdentifierUpdateNotification>
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelRotationMonitor.class);
private UserPreferences mUserPreferences;
private Collection<State> mActiveStates;
private ScheduledFuture<?> mScheduledFuture;
private Listener<SourceEvent> 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<State> 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<SourceEvent> listener)
{
mSourceEventListener = listener;
}
/**
* Unregisters the external listener from receiving frequency rotation requests.
*/
@Override
public void removeSourceEventListener()
{
mSourceEventListener = null;
}
@Override
public Listener<IdentifierUpdateNotification> 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()
{
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
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<Long> mFrequencies;
private int mFrequencyListPointer = 0;
private ChannelSpecification mChannelSpecification;
private Listener<ReusableComplexBuffer> mReusableComplexBufferListener;
private Listener<Heartbeat> mHeartbeatListener;
private String mPreferredTuner;
private AtomicBoolean mChangingChannels = new AtomicBoolean();
private boolean mStarted;
private ConsumerSourceEventAdapter mConsumerSourceEventAdapter = new ConsumerSourceEventAdapter();
public MultiFrequencyTunerChannelSource(TunerModel tunerModel, TunerChannelSource tunerChannelSource,
List<Long> 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<Heartbeat> 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<Heartbeat> 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<ReusableComplexBuffer> complexBufferListener)
{
mReusableComplexBufferListener = complexBufferListener;
if(mTunerChannelSource != null)
{
mTunerChannelSource.setListener(complexBufferListener);
}
}
@Override
public void removeListener(Listener<ReusableComplexBuffer> 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<SourceEvent>
{
@Override
public void receive(SourceEvent sourceEvent)
{
broadcastConsumerSourceEvent(sourceEvent);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
* *****************************************************************************
*/
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() : "") + "]");

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.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));
}
/**

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.spectrum;
import com.jidesoft.swing.JideSplitPane;
@@ -445,7 +449,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
//Fire frequency and sample rate change events so that the spectrum
//and overlay panels can synchronize
process(SourceEvent.frequencyChange(mTuner.getTunerController().getFrequency()));
process(SourceEvent.frequencyChange(null, mTuner.getTunerController().getFrequency()));
process(SourceEvent.sampleRateChange(mTuner.getTunerController().getSampleRate()));
}