Compare commits

..

16 Commits

Author SHA1 Message Date
Denny
88f124af9d Updates build to 0.5.0 Alpha 6. 2020-10-26 04:27:20 -04:00
Denny Sheirer
d34460c4e3 #986 Resolves issue with HackRF tuner not loading correctly. (#987)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-26 04:26:29 -04:00
Denny Sheirer
f4f3db7436 #973 Adds concurrent locking to primary tuner controller methods. (#983)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-25 08:59:06 -04:00
Denny
3ebfa90ca1 Updates build to 0.5.0 Alpha 5 2020-10-25 07:51:13 -04:00
Denny Sheirer
733fdd0d7a #981 Sets channel short title as ignore for serialization. (#982)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-25 07:49:12 -04:00
Noah Lynch
09893605eb Only update ViewByIdentifierEditor's list when the editor is selected (#944)
* Add type parameters to TableColumns to fix compiler warnings

* Only update AliasViewByIdentifierEditor list when the editor is selected

This prevents the editor's list from being rebuilt in the background as
aliases are updated.  This caused a massive slowdown when editing many
aliases with the AliasBulkEditor.

Bulk editing 1000 aliases apply time reduced from over a minute to under
5 seconds.
2020-10-25 06:54:04 -04:00
Robert Scullin
57468ef932 Fixes #740 - Adds Encrypted state to the Status column for P25 Phase 1 (#975) 2020-10-25 06:46:02 -04:00
Robert Scullin
c6afb1c133 Fixes #815 - Change usable bandwidth percentage based on Airspy samplerate (#976) 2020-10-25 06:33:25 -04:00
Denny Sheirer
05c0b51fde #979 Updates DMR Tier III channel number issues and provides user option to ignore CRC checksums for DMR decoder with RAS-enabled systems. Adds support for Tier III traffic channel system parameters SLCO message. (#980)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-25 06:09:57 -04:00
Denny Sheirer
de8cfa24f9 #959 Updates DMR decode event tracking and Con+ channel grant management. (#978)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-25 04:51:36 -04:00
Denny Sheirer
fe714c69a7 #956 Resolves issue with null pointer in identifier collection on channel start. (#977)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-24 04:43:15 -04:00
Denny Sheirer
ac219cf2ba #969 Resolves issue where P25P2 NAC value is saved incorrectly by configuration editor. (#970)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-17 04:47:48 -04:00
Denny Sheirer
77b1102100 #942 Adds channel number to neighbor report logging. (#965)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-16 07:02:29 -04:00
Denny Sheirer
5117e6cbe8 #961 Resolves issue where invoking channel actions from Swing thread causes error on JavaFX GUI thread. Removes the Start/Disable/Delete commands from the Swing menu options and replaces with a single View/Edit call to the JavaFX channel editor. Although this reduces ease/functionality, it sidesteps code complications until the main GUI can be recreated in JavaFX. (#964)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-16 06:38:05 -04:00
Denny Sheirer
61c4e13c3e #954 Channel rotation delay default value too small for MPT1327 and P25 channels. (#963)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-16 06:04:17 -04:00
Denny Sheirer
f1190b7be3 #960 Adds handling to broadcastify streaming to not log an error when broadcastify tells us to skip an audio upload. This is not an error because another broadcaster has already uploaded the audio segment. (#962)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-16 05:19:50 -04:00
52 changed files with 1314 additions and 665 deletions

View File

@@ -33,7 +33,7 @@ repositories {
maven { url "https://jitpack.io" }
}
version = '0.5.0-alpha4'
version = '0.5.0-alpha6'
sourceCompatibility = '13'
sourceSets {

View File

@@ -323,6 +323,11 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
audioRecording.removePendingReplay();
}
}
else if(urlResponse.startsWith("1 SKIPPED"))
{
//Broadcastify is telling us to skip audio upload - someone already uploaded it
audioRecording.removePendingReplay();
}
else
{
mLog.error("Broadcastify calls API upload URL request failed [" + urlResponse + "]");

View File

@@ -25,7 +25,8 @@ import io.github.dsheirer.channel.state.State;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.controller.channel.ChannelModel;
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
import io.github.dsheirer.controller.channel.ChannelUtils;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.playlist.channel.ViewChannelRequest;
import io.github.dsheirer.icon.IconModel;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
@@ -412,11 +413,12 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
if(channel != null)
{
popupMenu.add(ChannelUtils.getContextMenu(mChannelModel, mChannelProcessingManager, channel, mTable));
JMenuItem viewChannel = new JMenuItem("View/Edit: " + channel.getShortTitle());
viewChannel.addActionListener(e2 -> MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel)));
popupMenu.add(viewChannel);
populated = true;
}
}
}
}

View File

@@ -126,8 +126,6 @@ public class Channel extends Configuration implements Listener<SourceEvent>
mChannelID = UNIQUE_ID++;
}
/**
* Creates a (new) deep copy of this channel
*/
@@ -183,6 +181,70 @@ public class Channel extends Configuration implements Listener<SourceEvent>
return channel;
}
/**
* Creates a short title containing the system, site and channel name where each value is constrained to
* ten characters each.
* @return short title
*/
@JsonIgnore
public String getShortTitle()
{
StringBuilder sb = new StringBuilder();
if(getSystem() != null)
{
if(getSystem().length() > 10)
{
sb.append(getSystem().substring(0, 10)).append("..");
}
else
{
sb.append(getSystem());
}
}
else
{
sb.append("No System");
}
sb.append("/");
if(getSite() != null)
{
if(getSite().length() > 10)
{
sb.append(getSite().substring(0, 10)).append("..");
}
else
{
sb.append(getSite());
}
}
else
{
sb.append("No Site");
}
sb.append("/");
if(getName() != null)
{
if(getName().length() > 10)
{
sb.append(getName().substring(0, 10)).append("..");
}
else
{
sb.append(getName());
}
}
else
{
sb.append("No Channel");
}
return sb.toString();
}
/**
* Updates the frequencies property as the source configuration changes or is initialized.
*/

View File

@@ -1,134 +0,0 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.controller.channel;
import io.github.dsheirer.channel.state.DecoderState;
import io.github.dsheirer.module.ProcessingChain;
import io.github.dsheirer.module.decode.event.ActivitySummaryFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JSeparator;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ChannelUtils
{
/**
* Creates a context menu for the channel argument
*/
public static JMenu getContextMenu(final ChannelModel channelModel,
final ChannelProcessingManager channelProcessingManager,
final Channel channel,
final Component anchor)
{
if(channel != null)
{
JMenu menu = new JMenu("Channel: " + channel.getName());
if(channel.isProcessing())
{
JMenuItem disable = new JMenuItem("Disable");
disable.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
channelProcessingManager.receive(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_DISABLE));
}
});
menu.add(disable);
menu.add(new JSeparator());
JMenuItem actySummaryItem =
new JMenuItem("Activity Summary");
actySummaryItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
StringBuilder sb = new StringBuilder();
ProcessingChain chain = channelProcessingManager.getProcessingChain(channel);
if(chain != null)
{
for(DecoderState decoderState : chain.getDecoderStates())
{
sb.append(decoderState.getActivitySummary());
}
}
new ActivitySummaryFrame(sb.toString(), anchor);
}
});
menu.add(actySummaryItem);
}
else
{
JMenuItem enable = new JMenuItem("Enable");
enable.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
channelProcessingManager.receive(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_ENABLE));
}
});
menu.add(enable);
}
menu.add(new JSeparator());
JMenuItem deleteItem = new JMenuItem("Delete");
deleteItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
int response = JOptionPane.showConfirmDialog(anchor,
"Do you want to delete channel " + channel.getName() + "?",
"Are you sure?", JOptionPane.YES_NO_CANCEL_OPTION);
if(response == JOptionPane.YES_OPTION)
{
channelModel.receive(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_DELETE));
}
}
});
menu.add(deleteItem);
return menu;
}
return null;
}
}

View File

@@ -56,7 +56,7 @@ public class ChannelStartProcessingRequest extends ModuleEventBusMessage
{
mChannel = channel;
mChannelDescriptor = channelDescriptor;
mIdentifierCollection = identifierCollection;
mIdentifierCollection = (identifierCollection != null ? identifierCollection : new IdentifierCollection());
mTrafficChannelManager = trafficChannelManager;
}

View File

@@ -165,34 +165,25 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
{
PolyphaseChannelSource channelSource = null;
try
List<Integer> polyphaseIndexes = mChannelCalculator.getChannelIndexes(tunerChannel);
IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
if(outputProcessor != null)
{
List<Integer> polyphaseIndexes = mChannelCalculator.getChannelIndexes(tunerChannel);
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
if(outputProcessor != null)
try
{
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
mChannelCalculator.getChannelSampleRate(), centerFrequency, channelSpecification);
try
{
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
mChannelCalculator.getChannelSampleRate(), centerFrequency, channelSpecification);
mChannelSources.add(channelSource);
}
catch(FilterDesignException fde)
{
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
}
mChannelSources.add(channelSource);
}
catch(FilterDesignException fde)
{
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
}
}
catch(IllegalArgumentException iae)
{
mLog.info("Can't provide DDC for " + tunerChannel.toString() + " due to channelizer frequency [" +
mChannelCalculator.getCenterFrequency() + "] and sample rate [" +
(mChannelCalculator.getChannelCount() * mChannelCalculator.getChannelBandwidth()) + "]");
}
return channelSource;

View File

