#6 DMR Decoder (#947)

* #6 DMR decoder feature - work in progress

* #6 DMR Decoder.

* #6 DMR Decoder.  Code review updates.

Co-authored-by: Zhenyu Mao <maozhenyu@foxmail.com>
Co-authored-by: Denny <denny@denny-desktop>
This commit is contained in:
Denny Sheirer
2020-10-12 04:57:59 -04:00
committed by GitHub
parent b061db1189
commit 7eec82deee
436 changed files with 36258 additions and 2409 deletions

View File

@@ -120,7 +120,7 @@ public class JavaFxWindowManager extends Application
private void setup()
{
//Register this class to receive events via each method annotated with @Subscribe
MyEventBus.getEventBus().register(this);
MyEventBus.getGlobalEventBus().register(this);
//Register JavaFX icon fonts
IconFontFX.register(jiconfont.icons.font_awesome.FontAwesome.getIconFont());
@@ -162,6 +162,8 @@ public class JavaFxWindowManager extends Application
*/
public void shutdown()
{
MyEventBus.getGlobalEventBus().unregister(this);
if(mChannelMapStage != null)
{
mUserPreferences.getJavaFxPreferences().unmonitor(mChannelMapStage);

View File

@@ -337,15 +337,15 @@ public class SDRTrunk implements Listener<TunerEvent>
JMenu viewMenu = new JMenu("View");
JMenuItem viewPlaylistItem = new JMenuItem("Playlist Editor");
viewPlaylistItem.addActionListener(e -> MyEventBus.getEventBus().post(new ViewPlaylistRequest()));
viewPlaylistItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewPlaylistRequest()));
viewMenu.add(viewPlaylistItem);
JMenuItem preferencesItem = new JMenuItem("User Preferences");
preferencesItem.addActionListener(e -> MyEventBus.getEventBus().post(new ViewUserPreferenceEditorRequest()));
preferencesItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest()));
viewMenu.add(preferencesItem);
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
settingsMenu.addActionListener(arg0 -> MyEventBus.getEventBus().post(new ViewIconManagerRequest()));
settingsMenu.addActionListener(arg0 -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
viewMenu.add(settingsMenu);
JMenuItem logFilesMenu = new JMenuItem("Logs & Recordings");

View File

@@ -0,0 +1,201 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
import io.github.dsheirer.gui.instrument.chart.DoubleLineChart;
import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart;
import io.github.dsheirer.gui.instrument.chart.PhaseLineChart;
import io.github.dsheirer.gui.instrument.chart.SymbolChart;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.dmr.DMRDecoderInstrumented;
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DMRPane extends ComplexDecoderPane
{
private final static Logger mLog = LoggerFactory.getLogger(DMRPane.class);
private HBox mSampleChartBox;
private ComplexSampleLineChart mSampleLineChart;
private EyeDiagramChart mEyeDiagramChart;
private HBox mDecoderChartBox;
private SymbolChart mSymbolChart;
private PhaseLineChart mPLLPhaseErrorLineChart;
private DoubleLineChart mPLLFrequencyLineChart;
private DoubleLineChart mSamplesPerSymbolLineChart;
private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster();
private DMRDecoderInstrumented mDecoder = new DMRDecoderInstrumented(new DecodeConfigDMR());
public DMRPane()
{
super(DecoderType.P25_PHASE1);
init();
}
private void init()
{
addListener(getDecoder());
getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
getDecoder().setComplexSymbolListener(getSymbolChart());
getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart());
getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart());
getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart());
mFilteredBufferBroadcaster.addListener(getSampleLineChart());
HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS);
getChildren().addAll(getSampleChartBox(), getDecoderChartBox());
mDecoder.setMessageListener(new Listener<IMessage>()
{
@Override
public void receive(IMessage message)
{
mLog.debug(message.toString());
}
});
}
@Override
public void setSampleRate(double sampleRate)
{
mLog.debug("Configuring for sample rate: " + sampleRate);
mDecoder.setSampleRate(sampleRate);
double samplesPerSymbol = sampleRate / 4800.0;
getSampleLineChart().setSamplesPerSymbol((int) samplesPerSymbol);
}
private DMRDecoderInstrumented getDecoder()
{
return mDecoder;
}
private SymbolChart getSymbolChart()
{
if(mSymbolChart == null)
{
mSymbolChart = new SymbolChart(10);
}
return mSymbolChart;
}
private HBox getDecoderChartBox()
{
if(mDecoderChartBox == null)
{
mDecoderChartBox = new HBox();
mDecoderChartBox.setMaxHeight(Double.MAX_VALUE);
getSymbolChart().setMaxWidth(Double.MAX_VALUE);
getPLLPhaseErrorLineChart().setMaxWidth(Double.MAX_VALUE);
getPLLFrequencyLineChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSymbolChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS);
mDecoderChartBox.getChildren().addAll(getSymbolChart(), getPLLPhaseErrorLineChart(),
getPLLFrequencyLineChart());
}
return mDecoderChartBox;
}
private HBox getSampleChartBox()
{
if(mSampleChartBox == null)
{
mSampleChartBox = new HBox();
mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE);
getSamplesPerSymbolLineChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS);
HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS);
HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS);
mSampleChartBox.getChildren().addAll(getSampleLineChart(), getEyeDiagramChart(),
getSamplesPerSymbolLineChart());
}
return mSampleChartBox;
}
private ComplexSampleLineChart getSampleLineChart()
{
if(mSampleLineChart == null)
{
mSampleLineChart = new ComplexSampleLineChart("Raw Samples", 100, 10);
}
return mSampleLineChart;
}
private EyeDiagramChart getEyeDiagramChart()
{
if(mEyeDiagramChart == null)
{
mEyeDiagramChart = new EyeDiagramChart(10, "Symbol: 3-4");
}
return mEyeDiagramChart;
}
private PhaseLineChart getPLLPhaseErrorLineChart()
{
if(mPLLPhaseErrorLineChart == null)
{
mPLLPhaseErrorLineChart = new PhaseLineChart(40);
}
return mPLLPhaseErrorLineChart;
}
private DoubleLineChart getPLLFrequencyLineChart()
{
if(mPLLFrequencyLineChart == null)
{
mPLLFrequencyLineChart = new DoubleLineChart("PLL Frequency", -500, 500, 50, 40);
}
return mPLLFrequencyLineChart;
}
private DoubleLineChart getSamplesPerSymbolLineChart()
{
if(mSamplesPerSymbolLineChart == null)
{
mSamplesPerSymbolLineChart = new DoubleLineChart("Sample Point", 9.5, 11.5, 0.1, 40);
}
return mSamplesPerSymbolLineChart;
}
}

View File

