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 { buildscript {
repositories { repositories {
jcenter() jcenter()
@@ -55,6 +75,7 @@ dependencies {
compile 'org.apache.axis:axis:1.4' compile 'org.apache.axis:axis:1.4'
compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.3' 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-javafx:2.1.1'
compile 'org.kordamp.ikonli:ikonli-swing:2.1.1'
compile 'org.kordamp.ikonli:ikonli-fontawesome-pack:2.1.1' compile 'org.kordamp.ikonli:ikonli-fontawesome-pack:2.1.1'
compile 'com.google.guava:guava:27.0-jre' compile 'com.google.guava:guava:27.0-jre'
} }

View File

@@ -1,7 +1,7 @@
/* /*
* ****************************************************************************** * ******************************************************************************
* sdrtrunk * 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 * 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 * 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.FrequencyConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier; import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier; 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.DecoderLogicalChannelNameIdentifier;
import io.github.dsheirer.identifier.decoder.DecoderStateIdentifier;
import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -54,7 +54,7 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
private SiteConfigurationIdentifier mSiteConfigurationIdentifier; private SiteConfigurationIdentifier mSiteConfigurationIdentifier;
private ChannelNameConfigurationIdentifier mChannelNameConfigurationIdentifier; private ChannelNameConfigurationIdentifier mChannelNameConfigurationIdentifier;
private FrequencyConfigurationIdentifier mFrequencyConfigurationIdentifier; private FrequencyConfigurationIdentifier mFrequencyConfigurationIdentifier;
private DecoderStateIdentifier mDecoderStateIdentifier = DecoderStateIdentifier.IDLE; private ChannelStateIdentifier mChannelStateIdentifier = ChannelStateIdentifier.IDLE;
private DecoderLogicalChannelNameIdentifier mDecoderLogicalChannelNameIdentifier; private DecoderLogicalChannelNameIdentifier mDecoderLogicalChannelNameIdentifier;
private DecoderTypeConfigurationIdentifier mDecoderTypeConfigurationIdentifier; private DecoderTypeConfigurationIdentifier mDecoderTypeConfigurationIdentifier;
private Identifier mFromIdentifier; private Identifier mFromIdentifier;
@@ -145,14 +145,14 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
/** /**
* Decoder state identifier * Decoder state identifier
*/ */
public DecoderStateIdentifier getDecoderStateIdentifier() public ChannelStateIdentifier getChannelStateIdentifier()
{ {
return mDecoderStateIdentifier; return mChannelStateIdentifier;
} }
public boolean hasDecoderStateIdentifier() public boolean hasDecoderStateIdentifier()
{ {
return mDecoderStateIdentifier != null; return mChannelStateIdentifier != null;
} }
/** /**
@@ -250,7 +250,7 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
switch(identifier.getForm()) switch(identifier.getForm())
{ {
case ALIAS_LIST: case ALIAS_LIST:
if(update.isAdded()) if(update.isAdd())
{ {
mAliasList = mAliasModel.getAliasList((AliasListConfigurationIdentifier)identifier); mAliasList = mAliasModel.getAliasList((AliasListConfigurationIdentifier)identifier);
@@ -268,23 +268,58 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
} }
break; break;
case CHANNEL: 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); broadcastUpdate(ChannelMetadataField.CONFIGURATION_CHANNEL);
break; break;
case CHANNEL_FREQUENCY: 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); broadcastUpdate(ChannelMetadataField.CONFIGURATION_FREQUENCY);
break; break;
case DECODER_TYPE: 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); broadcastUpdate(ChannelMetadataField.DECODER_TYPE);
break; break;
case SITE: 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); broadcastUpdate(ChannelMetadataField.CONFIGURATION_SITE);
break; break;
case SYSTEM: 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); broadcastUpdate(ChannelMetadataField.CONFIGURATION_SYSTEM);
break; break;
} }
@@ -293,11 +328,25 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
switch(identifier.getForm()) switch(identifier.getForm())
{ {
case CHANNEL_NAME: 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); broadcastUpdate(ChannelMetadataField.DECODER_CHANNEL_NAME);
break; break;
case STATE: 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); broadcastUpdate(ChannelMetadataField.DECODER_STATE);
break; break;
} }
@@ -305,7 +354,7 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
case USER: case USER:
if(identifier.getRole() == Role.FROM) if(identifier.getRole() == Role.FROM)
{ {
mFromIdentifier = update.isAdded() ? identifier : null; mFromIdentifier = update.isAdd() ? identifier : null;
if(mAliasList != null && mFromIdentifier != null) if(mAliasList != null && mFromIdentifier != null)
{ {
@@ -320,7 +369,7 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
} }
else if(identifier.getRole() == Role.TO) else if(identifier.getRole() == Role.TO)
{ {
mToIdentifier = update.isAdded() ? identifier : null; mToIdentifier = update.isAdd() ? identifier : null;
if(mAliasList != null && mToIdentifier != null) if(mAliasList != null && mToIdentifier != null)
{ {

View File

@@ -1,7 +1,7 @@
/* /*
* ****************************************************************************** * ******************************************************************************
* sdrtrunk * 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 * 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 * 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) switch(columnIndex)
{ {
case COLUMN_DECODER_STATE: case COLUMN_DECODER_STATE:
return channelMetadata.getDecoderStateIdentifier(); return channelMetadata.getChannelStateIdentifier();
case COLUMN_DECODER_TYPE: case COLUMN_DECODER_TYPE:
return channelMetadata.getDecoderTypeConfigurationIdentifier(); return channelMetadata.getDecoderTypeConfigurationIdentifier();
case COLUMN_DECODER_LOGICAL_CHANNEL_NAME: case COLUMN_DECODER_LOGICAL_CHANNEL_NAME:

View File

@@ -1,7 +1,7 @@
/* /*
* ****************************************************************************** * ******************************************************************************
* sdrtrunk * 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 * 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 * 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.icon.IconManager;
import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; 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.module.ProcessingChain;
import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference; import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference;
@@ -347,9 +347,9 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
Color background = table.getBackground(); Color background = table.getBackground();
Color foreground = table.getForeground(); 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()); label.setText(state.getDisplayValue());
if(mBackgroundColors.containsKey(state)) if(mBackgroundColors.containsKey(state))

View File

@@ -1,6 +1,7 @@
/******************************************************************************* /*
* ******************************************************************************
* sdrtrunk * 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 * 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 * 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 * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
******************************************************************************/ */
package io.github.dsheirer.channel.state; package io.github.dsheirer.channel.state;
import io.github.dsheirer.alias.AliasModel; 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.FrequencyConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier; import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier; 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.Module;
import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.event.IDecodeEvent; 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.SourceEvent;
import io.github.dsheirer.source.SourceType; import io.github.dsheirer.source.SourceType;
import io.github.dsheirer.source.config.SourceConfigTuner; 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.Heartbeat;
import io.github.dsheirer.source.heartbeat.IHeartbeatListener; import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.List;
public class ChannelState extends Module implements IChannelEventProvider, IDecodeEventProvider, public class ChannelState extends Module implements IChannelEventProvider, IDecodeEventProvider,
IDecoderStateEventListener, IDecoderStateEventProvider, ISourceEventListener, ISourceEventProvider, IDecoderStateEventListener, IDecoderStateEventProvider, ISourceEventListener, ISourceEventProvider,
IHeartbeatListener, ISquelchStateProvider, IOverflowListener, IdentifierUpdateListener, IdentifierUpdateProvider IHeartbeatListener, ISquelchStateProvider, IOverflowListener, IdentifierUpdateListener, IdentifierUpdateProvider
@@ -76,6 +79,7 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco
private Channel mChannel; private Channel mChannel;
private SourceEventListener mInternalSourceEventListener; private SourceEventListener mInternalSourceEventListener;
private ChannelMetadata mChannelMetadata; private ChannelMetadata mChannelMetadata;
private IdentifierUpdateNotificationProxy mIdentifierUpdateNotificationProxy = new IdentifierUpdateNotificationProxy();
private boolean mSquelchLocked = false; private boolean mSquelchLocked = false;
private boolean mSelected = false; private boolean mSelected = false;
@@ -108,6 +112,7 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco
{ {
mChannel = channel; mChannel = channel;
mChannelMetadata = new ChannelMetadata(aliasModel); mChannelMetadata = new ChannelMetadata(aliasModel);
mIdentifierCollection.setIdentifierUpdateListener(mIdentifierUpdateNotificationProxy);
createConfigurationIdentifiers(channel); createConfigurationIdentifiers(channel);
} }
@@ -136,8 +141,17 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco
} }
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER) if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{ {
TunerChannel tunerChannel = ((SourceConfigTuner)channel.getSourceConfiguration()).getTunerChannel(); long frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency();
mIdentifierCollection.update(FrequencyConfigurationIdentifier.create(tunerChannel.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 @Override
public void setIdentifierUpdateListener(Listener<IdentifierUpdateNotification> listener) public void setIdentifierUpdateListener(Listener<IdentifierUpdateNotification> listener)
{ {
mIdentifierCollection.setIdentifierUpdateListener(listener); mIdentifierUpdateNotificationProxy.setListener(listener);
} }
@Override @Override
public void removeIdentifierUpdateListener() public void removeIdentifierUpdateListener()
{ {
mIdentifierCollection.removeIdentifierUpdateListener(); mIdentifierUpdateNotificationProxy.removeListener();
} }
/** /**
@@ -189,7 +203,7 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco
{ {
mState = State.IDLE; mState = State.IDLE;
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE)); broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
mIdentifierCollection.update(DecoderStateIdentifier.IDLE); mIdentifierCollection.update(ChannelStateIdentifier.IDLE);
mSourceOverflow = false; mSourceOverflow = false;
} }
@@ -358,7 +372,7 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco
broadcast(SquelchState.SQUELCH); broadcast(SquelchState.SQUELCH);
updateFadeTimeout(); updateFadeTimeout();
mState = state; mState = state;
mIdentifierCollection.update(DecoderStateIdentifier.ACTIVE); mIdentifierCollection.update(ChannelStateIdentifier.ACTIVE);
break; break;
case CONTROL: case CONTROL:
//Don't allow traffic channels to be control channels, otherwise they can't transition to teardown //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); broadcast(SquelchState.SQUELCH);
updateFadeTimeout(); updateFadeTimeout();
mState = state; mState = state;
mIdentifierCollection.update(DecoderStateIdentifier.CONTROL); mIdentifierCollection.update(ChannelStateIdentifier.CONTROL);
} }
break; break;
case DATA: case DATA:
broadcast(SquelchState.SQUELCH); broadcast(SquelchState.SQUELCH);
updateFadeTimeout(); updateFadeTimeout();
mState = state; mState = state;
mIdentifierCollection.update(DecoderStateIdentifier.DATA); mIdentifierCollection.update(ChannelStateIdentifier.DATA);
break; break;
case ENCRYPTED: case ENCRYPTED:
broadcast(SquelchState.SQUELCH); broadcast(SquelchState.SQUELCH);
updateFadeTimeout(); updateFadeTimeout();
mState = state; mState = state;
mIdentifierCollection.update(DecoderStateIdentifier.ENCRYPTED); mIdentifierCollection.update(ChannelStateIdentifier.ENCRYPTED);
break; break;
case CALL: case CALL:
broadcast(SquelchState.UNSQUELCH); broadcast(SquelchState.UNSQUELCH);
updateFadeTimeout(); updateFadeTimeout();
mState = state; mState = state;
mIdentifierCollection.update(DecoderStateIdentifier.CALL); mIdentifierCollection.update(ChannelStateIdentifier.CALL);
break; break;
case FADE: case FADE:
processFadeState(); processFadeState();
mIdentifierCollection.update(ChannelStateIdentifier.FADE);
break; break;
case IDLE: case IDLE:
processIdleState(); processIdleState();
mIdentifierCollection.update(ChannelStateIdentifier.IDLE);
break; break;
case TEARDOWN: case TEARDOWN:
processTeardownState(); processTeardownState();
break; break;
case RESET: case RESET:
mState = State.IDLE; mState = State.IDLE;
mIdentifierCollection.update(DecoderStateIdentifier.IDLE); mIdentifierCollection.update(ChannelStateIdentifier.IDLE);
break; break;
default: default:
break; break;
@@ -447,7 +463,7 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco
{ {
updateResetTimeout(); updateResetTimeout();
mState = State.FADE; mState = State.FADE;
mIdentifierCollection.update(DecoderStateIdentifier.FADE); mIdentifierCollection.update(ChannelStateIdentifier.FADE);
broadcast(SquelchState.SQUELCH); broadcast(SquelchState.SQUELCH);
} }
@@ -462,7 +478,7 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco
} }
mState = State.IDLE; mState = State.IDLE;
mIdentifierCollection.update(DecoderStateIdentifier.IDLE); mIdentifierCollection.update(ChannelStateIdentifier.IDLE);
} }
private void processTeardownState() private void processTeardownState()
@@ -470,7 +486,7 @@ public class ChannelState extends Module implements IChannelEventProvider, IDeco
broadcast(SquelchState.SQUELCH); broadcast(SquelchState.SQUELCH);
mState = State.TEARDOWN; mState = State.TEARDOWN;
mIdentifierCollection.update(DecoderStateIdentifier.TEARDOWN); mIdentifierCollection.update(ChannelStateIdentifier.TEARDOWN);
if(mChannel.isTrafficChannel()) 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 //Rebroadcast source frequency change events for the decoder(s) to process
long frequency = sourceEvent.getValue().longValue(); long frequency = sourceEvent.getValue().longValue();
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency)); 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; break;
case NOTIFICATION_MEASURED_FREQUENCY_ERROR: case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
//Rebroadcast frequency error measurements to external listener if we're currently //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; package io.github.dsheirer.channel.state;
import io.github.dsheirer.channel.IChannelDescriptor; 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.DECODER_TYPE &&
identifierUpdateNotification.getIdentifier().getForm() != Form.CHANNEL_DESCRIPTOR) 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) if(identifierUpdateNotification.getOperation() == IdentifierUpdateNotification.Operation.ADD)

View File

@@ -1,7 +1,7 @@
/* /*
* ****************************************************************************** * ******************************************************************************
* sdrtrunk * 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 * 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 * 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.IDecodeEvent;
import io.github.dsheirer.module.decode.event.MessageActivityModel; import io.github.dsheirer.module.decode.event.MessageActivityModel;
import io.github.dsheirer.module.log.EventLogManager; import io.github.dsheirer.module.log.EventLogManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.record.RecorderManager; import io.github.dsheirer.record.RecorderManager;
import io.github.dsheirer.record.RecorderType; import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.binary.BinaryRecorder; import io.github.dsheirer.record.binary.BinaryRecorder;
@@ -76,6 +77,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
private RecorderManager mRecorderManager; private RecorderManager mRecorderManager;
private SourceManager mSourceManager; private SourceManager mSourceManager;
private AliasModel mAliasModel; private AliasModel mAliasModel;
private UserPreferences mUserPreferences;
/** /**
* Constructs the channel processing manager * 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 recorderManager for receiving audio packets produced by the channel
* @param sourceManager for obtaining a tuner channel source for the channel * @param sourceManager for obtaining a tuner channel source for the channel
* @param aliasModel for aliasing of identifiers produced by 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, public ChannelProcessingManager(ChannelMapModel channelMapModel, EventLogManager eventLogManager,
RecorderManager recorderManager, SourceManager sourceManager, AliasModel aliasModel) RecorderManager recorderManager, SourceManager sourceManager,
AliasModel aliasModel, UserPreferences userPreferences)
{ {
mChannelMapModel = channelMapModel; mChannelMapModel = channelMapModel;
mEventLogManager = eventLogManager; mEventLogManager = eventLogManager;
mRecorderManager = recorderManager; mRecorderManager = recorderManager;
mSourceManager = sourceManager; mSourceManager = sourceManager;
mAliasModel = aliasModel; mAliasModel = aliasModel;
mUserPreferences = userPreferences;
mChannelMetadataModel = new ChannelMetadataModel(); mChannelMetadataModel = new ChannelMetadataModel();
} }
@@ -262,7 +267,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
processingChain.addFrequencyChangeListener(channel); processingChain.addFrequencyChangeListener(channel);
/* Processing Modules */ /* Processing Modules */
List<Module> modules = DecoderFactory.getModules(mChannelMapModel, channel, mAliasModel); List<Module> modules = DecoderFactory.getModules(mChannelMapModel, channel, mAliasModel, mUserPreferences);
processingChain.addModules(modules); processingChain.addModules(modules);
/* Setup message activity model with filtering */ /* Setup message activity model with filtering */

View File

@@ -1,7 +1,7 @@
/* /*
* ****************************************************************************** * ******************************************************************************
* sdrtrunk * 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 * 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 * 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); mSourceManager = new SourceManager(tunerModel, mSettingsManager, mUserPreferences);
mChannelProcessingManager = new ChannelProcessingManager(channelMapModel, eventLogManager, recorderManager, mChannelProcessingManager = new ChannelProcessingManager(channelMapModel, eventLogManager, recorderManager,
mSourceManager, aliasModel); mSourceManager, aliasModel, mUserPreferences);
mChannelModel.addListener(mChannelProcessingManager); mChannelModel.addListener(mChannelProcessingManager);
mChannelProcessingManager.addChannelEventListener(mChannelModel); 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 * 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 * 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 * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
package io.github.dsheirer.gui.preference; package io.github.dsheirer.gui.preference;
import io.github.dsheirer.gui.preference.directory.DirectoryPreferenceEditor; 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.gui.preference.tuner.TunerPreferenceEditor;
import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.preference.UserPreferences;
import javafx.scene.Node; import javafx.scene.Node;
@@ -38,9 +39,11 @@ public class PreferenceEditorFactory
return new DecodeEventViewPreferenceEditor(userPreferences); return new DecodeEventViewPreferenceEditor(userPreferences);
case DIRECTORY: case DIRECTORY:
return new DirectoryPreferenceEditor(userPreferences); return new DirectoryPreferenceEditor(userPreferences);
case SOURCE_CHANNEL_MULTIPLE_FREQUENCY:
return new ChannelMultipleFrequencyPreferenceEditor(userPreferences);
case TALKGROUP_FORMAT: case TALKGROUP_FORMAT:
return new TalkgroupFormatPreferenceEditor(userPreferences); return new TalkgroupFormatPreferenceEditor(userPreferences);
case TUNER: case SOURCE_TUNER_CHANNELIZER:
return new TunerPreferenceEditor(userPreferences); return new TunerPreferenceEditor(userPreferences);
} }

View File

@@ -1,7 +1,7 @@
/* /*
* ****************************************************************************** * ******************************************************************************
* sdrtrunk * 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 * 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 * it under the terms of the GNU General Public License as published by
@@ -27,8 +27,9 @@ public enum PreferenceEditorType
{ {
CHANNEL_EVENT("Channel Events"), CHANNEL_EVENT("Channel Events"),
DIRECTORY("Directories"), DIRECTORY("Directories"),
TALKGROUP_FORMAT("Talkgroups"), SOURCE_CHANNEL_MULTIPLE_FREQUENCY("Channel - Multiple Frequency"),
TUNER("Tuners"); SOURCE_TUNER_CHANNELIZER("Tuner Channelizer"),
TALKGROUP_FORMAT("Talkgroups");
private String mLabel; private String mLabel;

View File

@@ -1,7 +1,7 @@
/* /*
* ****************************************************************************** * ******************************************************************************
* sdrtrunk * 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 * 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 * 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); storageItem.setExpanded(true);
TreeItem<String> sourceItem = new TreeItem<>("Source"); 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); treeRoot.getChildren().add(sourceItem);
sourceItem.setExpanded(true); 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 * 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 * 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 * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,8 @@
package io.github.dsheirer.gui.preference.tuner; package io.github.dsheirer.gui.preference.tuner;
import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.tuner.ChannelizerType; import io.github.dsheirer.preference.source.ChannelizerType;
import io.github.dsheirer.preference.tuner.TunerPreference; import io.github.dsheirer.preference.source.TunerPreference;
import javafx.geometry.HPos; import javafx.geometry.HPos;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Orientation; import javafx.geometry.Orientation;

View File

@@ -1,7 +1,7 @@
/* /*
* ****************************************************************************** * ******************************************************************************
* sdrtrunk * 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 * 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 * it under the terms of the GNU General Public License as published by
@@ -48,12 +48,12 @@ public class IdentifierUpdateNotification
return mOperation; return mOperation;
} }
public boolean isAdded() public boolean isAdd()
{ {
return mOperation == Operation.ADD; return mOperation == Operation.ADD;
} }
public boolean isRemoved() public boolean isRemove()
{ {
return mOperation == Operation.REMOVE; return mOperation == Operation.REMOVE;
} }

View File

@@ -1,7 +1,7 @@
/* /*
* ****************************************************************************** * ******************************************************************************
* sdrtrunk * 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 * 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 * it under the terms of the GNU General Public License as published by
@@ -367,11 +367,11 @@ public class MutableIdentifierCollection extends IdentifierCollection implements
@Override @Override
public void receive(IdentifierUpdateNotification identifierUpdateNotification) public void receive(IdentifierUpdateNotification identifierUpdateNotification)
{ {
if(identifierUpdateNotification.isAdded()) if(identifierUpdateNotification.isAdd())
{ {
update(identifierUpdateNotification.getIdentifier()); update(identifierUpdateNotification.getIdentifier());
} }
else if(identifierUpdateNotification.isRemoved()) else if(identifierUpdateNotification.isRemove())
{ {
remove(identifierUpdateNotification.getIdentifier()); remove(identifierUpdateNotification.getIdentifier());
} }

View File

@@ -1,7 +1,7 @@
/* /*
* ****************************************************************************** * ******************************************************************************
* sdrtrunk * 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 * 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 * 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.identifier.Role;
import io.github.dsheirer.protocol.Protocol; 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 ChannelStateIdentifier ACTIVE = ChannelStateIdentifier.create(State.ACTIVE);
public static final DecoderStateIdentifier CALL = DecoderStateIdentifier.create(State.CALL); public static final ChannelStateIdentifier CALL = ChannelStateIdentifier.create(State.CALL);
public static final DecoderStateIdentifier CONTROL = DecoderStateIdentifier.create(State.CONTROL); public static final ChannelStateIdentifier CONTROL = ChannelStateIdentifier.create(State.CONTROL);
public static final DecoderStateIdentifier DATA = DecoderStateIdentifier.create(State.DATA); public static final ChannelStateIdentifier DATA = ChannelStateIdentifier.create(State.DATA);
public static final DecoderStateIdentifier ENCRYPTED = DecoderStateIdentifier.create(State.ENCRYPTED); public static final ChannelStateIdentifier ENCRYPTED = ChannelStateIdentifier.create(State.ENCRYPTED);
public static final DecoderStateIdentifier FADE = DecoderStateIdentifier.create(State.FADE); public static final ChannelStateIdentifier FADE = ChannelStateIdentifier.create(State.FADE);
public static final DecoderStateIdentifier IDLE = DecoderStateIdentifier.create(State.IDLE); public static final ChannelStateIdentifier IDLE = ChannelStateIdentifier.create(State.IDLE);
public static final DecoderStateIdentifier TEARDOWN = DecoderStateIdentifier.create(State.TEARDOWN); public static final ChannelStateIdentifier TEARDOWN = ChannelStateIdentifier.create(State.TEARDOWN);
public static final DecoderStateIdentifier RESET = DecoderStateIdentifier.create(State.RESET); 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); 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 * 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 @Override

View File

@@ -1,7 +1,7 @@
/* /*
* ****************************************************************************** * ******************************************************************************
* sdrtrunk * 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 * 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 * 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) if(module instanceof IHeartbeatProvider)
{ {
((IHeartbeatProvider)module).getHeartbeatManager().addHeartbeatListener(mHeartbeatBroadcaster); ((IHeartbeatProvider)module).addHeartbeatListener(mHeartbeatBroadcaster);
} }
if(module instanceof IMessageProvider) if(module instanceof IMessageProvider)
@@ -528,7 +528,7 @@ public class ProcessingChain implements Listener<ChannelEvent>
if(module instanceof IHeartbeatProvider) if(module instanceof IHeartbeatProvider)
{ {
((IHeartbeatProvider)module).getHeartbeatManager().removeHeartbeatListener(mHeartbeatBroadcaster); ((IHeartbeatProvider)module).removeHeartbeatListener(mHeartbeatBroadcaster);
} }
if(module instanceof IMessageProvider) if(module instanceof IMessageProvider)
@@ -571,7 +571,7 @@ public class ProcessingChain implements Listener<ChannelEvent>
mSourceEventBroadcaster.broadcast(SourceEvent.sampleRateChange(mSource.getSampleRate(), "Processing Chain Startup")); 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 //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 //Setup the channel state to monitor source overflow conditions
mSource.setOverflowListener(mChannelState); mSource.setOverflowListener(mChannelState);

View File

@@ -1,7 +1,7 @@
/* /*
* ****************************************************************************** * ******************************************************************************
* sdrtrunk * 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 * 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 * 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.alias.action.AliasActionManager;
import io.github.dsheirer.audio.AudioModule; import io.github.dsheirer.audio.AudioModule;
import io.github.dsheirer.channel.state.AlwaysUnsquelchedDecoderState; 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;
import io.github.dsheirer.controller.channel.Channel.ChannelType; import io.github.dsheirer.controller.channel.Channel.ChannelType;
import io.github.dsheirer.controller.channel.map.ChannelMap; 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.decode.tait.Tait1200DecoderState;
import io.github.dsheirer.module.demodulate.am.AMDemodulatorModule; import io.github.dsheirer.module.demodulate.am.AMDemodulatorModule;
import io.github.dsheirer.module.demodulate.fm.FMDemodulatorModule; 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.SourceType;
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import io.github.dsheirer.source.tuner.channel.ChannelRotationMonitor;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -109,9 +113,10 @@ public class DecoderFactory
* *
* @return list of configured decoders * @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())); modules.addAll(getAuxiliaryDecoders(channel.getAuxDecodeConfiguration()));
return modules; return modules;
} }
@@ -119,7 +124,8 @@ public class DecoderFactory
/** /**
* Constructs a primary decoder as specified in the decode configuration * 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>(); List<Module> modules = new ArrayList<Module>();
@@ -239,6 +245,15 @@ public class DecoderFactory
} }
modules.add(new P25AudioModule()); 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; break;
default: default:
throw new IllegalArgumentException("Unknown decoder type [" + decodeConfig.getDecoderType().toString() + "]"); throw new IllegalArgumentException("Unknown decoder type [" + decodeConfig.getDecoderType().toString() + "]");

View File

@@ -1,20 +1,22 @@
/******************************************************************************* /*
* SDR Trunk * ******************************************************************************
* Copyright (C) 2014-2016 Dennis Sheirer * sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/ * *****************************************************************************
*/
package io.github.dsheirer.module.decode.p25; package io.github.dsheirer.module.decode.p25;
import io.github.dsheirer.controller.channel.Channel; 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.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.SourceConfigurationEditor; import io.github.dsheirer.source.SourceConfigurationEditor;
import io.github.dsheirer.source.config.SourceConfigTuner; 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.config.SourceConfiguration;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -43,9 +46,9 @@ import java.awt.event.ActionListener;
public class P25DecoderEditor extends ValidatingEditor<Channel> 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 JComboBox<P25DecoderLSM.Modulation> mComboModulation;
private JCheckBox mIgnoreDataCalls; private JCheckBox mIgnoreDataCalls;
@@ -53,168 +56,166 @@ public class P25DecoderEditor extends ValidatingEditor<Channel>
private JSlider mTrafficChannelPoolSize; private JSlider mTrafficChannelPoolSize;
public P25DecoderEditor() public P25DecoderEditor()
{ {
init(); 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 );
} }
@Override private void init()
public void validate( Editor<Channel> editor ) throws EditorValidationException {
{ setLayout(new MigLayout("insets 0 0 0 0,wrap 3", "[right][grow,fill][]", ""));
/**
* 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();
if( config != null && !( config instanceof SourceConfigTuner ) ) mComboModulation = new JComboBox<P25DecoderLSM.Modulation>();
{ mComboModulation.setModel(new DefaultComboBoxModel<P25DecoderLSM.Modulation>(
throw new EditorValidationException( editor, P25DecoderLSM.Modulation.values()));
"<html><body width='175'><h3>LSM Simulcast</h3>" mComboModulation.setEnabled(false);
+ "<p>P25 LSM Simulcast decoder can only be used with " mComboModulation.addActionListener(new ActionListener()
+ "a tuner source. Please change the Source to use a tuner" {
+ " or change the P25 Decoder to C4FM modulation</p>" ); @Override
} public void actionPerformed(ActionEvent e)
} {
setModified(true);
}
});
if( editor instanceof AuxDecodeConfigurationEditor) add(new JLabel("Modulation:"));
{ add(mComboModulation);
AuxDecodeConfiguration config = ((AuxDecodeConfigurationEditor)editor).getConfiguration();
if( config != null && !config.getAuxDecoders().isEmpty() ) mIgnoreDataCalls = new JCheckBox("Ignore Data Calls");
{ mIgnoreDataCalls.setEnabled(false);
throw new EditorValidationException( editor, mIgnoreDataCalls.addActionListener(new ActionListener()
"<html><body width='175'><h3>Auxilary Decoders</h3>" {
+ "<p>The auxiliary decoders work with analog audio and are not " @Override
+ "compatible with the P25 Decoder. Please uncheck any auxiliary " public void actionPerformed(ActionEvent e)
+ "decoders that you have selected for this channel</p>" ); {
} setModified(true);
} }
} });
private void setControlsEnabled( boolean enabled ) add(mIgnoreDataCalls);
{
if( mComboModulation.isEnabled() != enabled )
{
mComboModulation.setEnabled( enabled );
}
if( mTrafficChannelPoolSize.isEnabled() != enabled ) mTrafficChannelPoolSize = new JSlider(JSlider.HORIZONTAL, DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_MINIMUM,
{ DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_MAXIMUM, DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT);
mTrafficChannelPoolSize.setEnabled( enabled ); 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);
}
});
if( mIgnoreDataCalls.isEnabled() != enabled ) mTrafficChannelPoolSizeLabel = new JLabel("Traffic Pool: " + mTrafficChannelPoolSize.getValue() + " ");
{
mIgnoreDataCalls.setEnabled( enabled );
}
}
@Override add(mTrafficChannelPoolSizeLabel);
public void setItem( Channel item ) add(mTrafficChannelPoolSize);
{ }
super.setItem( item );
if( hasItem() ) @Override
{ public void save()
setControlsEnabled( true ); {
if(hasItem() && isModified())
{
DecodeConfigP25Phase1 p25 = new DecodeConfigP25Phase1();
DecodeConfiguration config = getItem().getDecodeConfiguration(); p25.setModulation((P25Decoder.Modulation)mComboModulation.getSelectedItem());
p25.setIgnoreDataCalls(mIgnoreDataCalls.isSelected());
p25.setTrafficChannelPoolSize(mTrafficChannelPoolSize.getValue());
if( config instanceof DecodeConfigP25Phase1 ) getItem().setDecodeConfiguration(p25);
{ }
DecodeConfigP25Phase1 p25 = (DecodeConfigP25Phase1)config;
mComboModulation.setSelectedItem( p25.getModulation() ); setModified(false);
mIgnoreDataCalls.setSelected( p25.getIgnoreDataCalls() ); }
mTrafficChannelPoolSize.setValue( p25.getTrafficChannelPoolSize() );
setModified( false ); @Override
} public void validate(Editor<Channel> editor) throws EditorValidationException
else {
{ /**
mComboModulation.setSelectedItem( P25Decoder.Modulation.C4FM ); * Enforce CQPSK modulation uses a Tuner Source for I/Q sample data
mIgnoreDataCalls.setSelected( false ); */
mTrafficChannelPoolSize.setValue( DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT ); if(editor instanceof SourceConfigurationEditor)
setModified( true ); {
} SourceConfiguration config = ((SourceConfigurationEditor)editor).getSourceConfiguration();
}
else if(!(config instanceof SourceConfigTuner || config instanceof SourceConfigTunerMultipleFrequency))
{ {
setControlsEnabled( false ); throw new EditorValidationException(editor,
setModified( false ); "<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 * 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 * 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 * it under the terms of the GNU General Public License as published by
@@ -28,5 +28,6 @@ public enum PreferenceType
DECODE_EVENT, DECODE_EVENT,
DIRECTORY, DIRECTORY,
IDENTIFIER, IDENTIFIER,
MULTI_FREQUENCY,
TUNER; TUNER;
} }

View File

@@ -1,7 +1,7 @@
/* /*
* ****************************************************************************** * ******************************************************************************
* sdrtrunk * 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 * 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 * 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.event.DecodeEventPreference;
import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference; import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference;
import io.github.dsheirer.preference.playlist.PlaylistPreference; 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; import io.github.dsheirer.sample.Listener;
/** /**
@@ -48,6 +49,7 @@ public class UserPreferences implements Listener<PreferenceType>
{ {
private DecodeEventPreference mDecodeEventPreference; private DecodeEventPreference mDecodeEventPreference;
private DirectoryPreference mDirectoryPreference; private DirectoryPreference mDirectoryPreference;
private ChannelMultiFrequencyPreference mChannelMultiFrequencyPreference;
private PlaylistPreference mPlaylistPreference; private PlaylistPreference mPlaylistPreference;
private TalkgroupFormatPreference mTalkgroupFormatPreference; private TalkgroupFormatPreference mTalkgroupFormatPreference;
private TunerPreference mTunerPreference; private TunerPreference mTunerPreference;
@@ -76,6 +78,14 @@ public class UserPreferences implements Listener<PreferenceType>
return mDirectoryPreference; return mDirectoryPreference;
} }
/**
* Multiple frequency channel source preferences
*/
public ChannelMultiFrequencyPreference getChannelMultiFrequencyPreference()
{
return mChannelMultiFrequencyPreference;
}
/** /**
* Playlist preferences * Playlist preferences
*/ */
@@ -106,7 +116,8 @@ public class UserPreferences implements Listener<PreferenceType>
private void loadPreferenceTypes() private void loadPreferenceTypes()
{ {
mDecodeEventPreference = new DecodeEventPreference(this); mDecodeEventPreference = new DecodeEventPreference(this);
mDirectoryPreference = new DirectoryPreference(this::receive); mDirectoryPreference = new DirectoryPreference(this);
mChannelMultiFrequencyPreference = new ChannelMultiFrequencyPreference(this);
mPlaylistPreference = new PlaylistPreference(this, mDirectoryPreference); mPlaylistPreference = new PlaylistPreference(this, mDirectoryPreference);
mTalkgroupFormatPreference = new TalkgroupFormatPreference(this); mTalkgroupFormatPreference = new TalkgroupFormatPreference(this);
mTunerPreference = new TunerPreference(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 * 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 * 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 * 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 * Channelizer Type - identifies the type of channelizer that the tuner will use to provide DDR channels

View File

@@ -1,7 +1,7 @@
/* /*
* ****************************************************************************** * ******************************************************************************
* sdrtrunk * 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 * 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 * 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.Preference;
import io.github.dsheirer.preference.PreferenceType; import io.github.dsheirer.preference.PreferenceType;

View File

@@ -1,22 +1,27 @@
/******************************************************************************* /*
* SDR Trunk * ******************************************************************************
* Copyright (C) 2014 Dennis Sheirer * sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/ * *****************************************************************************
*/
package io.github.dsheirer.sample; package io.github.dsheirer.sample;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
@@ -25,8 +30,23 @@ import java.util.concurrent.CopyOnWriteArrayList;
*/ */
public class Broadcaster<T> implements Listener<T> 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<>(); 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 * Implements the Listener<T> interface to receive an element and broadcast that element to all registered
* listeners. * listeners.
@@ -116,9 +136,21 @@ public class Broadcaster<T> implements Listener<T>
*/ */
public void broadcast(T 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 * sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/ * *****************************************************************************
*/
package io.github.dsheirer.source; package io.github.dsheirer.source;
import io.github.dsheirer.module.Module; import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.IOverflowListener; import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType; 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.HeartbeatManager;
import io.github.dsheirer.source.heartbeat.IHeartbeatProvider; 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; 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 * 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 * sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/ * *****************************************************************************
*/
package io.github.dsheirer.source; package io.github.dsheirer.source;
import io.github.dsheirer.controller.channel.Channel; 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.config.SourceConfiguration;
import io.github.dsheirer.source.mixer.MixerSourceEditor; import io.github.dsheirer.source.mixer.MixerSourceEditor;
import io.github.dsheirer.source.tuner.TunerSourceEditor; import io.github.dsheirer.source.tuner.TunerSourceEditor;
import io.github.dsheirer.source.tuner.TunerSourceMultipleFrequencyEditor;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import javax.swing.DefaultComboBoxModel; import javax.swing.DefaultComboBoxModel;
@@ -37,99 +40,106 @@ public class SourceConfigurationEditor extends Editor<Channel>
private JComboBox<SourceType> mComboSources; private JComboBox<SourceType> mComboSources;
private MixerSourceEditor mMixerSourceEditor; private MixerSourceEditor mMixerSourceEditor;
private TunerSourceEditor mTunerSourceEditor; private TunerSourceEditor mTunerSourceEditor;
private TunerSourceMultipleFrequencyEditor mTunerSourceMultipleFrequencyEditor;
private Editor<Channel> mCurrentEditor; private Editor<Channel> mCurrentEditor;
public SourceConfigurationEditor( SourceManager sourceManager ) public SourceConfigurationEditor(SourceManager sourceManager)
{ {
mMixerSourceEditor = new MixerSourceEditor( sourceManager ); mMixerSourceEditor = new MixerSourceEditor(sourceManager);
mMixerSourceEditor.setSaveRequestListener( this ); mMixerSourceEditor.setSaveRequestListener(this);
mTunerSourceEditor = new TunerSourceEditor(sourceManager.getTunerModel()); mTunerSourceEditor = new TunerSourceEditor(sourceManager.getTunerModel());
mTunerSourceEditor.setSaveRequestListener( this ); mTunerSourceEditor.setSaveRequestListener(this);
init(); mTunerSourceMultipleFrequencyEditor = new TunerSourceMultipleFrequencyEditor(sourceManager.getTunerModel());
} mTunerSourceMultipleFrequencyEditor.setSaveRequestListener(this);
init();
}
public SourceConfiguration getSourceConfiguration() public SourceConfiguration getSourceConfiguration()
{ {
return hasItem() ? getItem().getSourceConfiguration() : null; return hasItem() ? getItem().getSourceConfiguration() : null;
} }
private void init() 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 = new JComboBox<SourceType>();
mComboSources.setModel( new DefaultComboBoxModel<SourceType>( SourceType.getTypes() ) ); mComboSources.setModel(new DefaultComboBoxModel<SourceType>(SourceType.getTypes()));
mComboSources.addActionListener( new ActionListener() mComboSources.addActionListener(new ActionListener()
{ {
@Override @Override
public void actionPerformed( ActionEvent e ) public void actionPerformed(ActionEvent e)
{ {
SourceType selected = (SourceType)mComboSources.getSelectedItem(); SourceType selected = (SourceType) mComboSources.getSelectedItem();
switch( selected ) switch(selected)
{ {
case MIXER: case MIXER:
setEditor( mMixerSourceEditor ); setEditor(mMixerSourceEditor);
break; break;
case TUNER: case TUNER:
setEditor( mTunerSourceEditor ); setEditor(mTunerSourceEditor);
break; break;
default: case TUNER_MULTIPLE_FREQUENCIES:
setEditor( new EmptyEditor<Channel>() ); setEditor(mTunerSourceMultipleFrequencyEditor);
break; break;
} default:
} setEditor(new EmptyEditor<Channel>());
}); break;
}
}
});
add( mComboSources ); add(mComboSources);
mCurrentEditor = mTunerSourceEditor; mCurrentEditor = mTunerSourceEditor;
add( mCurrentEditor ); add(mCurrentEditor, "span 1 2");
} }
private void setEditor( Editor<Channel> editor ) private void setEditor(Editor<Channel> editor)
{ {
if( mCurrentEditor != editor ) if(mCurrentEditor != editor)
{ {
//Set channel to null to force a save prompt as required //Set channel to null to force a save prompt as required
mCurrentEditor.setItem( null ); mCurrentEditor.setItem(null);
remove( mCurrentEditor ); remove(mCurrentEditor);
mCurrentEditor = editor; mCurrentEditor = editor;
mCurrentEditor.setSaveRequestListener( this ); mCurrentEditor.setSaveRequestListener(this);
add( mCurrentEditor ); add(mCurrentEditor, "span 1 2");
mCurrentEditor.setItem( getItem() ); mCurrentEditor.setItem(getItem());
revalidate(); revalidate();
} }
} }
@Override @Override
public void save() public void save()
{ {
if( hasItem() ) if(hasItem())
{ {
mCurrentEditor.save(); mCurrentEditor.save();
} }
} }
/** /**
* Sets the channel configuration. Note: this method must be invoked from * Sets the channel configuration. Note: this method must be invoked from
* the swing event dispatch thread. * the swing event dispatch thread.
*/ */
@Override @Override
public void setItem( Channel channel ) public void setItem(Channel channel)
{ {
super.setItem( channel ); super.setItem(channel);
if( hasItem() ) if(hasItem())
{ {
mComboSources.setSelectedItem( getItem().getSourceConfiguration().getSourceType() ); mComboSources.setSelectedItem(getItem().getSourceConfiguration().getSourceType());
repaint(); repaint();
mCurrentEditor.setItem( channel ); mCurrentEditor.setItem(channel);
} }
} }
} }

View File

@@ -1,6 +1,7 @@
/******************************************************************************* /*
* ******************************************************************************
* sdrtrunk * 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 * 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 * 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 * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
******************************************************************************/ */
package io.github.dsheirer.source; package io.github.dsheirer.source;
import java.util.EnumSet; import java.util.EnumSet;
@@ -31,6 +32,8 @@ public class SourceEvent
NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED, NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED,
NOTIFICATION_FREQUENCY_CHANGE, NOTIFICATION_FREQUENCY_CHANGE,
NOTIFICATION_FREQUENCY_CORRECTION_CHANGE, NOTIFICATION_FREQUENCY_CORRECTION_CHANGE,
NOTIFICATION_FREQUENCY_ROTATION_SUCCESS,
NOTIFICATION_FREQUENCY_ROTATION_FAILURE,
NOTIFICATION_MEASURED_FREQUENCY_ERROR, NOTIFICATION_MEASURED_FREQUENCY_ERROR,
NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED, NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED,
NOTIFICATION_SAMPLE_RATE_CHANGE, NOTIFICATION_SAMPLE_RATE_CHANGE,
@@ -38,6 +41,7 @@ public class SourceEvent
REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
REQUEST_FREQUENCY_CHANGE, REQUEST_FREQUENCY_CHANGE,
REQUEST_FREQUENCY_ROTATION,
REQUEST_SOURCE_DISPOSE, REQUEST_SOURCE_DISPOSE,
REQUEST_START_SAMPLE_STREAM, REQUEST_START_SAMPLE_STREAM,
REQUEST_STOP_SAMPLE_STREAM; REQUEST_STOP_SAMPLE_STREAM;
@@ -64,6 +68,14 @@ public class SourceEvent
mEventDescription = eventDescription; 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. * Private constructor. Use the static constructor methods to create an event.
*/ */
@@ -175,9 +187,9 @@ public class SourceEvent
* *
* @param frequency in hertz * @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 frequency in hertz
* @param eventDescription to add a tag to the event * @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); 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 @Override
public String toString() public String toString()
{ {

View File

@@ -1,7 +1,7 @@
/* /*
* ****************************************************************************** * ******************************************************************************
* sdrtrunk * 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 * 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 * 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.preference.UserPreferences;
import io.github.dsheirer.settings.SettingsManager; import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.config.SourceConfigTuner; 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.config.SourceConfiguration;
import io.github.dsheirer.source.mixer.MixerManager; import io.github.dsheirer.source.mixer.MixerManager;
import io.github.dsheirer.source.recording.RecordingSourceManager; import io.github.dsheirer.source.recording.RecordingSourceManager;
import io.github.dsheirer.source.tuner.TunerManager; import io.github.dsheirer.source.tuner.TunerManager;
import io.github.dsheirer.source.tuner.TunerModel; import io.github.dsheirer.source.tuner.TunerModel;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification; 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 public class SourceManager
{ {
@@ -86,7 +90,29 @@ public class SourceManager
retVal = mMixerManager.getSource(config); retVal = mMixerManager.getSource(config);
break; break;
case TUNER: 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; break;
case RECORDING: case RECORDING:
retVal = mRecordingSourceManager.getSource(config, channelSpecification); retVal = mRecordingSourceManager.getSource(config, channelSpecification);

View File

@@ -1,54 +1,51 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdr-trunk
* Copyright (C) 2014 Dennis Sheirer * Copyright (C) 2014-2018 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* it under the terms of the GNU General Public License as published by * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* the Free Software Foundation, either version 3 of the License, or * later version.
* (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* but WITHOUT ANY WARRANTY; without even the implied warranty of * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* 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; package io.github.dsheirer.source;
/**
* Source Configuration Types enumeration.
*/
public enum SourceType public enum SourceType
{ {
NONE( "No Source" ), NONE("No Source"),
MIXER( "Sound Card" ), MIXER("Sound Card"),
TUNER( "Tuner" ), TUNER("Tuner"),
RECORDING( "IQ Recording" ); TUNER_MULTIPLE_FREQUENCIES("Tuner - Multiple Frequencies"),
RECORDING("IQ Recording");
private String mDisplayString; private String mDisplayString;
SourceType( String displayString ) SourceType(String displayString)
{ {
mDisplayString = displayString; mDisplayString = displayString;
} }
public static SourceType[] getTypes() public static SourceType[] getTypes()
{ {
SourceType[] types = new SourceType[ 2 ]; SourceType[] types = new SourceType[3];
types[ 0 ] = SourceType.TUNER; types[0] = SourceType.TUNER;
types[ 1 ] = SourceType.MIXER; types[1] = SourceType.TUNER_MULTIPLE_FREQUENCIES;
types[2] = SourceType.MIXER;
return types; return types;
}
public String getDisplayString()
{
return mDisplayString;
} }
@Override @Override
public String toString() public String toString()
{ {
return mDisplayString; return mDisplayString;
} }
} }

View File

@@ -1,19 +1,17 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdr-trunk
* Copyright (C) 2014 Dennis Sheirer * Copyright (C) 2014-2018 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* it under the terms of the GNU General Public License as published by * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* the Free Software Foundation, either version 3 of the License, or * later version.
* (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* but WITHOUT ANY WARRANTY; without even the implied warranty of * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* 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; package io.github.dsheirer.source.config;
@@ -21,64 +19,74 @@ import io.github.dsheirer.source.SourceType;
public class SourceConfigFactory public class SourceConfigFactory
{ {
public static SourceConfiguration getDefaultSourceConfiguration() public static SourceConfiguration getDefaultSourceConfiguration()
{ {
return getSourceConfiguration( SourceType.TUNER ); return getSourceConfiguration(SourceType.TUNER);
} }
public static SourceConfiguration getSourceConfiguration( SourceType source ) public static SourceConfiguration getSourceConfiguration(SourceType source)
{ {
SourceConfiguration retVal; SourceConfiguration retVal;
switch( source ) switch(source)
{ {
case TUNER: case TUNER:
retVal = new SourceConfigTuner(); retVal = new SourceConfigTuner();
break; break;
case MIXER: case TUNER_MULTIPLE_FREQUENCIES:
retVal = new SourceConfigMixer(); retVal = new SourceConfigTunerMultipleFrequency();
break; break;
case NONE: case MIXER:
default: retVal = new SourceConfigMixer();
retVal = new SourceConfigNone(); break;
break; case NONE:
} default:
retVal = new SourceConfigNone();
break;
}
return retVal; return retVal;
} }
/** /**
* Creates a copy of the configuration * Creates a copy of the configuration
*/ */
public static SourceConfiguration copy( SourceConfiguration config ) public static SourceConfiguration copy(SourceConfiguration config)
{ {
if( config != null ) if(config != null)
{ {
switch( config.getSourceType() ) switch(config.getSourceType())
{ {
case MIXER: case MIXER:
SourceConfigMixer originalMixer = (SourceConfigMixer)config; SourceConfigMixer originalMixer = (SourceConfigMixer) config;
SourceConfigMixer copyMixer = new SourceConfigMixer(); SourceConfigMixer copyMixer = new SourceConfigMixer();
copyMixer.setChannel( originalMixer.getChannel() ); copyMixer.setChannel(originalMixer.getChannel());
copyMixer.setMixer( originalMixer.getMixer() ); copyMixer.setMixer(originalMixer.getMixer());
return copyMixer; return copyMixer;
case RECORDING: case RECORDING:
SourceConfigRecording originalRec = (SourceConfigRecording)config; SourceConfigRecording originalRec = (SourceConfigRecording) config;
SourceConfigRecording copyRec = new SourceConfigRecording(); SourceConfigRecording copyRec = new SourceConfigRecording();
copyRec.setFrequency( originalRec.getFrequency() ); copyRec.setFrequency(originalRec.getFrequency());
copyRec.setRecordingAlias( originalRec.getRecordingAlias() ); copyRec.setRecordingAlias(originalRec.getRecordingAlias());
return copyRec; return copyRec;
case TUNER: case TUNER:
SourceConfigTuner originalTuner = (SourceConfigTuner)config; SourceConfigTuner originalTuner = (SourceConfigTuner) config;
SourceConfigTuner copyTuner = new SourceConfigTuner(); SourceConfigTuner copyTuner = new SourceConfigTuner();
copyTuner.setFrequency( originalTuner.getFrequency() ); copyTuner.setPreferredTuner(originalTuner.getPreferredTuner());
return copyTuner; copyTuner.setFrequency(originalTuner.getFrequency());
case NONE: return copyTuner;
default: case TUNER_MULTIPLE_FREQUENCIES:
return new SourceConfigNone(); 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; 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 * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/ * *****************************************************************************
*/
package io.github.dsheirer.source.config; package io.github.dsheirer.source.config;
import com.fasterxml.jackson.annotation.JsonIgnore; 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 static DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
private long mFrequency = 0; private long mFrequency = 0;
private int mBandwidth = 12500;
private String mPreferredTuner; private String mPreferredTuner;
public SourceConfigTuner() public SourceConfigTuner()
@@ -42,9 +43,7 @@ public class SourceConfigTuner extends SourceConfiguration
public SourceConfigTuner(TunerChannel tunerChannel) public SourceConfigTuner(TunerChannel tunerChannel)
{ {
this(); this();
mFrequency = tunerChannel.getFrequency(); mFrequency = tunerChannel.getFrequency();
mBandwidth = tunerChannel.getBandwidth();
} }
@JacksonXmlProperty(isAttribute = true, localName = "frequency") @JacksonXmlProperty(isAttribute = true, localName = "frequency")
@@ -94,8 +93,8 @@ public class SourceConfigTuner extends SourceConfiguration
} }
@JsonIgnore @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 = SourceConfigNone.class, name = "sourceConfigNone"),
@JsonSubTypes.Type(value = SourceConfigRecording.class, name = "sourceConfigRecording"), @JsonSubTypes.Type(value = SourceConfigRecording.class, name = "sourceConfigRecording"),
@JsonSubTypes.Type(value = SourceConfigTuner.class, name = "sourceConfigTuner"), @JsonSubTypes.Type(value = SourceConfigTuner.class, name = "sourceConfigTuner"),
}) @JsonSubTypes.Type(value = SourceConfigTunerMultipleFrequency.class, name = "sourceConfigTunerMultipleFrequency")})
@JacksonXmlRootElement(localName = "source_configuration") @JacksonXmlRootElement(localName = "source_configuration")
public class SourceConfiguration extends Configuration public class SourceConfiguration extends Configuration
{ {

View File

@@ -1,6 +1,7 @@
/******************************************************************************* /*
* ******************************************************************************
* sdrtrunk * 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 * 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 * 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 * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
******************************************************************************/ */
package io.github.dsheirer.source.heartbeat; package io.github.dsheirer.source.heartbeat;
import io.github.dsheirer.sample.Listener;
public interface IHeartbeatProvider 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 * 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 * 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 * it under the terms of the GNU General Public License as published by
@@ -20,7 +20,7 @@
package io.github.dsheirer.source.tuner; package io.github.dsheirer.source.tuner;
import io.github.dsheirer.preference.UserPreferences; 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.Broadcaster;
import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.ISourceEventProcessor; 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 * This program is free software: you can redistribute it and/or modify
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any * it under the terms of the GNU General Public License as published by
* later version. * 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 * This program is distributed in the hope that it will be useful,
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * 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. * You should have received a copy of the GNU General Public License
* If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
******************************************************************************/ */
package io.github.dsheirer.source.tuner; package io.github.dsheirer.source.tuner;
import io.github.dsheirer.properties.SystemProperties; import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.Source; import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceException; 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.TunerEvent.Event;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification; import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel; 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 * Iterates current tuners to get a tuner channel source for the specified frequency and bandwidth
* specified in the channel config's source config object.
* *
* Returns null if no tuner can source the channel * 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(); if(tunerChannel != null && channelSpecification != null)
tunerChannel.setBandwidth(channelSpecification.getBandwidth());
Iterator<Tuner> it = mTuners.iterator();
Tuner tuner;
if(config.hasPreferredTuner())
{ {
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 try
{ {
retVal = tuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification); source = tuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification);
if(retVal != null)
{
return retVal;
}
} }
catch(Exception e) 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) return source;
{
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;
} }
} }

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 * 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 * 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 * 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 * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
******************************************************************************/ */
package io.github.dsheirer.source.tuner.channel; package io.github.dsheirer.source.tuner.channel;
import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.Listener;
@@ -117,7 +118,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
public void start() public void start()
{ {
//Broadcast current frequency and sample rate so consumer can configure correctly //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)); broadcastProducerSourceEvent(SourceEvent.startSampleStreamRequest(this));
mScheduledIntervalProcessor.start(); mScheduledIntervalProcessor.start();
} }
@@ -186,6 +187,10 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE: case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
setChannelFrequencyCorrection(sourceEvent.getValue().longValue()); setChannelFrequencyCorrection(sourceEvent.getValue().longValue());
break; break;
case NOTIFICATION_FREQUENCY_ROTATION_FAILURE:
case NOTIFICATION_FREQUENCY_ROTATION_SUCCESS:
//Ignore
break;
default: default:
mLog.error("Ignoring unrecognized source event: " + sourceEvent.getEvent() + " from [" + mLog.error("Ignoring unrecognized source event: " + sourceEvent.getEvent() + " from [" +
(sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "") + "]"); (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 * This program is free software: you can redistribute it and/or modify
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any * it under the terms of the GNU General Public License as published by
* later version. * 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 * This program is distributed in the hope that it will be useful,
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * 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. * You should have received a copy of the GNU General Public License
* If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
******************************************************************************/ */
package io.github.dsheirer.source.tuner.frequency; package io.github.dsheirer.source.tuner.frequency;
import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.ISourceEventProcessor;
@@ -267,7 +271,7 @@ public class FrequencyController
*/ */
protected void broadcastFrequencyChange() throws SourceException 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 * This program is free software: you can redistribute it and/or modify
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any * it under the terms of the GNU General Public License as published by
* later version. * 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 * This program is distributed in the hope that it will be useful,
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * 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. * You should have received a copy of the GNU General Public License
* If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
******************************************************************************/ */
package io.github.dsheirer.spectrum; package io.github.dsheirer.spectrum;
import com.jidesoft.swing.JideSplitPane; 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 //Fire frequency and sample rate change events so that the spectrum
//and overlay panels can synchronize //and overlay panels can synchronize
process(SourceEvent.frequencyChange(mTuner.getTunerController().getFrequency())); process(SourceEvent.frequencyChange(null, mTuner.getTunerController().getFrequency()));
process(SourceEvent.sampleRateChange(mTuner.getTunerController().getSampleRate())); process(SourceEvent.sampleRateChange(mTuner.getTunerController().getSampleRate()));
} }