@@ -124,7 +124,7 @@ public class AliasEditor extends TabPane
{
if(mAliasViewByIdentifierEditor == null)
{
mAliasViewByIdentifierEditor = new AliasViewByIdentifierEditor(mPlaylistManager);
mAliasViewByIdentifierEditor = new AliasViewByIdentifierEditor(mPlaylistManager, getAliasIdentifierTab().selectedProperty());
}
return mAliasViewByIdentifierEditor;

View File

@@ -25,11 +25,12 @@ import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.playlist.PlaylistManager;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.transformation.FilteredList;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
@@ -63,15 +64,19 @@ public class AliasViewByIdentifierEditor extends VBox
private TableView<AliasAndIdentifier> mAliasAndIdentifierTableView;
private TableColumn<AliasAndIdentifier,String> mIdentifierColumn;
private Button mViewAliasButton;
private boolean aliasListInvalidated;
public AliasViewByIdentifierEditor(PlaylistManager playlistManager)
public AliasViewByIdentifierEditor(PlaylistManager playlistManager, ReadOnlyBooleanProperty tabSelectedProperty)
{
aliasListInvalidated = false;
mPlaylistManager = playlistManager;
mPlaylistManager.getAliasModel().aliasList().addListener((ListChangeListener<Alias>)c -> {
//Refresh the list any time that the aliases change since we're using a static snapshot
AliasIDType selected = getAliasIDTypeComboBox().getSelectionModel().getSelectedItem();
getAliasIDTypeComboBox().getSelectionModel().select(null);
getAliasIDTypeComboBox().getSelectionModel().select(selected);
//Note if aliases change since we're using a static snapshot
mPlaylistManager.getAliasModel().aliasList().addListener((InvalidationListener)observable -> aliasListInvalidated = true);
//Only update the list when this tab is selected to avoid many slow updates in the background
tabSelectedProperty.addListener(observable -> {
if (aliasListInvalidated)
updateList();
});
GridPane gridPane = new GridPane();
@@ -131,6 +136,8 @@ public class AliasViewByIdentifierEditor extends VBox
}
getAliasAndIdentifierTableView().getSortOrder().setAll(mIdentifierColumn);
aliasListInvalidated = false;
}
/**
@@ -190,22 +197,22 @@ public class AliasViewByIdentifierEditor extends VBox
{
if(mAliasAndIdentifierTableView == null)
{
mAliasAndIdentifierTableView = new TableView(FXCollections.observableArrayList(AliasAndIdentifier.extractor()));
mAliasAndIdentifierTableView = new TableView<>(FXCollections.observableArrayList(AliasAndIdentifier.extractor()));
mAliasAndIdentifierTableView.setPlaceholder(new Label("No aliases or identifiers available"));
mIdentifierColumn = new TableColumn<>("Identifier");
mIdentifierColumn.setCellValueFactory(new PropertyValueFactory<>("identifier"));
mIdentifierColumn.setPrefWidth(350);
TableColumn aliasColumn = new TableColumn();
TableColumn<AliasAndIdentifier, String> aliasColumn = new TableColumn<>();
aliasColumn.setText("Alias");
aliasColumn.setCellValueFactory(new PropertyValueFactory<>("alias"));
aliasColumn.setPrefWidth(200);
TableColumn groupColumn = new TableColumn();
TableColumn<AliasAndIdentifier, String> groupColumn = new TableColumn<>();
groupColumn.setText("Group");
groupColumn.setCellValueFactory(new PropertyValueFactory<>("group"));
groupColumn.setPrefWidth(200);
mAliasAndIdentifierTableView.getColumns().addAll(mIdentifierColumn, aliasColumn, groupColumn);
mAliasAndIdentifierTableView.setMaxHeight(Double.MAX_VALUE);

View File

@@ -138,7 +138,7 @@ public class AMConfigurationEditor extends ChannelConfigurationEditor
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel());
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()

View File

@@ -84,6 +84,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
private EventLogConfigurationEditor mEventLogConfigurationEditor;
private RecordConfigurationEditor mRecordConfigurationEditor;
private ToggleSwitch mIgnoreDataCallsButton;
private ToggleSwitch mIgnoreCRCChecksumsButton;
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
private TableView<TimeslotFrequency> mTimeslotFrequencyTable;
private IntegerTextField mLogicalSlotNumberField;
@@ -149,17 +150,25 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
GridPane.setConstraints(getIgnoreDataCallsButton(), 2, row);
gridPane.getChildren().add(getIgnoreDataCallsButton());
Label directionLabel = new Label("Ignore Data Calls");
GridPane.setHalignment(directionLabel, HPos.LEFT);
GridPane.setConstraints(directionLabel, 3, row);
gridPane.getChildren().add(directionLabel);
Label ignoreDataLabel = new Label("Ignore Data Calls");
GridPane.setHalignment(ignoreDataLabel, HPos.LEFT);
GridPane.setConstraints(ignoreDataLabel, 3, row);
gridPane.getChildren().add(ignoreDataLabel);
GridPane.setConstraints(getIgnoreCRCChecksumsButton(), 4, row);
gridPane.getChildren().add(getIgnoreCRCChecksumsButton());
Label ignoreCRCLabel = new Label("Ignore CRC Checksums (RAS)");
GridPane.setHalignment(ignoreCRCLabel, HPos.LEFT);
GridPane.setConstraints(ignoreCRCLabel, 5, row);
gridPane.getChildren().add(ignoreCRCLabel);
Label timeslotTableLabel = new Label("Logical Slot Number (LSN) to Frequency Map. Required for: Connect Plus and Tier-III systems that don't use absolute frequencies");
GridPane.setHalignment(timeslotTableLabel, HPos.LEFT);
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 4, 1);
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 6, 1);
gridPane.getChildren().add(timeslotTableLabel);
GridPane.setConstraints(getTimeslotTable(), 0, ++row, 4, 3);
GridPane.setConstraints(getTimeslotTable(), 0, ++row, 6, 3);
gridPane.getChildren().add(getTimeslotTable());
VBox buttonsBox = new VBox();
@@ -167,7 +176,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
buttonsBox.setSpacing(10);
buttonsBox.getChildren().addAll(getAddTimeslotFrequencyButton(), getDeleteTimeslotFrequencyButton());
GridPane.setConstraints(buttonsBox, 4, row, 1, 3);
GridPane.setConstraints(buttonsBox, 6, row, 1, 3);
gridPane.getChildren().addAll(buttonsBox);
row += 3;
@@ -231,7 +240,10 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), true);
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(),
DecodeConfigDMR.CHANNEL_ROTATION_DELAY_MINIMUM_MS,
DecodeConfigDMR.CHANNEL_ROTATION_DELAY_MAXIMUM_MS,
DecodeConfigDMR.CHANNEL_ROTATION_DELAY_DEFAULT_MS);
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()
@@ -486,6 +498,19 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
return mIgnoreDataCallsButton;
}
private ToggleSwitch getIgnoreCRCChecksumsButton()
{
if(mIgnoreCRCChecksumsButton == null)
{
mIgnoreCRCChecksumsButton = new ToggleSwitch();
mIgnoreCRCChecksumsButton.setDisable(true);
mIgnoreCRCChecksumsButton.selectedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mIgnoreCRCChecksumsButton;
}
private Spinner<Integer> getTrafficChannelPoolSizeSpinner()
{
if(mTrafficChannelPoolSizeSpinner == null)
@@ -550,6 +575,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
@Override
protected void setDecoderConfiguration(DecodeConfiguration config)
{
getIgnoreCRCChecksumsButton().setDisable(config == null);
getIgnoreDataCallsButton().setDisable(config == null);
getTrafficChannelPoolSizeSpinner().setDisable(config == null);
getTimeslotTable().getItems().clear();
@@ -569,6 +595,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
DecodeConfigDMR decodeConfig = (DecodeConfigDMR)config;
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
getIgnoreCRCChecksumsButton().setSelected(decodeConfig.getIgnoreCRCChecksums());
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
for(TimeslotFrequency timeslotFrequency: decodeConfig.getTimeslotMap())
@@ -578,6 +605,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
}
else
{
getIgnoreCRCChecksumsButton().setSelected(false);
getIgnoreDataCallsButton().setSelected(false);
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(0);
getChannelRotationDelaySpinner().getValueFactory().setValue(200);
@@ -599,6 +627,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
}
// config.setChannelRotationDelay(getChannelRotationDelaySpinner().getValue());
config.setIgnoreCRCChecksums(getIgnoreCRCChecksumsButton().isSelected());
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
config.setTimeslotMap(new ArrayList<>(getTimeslotTable().getItems()));

View File

@@ -210,7 +210,7 @@ public class LTRConfigurationEditor extends ChannelConfigurationEditor
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel());
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()

View File

@@ -210,7 +210,7 @@ public class LTRNetConfigurationEditor extends ChannelConfigurationEditor
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel());
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()

View File

@@ -195,7 +195,10 @@ public class MPT1327ConfigurationEditor extends ChannelConfigurationEditor
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), true);
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(),
DecodeConfigMPT1327.CHANNEL_ROTATION_DELAY_MINIMUM_MS,
DecodeConfigMPT1327.CHANNEL_ROTATION_DELAY_MAXIMUM_MS,
DecodeConfigMPT1327.CHANNEL_ROTATION_DELAY_DEFAULT_MS);
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()

View File

@@ -200,7 +200,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel());
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()

View File

@@ -191,7 +191,10 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), true);
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(),
DecodeConfigP25Phase1.CHANNEL_ROTATION_DELAY_MINIMUM_MS,
DecodeConfigP25Phase1.CHANNEL_ROTATION_DELAY_MAXIMUM_MS,
DecodeConfigP25Phase1.CHANNEL_ROTATION_DELAY_DEFAULT_MS);
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()

View File

@@ -179,7 +179,7 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel());
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()
@@ -305,7 +305,7 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
config.setAutoDetectScrambleParameters(false);
int wacn = getWacnTextField().get();
int system = getSystemTextField().get();
int nac = getSystemTextField().get();
int nac = getNacTextField().get();
config.setScrambleParameters(new ScrambleParameters(wacn, system, nac));
}
else

View File

@@ -155,7 +155,7 @@ public class PassportConfigurationEditor extends ChannelConfigurationEditor
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel());
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()

View File

@@ -65,19 +65,42 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
private ComboBox<String> mPreferredTunerComboBox;
private VBox mFrequencyBoxContainer;
private Spinner<Integer> mChannelRotationDelaySpinner;
private boolean mAllowMultipleFrequencies;
private boolean mAllowMultipleFrequencies = false;
private int mFrequencyRotationDefault = ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_DEFAULT;
private int mFrequencyRotationMinimum = ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_MINIMUM;
private int mFrequencyRotationMaximum = ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_MAXIMUM;
/**
* Constructs an instance.
* Constructs an instance with support for multiple frequency values. The min and max rotation delay values
* define constraints for the channel rotation delay monitor, how long the monitor dwells on each frequency in a
* multi-frequency configuration.
*
* @param tunerModel for accessing list of tuners to select a preferred tuner
* @param allowMultipleFrequencies toggles behavior of the editor between single frequency (false) or multiple
* frequencies (true).
* @param minRotationDelay in milliseconds for channel/frequency dwell
* @param maxRotationDelay in milliseconds for channel/frequency dwell
*/
public FrequencyEditor(TunerModel tunerModel, boolean allowMultipleFrequencies)
public FrequencyEditor(TunerModel tunerModel, int minRotationDelay, int maxRotationDelay, int defaultRotationDelay)
{
mTunerModel = tunerModel;
mAllowMultipleFrequencies = allowMultipleFrequencies;
mAllowMultipleFrequencies = true;
mFrequencyRotationMinimum = minRotationDelay;
mFrequencyRotationMaximum = maxRotationDelay;
mFrequencyRotationDefault = defaultRotationDelay;
init();
}
/**
* Constructs an instance with a default behavior of single frequency support only.
* @param tunerModel for accessing list of tuners to select a preferred tuner
*/
public FrequencyEditor(TunerModel tunerModel)
{
mTunerModel = tunerModel;
init();
}
private void init()
{
HBox hBox = new HBox();
hBox.setPadding(new Insets(10,10,10,10));
hBox.setSpacing(10);
@@ -93,17 +116,25 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
tunerBox.setSpacing(10);
tunerBox.getChildren().addAll(preferredTunerLabel, getPreferredTunerComboBox());
Label frequencyRotationLabel = new Label("Frequency Rotation Delay (ms)");
HBox frequencyBox = new HBox();
frequencyBox.setAlignment(Pos.CENTER_LEFT);
frequencyBox.setSpacing(10);
frequencyBox.getChildren().addAll(frequencyRotationLabel, getFrequencyRotationDelaySpinner());
getFrequencyRotationDelaySpinner().disableProperty().bind(Bindings.greaterThan(2, Bindings.size(mFrequencyBoxes)));
VBox vbox = new VBox();
vbox.setSpacing(10);
vbox.setPadding(new Insets(0,0,0,20));
vbox.getChildren().addAll(tunerBox, frequencyBox);
if(mAllowMultipleFrequencies)
{
Label frequencyRotationLabel = new Label("Frequency Rotation Delay (ms)");
HBox frequencyBox = new HBox();
frequencyBox.setAlignment(Pos.CENTER_LEFT);
frequencyBox.setSpacing(10);
frequencyBox.getChildren().addAll(frequencyRotationLabel, getFrequencyRotationDelaySpinner());
getFrequencyRotationDelaySpinner().disableProperty().bind(Bindings.greaterThan(2, Bindings.size(mFrequencyBoxes)));
vbox.getChildren().addAll(tunerBox, frequencyBox);
}
else
{
vbox.getChildren().addAll(tunerBox);
}
hBox.getChildren().add(vbox);
@@ -225,7 +256,25 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
SourceConfigTunerMultipleFrequency sourceMulti = (SourceConfigTunerMultipleFrequency)sourceConfiguration;
setFrequencies(sourceMulti.getFrequencies());
preferredTuner = sourceMulti.getPreferredTuner();
getFrequencyRotationDelaySpinner().getValueFactory().setValue(sourceMulti.getFrequencyRotationDelay());
int rotationDelay = sourceMulti.getFrequencyRotationDelay();
if(rotationDelay < mFrequencyRotationMinimum)
{
rotationDelay = mFrequencyRotationMinimum;
//For backward compatibility, we'll automatically update the value in the source config
sourceMulti.setFrequencyRotationDelay(rotationDelay);
}
if(rotationDelay > mFrequencyRotationMaximum)
{
rotationDelay = mFrequencyRotationMaximum;
//For backward compatibility, we'll automatically update the value in the source config
sourceMulti.setFrequencyRotationDelay(rotationDelay);
}
getFrequencyRotationDelaySpinner().getValueFactory().setValue(rotationDelay);
}
else
{
@@ -236,7 +285,6 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
updatePreferredTuners();
if(preferredTuner != null)
{
if(!getPreferredTunerComboBox().getItems().contains(preferredTuner))
@@ -267,9 +315,8 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
mChannelRotationDelaySpinner.setTooltip(
new Tooltip("Delay on each frequency before rotating to next when seeking to next active channel frequency"));
mChannelRotationDelaySpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
int min = ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_MINIMUM;
int max = ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_MAXIMUM;
SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(min, max, min, 50);
SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(mFrequencyRotationMinimum,
mFrequencyRotationMaximum, mFrequencyRotationMinimum, 50);
mChannelRotationDelaySpinner.setValueFactory(svf);
mChannelRotationDelaySpinner.getValueFactory().valueProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
@@ -278,7 +325,6 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
return mChannelRotationDelaySpinner;
}
/**
* Updates the preferred tuner's combo box to reflect the current set of tuners.
*/

View File

@@ -74,7 +74,6 @@ import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -95,15 +94,16 @@ public class DMRDecoderState extends TimeslotDecoderState
private DMRTrafficChannelManager mTrafficChannelManager;
private DecodeEvent mCurrentCallEvent;
private long mCurrentFrequency;
private Map<Long,DecodeEvent> mDetectedCallEventsMap = new TreeMap<>();
private boolean mIgnoreCRCChecksums;
private Map<Integer,DecodeEvent> mDetectedCallEventsMap = new TreeMap<>();
private static final AddChannelRotationActiveStateRequest CAPACITY_PLUS_ACTIVE_STATE_REQUEST =
new AddChannelRotationActiveStateRequest(State.ACTIVE);
/**
* Constructs an DMR decoder state with an optional traffic channel manager.
* @param channel with configuration details
* @param timeslot for this decoder state (1 or 2)
* @param trafficChannelManager for handling traffic channel grants.
* @param configurationMonitor for tracking activity summary
*/
public DMRDecoderState(Channel channel, int timeslot, DMRTrafficChannelManager trafficChannelManager)
{
@@ -111,12 +111,18 @@ public class DMRDecoderState extends TimeslotDecoderState
mChannel = channel;
mTrafficChannelManager = trafficChannelManager;
//The decoder state passes all messages to the network configuration monitor, so we only contruct
//The decoder state passes all messages to the network configuration monitor, so we only construct
//the monitor for timeslot 1.
if(timeslot == 1)
{
mNetworkConfigurationMonitor = new DMRNetworkConfigurationMonitor(channel);
}
//For RAS protected systems, allows user to ignore CRC checksums and still decode the system
if(channel.getDecodeConfiguration() instanceof DecodeConfigDMR)
{
mIgnoreCRCChecksums = ((DecodeConfigDMR)channel.getDecodeConfiguration()).getIgnoreCRCChecksums();
}
}
/**
@@ -181,7 +187,7 @@ public class DMRDecoderState extends TimeslotDecoderState
@Override
public void receive(IMessage message)
{
if(message.isValid() && message.getTimeslot() == getTimeslot())
if(message.getTimeslot() == getTimeslot())
{
if(message instanceof VoiceMessage)
{
@@ -191,11 +197,11 @@ public class DMRDecoderState extends TimeslotDecoderState
{
processData((DataMessage)message);
}
else if(message instanceof LCMessage)
else if((message.isValid() || mIgnoreCRCChecksums) && message instanceof LCMessage)
{
processLinkControl((LCMessage)message, false);
}
else if(message instanceof DMRPacketMessage)
else if(message.isValid() && message instanceof DMRPacketMessage)
{
processPacket((DMRPacketMessage)message);
}
@@ -205,13 +211,14 @@ public class DMRDecoderState extends TimeslotDecoderState
}
}
//SLCO messages on timeslot 0 to catch capacity plus rest channel events
else if(message.isValid() && message.getTimeslot() == 0 && message instanceof LCMessage)
else if((message.isValid() || mIgnoreCRCChecksums) && message.getTimeslot() == 0 && message instanceof LCMessage)
{
processLinkControl((LCMessage)message, false);
}
//Pass the message to the network configuration monitor, if this decoder state has a non-null instance
if(mNetworkConfigurationMonitor != null && message.isValid() && message instanceof DMRMessage)
if(mNetworkConfigurationMonitor != null && (message.isValid() || mIgnoreCRCChecksums) &&
message instanceof DMRMessage)
{
mNetworkConfigurationMonitor.process((DMRMessage)message);
}
@@ -265,7 +272,7 @@ public class DMRDecoderState extends TimeslotDecoderState
*/
private void processPacket(DMRPacketMessage packet)
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA, getTimeslot()));
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
DecodeEvent packetEvent = DMRDecodeEvent.builder(packet.getTimestamp())
.eventDescription(DecodeEventType.DATA_PACKET.name())
@@ -302,7 +309,7 @@ public class DMRDecoderState extends TimeslotDecoderState
case MBC_ENC_HEADER:
case DATA_ENC_HEADER:
case CHANNEL_CONTROL_ENC_HEADER:
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA, getTimeslot()));
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
break;
}
@@ -317,13 +324,16 @@ public class DMRDecoderState extends TimeslotDecoderState
/**
* Process Data Messages
*
* Note: invalid messages are allowed to pass to this method. Messages are selectively checked for isValid()
* to overcome RAS implementation in certain systems.
*/
private void processData(DataMessage message)
{
switch(message.getSlotType().getDataType())
{
case CSBK:
if(message instanceof CSBKMessage)
if((message.isValid() || mIgnoreCRCChecksums) && message instanceof CSBKMessage)
{
processCSBK((CSBKMessage)message);
}
@@ -358,10 +368,12 @@ public class DMRDecoderState extends TimeslotDecoderState
processTerminator((Terminator)message);
}
break;
case MBC_BLOCK:
case RATE_1_OF_2_DATA:
case RATE_3_OF_4_DATA:
case RATE_1_DATA:
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
break;
case MBC_BLOCK:
case RESERVED_15:
case UNKNOWN:
default:
@@ -408,7 +420,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(csbk instanceof Acknowledge)
{
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.RESPONSE.name())
.eventDescription(DecodeEventType.RESPONSE.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details(((Acknowledge)csbk).getReason().toString())
@@ -423,7 +435,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(csbk instanceof Acknowledge)
{
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.RESPONSE.name())
.eventDescription(DecodeEventType.RESPONSE.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details(((Acknowledge)csbk).getReason().toString())
@@ -440,7 +452,7 @@ public class DMRDecoderState extends TimeslotDecoderState
{
case AUTHENTICATE_REGISTER_RADIO_CHECK_SERVICE:
DecodeEvent registerEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.COMMAND.name())
.eventDescription(DecodeEventType.COMMAND.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details(DecodeEventType.REGISTER.toString())
@@ -449,7 +461,7 @@ public class DMRDecoderState extends TimeslotDecoderState
break;
case CANCEL_CALL_SERVICE:
DecodeEvent cancelEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.COMMAND.name())
.eventDescription(DecodeEventType.COMMAND.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details("CANCEL CALL")
@@ -460,7 +472,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(csbk instanceof StunReviveKill)
{
DecodeEvent stunEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.COMMAND.name())
.eventDescription(DecodeEventType.COMMAND.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details(((StunReviveKill)csbk).getCommand() + " RADIO")
@@ -481,7 +493,7 @@ public class DMRDecoderState extends TimeslotDecoderState
ServiceRadioCheck src = (ServiceRadioCheck)csbk;
DecodeEvent checkEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.RADIO_CHECK.name())
.eventDescription(DecodeEventType.RADIO_CHECK.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details(src.getServiceDescription() + " SERVICE FOR " +
@@ -502,7 +514,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(aloha.hasRadioIdentifier())
{
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.RESPONSE.name())
.eventDescription(DecodeEventType.RESPONSE.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details("Aloha Acknowledge")
@@ -521,7 +533,7 @@ public class DMRDecoderState extends TimeslotDecoderState
{
case MASS_REGISTRATION:
DecodeEvent massEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.REGISTER.name())
.eventDescription(DecodeEventType.REGISTER.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details("MASS REGISTRATION")
@@ -532,7 +544,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(csbk instanceof VoteNowAdvice)
{
DecodeEvent voteEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.COMMAND.name())
.eventDescription(DecodeEventType.COMMAND.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details("VOTE NOW FOR " + ((VoteNowAdvice)csbk).getVotedSystemIdentityCode())
@@ -556,7 +568,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(csbk instanceof Protect)
{
DecodeEvent protectEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.COMMAND.name())
.eventDescription(DecodeEventType.COMMAND.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details("PROTECT: " + ((Protect)csbk).getProtectKind())
@@ -602,12 +614,37 @@ public class DMRDecoderState extends TimeslotDecoderState
{
ChannelGrant dataGrant = (ChannelGrant)csbk;
DMRChannel channel = dataGrant.getChannel();
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
if(hasTrafficChannelManager())
{
mTrafficChannelManager.processChannelGrant(channel, getMergedIdentifierCollection(csbk.getIdentifiers()),
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
csbk.getTimestamp(), csbk.isEncrypted());
}
else
{
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = DMRDecodeEvent.builder(csbk.getTimestamp())
.channel(channel)
.details(csbk.getOpcode().getLabel())
.eventDescription(DecodeEventType.DATA_CALL.toString())
.identifiers(mergedIdentifiers)
.timeslot(channel.getTimeslot())
.build();
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
}
else
{
//Update the ending timestamp for the event and rebroadcast
event.end(csbk.getTimestamp());
}
broadcast(event);
}
processCallDetection(dataGrant.getChannel(), dataGrant.getIdentifiers(), dataGrant.getTimestamp(), DecodeEventType.DATA_CALL);
}
break;
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
@@ -616,12 +653,37 @@ public class DMRDecoderState extends TimeslotDecoderState
{
ChannelGrant tgGrant = (ChannelGrant)csbk;
DMRChannel channel = tgGrant.getChannel();
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
if(hasTrafficChannelManager())
{
mTrafficChannelManager.processChannelGrant(channel, getMergedIdentifierCollection(csbk.getIdentifiers()),
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
}
processCallDetection(tgGrant.getChannel(), tgGrant.getIdentifiers(), tgGrant.getTimestamp(), DecodeEventType.CALL_GROUP);
else
{
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = DMRDecodeEvent.builder(csbk.getTimestamp())
.channel(channel)
.details(csbk.getOpcode().getLabel())
.eventDescription(DecodeEventType.CALL_GROUP.toString())
.identifiers(mergedIdentifiers)
.timeslot(channel.getTimeslot())
.build();
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
}
else
{
//Update the ending timestamp for the event and rebroadcast
event.end(csbk.getTimestamp());
}
broadcast(event);
}
}
break;
case STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT:
@@ -630,13 +692,36 @@ public class DMRDecoderState extends TimeslotDecoderState
{
ChannelGrant channelGrant = (ChannelGrant)csbk;
DMRChannel channel = channelGrant.getChannel();
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
if(hasTrafficChannelManager())
{
mTrafficChannelManager.processChannelGrant(channel, getMergedIdentifierCollection(csbk.getIdentifiers()),
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
}
processCallDetection(channelGrant.getChannel(), channelGrant.getIdentifiers(),
channelGrant.getTimestamp(), DecodeEventType.CALL_UNIT_TO_UNIT);
else
{
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = DMRDecodeEvent.builder(csbk.getTimestamp())
.channel(channel)
.details(csbk.getOpcode().getLabel())
.eventDescription(DecodeEventType.CALL_UNIT_TO_UNIT.toString())
.identifiers(mergedIdentifiers)
.timeslot(channel.getTimeslot())
.build();
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
}
else
{
//Update the ending timestamp for the event and rebroadcast
event.end(csbk.getTimestamp());
}
broadcast(event);
}
}
break;
case MOTOROLA_CAPMAX_ALOHA:
@@ -647,7 +732,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(cmAloha.hasRadioIdentifier())
{
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.RESPONSE.name())
.eventDescription(DecodeEventType.RESPONSE.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details("Aloha Acknowledge")
@@ -664,13 +749,38 @@ public class DMRDecoderState extends TimeslotDecoderState
if(csbk instanceof ConnectPlusDataChannelGrant)
{
ConnectPlusDataChannelGrant cpdcg = (ConnectPlusDataChannelGrant)csbk;
DMRChannel channel = cpdcg.getChannel();
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
if(hasTrafficChannelManager())
{
mTrafficChannelManager.processChannelGrant(cpdcg.getChannel(),
getMergedIdentifierCollection(csbk.getIdentifiers()), csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
csbk.getTimestamp(), csbk.isEncrypted());
}
else
{
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = DMRDecodeEvent.builder(csbk.getTimestamp())
.channel(channel)
.details(csbk.getOpcode().getLabel())
.eventDescription(DecodeEventType.DATA_CALL.toString())
.identifiers(mergedIdentifiers)
.timeslot(channel.getTimeslot())
.build();
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
}
else
{
//Update the ending timestamp for the event and rebroadcast
event.end(csbk.getTimestamp());
}
broadcast(event);
}
processCallDetection(cpdcg.getChannel(), cpdcg.getIdentifiers(), cpdcg.getTimestamp(),
DecodeEventType.DATA_CALL);
}
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
break;
@@ -697,14 +807,39 @@ public class DMRDecoderState extends TimeslotDecoderState
case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER:
if(csbk instanceof ConnectPlusVoiceChannelUser)
{
DMRChannel channel = ((ConnectPlusVoiceChannelUser)csbk).getChannel();
ConnectPlusVoiceChannelUser cpvcu = (ConnectPlusVoiceChannelUser)csbk;
DMRChannel channel = cpvcu.getChannel();
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
if(hasTrafficChannelManager())
{
mTrafficChannelManager.processChannelGrant(channel, getMergedIdentifierCollection(csbk.getIdentifiers()),
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
csbk.getTimestamp(), csbk.isEncrypted());
}
else
{
DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
if(isStale(detectedEvent, csbk.getTimestamp(), csbk.getIdentifiers()))
{
detectedEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.channel(channel)
.details(csbk.getOpcode().getLabel())
.eventDescription(DecodeEventType.CALL_GROUP.toString())
.identifiers(mergedIdentifiers)
.timeslot(channel.getTimeslot())
.build();
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), detectedEvent);
}
else
{
//Update the ending timestamp for the event and rebroadcast
detectedEvent.end(csbk.getTimestamp());
}
broadcast(detectedEvent);
}
processCallDetection(channel, csbk.getIdentifiers(), csbk.getTimestamp(),
DecodeEventType.CALL_GROUP);
}
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
break;
@@ -718,9 +853,6 @@ public class DMRDecoderState extends TimeslotDecoderState
broadcast(affiliateEvent);
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
break;
case STANDARD_MOVE_TSCC:
default:
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
break;
@@ -742,68 +874,22 @@ public class DMRDecoderState extends TimeslotDecoderState
}
/**
* Process Call Detections.
* Indicates if the event is a stale event, meaning that the event is null, or the event start exceeds the max
* valid call duration threshold, or if the event identifiers don't match the current identifiers.
*
* Note: once full support for Connect+ trunk tracking is implemented with the DMRTrafficChannelManager, this will
* have to be modified to detect if the trunked channel was allocated or if the call was simply detected. This
* would be contingent on having a map of Logical Slot Numbers to frequency for the traffic channel manager to make
* an allocation.
*
* @param channel for the call event
* @param identifiers for the call event
* @param timestamp of the event or update
* @param event to check for staleness
* @param timestamp to check the event against
* @param currentIdentifiers to compare against the event
* @return true if the event is stale.
*/
private void processCallDetection(DMRChannel channel, List<Identifier> identifiers, long timestamp,
DecodeEventType eventType)
private boolean isStale(DecodeEvent event, long timestamp, List<Identifier> currentIdentifiers)
{
//Check to see if there is a current call event to see if the detected call event is actually for this timeslot
//and we can then identify the LSN for this timeslot
if(mCurrentFrequency == 0 && mCurrentCallEvent != null &&
isSameCall(mCurrentCallEvent.getIdentifierCollection(), identifiers))
if(event == null || (timestamp - event.getTimeStart() > MAX_VALID_CALL_DURATION_MS))
{
mCurrentFrequency = channel.getDownlinkFrequency();
if(mNetworkConfigurationMonitor != null)
{
mNetworkConfigurationMonitor.setCurrentChannel(channel);
}
return true;
}
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
if(event == null)
{
event = DMRDecodeEvent.builder(timestamp)
.timeslot(getTimeslot())
.identifiers(getMergedIdentifierCollection(identifiers))
.eventDescription(eventType.toString())
.build();
// mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
}
else
{
if(event.getIdentifierCollection() != null &&
isSameCall(event.getIdentifierCollection(), identifiers) &&
FastMath.abs(timestamp - event.getTimeStart()) < MAX_VALID_CALL_DURATION_MS)
{
event.update(timestamp);
}
else
{
event = DMRDecodeEvent.builder(timestamp)
.timeslot(getTimeslot())
.identifiers(getMergedIdentifierCollection(identifiers))
.eventDescription(DecodeEventType.CALL_DETECT.toString())
.build();
// mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
}
}
//Only broadcast the call detect event if it doesn't match the current logical slot number
// if(mCurrentLSN == null || mCurrentLSN != channel.getLogicalSlotNumber())
// {
// broadcast(event);
// }
return !isSameCall(event.getIdentifierCollection(), currentIdentifiers);
}
/**
@@ -1006,7 +1092,7 @@ public class DMRDecoderState extends TimeslotDecoderState
ic.update(gpsInformation.getGPSLocation());
DecodeEvent gpsEvent = DMRDecodeEvent.builder(message.getTimestamp())
.eventDescription(DecodeEventType.GPS.name())
.eventDescription(DecodeEventType.GPS.toString())
.identifiers(ic)
.timeslot(message.getTimeslot())
.details("LOCATION:" + gpsInformation.getGPSLocation())

View File

@@ -313,7 +313,7 @@ public class DMRNetworkConfigurationMonitor
if(mTier3Model != null)
{
sb.append("\nNetwork Model").append(mTier3Model);
sb.append("\nNetwork Model: ").append(mTier3Model);
}
//Observed DMR Channels

View File

@@ -41,6 +41,7 @@ import io.github.dsheirer.message.MessageHistoryResponse;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.event.DMRChannelGrantEvent;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventHistory;
@@ -90,8 +91,12 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
IChannelEventProvider
{
private final static Logger mLog = LoggerFactory.getLogger(DMRTrafficChannelManager.class);
public static final String CHANNEL_START_REJECTED = "CHANNEL START REJECTED";
public static final String CHANNEL_START_REJECTED = " REJECTED - NO TUNER";
public static final String DATA_CALL_IGNORED = "DATA CALL IGNORED";
public static final String IGNORED = " - Ignored";
public static final String MAX_TRAFFIC_CHANNELS_EXCEEDED = "MAX TRAFFIC CHANNELS EXCEEDED";
public static final String NO_FREQUENCY = "NO FREQUENCY - CHECK LSN CHANNEL MAP";
public static final long EVENT_TIME_STALE_THRESHOLD = 5000; //5 seconds
private Queue<Channel> mAvailableTrafficChannelQueue = new ConcurrentLinkedQueue<>();
private List<Channel> mManagedTrafficChannels;
@@ -325,7 +330,19 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
if(event != null && isSameCall(identifierCollection, event.getIdentifierCollection()))
if(isStale(event, timestamp, identifierCollection)) //Create new event
{
event = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
.channel(channel)
.eventDescription(getEventType(opcode, identifierCollection, encrypted).toString())
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
.identifiers(identifierCollection)
.build();
mLSNGrantEventMap.put(lsn, event);
}
else //Update current event
{
Identifier from = getIdentifier(identifierCollection, Role.FROM);
@@ -336,115 +353,98 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
{
event.end(timestamp);
DMRChannelGrantEvent continuationGrantEvent = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
event = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
.channel(channel)
.eventDescription(getEventType(opcode, event.isEncrypted()).toString() + " - Continue")
.eventDescription(getEventType(opcode, identifierCollection, encrypted).toString() + " - Continue")
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
.identifiers(identifierCollection)
.build();
mLSNGrantEventMap.put(lsn, continuationGrantEvent);
broadcast(continuationGrantEvent);
mLSNGrantEventMap.put(lsn, event);
broadcast(event);
}
}
//update the ending timestamp so that the duration value is correctly calculated
event.update(timestamp);
broadcast(event);
//Even though we have an event, the initial channel grant may have been rejected. Check to see if there
//is a traffic channel allocated. If not, allocate one and update the event description.
long frequency = channel.getDownlinkFrequency();
if(frequency > 0 && frequency != mCurrentControlFrequency &&
!mAllocatedTrafficChannelFrequencyMap.containsKey(channel.getDownlinkFrequency()) &&
!(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode()))
{
Channel trafficChannel = mAvailableTrafficChannelQueue.poll();
if(trafficChannel != null)
{
event.setEventDescription(getEventType(opcode, event.isEncrypted()).toString());
event.setDetails("CHANNEL GRANT " + (encrypted ? " ENCRYPTED" : ""));
event.setChannelDescriptor(channel);
broadcast(event);
SourceConfigTuner sourceConfig = new SourceConfigTuner();
sourceConfig.setFrequency(frequency);
trafficChannel.setSourceConfiguration(sourceConfig);
mAllocatedTrafficChannelFrequencyMap.put(frequency, trafficChannel);
getInterModuleEventBus().post(new ChannelStartProcessingRequest(trafficChannel, channel, identifierCollection));
}
else
{
String description = (event.getEventDescription() != null ? event.getEventDescription() : "") +
" - MAX TRAFFIC CHANNELS EXCEEDED";
event.setEventDescription(description);
}
}
return;
}
if(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode())
{
DMRChannelGrantEvent channelGrantEvent = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
.encrypted(encrypted)
.channel(channel)
.eventDescription(getEventType(opcode, encrypted).toString() + " - Ignored")
.details("DATA CALL IGNORED")
.identifiers(identifierCollection)
.build();
broadcast(event);
mLSNGrantEventMap.put(channel.getLogicalSlotNumber(), channelGrantEvent);
broadcast(channelGrantEvent);
return;
}
DMRChannelGrantEvent channelGrantEvent = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
.encrypted(encrypted)
.channel(channel)
.eventDescription(getEventType(opcode, encrypted).toString())
.details("CHANNEL GRANT")
.identifiers(identifierCollection)
.build();
mLSNGrantEventMap.put(channel.getLogicalSlotNumber(), channelGrantEvent);
//Allocate a traffic channel for the downlink frequency if one isn't already allocated
//NOTE: we could also allocate a traffic channel for the uplink frequency here, in the future
//Even though we have an event, the initial or continuation channel grant may have been rejected. Check to see
// if there is a traffic channel allocated. If not, allocate one and update the event description.
long frequency = channel.getDownlinkFrequency();
if(frequency > 0 && frequency != mCurrentControlFrequency &&
!mAllocatedTrafficChannelFrequencyMap.containsKey(frequency))
if(frequency == 0)
{
if(event.getDetails() == null)
{
event.setDetails(NO_FREQUENCY);
}
else if(!event.getDetails().endsWith(NO_FREQUENCY))
{
event.setDetails(event.getDetails() + " - " + NO_FREQUENCY);
}
return;
}
if(frequency != mCurrentControlFrequency && !mAllocatedTrafficChannelFrequencyMap.containsKey(frequency))
{
if(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode())
{
if(event.getEventDescription() == null)
{
event.setEventDescription(getEventType(opcode, identifierCollection, encrypted) + IGNORED);
}
else if(!event.getEventDescription().endsWith(IGNORED))
{
event.setEventDescription(event.getEventDescription() + IGNORED);
}
if(event.getDetails() == null)
{
event.setDetails(DATA_CALL_IGNORED);
}
else if(!event.getDetails().endsWith(DATA_CALL_IGNORED))
{
event.setDetails(event.getDetails() + " - " + DATA_CALL_IGNORED);
}
broadcast(event);
return;
}
Channel trafficChannel = mAvailableTrafficChannelQueue.poll();
if(trafficChannel == null)
{
String details = (channelGrantEvent.getDetails() != null ? channelGrantEvent.getDetails() : "") +
" " + MAX_TRAFFIC_CHANNELS_EXCEEDED;
channelGrantEvent.setDetails(details);
channelGrantEvent.setEventDescription(channelGrantEvent.getEventDescription() + " - Ignored");
}
else
if(trafficChannel != null)
{
SourceConfigTuner sourceConfig = new SourceConfigTuner();
sourceConfig.setFrequency(frequency);
trafficChannel.setSourceConfiguration(sourceConfig);
mAllocatedTrafficChannelFrequencyMap.put(frequency, trafficChannel);
broadcast(ChannelEvent.requestEnable(trafficChannel));
getInterModuleEventBus().post(new ChannelStartProcessingRequest(trafficChannel, channel,
identifierCollection));
}
else
{
if(event.getDetails() == null)
{
event.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED);
}
else if(!event.getDetails().endsWith(MAX_TRAFFIC_CHANNELS_EXCEEDED))
{
event.setDetails(event.getDetails() + " - " + MAX_TRAFFIC_CHANNELS_EXCEEDED);
}
}
}
broadcast(channelGrantEvent);
}
/**
* Creates a call event type description for the specified opcode and service options
*/
private DecodeEventType getEventType(Opcode opcode, boolean encrypted)
private DecodeEventType getEventType(Opcode opcode, IdentifierCollection identifierCollection, boolean encrypted)
{
DecodeEventType type = null;
@@ -452,10 +452,22 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
{
case STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT:
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER:
type = encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP;
break;
case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER:
Identifier to = identifierCollection.getToIdentifier();
if(to instanceof DMRRadio)
{
type = encrypted ? DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED : DecodeEventType.CALL_UNIT_TO_UNIT;
}
else
{
type = encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP;
}
break;
case STANDARD_PRIVATE_VOICE_CHANNEL_GRANT:
case STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT:
type = encrypted ? DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED : DecodeEventType.CALL_UNIT_TO_UNIT;
@@ -535,6 +547,31 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
return Objects.equals(toIdentifier1, toIdentifier2);
}
/**
* Indicates if the event is a stale event, meaning that the event is null, or the event start exceeds the max
* valid call duration threshold, or if the event identifiers don't match the current identifiers.
*
* @param event to check for staleness
* @param timestamp to check the event against
* @param currentIdentifiers to compare against the event
* @return true if the event is stale.
*/
private boolean isStale(DecodeEvent event, long timestamp, IdentifierCollection currentIdentifiers)
{
if(event == null)
{
return true;
}
if(timestamp - event.getTimeEnd() > EVENT_TIME_STALE_THRESHOLD)
{
return true;
}
return !isSameCall(event.getIdentifierCollection(), currentIdentifiers);
}
/**
* Retrieves the first identifier with a TO role.
*
@@ -617,9 +654,8 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
/**
* Removes any call events that are associated with the specified frequency.
* @param frequency that was removed
* @param description to optionally append to the details for the event
*/
private void cleanupCallEvents(long frequency, String description)
private void removeCallEvents(long frequency)
{
List<Integer> lsnsToRemove = new ArrayList<>();
@@ -633,20 +669,35 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
for(Integer lsn: lsnsToRemove)
{
DMRChannelGrantEvent event = mLSNGrantEventMap.remove(lsn);
mLSNGrantEventMap.remove(lsn);
}
}
private void updateCallEventDetails(long frequency, String detailFragment)
{
List<Integer> lsnsToUpdate = new ArrayList<>();
for(Map.Entry<Integer,DMRChannelGrantEvent> entry: mLSNGrantEventMap.entrySet())
{
if(entry.getValue().getChannelDescriptor().getDownlinkFrequency() == frequency)
{
lsnsToUpdate.add(entry.getKey());
}
}
for(Integer lsn: lsnsToUpdate)
{
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
if(event != null)
{
if(description != null)
if(event.getDetails() == null)
{
if(event.getEventDescription() != null)
{
event.setEventDescription(event.getEventDescription() + " - " + description);
}
else
{
event.setEventDescription(description);
}
event.setDetails(detailFragment);
}
else if(!event.getDetails().endsWith(detailFragment))
{
event.setDetails(event.getDetails() + detailFragment);
}
broadcast(event);
@@ -659,36 +710,64 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
{
Channel channel = channelEvent.getChannel();
if(channel.isTrafficChannel() && (channelEvent.getEvent() == Event.NOTIFICATION_PROCESSING_STOP ||
channelEvent.getEvent() == Event.NOTIFICATION_PROCESSING_START_REJECTED))
if(channel.isTrafficChannel())
{
String optionalDescription = (channelEvent.getEvent() == Event.NOTIFICATION_PROCESSING_START_REJECTED ?
"Rejected" : null);
long frequencyToRemove = 0;
for(Map.Entry<Long,Channel> entry: mAllocatedTrafficChannelFrequencyMap.entrySet())
switch(channelEvent.getEvent())
{
if(channel == entry.getValue() && entry.getKey() != null)
{
frequencyToRemove = entry.getKey();
cleanupCallEvents(entry.getKey(), optionalDescription);
case NOTIFICATION_PROCESSING_STOP:
long frequencyToRemove = 0;
for(Map.Entry<Long,Channel> entry: mAllocatedTrafficChannelFrequencyMap.entrySet())
{
if(channel == entry.getValue() && entry.getKey() != null)
{
frequencyToRemove = entry.getKey();
removeCallEvents(entry.getKey());
break;
}
}
if(frequencyToRemove > 0)
{
mAllocatedTrafficChannelFrequencyMap.remove(frequencyToRemove);
//Unlock the frequency in the channel rotation monitor
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToRemove));
}
//Add the traffic channel back to the queue to be reused
if(!mAvailableTrafficChannelQueue.contains(channel))
{
mAvailableTrafficChannelQueue.add(channel);
}
break;
}
}
case NOTIFICATION_PROCESSING_START_REJECTED:
long frequencyToUpdate = 0;
if(frequencyToRemove > 0)
{
mAllocatedTrafficChannelFrequencyMap.remove(frequencyToRemove);
for(Map.Entry<Long,Channel> entry: mAllocatedTrafficChannelFrequencyMap.entrySet())
{
if(channel == entry.getValue() && entry.getKey() != null)
{
frequencyToUpdate = entry.getKey();
updateCallEventDetails(entry.getKey(), CHANNEL_START_REJECTED);
break;
}
}
//Unlock the frequency in the channel rotation monitor
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToRemove));
}
if(frequencyToUpdate > 0)
{
mAllocatedTrafficChannelFrequencyMap.remove(frequencyToUpdate);
//Add the traffic channel back to the queue to be reused
if(!mAvailableTrafficChannelQueue.contains(channel))
{
mAvailableTrafficChannelQueue.add(channel);
//Unlock the frequency in the channel rotation monitor
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToUpdate));
}
//Add the traffic channel back to the queue to be reused
if(!mAvailableTrafficChannelQueue.contains(channel))
{
mAvailableTrafficChannelQueue.add(channel);
}
break;
}
}
}

View File

@@ -34,8 +34,12 @@ import java.util.List;
*/
public class DecodeConfigDMR extends DecodeConfiguration
{
public static final int CHANNEL_ROTATION_DELAY_MINIMUM_MS = 200;
public static final int CHANNEL_ROTATION_DELAY_DEFAULT_MS = 500;
public static final int CHANNEL_ROTATION_DELAY_MAXIMUM_MS = 2000;
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
private boolean mIgnoreDataCalls = true;
private boolean mIgnoreCRCChecksums = false;
private List<TimeslotFrequency> mTimeslotMap = new ArrayList<>();
public DecodeConfigDMR()
@@ -87,6 +91,24 @@ public class DecodeConfigDMR extends DecodeConfiguration
mIgnoreDataCalls = ignore;
}
/**
* Indicates if decoder should ignore CRC checksums when validating decoded messages
*/
@JacksonXmlProperty(isAttribute = true, localName = "ignore_crc")
public boolean getIgnoreCRCChecksums()
{
return mIgnoreCRCChecksums;
}
/**
* Sets flag to ignore CRC checksums
* @param ignore true to ignore CRC checksums.
*/
public void setIgnoreCRCChecksums(boolean ignore)
{
mIgnoreCRCChecksums = ignore;
}
/**
* Traffic channel pool size.
* @return

View File

@@ -88,7 +88,6 @@ public class DMRLogicalChannel extends DMRChannel
StringBuilder sb = new StringBuilder();
sb.append("LSN:").append(getLogicalSlotNumber());
sb.append(" LCN:").append(getValue());
sb.append(" TS:").append(getTimeslot());
return sb.toString();
}
}

View File

@@ -0,0 +1,40 @@
package io.github.dsheirer.module.decode.dmr.channel;
/**
* DMR Tier III Trunking Channel
*/
public class DMRTier3Channel extends DMRLogicalChannel
{
/**
* Constructs an instance. Note: radio reference uses a one based index, so we add a value of one to the
* calculated logical slot value for visual compatibility for users.
*
* @param channel number or repeater number
* @param timeslot
*/
public DMRTier3Channel(int channel, int timeslot)
{
super(channel, timeslot);
}
/**
* Logical slot number for this channel.
*
* Formula: LSN = (channel * 2) + timeslot
*
* @return logical slot number, a 1-based index value
*/
@Override
public int getLogicalSlotNumber()
{
int repeater = getRepeater();
if(repeater > 0)
{
return ((repeater) * 2) + getTimeslot();
}
return 0;
}
}

View File

@@ -79,8 +79,6 @@ public enum Opcode
MOTOROLA_CONPLUS_DATA_WINDOW_ANNOUNCEMENT(Vendor.MOTOROLA_CONNECT_PLUS, 28, "ENHANCED DATA REVERT WINDOW ANNOUNCEMENT"),
MOTOROLA_CONPLUS_DATA_WINDOW_GRANT(Vendor.MOTOROLA_CONNECT_PLUS, 29, "ENHANCED DATA REVERT WINDOW GRANT"),
//TODO: Capacity Max (Tier III) also supports Enhanced Data Revert channels with windowing ... they either
//TODO: use CSBKO 41 and 42, or they have a different pair of messages.
MOTOROLA_CAPMAX_ALOHA(Vendor.MOTOROLA_CAPACITY_PLUS, 25, "ALOHA"),
MOTOROLA_CAPPLUS_CALL_ALERT(Vendor.MOTOROLA_CAPACITY_PLUS, 31, "CALL ALERT"),

View File

@@ -26,6 +26,7 @@ import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
@@ -208,7 +209,7 @@ public class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
if(mChannel == null)
{
mChannel = new DMRLogicalChannel(getMoveToChannelNumber(), getMoveToTimeslot());
mChannel = new DMRTier3Channel(getMoveToChannelNumber(), getMoveToTimeslot());
}
return mChannel;

View File

@@ -24,7 +24,7 @@ import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
@@ -156,7 +156,7 @@ public class MoveTSCC extends CSBKMessage
}
else
{
mDMRChannel = new DMRLogicalChannel(getMessage().getInt(CHANNEL_NUMBER), 1);
mDMRChannel = new DMRTier3Channel(getMessage().getInt(CHANNEL_NUMBER), 1);
}
}

View File

@@ -24,6 +24,7 @@ import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -121,7 +122,7 @@ public class AdjacentSiteInformation extends Announcement implements ITimeslotFr
if(mChannel == null)
{
//Timeslot hard-coded to 1
mChannel = new DMRLogicalChannel(getMessage().getInt(NEIGHBOR_CHANNEL_NUMBER), 1);
mChannel = new DMRTier3Channel(getMessage().getInt(NEIGHBOR_CHANNEL_NUMBER), 1);
}
return mChannel;

View File

@@ -23,7 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
@@ -221,7 +221,7 @@ public class AnnounceWithdrawTSCC extends Announcement
}
else
{
mChannel1 = new DMRLogicalChannel(getMessage().getInt(CHANNEL_NUMBER_1), 1);
mChannel1 = new DMRTier3Channel(getMessage().getInt(CHANNEL_NUMBER_1), 1);
}
}
@@ -235,7 +235,7 @@ public class AnnounceWithdrawTSCC extends Announcement
{
if(mChannel2 == null)
{
mChannel2 = new DMRLogicalChannel(getMessage().getInt(CHANNEL_NUMBER_2), 1);
mChannel2 = new DMRTier3Channel(getMessage().getInt(CHANNEL_NUMBER_2), 1);
}
return mChannel2;

View File

@@ -24,6 +24,7 @@ import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -203,7 +204,7 @@ public class VoteNowAdvice extends Announcement implements ITimeslotFrequencyRec
if(mChannel == null)
{
mChannel = new DMRLogicalChannel(getMessage().getInt(VOTED_CHANNEL_NUMBER), 1);
mChannel = new DMRTier3Channel(getMessage().getInt(VOTED_CHANNEL_NUMBER), 1);
}
return mChannel;

View File

@@ -23,6 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -148,7 +149,7 @@ public abstract class ChannelGrant extends CSBKMessage implements ITimeslotFrequ
if(mChannel == null)
{
mChannel = new DMRLogicalChannel(getChannelNumber(), getChannelGrantTimeslot());
mChannel = new DMRTier3Channel(getChannelNumber(), getChannelGrantTimeslot());
}
return mChannel;

View File

@@ -41,6 +41,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ControlChanne
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.HyteraXPTChannel;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.NullMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ShortLCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.TrafficChannelSystemParameters;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.UnknownShortLCMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -152,11 +153,13 @@ public class LCMessageFactory
case SHORT_STANDARD_CONTROL_CHANNEL_SYSTEM_PARAMETERS:
slc = new ControlChannelSystemParameters(message, timestamp, timeslot);
break;
case SHORT_STANDARD_TRAFFIC_CHANNEL_SYSTEM_PARAMETERS:
slc = new TrafficChannelSystemParameters(message, timestamp, timeslot);
break;
case SHORT_HYTERA_XPT_CHANNEL:
case SHORT_STANDARD_XPT_CHANNEL:
slc = new HyteraXPTChannel(message, timestamp, timeslot);
break;
case SHORT_STANDARD_TRAFFIC_CHANNEL_SYSTEM_PARAMETERS:
case SHORT_STANDARD_UNKNOWN:
default:
slc = new UnknownShortLCMessage(message, timestamp, timeslot);

View File

@@ -0,0 +1,112 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.lc.shorty;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III - Traffic Channel System Parameters
*/
public class TrafficChannelSystemParameters extends ShortLCMessage
{
private static final int SYSTEM_IDENTITY_CODE_OFFSET = 4;
private static final int REGISTRATION_REQUIRED_FLAG = 18;
private static final int[] COMMON_SLOT_COUNTER = new int[]{19, 20, 21, 22, 23, 24, 25, 26, 27};
private SystemIdentityCode mSystemIdentityCode;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param message containing link control data
*/
public TrafficChannelSystemParameters(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
super(message, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC ERROR] ");
}
sb.append("SLC TIER III TRAFFIC CHANNEL ").append(getSystemIdentityCode().getModel());
sb.append(" NET:").append(getSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
if(isRegistrationRequired())
{
sb.append(" REGISTRATION REQUIRED");
}
sb.append(" SLOT COUNTER:").append(getCommonSlotCounter());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* System Identity Code structure
*/
public SystemIdentityCode getSystemIdentityCode()
{
if(mSystemIdentityCode == null)
{
mSystemIdentityCode = new SystemIdentityCode(getMessage(), SYSTEM_IDENTITY_CODE_OFFSET, false);
}
return mSystemIdentityCode;
}
/**
* Indicates if registration is required to access this network
*/
public boolean isRegistrationRequired()
{
return getMessage().get(REGISTRATION_REQUIRED_FLAG);
}
/**
* Common Slot Counter
*/
public int getCommonSlotCounter()
{
return getMessage().getInt(COMMON_SLOT_COUNTER);
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getSystemIdentityCode().getNetwork());
mIdentifiers.add(getSystemIdentityCode().getSite());
}
return mIdentifiers;
}
}