@@ -34,7 +34,8 @@ public class DecoderPaneFactory
DecoderType.LTR_NET,
DecoderType.MDC1200,
DecoderType.MPT1327,
DecoderType.TAIT_1200);
DecoderType.TAIT_1200,
DecoderType.DMR);
/**
* Creates a decoder pane for the decoder type
@@ -59,6 +60,8 @@ public class DecoderPaneFactory
throw new IllegalArgumentException("Use the getP25P1DecoderPane() method for P25 decoder type");
case P25_PHASE2:
return new P25Phase2HDQPSKPane();
case DMR:
return new DMRPane();
}
return getDefaultPane();

View File

@@ -151,12 +151,12 @@ public class PlaylistEditor extends BorderPane
MenuItem iconManagerItem = new MenuItem("_Icon Manager");
iconManagerItem.setAccelerator(new KeyCodeCombination(KeyCode.I, KeyCombination.ALT_ANY));
iconManagerItem.setOnAction(event -> MyEventBus.getEventBus().post(new ViewIconManagerRequest()));
iconManagerItem.setOnAction(event -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
viewMenu.getItems().add(iconManagerItem);
MenuItem userPreferenceItem = new MenuItem("_User Preferences");
userPreferenceItem.setAccelerator(new KeyCodeCombination(KeyCode.U, KeyCombination.ALT_ANY));
userPreferenceItem.setOnAction(event -> MyEventBus.getEventBus()
userPreferenceItem.setOnAction(event -> MyEventBus.getGlobalEventBus()
.post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.TALKGROUP_FORMAT)));
viewMenu.getItems().add(userPreferenceItem);

View File

@@ -172,7 +172,7 @@ public class AliasItemEditor extends Editor<Alias>
mPlaylistManager.getBroadcastModel().getConfiguredBroadcasts()
.addListener((ListChangeListener<ConfiguredBroadcast>)c -> updateStreamViews());
MyEventBus.getEventBus().register(this);
MyEventBus.getGlobalEventBus().register(this);
setMaxWidth(Double.MAX_VALUE);
@@ -410,6 +410,7 @@ public class AliasItemEditor extends Editor<Alias>
@Override
public void dispose()
{
MyEventBus.getGlobalEventBus().unregister(this);
}
private VBox getTitledPanesBox()
@@ -659,7 +660,7 @@ public class AliasItemEditor extends Editor<Alias>
if(selected != null)
{
MyEventBus.getEventBus().post(new ViewAliasIdentifierRequest(selected));
MyEventBus.getGlobalEventBus().post(new ViewAliasIdentifierRequest(selected));
}
}
});
@@ -687,6 +688,12 @@ public class AliasItemEditor extends Editor<Alias>
p25Menu.getItems().add(new SeparatorMenuItem());
p25Menu.getItems().add(new AddTonesItem("Audio Tones (Phase 2 Only)"));
Menu dmrMenu = new ProtocolMenu(Protocol.DMR);
dmrMenu.getItems().add(new AddTalkgroupItem(Protocol.DMR));
dmrMenu.getItems().add(new AddTalkgroupRangeItem(Protocol.DMR));
dmrMenu.getItems().add(new AddRadioIdItem(Protocol.DMR));
dmrMenu.getItems().add(new AddRadioIdRangeItem(Protocol.DMR));
Menu fleetsyncMenu = new ProtocolMenu(Protocol.FLEETSYNC);
fleetsyncMenu.getItems().add(new AddTalkgroupItem(Protocol.FLEETSYNC));
fleetsyncMenu.getItems().add(new AddTalkgroupRangeItem(Protocol.FLEETSYNC));
@@ -715,8 +722,8 @@ public class AliasItemEditor extends Editor<Alias>
Menu lojackMenu = new ProtocolMenu(Protocol.LOJACK);
lojackMenu.getItems().add(new AddLojackItem());
mAddIdentifierButton.getItems().addAll(p25Menu, fleetsyncMenu, ltrMenu, mdcMenu, mptMenu, passportMenu,
taitMenu, new SeparatorMenuItem(), lojackMenu);
mAddIdentifierButton.getItems().addAll(p25Menu, dmrMenu, fleetsyncMenu, ltrMenu, mdcMenu, mptMenu,
passportMenu, taitMenu, new SeparatorMenuItem(), lojackMenu);
}
return mAddIdentifierButton;

View File

@@ -178,7 +178,7 @@ public class AliasViewByIdentifierEditor extends VBox
if(selected != null)
{
MyEventBus.getEventBus().post(new ViewAliasRequest(selected.getAlias()));
MyEventBus.getGlobalEventBus().post(new ViewAliasRequest(selected.getAlias()));
}
});
}

View File

@@ -237,6 +237,10 @@ public class RadioIdEditor extends IdentifierEditor<Radio>
"Format: 0 - 16777215"));
mRadioDetails.add(new RadioDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
"Format: 0 - FFFFFF"));
mRadioDetails.add(new RadioDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
"Format: 0 - 16777215"));
mRadioDetails.add(new RadioDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
"Format: 0 - FFFFFF"));
mRadioDetails.add(new RadioDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0x7FFFFF),
"Format: 0 - 8388607"));
mRadioDetails.add(new RadioDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0x7FFFFF),

View File

@@ -239,6 +239,10 @@ public class TalkgroupEditor extends IdentifierEditor<Talkgroup>
"Format: 0 - 65535"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
"Format: 0 - FFFF"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
"Format: 0 - 16,777,215"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
"Format: 0 - FFFFFF"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.FLEETSYNC, IntegerFormat.FORMATTED,
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.LTR, IntegerFormat.FORMATTED, new LtrFormatter(0,0x3FFF),

View File

@@ -358,7 +358,7 @@ public abstract class ChannelConfigurationEditor extends Editor<Channel>
if(optionalButtonType.isPresent() && optionalButtonType.get() == ButtonType.YES)
{
MyEventBus.getEventBus().post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.JMBE_LIBRARY));
MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.JMBE_LIBRARY));
return;
}
}

View File

@@ -52,6 +52,8 @@ public class ChannelConfigurationEditorFactory
{
case AM:
return new AMConfigurationEditor(playlistManager, userPreferences);
case DMR:
return new DMRConfigurationEditor(playlistManager, userPreferences);
case NBFM:
return new NBFMConfigurationEditor(playlistManager, userPreferences);
case LTR_NET:

View File

@@ -0,0 +1,726 @@
/*
*
* * ******************************************************************************
* * 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.gui.playlist.channel;
import io.github.dsheirer.gui.control.IntegerTextField;
import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor;
import io.github.dsheirer.gui.playlist.record.RecordConfigurationEditor;
import io.github.dsheirer.gui.playlist.source.FrequencyEditor;
import io.github.dsheirer.gui.playlist.source.FrequencyField;
import io.github.dsheirer.gui.playlist.source.SourceConfigurationEditor;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.log.EventLogType;
import io.github.dsheirer.module.log.config.EventLogConfiguration;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.source.config.SourceConfiguration;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Tooltip;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import org.controlsfx.control.ToggleSwitch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* DMR channel configuration editor
*/
public class DMRConfigurationEditor extends ChannelConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger(DMRConfigurationEditor.class);
private TitledPane mDecoderPane;
private TitledPane mEventLogPane;
private TitledPane mRecordPane;
private TitledPane mSourcePane;
private SourceConfigurationEditor mSourceConfigurationEditor;
private EventLogConfigurationEditor mEventLogConfigurationEditor;
private RecordConfigurationEditor mRecordConfigurationEditor;
private ToggleSwitch mIgnoreDataCallsButton;
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
private TableView<TimeslotFrequency> mTimeslotFrequencyTable;
private IntegerTextField mLogicalSlotNumberField;
private FrequencyField mDownlinkFrequencyField;
// private FrequencyField mUplinkFrequencyField;
private Button mAddTimeslotFrequencyButton;
private Button mDeleteTimeslotFrequencyButton;
private Spinner<Integer> mChannelRotationDelaySpinner;
/**
* Constructs an instance
* @param playlistManager
*/
public DMRConfigurationEditor(PlaylistManager playlistManager, UserPreferences userPreferences)
{
super(playlistManager, userPreferences);
getTitledPanesBox().getChildren().add(getSourcePane());
getTitledPanesBox().getChildren().add(getDecoderPane());
getTitledPanesBox().getChildren().add(getEventLogPane());
getTitledPanesBox().getChildren().add(getRecordPane());
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.DMR;
}
private TitledPane getSourcePane()
{
if(mSourcePane == null)
{
mSourcePane = new TitledPane("Source", getSourceConfigurationEditor());
mSourcePane.setExpanded(true);
}
return mSourcePane;
}
private TitledPane getDecoderPane()
{
if(mDecoderPane == null)
{
mDecoderPane = new TitledPane();
mDecoderPane.setText("Decoder: DMR");
mDecoderPane.setExpanded(true);
GridPane gridPane = new GridPane();
gridPane.setPadding(new Insets(10,10,10,10));
gridPane.setHgap(10);
gridPane.setVgap(10);
int row = 0;
Label poolSizeLabel = new Label("Max Traffic Channels");
GridPane.setHalignment(poolSizeLabel, HPos.RIGHT);
GridPane.setConstraints(poolSizeLabel, 0, row);
gridPane.getChildren().add(poolSizeLabel);
GridPane.setConstraints(getTrafficChannelPoolSizeSpinner(), 1, row);
gridPane.getChildren().add(getTrafficChannelPoolSizeSpinner());
GridPane.setConstraints(getIgnoreDataCallsButton(), 2, row);
gridPane.getChildren().add(getIgnoreDataCallsButton());
Label directionLabel = new Label("Ignore Data Calls");
GridPane.setHalignment(directionLabel, HPos.LEFT);
GridPane.setConstraints(directionLabel, 3, row);
gridPane.getChildren().add(directionLabel);
Label timeslotTableLabel = new Label("Logical Slot Number (LSN) to Frequency Map. Required for: Connect Plus and Tier-III systems that don't use absolute frequencies");
GridPane.setHalignment(timeslotTableLabel, HPos.LEFT);
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 4, 1);
gridPane.getChildren().add(timeslotTableLabel);
GridPane.setConstraints(getTimeslotTable(), 0, ++row, 4, 3);
gridPane.getChildren().add(getTimeslotTable());
VBox buttonsBox = new VBox();
buttonsBox.setAlignment(Pos.CENTER);
buttonsBox.setSpacing(10);
buttonsBox.getChildren().addAll(getAddTimeslotFrequencyButton(), getDeleteTimeslotFrequencyButton());
GridPane.setConstraints(buttonsBox, 4, row, 1, 3);
gridPane.getChildren().addAll(buttonsBox);
row += 3;
HBox editorBox = new HBox();
editorBox.setAlignment(Pos.CENTER_LEFT);
editorBox.setSpacing(5);
Label lsnLabel = new Label("LSN");
editorBox.getChildren().addAll(lsnLabel,getLogicalSlotNumberField());
Label downlinkLabel = new Label("Frequency (MHz)");
downlinkLabel.setPadding(new Insets(0,0,0,5));
editorBox.getChildren().addAll(downlinkLabel,getDownlinkFrequencyField());
// Label uplinkLabel = new Label("Uplink");
// uplinkLabel.setPadding(new Insets(0,0,0,5));
// editorBox.getChildren().addAll(uplinkLabel,getUplinkFrequencyField());
GridPane.setConstraints(editorBox, 0, row, 4, 1);
gridPane.getChildren().add(editorBox);
mDecoderPane.setContent(gridPane);
}
return mDecoderPane;
}
private TitledPane getEventLogPane()
{
if(mEventLogPane == null)
{
mEventLogPane = new TitledPane("Logging", getEventLogConfigurationEditor());
mEventLogPane.setExpanded(false);
}
return mEventLogPane;
}
private TitledPane getRecordPane()
{
if(mRecordPane == null)
{
mRecordPane = new TitledPane();
mRecordPane.setText("Recording");
mRecordPane.setExpanded(false);
Label notice = new Label("Note: use aliases to control call audio recording");
notice.setPadding(new Insets(10, 10, 0, 10));
VBox vBox = new VBox();
vBox.getChildren().addAll(getRecordConfigurationEditor(), notice);
mRecordPane.setContent(vBox);
}
return mRecordPane;
}
private SourceConfigurationEditor getSourceConfigurationEditor()
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), true);
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mSourceConfigurationEditor;
}
private EventLogConfigurationEditor getEventLogConfigurationEditor()
{
if(mEventLogConfigurationEditor == null)
{
List<EventLogType> types = new ArrayList<>();
types.add(EventLogType.CALL_EVENT);
types.add(EventLogType.DECODED_MESSAGE);
types.add(EventLogType.TRAFFIC_CALL_EVENT);
types.add(EventLogType.TRAFFIC_DECODED_MESSAGE);
mEventLogConfigurationEditor = new EventLogConfigurationEditor(types);
mEventLogConfigurationEditor.setPadding(new Insets(5,5,5,5));
mEventLogConfigurationEditor.modifiedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mEventLogConfigurationEditor;
}
private TableView<TimeslotFrequency> getTimeslotTable()
{
if(mTimeslotFrequencyTable == null)
{
mTimeslotFrequencyTable = new TableView<>(FXCollections.observableArrayList(TimeslotFrequency.extractor()));
mTimeslotFrequencyTable.setPrefHeight(100.0);
TableColumn<TimeslotFrequency,Number> numberColumn = new TableColumn("LSN");
numberColumn.setPrefWidth(70);
numberColumn.setCellValueFactory(cellData -> cellData.getValue().getNumberProperty());
mTimeslotFrequencyTable.getColumns().addAll(numberColumn);
mTimeslotFrequencyTable.getSortOrder().add(numberColumn);
TableColumn<TimeslotFrequency,Number> downlinkColumn = new TableColumn("Frequency (MHz)");
downlinkColumn.setCellValueFactory(cellData -> cellData.getValue().getDownlinkMHz());
downlinkColumn.setPrefWidth(175);
mTimeslotFrequencyTable.getColumns().addAll(downlinkColumn);
// TableColumn<TimeslotFrequency,Number> uplinkColumn = new TableColumn("Uplink (MHz)");
// uplinkColumn.setCellValueFactory(cellData -> cellData.getValue().getUplinkMHz());
// mTimeslotFrequencyTable.getColumns().addAll(uplinkColumn);
mTimeslotFrequencyTable.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> setTimeslot(newValue));
}
return mTimeslotFrequencyTable;
}
/**
* Sets the specified timeslot frequency into the editor
*/
private void setTimeslot(TimeslotFrequency timeslot)
{
//Preserve the current modified flag state since setting values in the editor will change it.
boolean modified = modifiedProperty().get();
getLogicalSlotNumberField().setDisable(timeslot == null);
getDownlinkFrequencyField().setDisable(timeslot == null);
// getUplinkFrequencyField().setDisable(timeslot == null);
getDeleteTimeslotFrequencyButton().setDisable(timeslot == null);
if(timeslot != null)
{
getLogicalSlotNumberField().set(timeslot.getNumber());
getDownlinkFrequencyField().set(timeslot.getDownlinkFrequency());
// getUplinkFrequencyField().set(timeslot.getUplinkFrequency());
}
else
{
getLogicalSlotNumberField().set(0);
getDownlinkFrequencyField().set(0);
// getUplinkFrequencyField().set(0);
}
modifiedProperty().set(modified);
}
private Button getAddTimeslotFrequencyButton()
{
if(mAddTimeslotFrequencyButton == null)
{
mAddTimeslotFrequencyButton = new Button("Add");
mAddTimeslotFrequencyButton.setMaxWidth(Double.MAX_VALUE);
mAddTimeslotFrequencyButton.setOnAction(event -> addTimeslot());
}
return mAddTimeslotFrequencyButton;
}
/**
* Adds a new timeslot frequency value and makes a best guess of the next sequential LSN number
*/
private void addTimeslot()
{
int lsn = 1;
while(hasLSN(lsn) && lsn <= 64) //64 is an arbitrary value to keep it from going too high
{
lsn++;
}
TimeslotFrequency timeslotFrequency = new TimeslotFrequency();
timeslotFrequency.setNumber(lsn);
getTimeslotTable().getItems().add(timeslotFrequency);
getTimeslotTable().scrollTo(timeslotFrequency);
getTimeslotTable().getSelectionModel().select(timeslotFrequency);
modifiedProperty().set(true);
}
/**
* Searches the current timeslot frequency list to determine if the specified lsn is already listed
*/
private boolean hasLSN(int lsn)
{
for(TimeslotFrequency timeslotFrequency: getTimeslotTable().getItems())
{
if(timeslotFrequency.getNumber() == lsn)
{
return true;
}
}
return false;
}
private Button getDeleteTimeslotFrequencyButton()
{
if(mDeleteTimeslotFrequencyButton == null)
{
mDeleteTimeslotFrequencyButton = new Button("Delete");
mDeleteTimeslotFrequencyButton.setDisable(true);
mDeleteTimeslotFrequencyButton.setMaxWidth(Double.MAX_VALUE);
mDeleteTimeslotFrequencyButton.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
if(selected != null)
{
getTimeslotTable().getItems().remove(selected);
modifiedProperty().set(true);
}
}
});
}
return mDeleteTimeslotFrequencyButton;
}
private IntegerTextField getLogicalSlotNumberField()
{
if(mLogicalSlotNumberField == null)
{
mLogicalSlotNumberField = new IntegerTextField();
mLogicalSlotNumberField.setDisable(true);
mLogicalSlotNumberField.setPrefWidth(65);
mLogicalSlotNumberField.textProperty().addListener((observable, oldValue, newValue) -> {
TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
if(selected != null)
{
Integer value = mLogicalSlotNumberField.get();
if(value != null)
{
selected.setNumber(value);
}
}
modifiedProperty().set(true);
});
}
return mLogicalSlotNumberField;
}
private FrequencyField getDownlinkFrequencyField()
{
if(mDownlinkFrequencyField == null)
{
mDownlinkFrequencyField = new FrequencyField();
mDownlinkFrequencyField.setDisable(true);
mDownlinkFrequencyField.textProperty().addListener(new ChangeListener<String>()
{
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
{
TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
if(selected != null)
{
selected.setDownlinkFrequency(mDownlinkFrequencyField.get());
}
modifiedProperty().set(true);
}
});
}
return mDownlinkFrequencyField;
}
// private FrequencyField getUplinkFrequencyField()
// {
// if(mUplinkFrequencyField == null)
// {
// mUplinkFrequencyField = new FrequencyField();
// mUplinkFrequencyField.setDisable(true);
// mUplinkFrequencyField.textProperty().addListener(new ChangeListener<String>()
// {
// @Override
// public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
// {
// TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
//
// if(selected != null)
// {
// selected.setUplinkFrequency(mUplinkFrequencyField.get());
// int lsn = selected.getNumber();
// selected.setNumber(-1);
// selected.setNumber(lsn);
// }
//
// modifiedProperty().set(true);
// }
// });
// }
//
// return mUplinkFrequencyField;
// }
private ToggleSwitch getIgnoreDataCallsButton()
{
if(mIgnoreDataCallsButton == null)
{
mIgnoreDataCallsButton = new ToggleSwitch();
mIgnoreDataCallsButton.setDisable(true);
mIgnoreDataCallsButton.selectedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mIgnoreDataCallsButton;
}
private Spinner<Integer> getTrafficChannelPoolSizeSpinner()
{
if(mTrafficChannelPoolSizeSpinner == null)
{
mTrafficChannelPoolSizeSpinner = new Spinner();
mTrafficChannelPoolSizeSpinner.setDisable(true);
mTrafficChannelPoolSizeSpinner.setTooltip(
new Tooltip("Maximum number of traffic channels that can be created by the decoder"));
mTrafficChannelPoolSizeSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 50);
mTrafficChannelPoolSizeSpinner.setValueFactory(svf);
mTrafficChannelPoolSizeSpinner.getValueFactory().valueProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mTrafficChannelPoolSizeSpinner;
}
/**
* Channel rotation monitor delay value. This dictates how long the decoder will remain on each frequency before
* rotating to the next frequency in the list
* @return spinner
*/
private Spinner<Integer> getChannelRotationDelaySpinner()
{
if(mChannelRotationDelaySpinner == null)
{
mChannelRotationDelaySpinner = new Spinner();
mChannelRotationDelaySpinner.setDisable(true);
mChannelRotationDelaySpinner.setTooltip(
new Tooltip("Delay on each frequency before rotating to next when seeking to next active channel frequency"));
mChannelRotationDelaySpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(200, 2000, 200, 50);
mChannelRotationDelaySpinner.setValueFactory(svf);
mChannelRotationDelaySpinner.getValueFactory().valueProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mChannelRotationDelaySpinner;
}
private RecordConfigurationEditor getRecordConfigurationEditor()
{
if(mRecordConfigurationEditor == null)
{
List<RecorderType> types = new ArrayList<>();
types.add(RecorderType.BASEBAND);
types.add(RecorderType.DEMODULATED_BIT_STREAM);
types.add(RecorderType.MBE_CALL_SEQUENCE);
types.add(RecorderType.TRAFFIC_BASEBAND);
types.add(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM);
types.add(RecorderType.TRAFFIC_MBE_CALL_SEQUENCE);
mRecordConfigurationEditor = new RecordConfigurationEditor(types);
mRecordConfigurationEditor.setDisable(true);
mRecordConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mRecordConfigurationEditor;
}
@Override
protected void setDecoderConfiguration(DecodeConfiguration config)
{
getIgnoreDataCallsButton().setDisable(config == null);
getTrafficChannelPoolSizeSpinner().setDisable(config == null);
getTimeslotTable().getItems().clear();
getTimeslotTable().setDisable(config == null);
getAddTimeslotFrequencyButton().setDisable(config == null);
getDeleteTimeslotFrequencyButton().setDisable(true);
getLogicalSlotNumberField().set(0);
getLogicalSlotNumberField().setDisable(true);
getDownlinkFrequencyField().set(0);
getDownlinkFrequencyField().setDisable(true);
// getUplinkFrequencyField().set(0);
// getUplinkFrequencyField().setDisable(true);
getChannelRotationDelaySpinner().setDisable(config == null);
if(config instanceof DecodeConfigDMR)
{
DecodeConfigDMR decodeConfig = (DecodeConfigDMR)config;
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
for(TimeslotFrequency timeslotFrequency: decodeConfig.getTimeslotMap())
{
getTimeslotTable().getItems().add(timeslotFrequency.copy());
}
}
else
{
getIgnoreDataCallsButton().setSelected(false);
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(0);
getChannelRotationDelaySpinner().getValueFactory().setValue(200);
}
}
@Override
protected void saveDecoderConfiguration()
{
DecodeConfigDMR config;
if(getItem().getDecodeConfiguration() instanceof DecodeConfigDMR)
{
config = (DecodeConfigDMR)getItem().getDecodeConfiguration();
}
else
{
config = new DecodeConfigDMR();
}
// config.setChannelRotationDelay(getChannelRotationDelaySpinner().getValue());
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
config.setTimeslotMap(new ArrayList<>(getTimeslotTable().getItems()));
getItem().setDecodeConfiguration(config);
}
@Override
protected void setEventLogConfiguration(EventLogConfiguration config)
{
getEventLogConfigurationEditor().setItem(config);
}
@Override
protected void saveEventLogConfiguration()
{
getEventLogConfigurationEditor().save();
if(getEventLogConfigurationEditor().getItem().getLoggers().isEmpty())
{
getItem().setEventLogConfiguration(null);
}
else
{
getItem().setEventLogConfiguration(getEventLogConfigurationEditor().getItem());
}
}
@Override
protected void setAuxDecoderConfiguration(AuxDecodeConfiguration config)
{
//no-op
}
@Override
protected void saveAuxDecoderConfiguration()
{
//no-op
}
@Override
protected void setRecordConfiguration(RecordConfiguration config)
{
getRecordConfigurationEditor().setDisable(config == null);
getRecordConfigurationEditor().setItem(config);
}
@Override
protected void saveRecordConfiguration()
{
getRecordConfigurationEditor().save();
RecordConfiguration config = getRecordConfigurationEditor().getItem();
getItem().setRecordConfiguration(config);
}
@Override
protected void setSourceConfiguration(SourceConfiguration config)
{
getSourceConfigurationEditor().setSourceConfiguration(config);
}
@Override
protected void saveSourceConfiguration()
{
getSourceConfigurationEditor().save();
SourceConfiguration sourceConfiguration = getSourceConfigurationEditor().getSourceConfiguration();
getItem().setSourceConfiguration(sourceConfiguration);
}
/**
* Channel tuner channel source frequencies value factory
*/
public class FrequencyCellValueFactory implements Callback<TableColumn.CellDataFeatures<TimeslotFrequency, String>,
ObservableValue<String>>
{
private SimpleStringProperty mFrequency = new SimpleStringProperty();
private boolean mIsDownlink;
public FrequencyCellValueFactory(boolean isDownlink)
{
mIsDownlink = isDownlink;
}
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<TimeslotFrequency, String> param)
{
if(param.getValue() != null)
{
long frequency = (mIsDownlink ? param.getValue().getDownlinkFrequency() : param.getValue().getUplinkFrequency());
mFrequency.set(String.valueOf(frequency / 1E6));
}
else
{
mFrequency.set(null);
}
return mFrequency;
}
}
public class DownlinkPropertyValueFactory extends PropertyValueFactory<TimeslotFrequency,String>
{
private StringProperty mStringProperty = new SimpleStringProperty();
public DownlinkPropertyValueFactory()
{
super("downlinkFrequency");
}
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<TimeslotFrequency,String> param)
{
if(param.getValue() != null)
{
mStringProperty.set(String.valueOf(param.getValue().getDownlinkFrequency() / 1E6));
}
else
{
mStringProperty.setValue(null);
}
return mStringProperty;
}
}
}

