Compare commits

...

14 Commits

Author SHA1 Message Date
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 1311 additions and 662 deletions

View File

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

View File

@@ -323,6 +323,11 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
audioRecording.removePendingReplay(); audioRecording.removePendingReplay();
} }
} }
else if(urlResponse.startsWith("1 SKIPPED"))
{
//Broadcastify is telling us to skip audio upload - someone already uploaded it
audioRecording.removePendingReplay();
}
else else
{ {
mLog.error("Broadcastify calls API upload URL request failed [" + urlResponse + "]"); 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.Channel;
import io.github.dsheirer.controller.channel.ChannelModel; import io.github.dsheirer.controller.channel.ChannelModel;
import io.github.dsheirer.controller.channel.ChannelProcessingManager; 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.icon.IconModel;
import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
@@ -412,11 +413,12 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
if(channel != null) 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; populated = true;
} }
} }
} }
} }

View File

@@ -126,8 +126,6 @@ public class Channel extends Configuration implements Listener<SourceEvent>
mChannelID = UNIQUE_ID++; mChannelID = UNIQUE_ID++;
} }
/** /**
* Creates a (new) deep copy of this channel * Creates a (new) deep copy of this channel
*/ */
@@ -183,6 +181,70 @@ public class Channel extends Configuration implements Listener<SourceEvent>
return channel; 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. * 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; mChannel = channel;
mChannelDescriptor = channelDescriptor; mChannelDescriptor = channelDescriptor;
mIdentifierCollection = identifierCollection; mIdentifierCollection = (identifierCollection != null ? identifierCollection : new IdentifierCollection());
mTrafficChannelManager = trafficChannelManager; mTrafficChannelManager = trafficChannelManager;
} }

View File