View File

@@ -34,7 +34,7 @@ import io.github.dsheirer.protocol.Protocol;
public class DecodeEvent implements IDecodeEvent
{
private long mTimeStart;
private long mDuration;
private long mTimeEnd;
private String mEventDescription;
private IdentifierCollection mIdentifierCollection;
private IChannelDescriptor mChannelDescriptor;
@@ -66,7 +66,7 @@ public class DecodeEvent implements IDecodeEvent
{
if(timestamp > mTimeStart)
{
mDuration = timestamp - mTimeStart;
mTimeEnd = timestamp;
}
}
@@ -87,13 +87,26 @@ public class DecodeEvent implements IDecodeEvent
return mTimeStart;
}
/**
* Event end in milliseconds
*/
public long getTimeEnd()
{
return mTimeEnd;
}
/**
* Event duration in milliseconds
*/
@Override
public long getDuration()
{
return mDuration;
if(mTimeEnd < mTimeStart)
{
return 0;
}
return mTimeEnd - mTimeStart;
}
/**
@@ -101,7 +114,7 @@ public class DecodeEvent implements IDecodeEvent
*/
public void setDuration(long duration)
{
mDuration = duration;
mTimeEnd = mTimeStart + duration;
}
/**
@@ -229,7 +242,7 @@ public class DecodeEvent implements IDecodeEvent
{
sb.append(" IDS:").append(Joiner.on(",").join(mIdentifierCollection.getIdentifiers()));
}
sb.append(" DURATION:").append(mDuration);
sb.append(" DURATION:").append(getDuration());
sb.append(" CHANNEL:").append(mChannelDescriptor);
return sb.toString();
}

View File

@@ -38,6 +38,12 @@ public interface IDecodeEvent
*/
long getTimeStart();
/**
* Event end time
* @return timestamp in milliseconds
*/
long getTimeEnd();
/**
* Event duration.
*

View File

@@ -30,6 +30,10 @@ import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigMPT1327 extends DecodeConfiguration implements WithCallTimeout
{
public static final int CHANNEL_ROTATION_DELAY_MINIMUM_MS = 500;
public static final int CHANNEL_ROTATION_DELAY_DEFAULT_MS = 500;
public static final int CHANNEL_ROTATION_DELAY_MAXIMUM_MS = 2000;
private String mChannelMapName;
private Sync mSync = Sync.NORMAL;

View File

@@ -30,6 +30,10 @@ import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigP25Phase1 extends DecodeConfiguration
{
public static final int CHANNEL_ROTATION_DELAY_MINIMUM_MS = 400;
public static final int CHANNEL_ROTATION_DELAY_DEFAULT_MS = 500;
public static final int CHANNEL_ROTATION_DELAY_MAXIMUM_MS = 2000;
private P25P1Decoder.Modulation mModulation = P25P1Decoder.Modulation.C4FM;
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;

View File

@@ -823,15 +823,20 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
}
else
{
mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf());
mCurrentCallEvent.end(timestamp);
broadcast(mCurrentCallEvent);
if(type == DecodeEventType.CALL_ENCRYPTED)
{
mCurrentCallEvent.setEventDescription(type.toString());
mCurrentCallEvent.setDetails(details);
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ENCRYPTED));
}
else
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL));
}
mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf());
mCurrentCallEvent.end(timestamp);
broadcast(mCurrentCallEvent);
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL));
}
}

View File

@@ -79,6 +79,7 @@ public class AdjacentStatusBroadcast extends OSPMessage implements IFrequencyBan
sb.append(" SYSTEM:").append(getSystem());
sb.append(" RFSS:").append(getRfss());
sb.append(" SITE:").append(getSite());
sb.append(" CHANNEL:").append(getChannel());
sb.append(" LRA:").append(getLocationRegistrationArea());
sb.append(" FLAGS ").append(getSiteFlags());
sb.append(" SERVICES ").append(getSystemServiceClass());

View File

@@ -19,6 +19,8 @@ import io.github.dsheirer.source.SourceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
/**
* Monitors measured frequency error PPM values from the tuner and (when enabled) applies the current
* error measurement to the tuner controller to adjust the tuner's PPM and align the frequency value.
@@ -32,6 +34,7 @@ public class FrequencyErrorCorrectionManager
private long mObservationPeriodStart;
private double mPPMRequired;
private boolean mEnabled = true;
private DecimalFormat mDecimalFormat = new DecimalFormat("0.0");
private TunerController mTunerController;
/**
@@ -72,7 +75,7 @@ public class FrequencyErrorCorrectionManager
try
{
mLog.info("Auto-Correcting Tuner PPM to [" + frequencyCorrection + "]");
mLog.info("Auto-Correcting Tuner PPM to [" + mDecimalFormat.format(frequencyCorrection) + "]");
mTunerController.setFrequencyCorrection(frequencyCorrection);
reset();

View File

@@ -275,6 +275,14 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
return (int)(getBandwidth() * mUsableBandwidthPercentage);
}
/**
* Sets the usable bandwidth percentage -- this can change based on samplerate for some tuners
*/
public void setUsableBandwidthPercentage(double usableBandwidthPercentage)
{
mUsableBandwidthPercentage = usableBandwidthPercentage;
}
/**
* Usable half bandwidth - total bandwidth minus unusable space at either end of the spectrum.
*

View File

@@ -41,6 +41,7 @@ import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
public class AirspyTunerController extends USBTunerController
{
@@ -79,6 +80,7 @@ public class AirspyTunerController extends USBTunerController
private AirspyDeviceInformation mDeviceInfo;
private List<AirspySampleRate> mSampleRates = new ArrayList<>();
private int mSampleRate = 0;
private ReentrantLock mLock = new ReentrantLock();
private USBTransferProcessor mUSBTransferProcessor;
public AirspyTunerController(Device device) throws SourceException
@@ -347,32 +349,35 @@ public class AirspyTunerController extends USBTunerController
@Override
public void setTunedFrequency(long frequency) throws SourceException
{
if(FREQUENCY_MIN <= frequency && frequency <= FREQUENCY_MAX)
mLock.lock();
try
{
ByteBuffer buffer = ByteBuffer.allocateDirect(4);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt((int) frequency);
buffer.rewind();
try
if(FREQUENCY_MIN <= frequency && frequency <= FREQUENCY_MAX)
{
write(Command.SET_FREQUENCY, 0, 0, buffer);
ByteBuffer buffer = ByteBuffer.allocateDirect(4);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt((int) frequency);
buffer.rewind();
try
{
write(Command.SET_FREQUENCY, 0, 0, buffer);
}
catch(UsbException e)
{
mLog.error("error setting frequency [" + frequency + "]", e);
throw new SourceException("error setting frequency [" + frequency + "]", e);
}
}
catch(UsbException e)
else
{
mLog.error("error setting frequency [" + frequency + "]", e);
throw new SourceException("error setting frequency [" +
frequency + "]", e);
throw new SourceException("Frequency [" + frequency + "] outside " + "of tunable range " + FREQUENCY_MIN + "-" + FREQUENCY_MAX);
}
}
else
finally
{
throw new SourceException("Frequency [" + frequency + "] outside "
+ "of tunable range " + FREQUENCY_MIN + "-" + FREQUENCY_MAX);
mLock.unlock();
}
}
@@ -406,6 +411,25 @@ public class AirspyTunerController extends USBTunerController
{
mSampleRate = rate.getRate();
mFrequencyController.setSampleRate(mSampleRate);
// Update the Usable Bandwidth Percentage, as the usable range varies with the sample rate.
// Values below are from https://github.com/DSheirer/sdrtrunk/issues/815#issuecomment-699859590
switch(mSampleRate)
{
default:
case 10000000:
super.setUsableBandwidthPercentage(0.90);
break;
case 6000000:
super.setUsableBandwidthPercentage(0.83);
break;
case 3000000:
super.setUsableBandwidthPercentage(0.66);
break;
case 2500000:
super.setUsableBandwidthPercentage(0.60);
break;
}
}
}
}
@@ -475,14 +499,22 @@ public class AirspyTunerController extends USBTunerController
* @throws LibUsbException on unsuccessful read operation
* @throws UsbException on USB error
*/
public void setMixerAGC(boolean enabled)
throws LibUsbException, UsbException
public void setMixerAGC(boolean enabled) throws LibUsbException, UsbException
{
int result = readByte(Command.SET_MIXER_AGC, 0, (enabled ? 1 : 0), true);
mLock.lock();
if(result != LibUsb.SUCCESS)
try
{
throw new UsbException("Couldnt set mixer AGC enabled: " + enabled);
int result = readByte(Command.SET_MIXER_AGC, 0, (enabled ? 1 : 0), true);
if(result != LibUsb.SUCCESS)
{
throw new UsbException("Couldnt set mixer AGC enabled: " + enabled);
}
}
finally
{
mLock.unlock();;
}
}
@@ -495,11 +527,20 @@ public class AirspyTunerController extends USBTunerController
*/
public void setLNAAGC(boolean enabled) throws LibUsbException, UsbException
{
int result = readByte(Command.SET_LNA_AGC, 0, (enabled ? 1 : 0), true);
mLock.lock();
if(result != LibUsb.SUCCESS)
try
{
throw new UsbException("Couldnt set LNA AGC enabled: " + enabled);
int result = readByte(Command.SET_LNA_AGC, 0, (enabled ? 1 : 0), true);
if(result != LibUsb.SUCCESS)
{
throw new UsbException("Couldnt set LNA AGC enabled: " + enabled);
}
}
finally
{
mLock.unlock();
}
}
@@ -523,22 +564,30 @@ public class AirspyTunerController extends USBTunerController
* @throws UsbException on error in USB transfer
* @throws IllegalArgumentException if gain value is invalid
*/
public void setLNAGain(int gain)
throws LibUsbException, UsbException, IllegalArgumentException
public void setLNAGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException
{
if(LNA_GAIN_MIN <= gain && gain <= LNA_GAIN_MAX)
{
int result = readByte(Command.SET_LNA_GAIN, 0, gain, true);
mLock.lock();
if(result != LibUsb.SUCCESS)
try
{
if(LNA_GAIN_MIN <= gain && gain <= LNA_GAIN_MAX)
{
throw new UsbException("Couldnt set LNA gain to: " + gain);
int result = readByte(Command.SET_LNA_GAIN, 0, gain, true);
if(result != LibUsb.SUCCESS)
{
throw new UsbException("Couldnt set LNA gain to: " + gain);
}
}
else
{
throw new IllegalArgumentException("LNA gain value [" + gain +
"] is outside value range: " + LNA_GAIN_MIN + "-" + LNA_GAIN_MAX);
}
}
else
finally
{
throw new IllegalArgumentException("LNA gain value [" + gain +
"] is outside value range: " + LNA_GAIN_MIN + "-" + LNA_GAIN_MAX);
mLock.unlock();
}
}
@@ -550,22 +599,30 @@ public class AirspyTunerController extends USBTunerController
* @throws UsbException on error in USB transfer
* @throws IllegalArgumentException if gain value is invalid
*/
public void setMixerGain(int gain)
throws LibUsbException, UsbException, IllegalArgumentException
public void setMixerGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException
{
if(MIXER_GAIN_MIN <= gain && gain <= MIXER_GAIN_MAX)
{
int result = readByte(Command.SET_MIXER_GAIN, 0, gain, true);
mLock.lock();
if(result != LibUsb.SUCCESS)
try
{
if(MIXER_GAIN_MIN <= gain && gain <= MIXER_GAIN_MAX)
{
throw new UsbException("Couldnt set mixer gain to: " + gain);
int result = readByte(Command.SET_MIXER_GAIN, 0, gain, true);
if(result != LibUsb.SUCCESS)
{
throw new UsbException("Couldnt set mixer gain to: " + gain);
}
}
else
{
throw new IllegalArgumentException("Mixer gain value [" + gain +
"] is outside value range: " + MIXER_GAIN_MIN + "-" + MIXER_GAIN_MAX);
}
}
else
finally
{
throw new IllegalArgumentException("Mixer gain value [" + gain +
"] is outside value range: " + MIXER_GAIN_MIN + "-" + MIXER_GAIN_MAX);
mLock.unlock();
}
}
@@ -577,22 +634,30 @@ public class AirspyTunerController extends USBTunerController
* @throws UsbException on error in USB transfer
* @throws IllegalArgumentException if gain value is invalid
*/
public void setIFGain(int gain)
throws LibUsbException, UsbException, IllegalArgumentException
public void setIFGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException
{
if(IF_GAIN_MIN <= gain && gain <= IF_GAIN_MAX)
{
int result = readByte(Command.SET_VGA_GAIN, 0, gain, true);
mLock.lock();
if(result != LibUsb.SUCCESS)
try
{
if(IF_GAIN_MIN <= gain && gain <= IF_GAIN_MAX)
{
throw new UsbException("Couldnt set VGA gain to: " + gain);
int result = readByte(Command.SET_VGA_GAIN, 0, gain, true);
if(result != LibUsb.SUCCESS)
{
throw new UsbException("Couldnt set VGA gain to: " + gain);
}
}
else
{
throw new IllegalArgumentException("VGA gain value [" + gain +
"] is outside value range: " + IF_GAIN_MIN + "-" + IF_GAIN_MAX);
}
}
else
finally
{
throw new IllegalArgumentException("VGA gain value [" + gain +
"] is outside value range: " + IF_GAIN_MIN + "-" + IF_GAIN_MAX);
mLock.unlock();
}
}

View File

@@ -46,6 +46,7 @@ public class ChannelRotationMonitor extends Module implements ISourceEventProvid
Listener<DecoderStateEvent>
{
public static final int CHANNEL_ROTATION_DELAY_MINIMUM = 200;
public static final int CHANNEL_ROTATION_DELAY_DEFAULT = 500;
public static final int CHANNEL_ROTATION_DELAY_MAXIMUM = 2000;
private final static Logger mLog = LoggerFactory.getLogger(ChannelRotationMonitor.class);

View File

@@ -42,6 +42,7 @@ import javax.usb.UsbException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.concurrent.locks.ReentrantLock;
public abstract class FCDTunerController extends TunerController
{
@@ -60,6 +61,7 @@ public abstract class FCDTunerController extends TunerController
private FCDConfiguration mConfiguration = new FCDConfiguration();
protected ComplexMixer mComplexMixer;
protected ReentrantLock mLock = new ReentrantLock();
/**
@@ -302,6 +304,8 @@ public abstract class FCDTunerController extends TunerController
*/
public void setTunedFrequency(long frequency) throws SourceException
{
mLock.lock();
try
{
send(FCDCommand.APP_SET_FREQUENCY_HZ, frequency);
@@ -311,6 +315,10 @@ public abstract class FCDTunerController extends TunerController
throw new SourceException("Couldn't set FCD Local " +
"Oscillator Frequency [" + frequency + "]", e);
}
finally
{
mLock.unlock();
}
}
/**

View File

@@ -176,10 +176,18 @@ public class FCD1TunerController extends FCDTunerController
* @throws UsbException
* @throws UsbClaimException
*/
public void setLNAGain(LNAGain gain)
throws UsbException, UsbClaimException
public void setLNAGain(LNAGain gain) throws UsbException, UsbClaimException
{
send(FCDCommand.APP_SET_LNA_GAIN, gain.getSetting());
mLock.lock();
try
{
send(FCDCommand.APP_SET_LNA_GAIN, gain.getSetting());
}
finally
{
mLock.unlock();
}
}
/**
@@ -269,7 +277,16 @@ public class FCD1TunerController extends FCDTunerController
//Merge the results
long correction = longGain | phase;
send(FCDCommand.APP_SET_IQ_CORRECTION, correction);
mLock.lock();
try
{
send(FCDCommand.APP_SET_IQ_CORRECTION, correction);
}
finally
{
mLock.unlock();
}
}
/**
@@ -402,15 +419,16 @@ public class FCD1TunerController extends FCDTunerController
//Merge the results
long correction = shiftedQuadrature | maskedInphase;
// Log.info( "FCD1: setting iq correction," +
// " inphase:" + inphase +
// " masked inphase:" + maskedInphase +
// " quad:" + quadrature +
// " masked quad:" + maskedQuadrature +
// " shifted quadrature:" + shiftedQuadrature +
// " correction:" + correction );
mLock.lock();
send(FCDCommand.APP_SET_DC_CORRECTION, correction);
try
{
send(FCDCommand.APP_SET_DC_CORRECTION, correction);
}
finally
{
mLock.unlock();
}
}
private void getDCIQCorrection() throws UsbClaimException,
@@ -464,10 +482,18 @@ public class FCD1TunerController extends FCDTunerController
return mLNAEnhance;
}
public void setLNAEnhance(LNAEnhance enhance)
throws UsbClaimException, UsbException
public void setLNAEnhance(LNAEnhance enhance) throws UsbClaimException, UsbException
{
send(FCDCommand.APP_SET_LNA_ENHANCE, enhance.getSetting());
mLock.lock();
try
{
send(FCDCommand.APP_SET_LNA_ENHANCE, enhance.getSetting());
}
finally
{
mLock.unlock();
}
}
private void getMixerGainSetting() throws UsbClaimException, UsbException
@@ -488,7 +514,16 @@ public class FCD1TunerController extends FCDTunerController
public void setMixerGain(MixerGain gain) throws UsbClaimException, UsbException
{
send(FCDCommand.APP_SET_MIXER_GAIN, gain.getSetting());
mLock.lock();
try
{
send(FCDCommand.APP_SET_MIXER_GAIN, gain.getSetting());
}
finally
{
mLock.unlock();
}
}
public enum Block

View File

@@ -83,6 +83,8 @@ public class FCD2TunerController extends FCDTunerController
public void setLNAGain(boolean enabled) throws SourceException
{
mLock.lock();
try
{
send(FCDCommand.APP_SET_LNA_GAIN, enabled ? 1 : 0);
@@ -91,10 +93,16 @@ public class FCD2TunerController extends FCDTunerController
{
throw new SourceException("error while setting LNA Gain", e);
}
finally
{
mLock.unlock();
}
}
public void setMixerGain(boolean enabled) throws SourceException
{
mLock.lock();
try
{
send(FCDCommand.APP_SET_MIXER_GAIN, enabled ? 1 : 0);
@@ -103,6 +111,10 @@ public class FCD2TunerController extends FCDTunerController
{
throw new SourceException("error while setting Mixer Gain", e);
}
finally
{
mLock.unlock();
}
}
@Override
@@ -159,6 +171,8 @@ public class FCD2TunerController extends FCDTunerController
public void setDCCorrection(int value)
{
mLock.lock();
try
{
send(FCDCommand.APP_SET_DC_CORRECTION, value);
@@ -167,6 +181,10 @@ public class FCD2TunerController extends FCDTunerController
{
mLog.error("error setting dc correction to [" + value + "]", e);
}
finally
{
mLock.unlock();
}
}
public int getIQCorrection()
@@ -191,6 +209,8 @@ public class FCD2TunerController extends FCDTunerController
public void setIQCorrection(int value)
{
mLock.lock();
try
{
send(FCDCommand.APP_SET_IQ_CORRECTION, value);
@@ -199,5 +219,9 @@ public class FCD2TunerController extends FCDTunerController
{
mLog.error("error setting IQ correction to [" + value + "]", e);
}
finally
{
mLock.unlock();
}
}
}

View File

@@ -41,6 +41,7 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.concurrent.locks.ReentrantLock;
public class HackRFTunerController extends USBTunerController
{
@@ -69,6 +70,7 @@ public class HackRFTunerController extends USBTunerController
private Device mDevice;
private DeviceDescriptor mDeviceDescriptor;
private DeviceHandle mDeviceHandle;
private ReentrantLock mLock = new ReentrantLock();
public HackRFTunerController(Device device, DeviceDescriptor descriptor) throws SourceException
{
@@ -246,9 +248,16 @@ public class HackRFTunerController extends USBTunerController
*/
public void setBasebandFilter(BasebandFilter filter) throws UsbException
{
write(Request.BASEBAND_FILTER_BANDWIDTH_SET,
filter.getLowValue(),
filter.getHighValue());
mLock.lock();
try
{
write(Request.BASEBAND_FILTER_BANDWIDTH_SET, filter.getLowValue(), filter.getHighValue());
}
finally
{
mLock.unlock();
}
}
/**
@@ -256,9 +265,17 @@ public class HackRFTunerController extends USBTunerController
*/
public void setAmplifierEnabled(boolean enabled) throws UsbException
{
write(Request.AMP_ENABLE, (enabled ? 1 : 0), 0);
mLock.lock();
mAmplifierEnabled = enabled;
try
{
write(Request.AMP_ENABLE, (enabled ? 1 : 0), 0);
mAmplifierEnabled = enabled;
}
finally
{
mLock.unlock();;
}
}
public boolean getAmplifier()
@@ -271,11 +288,20 @@ public class HackRFTunerController extends USBTunerController
*/
public void setLNAGain(HackRFLNAGain gain) throws UsbException
{
int result = readByte(Request.SET_LNA_GAIN, 0, gain.getValue(), true);
mLock.lock();
if(result != 1)
try
{
throw new UsbException("couldn't set lna gain to " + gain);
int result = readByte(Request.SET_LNA_GAIN, 0, gain.getValue(), true);
if(result != 1)
{
throw new UsbException("couldn't set lna gain to " + gain);
}
}
finally
{
mLock.unlock();
}
}
@@ -284,11 +310,20 @@ public class HackRFTunerController extends USBTunerController
*/
public void setVGAGain(HackRFVGAGain gain) throws UsbException
{
int result = readByte(Request.SET_VGA_GAIN, 0, gain.getValue(), true);
mLock.lock();
if(result != 1)
try
{
throw new UsbException("couldn't set vga gain to " + gain);
int result = readByte(Request.SET_VGA_GAIN, 0, gain.getValue(), true);
if(result != 1)
{
throw new UsbException("couldn't set vga gain to " + gain);
}
}
finally
{
mLock.unlock();
}
}
@@ -303,27 +338,34 @@ public class HackRFTunerController extends USBTunerController
@Override
public void setTunedFrequency(long frequency) throws SourceException
{
ByteBuffer buffer = ByteBuffer.allocateDirect(8);
buffer.order(ByteOrder.LITTLE_ENDIAN);
int mhz = (int)(frequency / 1E6);
int hz = (int)(frequency - (mhz * 1E6));
buffer.putInt(mhz);
buffer.putInt(hz);
buffer.rewind();
mLock.lock();
try
{
write(Request.SET_FREQUENCY, 0, 0, buffer);
}
catch(UsbException e)
{
mLog.error("error setting frequency [" + frequency + "]", e);
ByteBuffer buffer = ByteBuffer.allocateDirect(8);
buffer.order(ByteOrder.LITTLE_ENDIAN);
throw new SourceException("error setting frequency [" +
frequency + "]", e);
int mhz = (int)(frequency / 1E6);
int hz = (int)(frequency - (mhz * 1E6));
buffer.putInt(mhz);
buffer.putInt(hz);
buffer.rewind();
try
{
write(Request.SET_FREQUENCY, 0, 0, buffer);
}
catch(UsbException e)
{
mLog.error("error setting frequency [" + frequency + "]", e);
throw new SourceException("error setting frequency [" + frequency + "]", e);
}
}
finally
{
mLock.unlock();
}
}
@@ -358,8 +400,7 @@ public class HackRFTunerController extends USBTunerController
}
catch(UsbException e)
{
throw new SourceException("Error while applying tuner "
+ "configuration", e);
throw new SourceException("Error while applying tuner configuration", e);
}
try
@@ -373,8 +414,7 @@ public class HackRFTunerController extends USBTunerController
}
else
{
throw new IllegalArgumentException("Invalid tuner configuration "
+ "type [" + config.getClass() + "]");
throw new IllegalArgumentException("Invalid tuner configuration type [" + config.getClass() + "]");
}
}

View File

@@ -46,6 +46,7 @@ import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
public abstract class RTL2832TunerController extends USBTunerController
{
@@ -60,6 +61,7 @@ public abstract class RTL2832TunerController extends USBTunerController
public final static long TIMEOUT_US = 1000000l; //uSeconds
public final static byte REQUEST_ZERO = (byte) 0;
public final static byte EEPROM_ADDRESS = (byte) 0xA0;
protected ReentrantLock mLock = new ReentrantLock();
public final static byte[] sFIR_COEFFICIENTS =
{

View File

@@ -384,6 +384,8 @@ public class E4KTunerController extends RTL2832TunerController
actualFrequency = calculateActualFrequency(pll, z, x);
}
mLock.lock();
/* Apply the actual frequency */
try
{
@@ -428,6 +430,10 @@ public class E4KTunerController extends RTL2832TunerController
throw new SourceException("E4K tuner controller - error tuning "
+ "frequency [" + frequency + "]", e);
}
finally
{
mLock.unlock();
}
}
private long calculateActualFrequency(PLL pll, byte z, int x)
@@ -657,38 +663,46 @@ public class E4KTunerController extends RTL2832TunerController
* @param gain
* @throws UsbException
*/
public void setLNAGain(E4KLNAGain gain, boolean controlI2CRepeater)
throws UsbException
public void setLNAGain(E4KLNAGain gain, boolean controlI2CRepeater) throws UsbException
{
if(controlI2CRepeater)
mLock.lock();
try
{
enableI2CRepeater(mDeviceHandle, true);
if(controlI2CRepeater)
{
enableI2CRepeater(mDeviceHandle, true);
}
if(gain == E4KLNAGain.AUTOMATIC)
{
writeMaskedE4KRegister(Register.AGC1,
AGC1_MOD_MASK,
AGCMode.IF_SERIAL_LNA_AUTON.getValue(),
false);
}
else
{
writeMaskedE4KRegister(Register.AGC1,
AGC1_MOD_MASK,
AGCMode.SERIAL.getValue(),
false);
writeMaskedE4KRegister(E4KLNAGain.getRegister(),
E4KLNAGain.getMask(),
gain.getValue(),
false);
}
if(controlI2CRepeater)
{
enableI2CRepeater(mDeviceHandle, false);
}
}
if(gain == E4KLNAGain.AUTOMATIC)
finally
{
writeMaskedE4KRegister(Register.AGC1,
AGC1_MOD_MASK,
AGCMode.IF_SERIAL_LNA_AUTON.getValue(),
false);
}
else
{
writeMaskedE4KRegister(Register.AGC1,
AGC1_MOD_MASK,
AGCMode.SERIAL.getValue(),
false);
writeMaskedE4KRegister(E4KLNAGain.getRegister(),
E4KLNAGain.getMask(),
gain.getValue(),
false);
}
if(controlI2CRepeater)
{
enableI2CRepeater(mDeviceHandle, false);
mLock.unlock();
}
}
@@ -699,8 +713,16 @@ public class E4KTunerController extends RTL2832TunerController
*/
public E4KLNAGain getLNAGain(boolean controlI2CRepeater) throws UsbException
{
return E4KLNAGain.fromRegisterValue(
readE4KRegister(E4KLNAGain.getRegister(), controlI2CRepeater));
mLock.lock();
try
{
return E4KLNAGain.fromRegisterValue(readE4KRegister(E4KLNAGain.getRegister(), controlI2CRepeater));
}
finally
{
mLock.unlock();
}
}
/**
@@ -711,13 +733,18 @@ public class E4KTunerController extends RTL2832TunerController
* @param gain
* @throws UsbException
*/
public void setEnhanceGain(E4KEnhanceGain gain, boolean controlI2CRepeater)
throws UsbException
public void setEnhanceGain(E4KEnhanceGain gain, boolean controlI2CRepeater) throws UsbException
{
writeMaskedE4KRegister(E4KEnhanceGain.getRegister(),
E4KEnhanceGain.getMask(),
gain.getValue(),
controlI2CRepeater);
mLock.lock();
try
{
writeMaskedE4KRegister(E4KEnhanceGain.getRegister(), E4KEnhanceGain.getMask(), gain.getValue(), controlI2CRepeater);
}
finally
{
mLock.unlock();
}
}
/**
@@ -728,72 +755,90 @@ public class E4KTunerController extends RTL2832TunerController
* @return
* @throws UsbException
*/
public E4KEnhanceGain getEnhanceGain(boolean controlI2CRepeater)
throws UsbException
public E4KEnhanceGain getEnhanceGain(boolean controlI2CRepeater) throws UsbException
{
return E4KEnhanceGain.fromRegisterValue(
readE4KRegister(E4KEnhanceGain.getRegister(), controlI2CRepeater));
}
mLock.lock();
public void setMixerGain(E4KMixerGain gain, boolean controlI2CRepeater)
throws UsbException
{
if(controlI2CRepeater)
try
{
enableI2CRepeater(mDeviceHandle, true);
return E4KEnhanceGain.fromRegisterValue(readE4KRegister(E4KEnhanceGain.getRegister(), controlI2CRepeater));
}
boolean localI2CRepeaterControl = false;
if(gain == E4KMixerGain.AUTOMATIC)
finally
{
writeMaskedE4KRegister(Register.AGC7,
AGC7_MIXER_GAIN_MASK,
AGC7_MIX_GAIN_AUTO,
localI2CRepeaterControl);
}
else
{
writeMaskedE4KRegister(Register.AGC7,
AGC7_MIXER_GAIN_MASK,
AGC7_MIX_GAIN_MANUAL,
localI2CRepeaterControl);
/* Set the desired manual gain setting */
writeMaskedE4KRegister(E4KMixerGain.getRegister(),
E4KMixerGain.getMask(),
gain.getValue(),
localI2CRepeaterControl);
}
if(controlI2CRepeater)
{
enableI2CRepeater(mDeviceHandle, false);
mLock.unlock();
}
}
public E4KMixerGain getMixerGain(boolean controlI2CRepeater)
throws UsbException
public void setMixerGain(E4KMixerGain gain, boolean controlI2CRepeater) throws UsbException
{
byte autoOrManual = readMaskedE4KRegister(Register.AGC7,
AGC7_MIXER_GAIN_MASK,
controlI2CRepeater);
mLock.lock();
if(autoOrManual == AGC7_MIX_GAIN_AUTO)
try
{
return E4KMixerGain.AUTOMATIC;
if(controlI2CRepeater)
{
enableI2CRepeater(mDeviceHandle, true);
}
boolean localI2CRepeaterControl = false;
if(gain == E4KMixerGain.AUTOMATIC)
{
writeMaskedE4KRegister(Register.AGC7,
AGC7_MIXER_GAIN_MASK,
AGC7_MIX_GAIN_AUTO,
localI2CRepeaterControl);
}
else
{
writeMaskedE4KRegister(Register.AGC7,
AGC7_MIXER_GAIN_MASK,
AGC7_MIX_GAIN_MANUAL,
localI2CRepeaterControl);
/* Set the desired manual gain setting */
writeMaskedE4KRegister(E4KMixerGain.getRegister(),
E4KMixerGain.getMask(),
gain.getValue(),
localI2CRepeaterControl);
}
if(controlI2CRepeater)
{
enableI2CRepeater(mDeviceHandle, false);
}
}
else
finally
{
int register = readE4KRegister(E4KMixerGain.getRegister(),
controlI2CRepeater);
return E4KMixerGain.fromRegisterValue(register);
mLock.unlock();
}
}
public void setIFStage1Gain(IFStage1Gain gain,
boolean controlI2CRepeater) throws UsbException
public E4KMixerGain getMixerGain(boolean controlI2CRepeater) throws UsbException
{
mLock.lock();
try
{
byte autoOrManual = readMaskedE4KRegister(Register.AGC7, AGC7_MIXER_GAIN_MASK, controlI2CRepeater);
if(autoOrManual == AGC7_MIX_GAIN_AUTO)
{
return E4KMixerGain.AUTOMATIC;
}
else
{
int register = readE4KRegister(E4KMixerGain.getRegister(), controlI2CRepeater);
return E4KMixerGain.fromRegisterValue(register);
}
}
finally
{
mLock.unlock();
}
}
public void setIFStage1Gain(IFStage1Gain gain, boolean controlI2CRepeater) throws UsbException
{
writeMaskedE4KRegister(IFStage1Gain.getRegister(),
IFStage1Gain.getMask(),
@@ -959,8 +1004,7 @@ public class E4KTunerController extends RTL2832TunerController
}
}
public void setBand(long frequency,
boolean controlI2CRepeater) throws UsbException
public void setBand(long frequency, boolean controlI2CRepeater) throws UsbException
{
if(controlI2CRepeater)
{
@@ -997,8 +1041,7 @@ public class E4KTunerController extends RTL2832TunerController
}
}
private void setRFFilter(long frequency,
boolean controlI2CRepeater) throws UsbException
private void setRFFilter(long frequency, boolean controlI2CRepeater) throws UsbException
{
RFFilter filter = RFFilter.fromFrequency(frequency);

View File

@@ -180,6 +180,8 @@ public class R820TTunerController extends RTL2832TunerController
@Override
public void setTunedFrequency(long frequency) throws SourceException
{
mLock.lock();
try
{
enableI2CRepeater(mDeviceHandle, true);
@@ -200,6 +202,10 @@ public class R820TTunerController extends RTL2832TunerController
+ "while setting frequency [" + frequency + "] - " +
e.getLocalizedMessage());
}
finally
{
mLock.unlock();
}
}
/**
@@ -693,7 +699,16 @@ public class R820TTunerController extends RTL2832TunerController
*/
public void setLNAGain(R820TLNAGain gain, boolean controlI2C) throws UsbException
{
writeR820TRegister(Register.LNA_GAIN, gain.getSetting(), controlI2C);
mLock.lock();
try
{
writeR820TRegister(Register.LNA_GAIN, gain.getSetting(), controlI2C);
}
finally
{
mLock.unlock();
}
}
/**
@@ -701,7 +716,16 @@ public class R820TTunerController extends RTL2832TunerController
*/
public void setMixerGain(R820TMixerGain gain, boolean controlI2C) throws UsbException
{
writeR820TRegister(Register.MIXER_GAIN, gain.getSetting(), controlI2C);
mLock.lock();
try
{
writeR820TRegister(Register.MIXER_GAIN, gain.getSetting(), controlI2C);
}
finally
{
mLock.unlock();
}
}
/**
@@ -709,7 +733,16 @@ public class R820TTunerController extends RTL2832TunerController
*/
public void setVGAGain(R820TVGAGain gain, boolean controlI2C) throws UsbException
{
writeR820TRegister(Register.VGA_GAIN, gain.getSetting(), controlI2C);
mLock.lock();
try
{
writeR820TRegister(Register.VGA_GAIN, gain.getSetting(), controlI2C);
}
finally
{
mLock.unlock();
}
}
/**

View File

@@ -25,9 +25,10 @@ import com.jidesoft.swing.JideSplitPane;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.controller.channel.ChannelModel;
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
import io.github.dsheirer.controller.channel.ChannelUtils;
import io.github.dsheirer.dsp.filter.Window.WindowType;
import io.github.dsheirer.dsp.filter.smoothing.SmoothingFilter.SmoothingType;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.playlist.channel.ViewChannelRequest;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.Listener;
@@ -635,26 +636,22 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
if(event.getComponent() == mOverlayPanel)
{
ArrayList<Channel> channels =
mOverlayPanel.getChannelsAtFrequency(frequency);
ArrayList<Channel> channels = mOverlayPanel.getChannelsAtFrequency(frequency);
JMenu channelMenu = new JMenu("Channels");
for(Channel channel : channels)
{
JMenu channelMenu = ChannelUtils.getContextMenu(mChannelModel,
mChannelProcessingManager, channel,
SpectralDisplayPanel.this);
if(channelMenu != null)
{
contextMenu.add(channelMenu);
}
JMenuItem viewChannel = new JMenuItem("View/Edit: " + channel.getShortTitle());
viewChannel.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel)));
channelMenu.add(viewChannel);
}
contextMenu.add(channelMenu);
if(!channels.isEmpty())
{
contextMenu.add(new JSeparator());
}
}
/**