View File

@@ -276,7 +276,7 @@ public class MPT1327ConfigurationEditor extends ChannelConfigurationEditor
channelMapName = getChannelMapComboBox().getSelectionModel().getSelectedItem().getName();
}
MyEventBus.getEventBus().post(new ViewChannelMapEditorRequest(channelMapName));
MyEventBus.getGlobalEventBus().post(new ViewChannelMapEditorRequest(channelMapName));
}
});
}

View File

@@ -94,7 +94,7 @@ public class PlaylistManagerEditor extends HBox
mUserPreferences = userPreferences;
//Register to receive preferences updates
MyEventBus.getEventBus().register(this);
MyEventBus.getGlobalEventBus().register(this);
setPadding(new Insets(5, 5, 5, 5));
HBox.setHgrow(getPlaylistTableView(), Priority.ALWAYS);
@@ -102,6 +102,11 @@ public class PlaylistManagerEditor extends HBox
updateButtons();
}
public void dispose()
{
MyEventBus.getGlobalEventBus().unregister(this);
}
/**
* Indicates if the path argument is the same as the current playlist path
*/

View File

@@ -376,7 +376,7 @@ public class FrequencyEditor extends VBox
if(getShowCreatedChannelCheckBox().selectedProperty().get())
{
MyEventBus.getEventBus().post(new ViewChannelRequest(channel));
MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel));
}
else
{

View File

@@ -31,7 +31,7 @@ public enum ModeDecoderType
{
AM("am", DecoderType.AM),
APCO25("p25", DecoderType.P25_PHASE1),
DMR("dmr", null),
DMR("dmr", DecoderType.DMR),
DSTAR("d-star", null),
FM("fm", DecoderType.NBFM),
FMN("fmn", DecoderType.NBFM),

View File

@@ -24,6 +24,7 @@ import io.github.dsheirer.identifier.talkgroup.LTRTalkgroup;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.identifier.talkgroup.UnknownTalkgroupIdentifier;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.mpt1327.identifier.MPT1327Talkgroup;
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
import io.github.dsheirer.module.decode.passport.identifier.PassportTalkgroup;
@@ -33,7 +34,9 @@ import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormat
import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.rrapi.type.Flavor;
import io.github.dsheirer.rrapi.type.Site;
import io.github.dsheirer.rrapi.type.SiteFrequency;
import io.github.dsheirer.rrapi.type.System;
import io.github.dsheirer.rrapi.type.SystemInformation;
import io.github.dsheirer.rrapi.type.Tag;
import io.github.dsheirer.rrapi.type.Talkgroup;
import io.github.dsheirer.rrapi.type.Type;
@@ -42,6 +45,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -228,6 +232,19 @@ public class RadioReferenceDecoder
return null;
}
/**
* Provides the radio reference Type for the system information instance
*/
public Type getType(SystemInformation systemInformation)
{
if(systemInformation != null)
{
return mTypeMap.get(systemInformation.getTypeId());
}
return null;
}
/**
* Provides the radio reference Flavor for the system
*/
@@ -241,6 +258,19 @@ public class RadioReferenceDecoder
return null;
}
/**
* Provides the radio reference Flavor for the system information instance
*/
public Flavor getFlavor(SystemInformation systemInformation)
{
if(systemInformation != null)
{
return mFlavorMap.get(systemInformation.getFlavorId());
}
return null;
}
/**
* Provides the radio reference voice/traffic channel protocol for the system
*/
@@ -302,6 +332,66 @@ public class RadioReferenceDecoder
return false;
}
/**
* Indicates if the specified site is a DMR Connect Plus site that has site frequencies that can be converted
* to timeslot frequencies
* @param systemInformation to determine the system type
* @param site containing site frequencies
*/
public boolean hasTimeslotFrequencies(SystemInformation systemInformation, Site site)
{
Type type = getType(systemInformation);
Flavor flavor = getFlavor(systemInformation);
return type != null && flavor != null && type.getName().contains("DMR") && !site.getSiteFrequencies().isEmpty();
}
/**
* Creates a list of timeslot to frequency mappings from the radio reference site's list of site frequencies for a
* MotoTRBO Connect Plus site
* @param systemInformation to detect the system type (DMR) and flavor (Connect Plus).
* @param site containing 1 or more site frequencies
* @return list of timeslot frequency mappings or an empty list.
*/
public List<TimeslotFrequency> getTimeslotFrequencies(SystemInformation systemInformation, Site site)
{
if(hasTimeslotFrequencies(systemInformation, site))
{
List<TimeslotFrequency> frequencies = new ArrayList<>();
for(SiteFrequency siteFrequency: site.getSiteFrequencies())
{
int lcn = siteFrequency.getLogicalChannelNumber();
if(siteFrequency.getChannelId() != null)
{
try
{
lcn = Integer.parseInt(siteFrequency.getChannelId());
}
catch(Exception e)
{
//Do nothing, we couldn't parse the LSN from the channel ID value
}
}
int lsn = (lcn - 1) * 2 + 1;
TimeslotFrequency timeslot1Frequency = new TimeslotFrequency();
timeslot1Frequency.setNumber(lsn);
timeslot1Frequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
frequencies.add(timeslot1Frequency);
TimeslotFrequency timeslot2Frequency = new TimeslotFrequency();
timeslot2Frequency.setNumber(lsn + 1);
timeslot2Frequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
frequencies.add(timeslot2Frequency);
}
return frequencies;
}
return Collections.emptyList();
}
/**
* Identifies the sdrtrunk protocol used by the system.
* @param system to identify
@@ -320,6 +410,8 @@ public class RadioReferenceDecoder
switch(type.getName())
{
case "DMR":
return Protocol.DMR;
case "LTR":
if(flavor.getName().contentEquals("Standard") || flavor.getName().contentEquals("Net"))
{
@@ -341,7 +433,6 @@ public class RadioReferenceDecoder
return Protocol.APCO25;
}
break;
case "DMR":
case "NXDN":
case "EDACS":
case "TETRA":
@@ -371,6 +462,8 @@ public class RadioReferenceDecoder
{
switch(type.getName())
{
case "DMR":
return DecoderType.DMR;
case "LTR":
if(flavor.getName().contentEquals("Net"))
{
@@ -403,7 +496,6 @@ public class RadioReferenceDecoder
return DecoderType.P25_PHASE1;
}
break;
case "DMR":
case "NXDN":
case "EDACS":

View File

@@ -27,6 +27,8 @@ import io.github.dsheirer.gui.playlist.channel.ViewChannelRequest;
import io.github.dsheirer.module.decode.DecoderFactory;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
@@ -222,6 +224,12 @@ public class SiteEditor extends GridPane
switch(decoderType)
{
case DMR:
DecodeConfigDMR dmr = new DecodeConfigDMR();
List<TimeslotFrequency> timeslotFrequencies = mRadioReferenceDecoder
.getTimeslotFrequencies(systemInformation, site);
dmr.setTimeslotMap(timeslotFrequencies);
return dmr;
case P25_PHASE1:
DecodeConfiguration p1config = DecoderFactory.getDecodeConfiguration(decoderType);
@@ -290,6 +298,7 @@ public class SiteEditor extends GridPane
}
}
public void setSite(EnrichedSite site, System system, SystemInformation systemInformation, RadioReferenceDecoder decoder)
{
mCurrentSite = site;
@@ -473,7 +482,7 @@ public class SiteEditor extends GridPane
if(getGoToChannelEditorCheckBox().isSelected() && gotoChannel != null)
{
MyEventBus.getEventBus().post(new ViewChannelRequest(gotoChannel));
MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(gotoChannel));
}
}
@@ -535,7 +544,7 @@ public class SiteEditor extends GridPane
if(getGoToChannelEditorCheckBox().isSelected())
{
MyEventBus.getEventBus().post(new ViewChannelRequest(channel));
MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel));
}
}
}
@@ -609,7 +618,7 @@ public class SiteEditor extends GridPane
if(getGoToChannelEditorCheckBox().isSelected())
{
MyEventBus.getEventBus().post(new ViewChannelRequest(channel));
MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel));
}
}
else
@@ -637,7 +646,7 @@ public class SiteEditor extends GridPane
if(getGoToChannelEditorCheckBox().isSelected() && gotoChannel != null)
{
MyEventBus.getEventBus().post(new ViewChannelRequest(gotoChannel));
MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(gotoChannel));
}
}
}
@@ -701,7 +710,7 @@ public class SiteEditor extends GridPane
if(getGoToChannelEditorCheckBox().isSelected())
{
MyEventBus.getEventBus().post(new ViewChannelRequest(channel));
MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel));
}
}
else
@@ -730,7 +739,7 @@ public class SiteEditor extends GridPane
if(getGoToChannelEditorCheckBox().isSelected() && gotoChannel != null)
{
MyEventBus.getEventBus().post(new ViewChannelRequest(gotoChannel));
MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(gotoChannel));
}
}
}

View File

@@ -105,7 +105,7 @@ public class SystemTalkgroupSelectionEditor extends GridPane
public SystemTalkgroupSelectionEditor(UserPreferences userPreferences, PlaylistManager playlistManager)
{
//Register to receive flash alias box requests
MyEventBus.getEventBus().register(this);
MyEventBus.getGlobalEventBus().register(this);
mUserPreferences = userPreferences;
mPlaylistManager = playlistManager;
@@ -172,6 +172,11 @@ public class SystemTalkgroupSelectionEditor extends GridPane
getChildren().add(getTalkgroupEditor());
}
public void dispose()
{
MyEventBus.getGlobalEventBus().unregister(this);
}
public void clear()
{
mTalkgroupList.clear();

View File

@@ -278,7 +278,7 @@ public class TalkgroupEditor extends GridPane
alert.setHeaderText("An alias list is required to create aliases");
alert.initOwner((getCreateAliasButton()).getScene().getWindow());
alert.showAndWait();
MyEventBus.getEventBus().post(new FlashAliasListComboBoxRequest());
MyEventBus.getGlobalEventBus().post(new FlashAliasListComboBoxRequest());
}
else if(mRadioReferenceDecoder != null && mTalkgroup != null && mSystem != null)
{
@@ -309,7 +309,7 @@ public class TalkgroupEditor extends GridPane
mEditAliasButton.setOnAction(event -> {
if(mAlias != null)
{
MyEventBus.getEventBus().post(new ViewAliasRequest(mAlias));
MyEventBus.getGlobalEventBus().post(new ViewAliasRequest(mAlias));
}
});
}

View File

@@ -26,8 +26,12 @@ import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.tuner.TunerModel;
import io.github.dsheirer.source.tuner.channel.rotation.ChannelRotationMonitor;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
@@ -36,6 +40,9 @@ import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import org.slf4j.Logger;
@@ -54,9 +61,10 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
private static final String NONE = "(none) ";
private TunerModel mTunerModel;
private FrequencyBox mPrimaryFrequencyBox;
private List<FrequencyBox> mFrequencyBoxes = new ArrayList<>();
private ObservableList<FrequencyBox> mFrequencyBoxes = FXCollections.observableArrayList();
private ComboBox<String> mPreferredTunerComboBox;
private VBox mFrequencyBoxContainer;
private Spinner<Integer> mChannelRotationDelaySpinner;
private boolean mAllowMultipleFrequencies;
/**
@@ -80,9 +88,24 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
hBox.getChildren().addAll(label, getFrequencyBoxContainer());
Label preferredTunerLabel = new Label("Preferred Tuner");
preferredTunerLabel.setAlignment(Pos.BASELINE_RIGHT);
preferredTunerLabel.setPadding(new Insets(0,0,0,20));
hBox.getChildren().addAll(preferredTunerLabel, getPreferredTunerComboBox());
HBox tunerBox = new HBox();
tunerBox.setAlignment(Pos.CENTER_LEFT);
tunerBox.setSpacing(10);
tunerBox.getChildren().addAll(preferredTunerLabel, getPreferredTunerComboBox());
Label frequencyRotationLabel = new Label("Frequency Rotation Delay (ms)");
HBox frequencyBox = new HBox();
frequencyBox.setAlignment(Pos.CENTER_LEFT);
frequencyBox.setSpacing(10);
frequencyBox.getChildren().addAll(frequencyRotationLabel, getFrequencyRotationDelaySpinner());
getFrequencyRotationDelaySpinner().disableProperty().bind(Bindings.greaterThan(2, Bindings.size(mFrequencyBoxes)));
VBox vbox = new VBox();
vbox.setSpacing(10);
vbox.setPadding(new Insets(0,0,0,20));
vbox.getChildren().addAll(tunerBox, frequencyBox);
hBox.getChildren().add(vbox);
setAlignment(Pos.TOP_LEFT);
getChildren().add(hBox);
@@ -164,6 +187,7 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
sourceConfigMulti.setFrequencies(frequencies);
sourceConfigMulti.setPreferredTuner(preferredTuner);
sourceConfigMulti.setFrequencyRotationDelay(getFrequencyRotationDelaySpinner().getValue());
setSourceConfiguration(sourceConfigMulti);
}
}
@@ -181,6 +205,9 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
disable(sourceConfiguration == null);
getFrequencyRotationDelaySpinner().getValueFactory()
.setValue(ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_MINIMUM);
if(sourceConfiguration == null)
{
setFrequencies(Collections.emptyList());
@@ -198,6 +225,7 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
SourceConfigTunerMultipleFrequency sourceMulti = (SourceConfigTunerMultipleFrequency)sourceConfiguration;
setFrequencies(sourceMulti.getFrequencies());
preferredTuner = sourceMulti.getPreferredTuner();
getFrequencyRotationDelaySpinner().getValueFactory().setValue(sourceMulti.getFrequencyRotationDelay());
}
else
{
@@ -226,6 +254,31 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
modifiedProperty().set(false);
}
/**
* Channel rotation monitor delay value. This dictates how long the decoder will remain on each frequency before
* rotating to the next frequency in the list
* @return spinner
*/
private Spinner<Integer> getFrequencyRotationDelaySpinner()
{
if(mChannelRotationDelaySpinner == null)
{
mChannelRotationDelaySpinner = new Spinner();
mChannelRotationDelaySpinner.setTooltip(
new Tooltip("Delay on each frequency before rotating to next when seeking to next active channel frequency"));
mChannelRotationDelaySpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
int min = ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_MINIMUM;
int max = ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_MAXIMUM;
SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(min, max, min, 50);
mChannelRotationDelaySpinner.setValueFactory(svf);
mChannelRotationDelaySpinner.getValueFactory().valueProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mChannelRotationDelaySpinner;
}
/**
* Updates the preferred tuner's combo box to reflect the current set of tuners.
*/

View File

@@ -24,7 +24,6 @@ import io.github.dsheirer.gui.preference.directory.DirectoryPreferenceEditor;
import io.github.dsheirer.gui.preference.duplicate.DuplicateCallPreferenceEditor;
import io.github.dsheirer.gui.preference.playback.PlaybackPreferenceEditor;
import io.github.dsheirer.gui.preference.record.RecordPreferenceEditor;
import io.github.dsheirer.gui.preference.tuner.ChannelMultipleFrequencyPreferenceEditor;
import io.github.dsheirer.gui.preference.tuner.TunerPreferenceEditor;
import io.github.dsheirer.preference.UserPreferences;
import javafx.scene.Node;
@@ -50,8 +49,6 @@ public class PreferenceEditorFactory
return new DirectoryPreferenceEditor(userPreferences);
case JMBE_LIBRARY:
return new JmbeLibraryPreferenceEditor(userPreferences);
case SOURCE_CHANNEL_MULTIPLE_FREQUENCY:
return new ChannelMultipleFrequencyPreferenceEditor(userPreferences);
case SOURCE_TUNER_CHANNELIZER:
return new TunerPreferenceEditor(userPreferences);
case TALKGROUP_FORMAT:

View File

@@ -30,7 +30,6 @@ public enum PreferenceEditorType
AUDIO_RECORD("Record"),
AUDIO_OUTPUT("Output/Tones"),
AUDIO_DUPLICATE_CALL_DETECTION("Duplicate Calls"),
SOURCE_CHANNEL_MULTIPLE_FREQUENCY("Channel - Multiple Frequency"),
SOURCE_TUNER_CHANNELIZER("Tuner Channelizer"),
TALKGROUP_FORMAT("Talkgroup & Radio ID"),
DEFAULT("Default");

View File

@@ -190,7 +190,6 @@ public class UserPreferencesEditor extends BorderPane
storageItem.setExpanded(true);
TreeItem<String> sourceItem = new TreeItem<>("Source");
sourceItem.getChildren().add(new TreeItem(PreferenceEditorType.SOURCE_CHANNEL_MULTIPLE_FREQUENCY));
sourceItem.getChildren().add(new TreeItem(PreferenceEditorType.SOURCE_TUNER_CHANNELIZER));
treeRoot.getChildren().add(sourceItem);
sourceItem.setExpanded(true);
@@ -247,7 +246,7 @@ public class UserPreferencesEditor extends BorderPane
Menu viewMenu = new Menu("View");
MenuItem playlistEditorItem = new MenuItem("Playlist Editor");
playlistEditorItem.setOnAction(event -> MyEventBus.getEventBus().post(new ViewPlaylistRequest()));
playlistEditorItem.setOnAction(event -> MyEventBus.getGlobalEventBus().post(new ViewPlaylistRequest()));
viewMenu.getItems().add(playlistEditorItem);
mMenuBar.getMenus().add(viewMenu);
}