@@ -165,34 +165,25 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
{ {
PolyphaseChannelSource channelSource = null; 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); try
if(outputProcessor != null)
{ {
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes); channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
mChannelCalculator.getChannelSampleRate(), centerFrequency, channelSpecification);
try mChannelSources.add(channelSource);
{ }
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener, catch(FilterDesignException fde)
mChannelCalculator.getChannelSampleRate(), centerFrequency, channelSpecification); {
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; return channelSource;

View File

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

View File

@@ -138,7 +138,7 @@ public class AMConfigurationEditor extends ChannelConfigurationEditor
{ {
if(mSourceConfigurationEditor == null) 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 //Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty() mSourceConfigurationEditor.modifiedProperty()

View File

@@ -84,6 +84,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
private EventLogConfigurationEditor mEventLogConfigurationEditor; private EventLogConfigurationEditor mEventLogConfigurationEditor;
private RecordConfigurationEditor mRecordConfigurationEditor; private RecordConfigurationEditor mRecordConfigurationEditor;
private ToggleSwitch mIgnoreDataCallsButton; private ToggleSwitch mIgnoreDataCallsButton;
private ToggleSwitch mIgnoreCRCChecksumsButton;
private Spinner<Integer> mTrafficChannelPoolSizeSpinner; private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
private TableView<TimeslotFrequency> mTimeslotFrequencyTable; private TableView<TimeslotFrequency> mTimeslotFrequencyTable;
private IntegerTextField mLogicalSlotNumberField; private IntegerTextField mLogicalSlotNumberField;
@@ -149,17 +150,25 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
GridPane.setConstraints(getIgnoreDataCallsButton(), 2, row); GridPane.setConstraints(getIgnoreDataCallsButton(), 2, row);
gridPane.getChildren().add(getIgnoreDataCallsButton()); gridPane.getChildren().add(getIgnoreDataCallsButton());
Label directionLabel = new Label("Ignore Data Calls"); Label ignoreDataLabel = new Label("Ignore Data Calls");
GridPane.setHalignment(directionLabel, HPos.LEFT); GridPane.setHalignment(ignoreDataLabel, HPos.LEFT);
GridPane.setConstraints(directionLabel, 3, row); GridPane.setConstraints(ignoreDataLabel, 3, row);
gridPane.getChildren().add(directionLabel); 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"); 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.setHalignment(timeslotTableLabel, HPos.LEFT);
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 4, 1); GridPane.setConstraints(timeslotTableLabel, 0, ++row, 6, 1);
gridPane.getChildren().add(timeslotTableLabel); gridPane.getChildren().add(timeslotTableLabel);
GridPane.setConstraints(getTimeslotTable(), 0, ++row, 4, 3); GridPane.setConstraints(getTimeslotTable(), 0, ++row, 6, 3);
gridPane.getChildren().add(getTimeslotTable()); gridPane.getChildren().add(getTimeslotTable());
VBox buttonsBox = new VBox(); VBox buttonsBox = new VBox();
@@ -167,7 +176,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
buttonsBox.setSpacing(10); buttonsBox.setSpacing(10);
buttonsBox.getChildren().addAll(getAddTimeslotFrequencyButton(), getDeleteTimeslotFrequencyButton()); buttonsBox.getChildren().addAll(getAddTimeslotFrequencyButton(), getDeleteTimeslotFrequencyButton());
GridPane.setConstraints(buttonsBox, 4, row, 1, 3); GridPane.setConstraints(buttonsBox, 6, row, 1, 3);
gridPane.getChildren().addAll(buttonsBox); gridPane.getChildren().addAll(buttonsBox);
row += 3; row += 3;
@@ -231,7 +240,10 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
{ {
if(mSourceConfigurationEditor == null) 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 //Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty() mSourceConfigurationEditor.modifiedProperty()
@@ -486,6 +498,19 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
return mIgnoreDataCallsButton; 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() private Spinner<Integer> getTrafficChannelPoolSizeSpinner()
{ {
if(mTrafficChannelPoolSizeSpinner == null) if(mTrafficChannelPoolSizeSpinner == null)
@@ -550,6 +575,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
@Override @Override
protected void setDecoderConfiguration(DecodeConfiguration config) protected void setDecoderConfiguration(DecodeConfiguration config)
{ {
getIgnoreCRCChecksumsButton().setDisable(config == null);
getIgnoreDataCallsButton().setDisable(config == null); getIgnoreDataCallsButton().setDisable(config == null);
getTrafficChannelPoolSizeSpinner().setDisable(config == null); getTrafficChannelPoolSizeSpinner().setDisable(config == null);
getTimeslotTable().getItems().clear(); getTimeslotTable().getItems().clear();
@@ -569,6 +595,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
DecodeConfigDMR decodeConfig = (DecodeConfigDMR)config; DecodeConfigDMR decodeConfig = (DecodeConfigDMR)config;
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls()); getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
getIgnoreCRCChecksumsButton().setSelected(decodeConfig.getIgnoreCRCChecksums());
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize()); getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
for(TimeslotFrequency timeslotFrequency: decodeConfig.getTimeslotMap()) for(TimeslotFrequency timeslotFrequency: decodeConfig.getTimeslotMap())
@@ -578,6 +605,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
} }
else else
{ {
getIgnoreCRCChecksumsButton().setSelected(false);
getIgnoreDataCallsButton().setSelected(false); getIgnoreDataCallsButton().setSelected(false);
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(0); getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(0);
getChannelRotationDelaySpinner().getValueFactory().setValue(200); getChannelRotationDelaySpinner().getValueFactory().setValue(200);
@@ -599,6 +627,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
} }
// config.setChannelRotationDelay(getChannelRotationDelaySpinner().getValue()); // config.setChannelRotationDelay(getChannelRotationDelaySpinner().getValue());
config.setIgnoreCRCChecksums(getIgnoreCRCChecksumsButton().isSelected());
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected()); config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue()); config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
config.setTimeslotMap(new ArrayList<>(getTimeslotTable().getItems())); config.setTimeslotMap(new ArrayList<>(getTimeslotTable().getItems()));

View File

@@ -210,7 +210,7 @@ public class LTRConfigurationEditor extends ChannelConfigurationEditor
{ {
if(mSourceConfigurationEditor == null) 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 //Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty() mSourceConfigurationEditor.modifiedProperty()

View File

@@ -210,7 +210,7 @@ public class LTRNetConfigurationEditor extends ChannelConfigurationEditor
{ {
if(mSourceConfigurationEditor == null) 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 //Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty() mSourceConfigurationEditor.modifiedProperty()

View File

@@ -195,7 +195,10 @@ public class MPT1327ConfigurationEditor extends ChannelConfigurationEditor
{ {
if(mSourceConfigurationEditor == null) 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 //Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty() mSourceConfigurationEditor.modifiedProperty()

View File

@@ -200,7 +200,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
{ {
if(mSourceConfigurationEditor == null) 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 //Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty() mSourceConfigurationEditor.modifiedProperty()

View File

@@ -191,7 +191,10 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
{ {
if(mSourceConfigurationEditor == null) 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 //Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty() mSourceConfigurationEditor.modifiedProperty()

View File

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

View File

@@ -155,7 +155,7 @@ public class PassportConfigurationEditor extends ChannelConfigurationEditor
{ {
if(mSourceConfigurationEditor == null) 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 //Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty() mSourceConfigurationEditor.modifiedProperty()

View File

@@ -65,19 +65,42 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
private ComboBox<String> mPreferredTunerComboBox; private ComboBox<String> mPreferredTunerComboBox;
private VBox mFrequencyBoxContainer; private VBox mFrequencyBoxContainer;
private Spinner<Integer> mChannelRotationDelaySpinner; 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 tunerModel for accessing list of tuners to select a preferred tuner
* @param allowMultipleFrequencies toggles behavior of the editor between single frequency (false) or multiple * @param minRotationDelay in milliseconds for channel/frequency dwell
* frequencies (true). * @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; 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 hBox = new HBox();
hBox.setPadding(new Insets(10,10,10,10)); hBox.setPadding(new Insets(10,10,10,10));
hBox.setSpacing(10); hBox.setSpacing(10);
@@ -93,17 +116,25 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
tunerBox.setSpacing(10); tunerBox.setSpacing(10);
tunerBox.getChildren().addAll(preferredTunerLabel, getPreferredTunerComboBox()); 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 vbox = new VBox();
vbox.setSpacing(10); vbox.setSpacing(10);
vbox.setPadding(new Insets(0,0,0,20)); 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); hBox.getChildren().add(vbox);
@@ -225,7 +256,25 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
SourceConfigTunerMultipleFrequency sourceMulti = (SourceConfigTunerMultipleFrequency)sourceConfiguration; SourceConfigTunerMultipleFrequency sourceMulti = (SourceConfigTunerMultipleFrequency)sourceConfiguration;
setFrequencies(sourceMulti.getFrequencies()); setFrequencies(sourceMulti.getFrequencies());
preferredTuner = sourceMulti.getPreferredTuner(); 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 else
{ {
@@ -236,7 +285,6 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
updatePreferredTuners(); updatePreferredTuners();
if(preferredTuner != null) if(preferredTuner != null)
{ {
if(!getPreferredTunerComboBox().getItems().contains(preferredTuner)) if(!getPreferredTunerComboBox().getItems().contains(preferredTuner))
@@ -267,9 +315,8 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
mChannelRotationDelaySpinner.setTooltip( mChannelRotationDelaySpinner.setTooltip(
new Tooltip("Delay on each frequency before rotating to next when seeking to next active channel frequency")); 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); mChannelRotationDelaySpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
int min = ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_MINIMUM; SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(mFrequencyRotationMinimum,
int max = ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_MAXIMUM; mFrequencyRotationMaximum, mFrequencyRotationMinimum, 50);
SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(min, max, min, 50);
mChannelRotationDelaySpinner.setValueFactory(svf); mChannelRotationDelaySpinner.setValueFactory(svf);
mChannelRotationDelaySpinner.getValueFactory().valueProperty() mChannelRotationDelaySpinner.getValueFactory().valueProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); .addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
@@ -278,7 +325,6 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
return mChannelRotationDelaySpinner; return mChannelRotationDelaySpinner;
} }
/** /**
* Updates the preferred tuner's combo box to reflect the current set of tuners. * 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.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest; import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -95,15 +94,16 @@ public class DMRDecoderState extends TimeslotDecoderState
private DMRTrafficChannelManager mTrafficChannelManager; private DMRTrafficChannelManager mTrafficChannelManager;
private DecodeEvent mCurrentCallEvent; private DecodeEvent mCurrentCallEvent;
private long mCurrentFrequency; 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 = private static final AddChannelRotationActiveStateRequest CAPACITY_PLUS_ACTIVE_STATE_REQUEST =
new AddChannelRotationActiveStateRequest(State.ACTIVE); new AddChannelRotationActiveStateRequest(State.ACTIVE);
/** /**
* Constructs an DMR decoder state with an optional traffic channel manager. * Constructs an DMR decoder state with an optional traffic channel manager.
* @param channel with configuration details * @param channel with configuration details
* @param timeslot for this decoder state (1 or 2)
* @param trafficChannelManager for handling traffic channel grants. * @param trafficChannelManager for handling traffic channel grants.
* @param configurationMonitor for tracking activity summary
*/ */
public DMRDecoderState(Channel channel, int timeslot, DMRTrafficChannelManager trafficChannelManager) public DMRDecoderState(Channel channel, int timeslot, DMRTrafficChannelManager trafficChannelManager)
{ {
@@ -111,12 +111,18 @@ public class DMRDecoderState extends TimeslotDecoderState
mChannel = channel; mChannel = channel;
mTrafficChannelManager = trafficChannelManager; 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. //the monitor for timeslot 1.
if(timeslot == 1) if(timeslot == 1)
{ {
mNetworkConfigurationMonitor = new DMRNetworkConfigurationMonitor(channel); 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 @Override
public void receive(IMessage message) public void receive(IMessage message)
{ {
if(message.isValid() && message.getTimeslot() == getTimeslot()) if(message.getTimeslot() == getTimeslot())
{ {
if(message instanceof VoiceMessage) if(message instanceof VoiceMessage)
{ {
@@ -191,11 +197,11 @@ public class DMRDecoderState extends TimeslotDecoderState
{ {
processData((DataMessage)message); processData((DataMessage)message);
} }
else if(message instanceof LCMessage) else if((message.isValid() || mIgnoreCRCChecksums) && message instanceof LCMessage)
{ {
processLinkControl((LCMessage)message, false); processLinkControl((LCMessage)message, false);
} }
else if(message instanceof DMRPacketMessage) else if(message.isValid() && message instanceof DMRPacketMessage)
{ {
processPacket((DMRPacketMessage)message); processPacket((DMRPacketMessage)message);
} }
@@ -205,13 +211,14 @@ public class DMRDecoderState extends TimeslotDecoderState
} }
} }
//SLCO messages on timeslot 0 to catch capacity plus rest channel events //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); processLinkControl((LCMessage)message, false);
} }
//Pass the message to the network configuration monitor, if this decoder state has a non-null instance //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); mNetworkConfigurationMonitor.process((DMRMessage)message);
} }
@@ -265,7 +272,7 @@ public class DMRDecoderState extends TimeslotDecoderState
*/ */
private void processPacket(DMRPacketMessage packet) 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()) DecodeEvent packetEvent = DMRDecodeEvent.builder(packet.getTimestamp())
.eventDescription(DecodeEventType.DATA_PACKET.name()) .eventDescription(DecodeEventType.DATA_PACKET.name())
@@ -302,7 +309,7 @@ public class DMRDecoderState extends TimeslotDecoderState
case MBC_ENC_HEADER: case MBC_ENC_HEADER:
case DATA_ENC_HEADER: case DATA_ENC_HEADER:
case CHANNEL_CONTROL_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; break;
} }
@@ -317,13 +324,16 @@ public class DMRDecoderState extends TimeslotDecoderState
/** /**
* Process Data Messages * 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) private void processData(DataMessage message)
{ {
switch(message.getSlotType().getDataType()) switch(message.getSlotType().getDataType())
{ {
case CSBK: case CSBK:
if(message instanceof CSBKMessage) if((message.isValid() || mIgnoreCRCChecksums) && message instanceof CSBKMessage)
{ {
processCSBK((CSBKMessage)message); processCSBK((CSBKMessage)message);
} }
@@ -358,10 +368,12 @@ public class DMRDecoderState extends TimeslotDecoderState
processTerminator((Terminator)message); processTerminator((Terminator)message);
} }
break; break;
case MBC_BLOCK:
case RATE_1_OF_2_DATA: case RATE_1_OF_2_DATA:
case RATE_3_OF_4_DATA: case RATE_3_OF_4_DATA:
case RATE_1_DATA: case RATE_1_DATA:
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
break;
case MBC_BLOCK:
case RESERVED_15: case RESERVED_15:
case UNKNOWN: case UNKNOWN:
default: default:
@@ -408,7 +420,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(csbk instanceof Acknowledge) if(csbk instanceof Acknowledge)
{ {
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.RESPONSE.name()) .eventDescription(DecodeEventType.RESPONSE.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot()) .timeslot(csbk.getTimeslot())
.details(((Acknowledge)csbk).getReason().toString()) .details(((Acknowledge)csbk).getReason().toString())
@@ -423,7 +435,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(csbk instanceof Acknowledge) if(csbk instanceof Acknowledge)
{ {
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.RESPONSE.name()) .eventDescription(DecodeEventType.RESPONSE.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot()) .timeslot(csbk.getTimeslot())
.details(((Acknowledge)csbk).getReason().toString()) .details(((Acknowledge)csbk).getReason().toString())
@@ -440,7 +452,7 @@ public class DMRDecoderState extends TimeslotDecoderState
{ {
case AUTHENTICATE_REGISTER_RADIO_CHECK_SERVICE: case AUTHENTICATE_REGISTER_RADIO_CHECK_SERVICE:
DecodeEvent registerEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) DecodeEvent registerEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.COMMAND.name()) .eventDescription(DecodeEventType.COMMAND.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot()) .timeslot(csbk.getTimeslot())
.details(DecodeEventType.REGISTER.toString()) .details(DecodeEventType.REGISTER.toString())
@@ -449,7 +461,7 @@ public class DMRDecoderState extends TimeslotDecoderState
break; break;
case CANCEL_CALL_SERVICE: case CANCEL_CALL_SERVICE:
DecodeEvent cancelEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) DecodeEvent cancelEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.COMMAND.name()) .eventDescription(DecodeEventType.COMMAND.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot()) .timeslot(csbk.getTimeslot())
.details("CANCEL CALL") .details("CANCEL CALL")
@@ -460,7 +472,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(csbk instanceof StunReviveKill) if(csbk instanceof StunReviveKill)
{ {
DecodeEvent stunEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) DecodeEvent stunEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.COMMAND.name()) .eventDescription(DecodeEventType.COMMAND.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot()) .timeslot(csbk.getTimeslot())
.details(((StunReviveKill)csbk).getCommand() + " RADIO") .details(((StunReviveKill)csbk).getCommand() + " RADIO")
@@ -481,7 +493,7 @@ public class DMRDecoderState extends TimeslotDecoderState
ServiceRadioCheck src = (ServiceRadioCheck)csbk; ServiceRadioCheck src = (ServiceRadioCheck)csbk;
DecodeEvent checkEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) DecodeEvent checkEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.RADIO_CHECK.name()) .eventDescription(DecodeEventType.RADIO_CHECK.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot()) .timeslot(csbk.getTimeslot())
.details(src.getServiceDescription() + " SERVICE FOR " + .details(src.getServiceDescription() + " SERVICE FOR " +
@@ -502,7 +514,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(aloha.hasRadioIdentifier()) if(aloha.hasRadioIdentifier())
{ {
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.RESPONSE.name()) .eventDescription(DecodeEventType.RESPONSE.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot()) .timeslot(csbk.getTimeslot())
.details("Aloha Acknowledge") .details("Aloha Acknowledge")
@@ -521,7 +533,7 @@ public class DMRDecoderState extends TimeslotDecoderState
{ {
case MASS_REGISTRATION: case MASS_REGISTRATION:
DecodeEvent massEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) DecodeEvent massEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.REGISTER.name()) .eventDescription(DecodeEventType.REGISTER.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot()) .timeslot(csbk.getTimeslot())
.details("MASS REGISTRATION") .details("MASS REGISTRATION")
@@ -532,7 +544,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(csbk instanceof VoteNowAdvice) if(csbk instanceof VoteNowAdvice)
{ {
DecodeEvent voteEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) DecodeEvent voteEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.COMMAND.name()) .eventDescription(DecodeEventType.COMMAND.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot()) .timeslot(csbk.getTimeslot())
.details("VOTE NOW FOR " + ((VoteNowAdvice)csbk).getVotedSystemIdentityCode()) .details("VOTE NOW FOR " + ((VoteNowAdvice)csbk).getVotedSystemIdentityCode())
@@ -556,7 +568,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(csbk instanceof Protect) if(csbk instanceof Protect)
{ {
DecodeEvent protectEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) DecodeEvent protectEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.COMMAND.name()) .eventDescription(DecodeEventType.COMMAND.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot()) .timeslot(csbk.getTimeslot())
.details("PROTECT: " + ((Protect)csbk).getProtectKind()) .details("PROTECT: " + ((Protect)csbk).getProtectKind())
@@ -602,12 +614,37 @@ public class DMRDecoderState extends TimeslotDecoderState
{ {
ChannelGrant dataGrant = (ChannelGrant)csbk; ChannelGrant dataGrant = (ChannelGrant)csbk;
DMRChannel channel = dataGrant.getChannel(); DMRChannel channel = dataGrant.getChannel();
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
if(hasTrafficChannelManager()) if(hasTrafficChannelManager())
{ {
mTrafficChannelManager.processChannelGrant(channel, getMergedIdentifierCollection(csbk.getIdentifiers()), mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted()); 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; break;
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT: case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
@@ -616,12 +653,37 @@ public class DMRDecoderState extends TimeslotDecoderState
{ {
ChannelGrant tgGrant = (ChannelGrant)csbk; ChannelGrant tgGrant = (ChannelGrant)csbk;
DMRChannel channel = tgGrant.getChannel(); DMRChannel channel = tgGrant.getChannel();
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
if(hasTrafficChannelManager()) if(hasTrafficChannelManager())
{ {
mTrafficChannelManager.processChannelGrant(channel, getMergedIdentifierCollection(csbk.getIdentifiers()), mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted()); 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; break;
case STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT: case STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT:
@@ -630,13 +692,36 @@ public class DMRDecoderState extends TimeslotDecoderState
{ {
ChannelGrant channelGrant = (ChannelGrant)csbk; ChannelGrant channelGrant = (ChannelGrant)csbk;
DMRChannel channel = channelGrant.getChannel(); DMRChannel channel = channelGrant.getChannel();
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
if(hasTrafficChannelManager()) if(hasTrafficChannelManager())
{ {
mTrafficChannelManager.processChannelGrant(channel, getMergedIdentifierCollection(csbk.getIdentifiers()), mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted()); csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
} }
processCallDetection(channelGrant.getChannel(), channelGrant.getIdentifiers(), else
channelGrant.getTimestamp(), DecodeEventType.CALL_UNIT_TO_UNIT); {
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; break;
case MOTOROLA_CAPMAX_ALOHA: case MOTOROLA_CAPMAX_ALOHA:
@@ -647,7 +732,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(cmAloha.hasRadioIdentifier()) if(cmAloha.hasRadioIdentifier())
{ {
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.RESPONSE.name()) .eventDescription(DecodeEventType.RESPONSE.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot()) .timeslot(csbk.getTimeslot())
.details("Aloha Acknowledge") .details("Aloha Acknowledge")
@@ -664,13 +749,38 @@ public class DMRDecoderState extends TimeslotDecoderState
if(csbk instanceof ConnectPlusDataChannelGrant) if(csbk instanceof ConnectPlusDataChannelGrant)
{ {
ConnectPlusDataChannelGrant cpdcg = (ConnectPlusDataChannelGrant)csbk; ConnectPlusDataChannelGrant cpdcg = (ConnectPlusDataChannelGrant)csbk;
DMRChannel channel = cpdcg.getChannel();
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
if(hasTrafficChannelManager()) if(hasTrafficChannelManager())
{ {
mTrafficChannelManager.processChannelGrant(cpdcg.getChannel(), mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
getMergedIdentifierCollection(csbk.getIdentifiers()), csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted()); 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())); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
break; break;
@@ -697,14 +807,39 @@ public class DMRDecoderState extends TimeslotDecoderState
case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER: case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER:
if(csbk instanceof ConnectPlusVoiceChannelUser) if(csbk instanceof ConnectPlusVoiceChannelUser)
{ {
DMRChannel channel = ((ConnectPlusVoiceChannelUser)csbk).getChannel(); ConnectPlusVoiceChannelUser cpvcu = (ConnectPlusVoiceChannelUser)csbk;
DMRChannel channel = cpvcu.getChannel();
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
if(hasTrafficChannelManager()) if(hasTrafficChannelManager())
{ {
mTrafficChannelManager.processChannelGrant(channel, getMergedIdentifierCollection(csbk.getIdentifiers()), mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted()); 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())); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
break; break;
@@ -718,9 +853,6 @@ public class DMRDecoderState extends TimeslotDecoderState
broadcast(affiliateEvent); broadcast(affiliateEvent);
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot())); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
break; break;
case STANDARD_MOVE_TSCC:
default: default:
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot())); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
break; 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 * @param event to check for staleness
* have to be modified to detect if the trunked channel was allocated or if the call was simply detected. This * @param timestamp to check the event against
* would be contingent on having a map of Logical Slot Numbers to frequency for the traffic channel manager to make * @param currentIdentifiers to compare against the event
* an allocation. * @return true if the event is stale.
*
* @param channel for the call event
* @param identifiers for the call event
* @param timestamp of the event or update
*/ */
private void processCallDetection(DMRChannel channel, List<Identifier> identifiers, long timestamp, private boolean isStale(DecodeEvent event, long timestamp, List<Identifier> currentIdentifiers)
DecodeEventType eventType)
{ {
//Check to see if there is a current call event to see if the detected call event is actually for this timeslot if(event == null || (timestamp - event.getTimeStart() > MAX_VALID_CALL_DURATION_MS))
//and we can then identify the LSN for this timeslot
if(mCurrentFrequency == 0 && mCurrentCallEvent != null &&
isSameCall(mCurrentCallEvent.getIdentifierCollection(), identifiers))
{ {
mCurrentFrequency = channel.getDownlinkFrequency(); return true;
if(mNetworkConfigurationMonitor != null)
{
mNetworkConfigurationMonitor.setCurrentChannel(channel);
}
} }
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber()); return !isSameCall(event.getIdentifierCollection(), currentIdentifiers);
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);
// }
} }
/** /**
@@ -1006,7 +1092,7 @@ public class DMRDecoderState extends TimeslotDecoderState
ic.update(gpsInformation.getGPSLocation()); ic.update(gpsInformation.getGPSLocation());
DecodeEvent gpsEvent = DMRDecodeEvent.builder(message.getTimestamp()) DecodeEvent gpsEvent = DMRDecodeEvent.builder(message.getTimestamp())
.eventDescription(DecodeEventType.GPS.name()) .eventDescription(DecodeEventType.GPS.toString())
.identifiers(ic) .identifiers(ic)
.timeslot(message.getTimeslot()) .timeslot(message.getTimeslot())
.details("LOCATION:" + gpsInformation.getGPSLocation()) .details("LOCATION:" + gpsInformation.getGPSLocation())

View File

@@ -313,7 +313,7 @@ public class DMRNetworkConfigurationMonitor
if(mTier3Model != null) if(mTier3Model != null)
{ {
sb.append("\nNetwork Model").append(mTier3Model); sb.append("\nNetwork Model: ").append(mTier3Model);
} }
//Observed DMR Channels //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.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; 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.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.dmr.message.data.csbk.Opcode;
import io.github.dsheirer.module.decode.event.DecodeEvent; import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventHistory; import io.github.dsheirer.module.decode.event.DecodeEventHistory;
@@ -90,8 +91,12 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
IChannelEventProvider IChannelEventProvider
{ {
private final static Logger mLog = LoggerFactory.getLogger(DMRTrafficChannelManager.class); 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 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 Queue<Channel> mAvailableTrafficChannelQueue = new ConcurrentLinkedQueue<>();
private List<Channel> mManagedTrafficChannels; private List<Channel> mManagedTrafficChannels;
@@ -325,7 +330,19 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn); 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); Identifier from = getIdentifier(identifierCollection, Role.FROM);
@@ -336,115 +353,98 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
{ {
event.end(timestamp); event.end(timestamp);
DMRChannelGrantEvent continuationGrantEvent = DMRChannelGrantEvent.channelGrantBuilder(timestamp) event = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
.channel(channel) .channel(channel)
.eventDescription(getEventType(opcode, event.isEncrypted()).toString() + " - Continue") .eventDescription(getEventType(opcode, identifierCollection, encrypted).toString() + " - Continue")
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : "")) .details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
.identifiers(identifierCollection) .identifiers(identifierCollection)
.build(); .build();
mLSNGrantEventMap.put(lsn, continuationGrantEvent); mLSNGrantEventMap.put(lsn, event);
broadcast(continuationGrantEvent); broadcast(event);
} }
} }
//update the ending timestamp so that the duration value is correctly calculated //update the ending timestamp so that the duration value is correctly calculated
event.update(timestamp); 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()) broadcast(event);
{
DMRChannelGrantEvent channelGrantEvent = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
.encrypted(encrypted)
.channel(channel)
.eventDescription(getEventType(opcode, encrypted).toString() + " - Ignored")
.details("DATA CALL IGNORED")
.identifiers(identifierCollection)
.build();
mLSNGrantEventMap.put(channel.getLogicalSlotNumber(), channelGrantEvent); //Even though we have an event, the initial or continuation channel grant may have been rejected. Check to see
broadcast(channelGrantEvent); // if there is a traffic channel allocated. If not, allocate one and update the event description.
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
long frequency = channel.getDownlinkFrequency(); long frequency = channel.getDownlinkFrequency();
if(frequency > 0 && frequency != mCurrentControlFrequency && if(frequency == 0)
!mAllocatedTrafficChannelFrequencyMap.containsKey(frequency))
{ {
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(); Channel trafficChannel = mAvailableTrafficChannelQueue.poll();
if(trafficChannel == null) if(trafficChannel != null)
{
String details = (channelGrantEvent.getDetails() != null ? channelGrantEvent.getDetails() : "") +
" " + MAX_TRAFFIC_CHANNELS_EXCEEDED;
channelGrantEvent.setDetails(details);
channelGrantEvent.setEventDescription(channelGrantEvent.getEventDescription() + " - Ignored");
}
else
{ {
SourceConfigTuner sourceConfig = new SourceConfigTuner(); SourceConfigTuner sourceConfig = new SourceConfigTuner();
sourceConfig.setFrequency(frequency); sourceConfig.setFrequency(frequency);
trafficChannel.setSourceConfiguration(sourceConfig); trafficChannel.setSourceConfiguration(sourceConfig);
mAllocatedTrafficChannelFrequencyMap.put(frequency, trafficChannel); 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 * 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; DecodeEventType type = null;
@@ -452,10 +452,22 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
{ {
case STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT: case STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT:
case STANDARD_BROADCAST_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; type = encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP;
break; 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_PRIVATE_VOICE_CHANNEL_GRANT:
case STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT: case STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT:
type = encrypted ? DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED : DecodeEventType.CALL_UNIT_TO_UNIT; 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); 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. * 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. * Removes any call events that are associated with the specified frequency.
* @param frequency that was removed * @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<>(); List<Integer> lsnsToRemove = new ArrayList<>();
@@ -633,20 +669,35 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
for(Integer lsn: lsnsToRemove) 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(event != null)
{ {
if(description != null) if(event.getDetails() == null)
{ {
if(event.getEventDescription() != null) event.setDetails(detailFragment);
{ }
event.setEventDescription(event.getEventDescription() + " - " + description); else if(!event.getDetails().endsWith(detailFragment))
} {
else event.setDetails(event.getDetails() + detailFragment);
{
event.setEventDescription(description);
}
} }
broadcast(event); broadcast(event);
@@ -659,36 +710,64 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
{ {
Channel channel = channelEvent.getChannel(); Channel channel = channelEvent.getChannel();
if(channel.isTrafficChannel() && (channelEvent.getEvent() == Event.NOTIFICATION_PROCESSING_STOP || if(channel.isTrafficChannel())
channelEvent.getEvent() == Event.NOTIFICATION_PROCESSING_START_REJECTED))
{ {
String optionalDescription = (channelEvent.getEvent() == Event.NOTIFICATION_PROCESSING_START_REJECTED ? switch(channelEvent.getEvent())
"Rejected" : null);
long frequencyToRemove = 0;
for(Map.Entry<Long,Channel> entry: mAllocatedTrafficChannelFrequencyMap.entrySet())
{ {
if(channel == entry.getValue() && entry.getKey() != null) case NOTIFICATION_PROCESSING_STOP:
{ long frequencyToRemove = 0;
frequencyToRemove = entry.getKey();
cleanupCallEvents(entry.getKey(), optionalDescription); 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; break;
} case NOTIFICATION_PROCESSING_START_REJECTED:
} long frequencyToUpdate = 0;
if(frequencyToRemove > 0) for(Map.Entry<Long,Channel> entry: mAllocatedTrafficChannelFrequencyMap.entrySet())
{ {
mAllocatedTrafficChannelFrequencyMap.remove(frequencyToRemove); 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 if(frequencyToUpdate > 0)
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToRemove)); {
} mAllocatedTrafficChannelFrequencyMap.remove(frequencyToUpdate);
//Add the traffic channel back to the queue to be reused //Unlock the frequency in the channel rotation monitor
if(!mAvailableTrafficChannelQueue.contains(channel)) getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToUpdate));
{ }
mAvailableTrafficChannelQueue.add(channel);
//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 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 int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
private boolean mIgnoreDataCalls = true; private boolean mIgnoreDataCalls = true;
private boolean mIgnoreCRCChecksums = false;
private List<TimeslotFrequency> mTimeslotMap = new ArrayList<>(); private List<TimeslotFrequency> mTimeslotMap = new ArrayList<>();
public DecodeConfigDMR() public DecodeConfigDMR()
@@ -87,6 +91,24 @@ public class DecodeConfigDMR extends DecodeConfiguration
mIgnoreDataCalls = ignore; 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. * Traffic channel pool size.
* @return * @return

View File

@@ -88,7 +88,6 @@ public class DMRLogicalChannel extends DMRChannel
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("LSN:").append(getLogicalSlotNumber()); sb.append("LSN:").append(getLogicalSlotNumber());
sb.append(" LCN:").append(getValue()); sb.append(" LCN:").append(getValue());
sb.append(" TS:").append(getTimeslot());
return sb.toString(); 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_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"), 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_CAPMAX_ALOHA(Vendor.MOTOROLA_CAPACITY_PLUS, 25, "ALOHA"),
MOTOROLA_CAPPLUS_CALL_ALERT(Vendor.MOTOROLA_CAPACITY_PLUS, 31, "CALL ALERT"), 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.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; 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.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.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency; import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
@@ -208,7 +209,7 @@ public class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
if(mChannel == null) if(mChannel == null)
{ {
mChannel = new DMRLogicalChannel(getMoveToChannelNumber(), getMoveToTimeslot()); mChannel = new DMRTier3Channel(getMoveToChannelNumber(), getMoveToTimeslot());
} }
return mChannel; 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.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; 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.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.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH; 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.SlotType;
@@ -156,7 +156,7 @@ public class MoveTSCC extends CSBKMessage
} }
else 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.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; 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.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.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency; import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -121,7 +122,7 @@ public class AdjacentSiteInformation extends Announcement implements ITimeslotFr
if(mChannel == null) if(mChannel == null)
{ {
//Timeslot hard-coded to 1 //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; return mChannel;

View File

@@ -23,7 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; 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.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.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
@@ -221,7 +221,7 @@ public class AnnounceWithdrawTSCC extends Announcement
} }
else 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) if(mChannel2 == null)
{ {
mChannel2 = new DMRLogicalChannel(getMessage().getInt(CHANNEL_NUMBER_2), 1); mChannel2 = new DMRTier3Channel(getMessage().getInt(CHANNEL_NUMBER_2), 1);
} }
return mChannel2; 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.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; 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.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.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency; import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -203,7 +204,7 @@ public class VoteNowAdvice extends Announcement implements ITimeslotFrequencyRec
if(mChannel == null) if(mChannel == null)
{ {
mChannel = new DMRLogicalChannel(getMessage().getInt(VOTED_CHANNEL_NUMBER), 1); mChannel = new DMRTier3Channel(getMessage().getInt(VOTED_CHANNEL_NUMBER), 1);
} }
return mChannel; 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.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; 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.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.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency; import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -148,7 +149,7 @@ public abstract class ChannelGrant extends CSBKMessage implements ITimeslotFrequ
if(mChannel == null) if(mChannel == null)
{ {
mChannel = new DMRLogicalChannel(getChannelNumber(), getChannelGrantTimeslot()); mChannel = new DMRTier3Channel(getChannelNumber(), getChannelGrantTimeslot());
} }
return mChannel; 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.HyteraXPTChannel;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.NullMessage; 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.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 io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.UnknownShortLCMessage;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -152,11 +153,13 @@ public class LCMessageFactory
case SHORT_STANDARD_CONTROL_CHANNEL_SYSTEM_PARAMETERS: case SHORT_STANDARD_CONTROL_CHANNEL_SYSTEM_PARAMETERS:
slc = new ControlChannelSystemParameters(message, timestamp, timeslot); slc = new ControlChannelSystemParameters(message, timestamp, timeslot);
break; break;
case SHORT_STANDARD_TRAFFIC_CHANNEL_SYSTEM_PARAMETERS:
slc = new TrafficChannelSystemParameters(message, timestamp, timeslot);
break;
case SHORT_HYTERA_XPT_CHANNEL: case SHORT_HYTERA_XPT_CHANNEL:
case SHORT_STANDARD_XPT_CHANNEL: case SHORT_STANDARD_XPT_CHANNEL:
slc = new HyteraXPTChannel(message, timestamp, timeslot); slc = new HyteraXPTChannel(message, timestamp, timeslot);
break; break;
case SHORT_STANDARD_TRAFFIC_CHANNEL_SYSTEM_PARAMETERS:
case SHORT_STANDARD_UNKNOWN: case SHORT_STANDARD_UNKNOWN:
default: default:
slc = new UnknownShortLCMessage(message, timestamp, timeslot); 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 public class DecodeEvent implements IDecodeEvent
{ {
private long mTimeStart; private long mTimeStart;
private long mDuration; private long mTimeEnd;
private String mEventDescription; private String mEventDescription;
private IdentifierCollection mIdentifierCollection; private IdentifierCollection mIdentifierCollection;
private IChannelDescriptor mChannelDescriptor; private IChannelDescriptor mChannelDescriptor;
@@ -66,7 +66,7 @@ public class DecodeEvent implements IDecodeEvent
{ {
if(timestamp > mTimeStart) if(timestamp > mTimeStart)
{ {
mDuration = timestamp - mTimeStart; mTimeEnd = timestamp;
} }
} }
@@ -87,13 +87,26 @@ public class DecodeEvent implements IDecodeEvent
return mTimeStart; return mTimeStart;
} }
/**
* Event end in milliseconds
*/
public long getTimeEnd()
{
return mTimeEnd;
}
/** /**
* Event duration in milliseconds * Event duration in milliseconds
*/ */
@Override @Override
public long getDuration() 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) 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(" IDS:").append(Joiner.on(",").join(mIdentifierCollection.getIdentifiers()));
} }
sb.append(" DURATION:").append(mDuration); sb.append(" DURATION:").append(getDuration());
sb.append(" CHANNEL:").append(mChannelDescriptor); sb.append(" CHANNEL:").append(mChannelDescriptor);
return sb.toString(); return sb.toString();
} }

View File

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

View File

@@ -30,6 +30,10 @@ import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigMPT1327 extends DecodeConfiguration implements WithCallTimeout 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 String mChannelMapName;
private Sync mSync = Sync.NORMAL; 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 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 P25P1Decoder.Modulation mModulation = P25P1Decoder.Modulation.C4FM;
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT; private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;

View File

@@ -823,15 +823,20 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
} }
else else
{ {
mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf());
mCurrentCallEvent.end(timestamp);
broadcast(mCurrentCallEvent);
if(type == DecodeEventType.CALL_ENCRYPTED) if(type == DecodeEventType.CALL_ENCRYPTED)
{ {
mCurrentCallEvent.setEventDescription(type.toString()); mCurrentCallEvent.setEventDescription(type.toString());
mCurrentCallEvent.setDetails(details); 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(" SYSTEM:").append(getSystem());
sb.append(" RFSS:").append(getRfss()); sb.append(" RFSS:").append(getRfss());
sb.append(" SITE:").append(getSite()); sb.append(" SITE:").append(getSite());
sb.append(" CHANNEL:").append(getChannel());
sb.append(" LRA:").append(getLocationRegistrationArea()); sb.append(" LRA:").append(getLocationRegistrationArea());
sb.append(" FLAGS ").append(getSiteFlags()); sb.append(" FLAGS ").append(getSiteFlags());
sb.append(" SERVICES ").append(getSystemServiceClass()); sb.append(" SERVICES ").append(getSystemServiceClass());

View File

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

View File

@@ -275,6 +275,14 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
return (int)(getBandwidth() * mUsableBandwidthPercentage); 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. * 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.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
public class AirspyTunerController extends USBTunerController public class AirspyTunerController extends USBTunerController
{ {
@@ -79,6 +80,7 @@ public class AirspyTunerController extends USBTunerController
private AirspyDeviceInformation mDeviceInfo; private AirspyDeviceInformation mDeviceInfo;
private List<AirspySampleRate> mSampleRates = new ArrayList<>(); private List<AirspySampleRate> mSampleRates = new ArrayList<>();
private int mSampleRate = 0; private int mSampleRate = 0;
private ReentrantLock mLock = new ReentrantLock();
private USBTransferProcessor mUSBTransferProcessor; private USBTransferProcessor mUSBTransferProcessor;
public AirspyTunerController(Device device) throws SourceException public AirspyTunerController(Device device) throws SourceException
@@ -347,32 +349,35 @@ public class AirspyTunerController extends USBTunerController
@Override @Override
public void setTunedFrequency(long frequency) throws SourceException public void setTunedFrequency(long frequency) throws SourceException
{ {
if(FREQUENCY_MIN <= frequency && frequency <= FREQUENCY_MAX) mLock.lock();
try
{ {
ByteBuffer buffer = ByteBuffer.allocateDirect(4); if(FREQUENCY_MIN <= frequency && frequency <= FREQUENCY_MAX)
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt((int) frequency);
buffer.rewind();
try
{ {
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("Frequency [" + frequency + "] outside " + "of tunable range " + FREQUENCY_MIN + "-" + FREQUENCY_MAX);
throw new SourceException("error setting frequency [" +
frequency + "]", e);
} }
} }
else finally
{ {
throw new SourceException("Frequency [" + frequency + "] outside " mLock.unlock();
+ "of tunable range " + FREQUENCY_MIN + "-" + FREQUENCY_MAX);
} }
} }
@@ -406,6 +411,25 @@ public class AirspyTunerController extends USBTunerController
{ {
mSampleRate = rate.getRate(); mSampleRate = rate.getRate();
mFrequencyController.setSampleRate(mSampleRate); 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 LibUsbException on unsuccessful read operation
* @throws UsbException on USB error * @throws UsbException on USB error
*/ */
public void setMixerAGC(boolean enabled) public void setMixerAGC(boolean enabled) throws LibUsbException, UsbException
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 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 UsbException on error in USB transfer
* @throws IllegalArgumentException if gain value is invalid * @throws IllegalArgumentException if gain value is invalid
*/ */
public void setLNAGain(int gain) public void setLNAGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException
throws LibUsbException, UsbException, IllegalArgumentException
{ {
if(LNA_GAIN_MIN <= gain && gain <= LNA_GAIN_MAX) mLock.lock();
{
int result = readByte(Command.SET_LNA_GAIN, 0, gain, true);
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 + mLock.unlock();
"] is outside value range: " + LNA_GAIN_MIN + "-" + LNA_GAIN_MAX);
} }
} }
@@ -550,22 +599,30 @@ public class AirspyTunerController extends USBTunerController
* @throws UsbException on error in USB transfer * @throws UsbException on error in USB transfer
* @throws IllegalArgumentException if gain value is invalid * @throws IllegalArgumentException if gain value is invalid
*/ */
public void setMixerGain(int gain) public void setMixerGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException
throws LibUsbException, UsbException, IllegalArgumentException
{ {
if(MIXER_GAIN_MIN <= gain && gain <= MIXER_GAIN_MAX) mLock.lock();
{
int result = readByte(Command.SET_MIXER_GAIN, 0, gain, true);
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 + mLock.unlock();
"] is outside value range: " + MIXER_GAIN_MIN + "-" + MIXER_GAIN_MAX);
} }
} }
@@ -577,22 +634,30 @@ public class AirspyTunerController extends USBTunerController
* @throws UsbException on error in USB transfer * @throws UsbException on error in USB transfer
* @throws IllegalArgumentException if gain value is invalid * @throws IllegalArgumentException if gain value is invalid
*/ */
public void setIFGain(int gain) public void setIFGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException
throws LibUsbException, UsbException, IllegalArgumentException
{ {
if(IF_GAIN_MIN <= gain && gain <= IF_GAIN_MAX) mLock.lock();
{
int result = readByte(Command.SET_VGA_GAIN, 0, gain, true);
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 + mLock.unlock();
"] is outside value range: " + IF_GAIN_MIN + "-" + IF_GAIN_MAX);
} }
} }

View File

@@ -46,6 +46,7 @@ public class ChannelRotationMonitor extends Module implements ISourceEventProvid
Listener<DecoderStateEvent> Listener<DecoderStateEvent>
{ {
public static final int CHANNEL_ROTATION_DELAY_MINIMUM = 200; 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; public static final int CHANNEL_ROTATION_DELAY_MAXIMUM = 2000;
private final static Logger mLog = LoggerFactory.getLogger(ChannelRotationMonitor.class); 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.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.util.concurrent.locks.ReentrantLock;
public abstract class FCDTunerController extends TunerController public abstract class FCDTunerController extends TunerController
{ {
@@ -60,6 +61,7 @@ public abstract class FCDTunerController extends TunerController
private FCDConfiguration mConfiguration = new FCDConfiguration(); private FCDConfiguration mConfiguration = new FCDConfiguration();
protected ComplexMixer mComplexMixer; 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 public void setTunedFrequency(long frequency) throws SourceException
{ {
mLock.lock();
try try
{ {
send(FCDCommand.APP_SET_FREQUENCY_HZ, frequency); 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 " + throw new SourceException("Couldn't set FCD Local " +
"Oscillator Frequency [" + frequency + "]", e); "Oscillator Frequency [" + frequency + "]", e);
} }
finally
{
mLock.unlock();
}
} }
/** /**

View File

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

View File

@@ -83,6 +83,8 @@ public class FCD2TunerController extends FCDTunerController
public void setLNAGain(boolean enabled) throws SourceException public void setLNAGain(boolean enabled) throws SourceException
{ {
mLock.lock();
try try
{ {
send(FCDCommand.APP_SET_LNA_GAIN, enabled ? 1 : 0); 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); throw new SourceException("error while setting LNA Gain", e);
} }
finally
{
mLock.unlock();
}
} }
public void setMixerGain(boolean enabled) throws SourceException public void setMixerGain(boolean enabled) throws SourceException
{ {
mLock.lock();
try try
{ {
send(FCDCommand.APP_SET_MIXER_GAIN, enabled ? 1 : 0); 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); throw new SourceException("error while setting Mixer Gain", e);
} }
finally
{
mLock.unlock();
}
} }
@Override @Override
@@ -159,6 +171,8 @@ public class FCD2TunerController extends FCDTunerController
public void setDCCorrection(int value) public void setDCCorrection(int value)
{ {
mLock.lock();
try try
{ {
send(FCDCommand.APP_SET_DC_CORRECTION, value); 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); mLog.error("error setting dc correction to [" + value + "]", e);
} }
finally
{
mLock.unlock();
}
} }
public int getIQCorrection() public int getIQCorrection()
@@ -191,6 +209,8 @@ public class FCD2TunerController extends FCDTunerController
public void setIQCorrection(int value) public void setIQCorrection(int value)
{ {
mLock.lock();
try try
{ {
send(FCDCommand.APP_SET_IQ_CORRECTION, value); 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); 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.nio.ByteOrder;
import java.util.Arrays; import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.concurrent.locks.ReentrantLock;
public class HackRFTunerController extends USBTunerController public class HackRFTunerController extends USBTunerController
{ {
@@ -69,6 +70,7 @@ public class HackRFTunerController extends USBTunerController
private Device mDevice; private Device mDevice;
private DeviceDescriptor mDeviceDescriptor; private DeviceDescriptor mDeviceDescriptor;
private DeviceHandle mDeviceHandle; private DeviceHandle mDeviceHandle;
private ReentrantLock mLock = new ReentrantLock();
public HackRFTunerController(Device device, DeviceDescriptor descriptor) throws SourceException public HackRFTunerController(Device device, DeviceDescriptor descriptor) throws SourceException
{ {
@@ -246,9 +248,16 @@ public class HackRFTunerController extends USBTunerController
*/ */
public void setBasebandFilter(BasebandFilter filter) throws UsbException public void setBasebandFilter(BasebandFilter filter) throws UsbException
{ {
write(Request.BASEBAND_FILTER_BANDWIDTH_SET, mLock.lock();
filter.getLowValue(),
filter.getHighValue()); 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 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() public boolean getAmplifier()
@@ -271,11 +288,18 @@ public class HackRFTunerController extends USBTunerController
*/ */
public void setLNAGain(HackRFLNAGain gain) throws UsbException public void setLNAGain(HackRFLNAGain gain) throws UsbException
{ {
int result = readByte(Request.SET_LNA_GAIN, 0, gain.getValue(), true); try
if(result != 1)
{ {
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 +308,20 @@ public class HackRFTunerController extends USBTunerController
*/ */
public void setVGAGain(HackRFVGAGain gain) throws UsbException 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 +336,34 @@ public class HackRFTunerController extends USBTunerController
@Override @Override
public void setTunedFrequency(long frequency) throws SourceException public void setTunedFrequency(long frequency) throws SourceException
{ {
ByteBuffer buffer = ByteBuffer.allocateDirect(8); mLock.lock();
buffer.order(ByteOrder.LITTLE_ENDIAN);
int mhz = (int)(frequency / 1E6);
int hz = (int)(frequency - (mhz * 1E6));
buffer.putInt(mhz);
buffer.putInt(hz);
buffer.rewind();
try try
{ {
write(Request.SET_FREQUENCY, 0, 0, buffer); ByteBuffer buffer = ByteBuffer.allocateDirect(8);
} buffer.order(ByteOrder.LITTLE_ENDIAN);
catch(UsbException e)
{
mLog.error("error setting frequency [" + frequency + "]", e);
throw new SourceException("error setting frequency [" + int mhz = (int)(frequency / 1E6);
frequency + "]", e); 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();
} }
} }

View File

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

View File

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

View File

@@ -180,6 +180,8 @@ public class R820TTunerController extends RTL2832TunerController
@Override @Override
public void setTunedFrequency(long frequency) throws SourceException public void setTunedFrequency(long frequency) throws SourceException
{ {
mLock.lock();
try try
{ {
enableI2CRepeater(mDeviceHandle, true); enableI2CRepeater(mDeviceHandle, true);
@@ -200,6 +202,10 @@ public class R820TTunerController extends RTL2832TunerController
+ "while setting frequency [" + frequency + "] - " + + "while setting frequency [" + frequency + "] - " +
e.getLocalizedMessage()); e.getLocalizedMessage());
} }
finally
{
mLock.unlock();
}
} }
/** /**
@@ -693,7 +699,16 @@ public class R820TTunerController extends RTL2832TunerController
*/ */
public void setLNAGain(R820TLNAGain gain, boolean controlI2C) throws UsbException 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 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 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.Channel;
import io.github.dsheirer.controller.channel.ChannelModel; import io.github.dsheirer.controller.channel.ChannelModel;
import io.github.dsheirer.controller.channel.ChannelProcessingManager; 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.Window.WindowType;
import io.github.dsheirer.dsp.filter.smoothing.SmoothingFilter.SmoothingType; 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.playlist.PlaylistManager;
import io.github.dsheirer.properties.SystemProperties; import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.Listener;
@@ -635,26 +636,22 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ReusableCom
if(event.getComponent() == mOverlayPanel) if(event.getComponent() == mOverlayPanel)
{ {
ArrayList<Channel> channels = ArrayList<Channel> channels = mOverlayPanel.getChannelsAtFrequency(frequency);
mOverlayPanel.getChannelsAtFrequency(frequency);
JMenu channelMenu = new JMenu("Channels");
for(Channel channel : channels) for(Channel channel : channels)
{ {
JMenu channelMenu = ChannelUtils.getContextMenu(mChannelModel, JMenuItem viewChannel = new JMenuItem("View/Edit: " + channel.getShortTitle());
mChannelProcessingManager, channel, viewChannel.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel)));
SpectralDisplayPanel.this); channelMenu.add(viewChannel);
if(channelMenu != null)
{
contextMenu.add(channelMenu);
}
} }
contextMenu.add(channelMenu);
if(!channels.isEmpty()) if(!channels.isEmpty())
{ {
contextMenu.add(new JSeparator()); contextMenu.add(new JSeparator());
} }
} }
/** /**