View File

@@ -82,13 +82,18 @@ public class JmbeLibraryPreferenceEditor extends VBox
mUserPreferences = userPreferences;
//Register to receive directory preference update notifications so we can update the path labels
MyEventBus.getEventBus().register(this);
MyEventBus.getGlobalEventBus().register(this);
setPadding(new Insets(10,10,10,10));
setSpacing(10);
getChildren().addAll(getEditorPane(), getButtonsBox(), getAlertUserWhenMissingCheckBox());
}
public void dispose()
{
MyEventBus.getGlobalEventBus().unregister(this);
}
private CheckBox getAlertUserWhenMissingCheckBox()
{
if(mAlertUserWhenMissingCheckBox == null)
@@ -214,7 +219,7 @@ public class JmbeLibraryPreferenceEditor extends VBox
if(buttonType == ButtonType.YES)
{
mLog.info("Posting JMBE editor request to event bus");
MyEventBus.getEventBus().post(new JmbeEditorRequest(release));
MyEventBus.getGlobalEventBus().post(new JmbeEditorRequest(release));
}
});
}

View File

@@ -98,12 +98,17 @@ public class DirectoryPreferenceEditor extends HBox
mDirectoryPreference = userPreferences.getDirectoryPreference();
//Register to receive directory preference update notifications so we can update the path labels
MyEventBus.getEventBus().register(this);
MyEventBus.getGlobalEventBus().register(this);
HBox.setHgrow(getEditorPane(), Priority.ALWAYS);
getChildren().add(getEditorPane());
}
public void dispose()
{
MyEventBus.getGlobalEventBus().unregister(this);
}
private GridPane getEditorPane()
{
if(mEditorPane == null)

View File

@@ -1,123 +0,0 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.gui.preference.tuner;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.source.ChannelMultiFrequencyPreference;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Spinner;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
/**
* Preference settings for channel source configurations containing multiple frequencies
*/
public class ChannelMultipleFrequencyPreferenceEditor extends HBox
{
private static final String HELP_TEXT = "Rotation delay determines how long a decoder will wait before rotating " +
"to the next frequency in a list for a rotating control channel configuration.";
private static final Insets MARGINS = new Insets(5,5,5,5);
private ChannelMultiFrequencyPreference mChannelMultiFrequencyPreference;
private GridPane mEditorPane;
private Label mRotationDelayLabel;
private Spinner<Integer> mRotationDelaySpinner;
private Button mResetButton;
private Label mHelpText;
public ChannelMultipleFrequencyPreferenceEditor(UserPreferences userPreferences)
{
mChannelMultiFrequencyPreference = userPreferences.getChannelMultiFrequencyPreference();
getChildren().add(getEditorPane());
}
private GridPane getEditorPane()
{
if(mEditorPane == null)
{
mEditorPane = new GridPane();
GridPane.setMargin(getRotationDelaySpinner(), new Insets(5, 5, 5, 5));
mEditorPane.add(getRotationDelaySpinner(), 0, 0);
GridPane.setMargin(getRotationDelayLabel(), new Insets(5, 5, 5, 5));
mEditorPane.add(getRotationDelayLabel(), 1, 0);
GridPane.setMargin(getResetButton(), MARGINS);
mEditorPane.add(getResetButton(), 2, 0);
GridPane.setMargin(getHelpText(), MARGINS);
mEditorPane.add(getHelpText(), 0, 1, 3, 1);
}
return mEditorPane;
}
public Spinner<Integer> getRotationDelaySpinner()
{
if(mRotationDelaySpinner == null)
{
int currentDelay = (int)(mChannelMultiFrequencyPreference.getRotationDelay() / 1000);
mRotationDelaySpinner = new Spinner<>(1, 60, currentDelay, 1);
mRotationDelaySpinner.setEditable(true);
mRotationDelaySpinner.valueProperty().addListener((observable, oldValue, newValue) -> {
mChannelMultiFrequencyPreference.setRotationDelay(newValue * 1000);
});
}
return mRotationDelaySpinner;
}
public Label getRotationDelayLabel()
{
if(mRotationDelayLabel == null)
{
mRotationDelayLabel = new Label("Rotation Delay (seconds)");
}
return mRotationDelayLabel;
}
public Button getResetButton()
{
if(mResetButton == null)
{
mResetButton = new Button("Reset");
mResetButton.setOnAction(event -> {
mChannelMultiFrequencyPreference.resetRotationDelay();
int currentDelay = (int)(mChannelMultiFrequencyPreference.getRotationDelay() / 1000);
getRotationDelaySpinner().getValueFactory().setValue(currentDelay);
});
}
return mResetButton;
}
public Label getHelpText()
{
if(mHelpText == null)
{
mHelpText = new Label(HELP_TEXT);
mHelpText.setWrapText(true);
}
return mHelpText;
}
}