Compare commits

...

20 Commits

Author SHA1 Message Date
Denny Sheirer
02363c3113 Merge pull request #1690 from DSheirer/1689-harris-patch-group-prefixes
#1689 Increment Build 0.6.0 Beta 3 and Remove TG/RA prefixes for Harris Patch Groups
2023-11-04 07:13:10 -04:00
Dennis Sheirer
cfaac0fcbc #1689 Increment Build 0.6.0 Beta 3 and Remove TG/RA prefixes for Harris patch groups. 2023-11-04 07:12:29 -04:00
Denny Sheirer
bee0405e00 Merge pull request #1688 from DSheirer/1687-rtl2832-fc0013
Support For RTL2832 With Embedded FC0013 Tuner
2023-11-04 06:54:55 -04:00
Dennis Sheirer
50bdf0ee81 #1687 Adds support for RTL2832 with embedded FC0013 tuner. 2023-11-04 06:52:37 -04:00
Denny Sheirer
a27ddbe0d0 Merge pull request #1679 from DSheirer/1643-r828d-v4-tuner
#1643 RTL2832/R828D aka RTL-SDR V4 Dongle & Bias-T Support
2023-10-22 08:40:33 -04:00
Dennis Sheirer
0d652b9153 #1643 Adds support for RTL2832 with embedded R828D tuner and RTL-SDR.com V4 dongles. Introduces Bias-T support for all RTL-2832 dongles. 2023-10-22 08:38:16 -04:00
Denny Sheirer
9468fd189b Merge pull request #1677 from DSheirer/1607-harris-p25-patch-groups
#1607 P25 P1/P2 L3Harris Patch Group Support
2023-10-21 07:00:41 -04:00
sheirerd
215737ad9b #1607 Add support for L3Harris dynamic regroup/patch messages and events. Includes parsing for L3Harris unknown opcodes 10, 42 and 43. 2023-10-21 06:58:11 -04:00
Denny Sheirer
123c25b142 Merge pull request #1672 from DSheirer/1650-fm-decoder-7_5-bandwidth
#1650 Adds 7.5 kHz channel bandwidth option for FM channels.
2023-10-15 09:22:48 -04:00
Dennis Sheirer
af56198f0e #1650 Adds 7.5 kHz channel bandwidth option for FM channels. 2023-10-15 09:21:38 -04:00
Denny Sheirer
3c73fdfd6e Merge pull request #1671 from DSheirer/1670-dmr-cap-plus-missing-call-audio
#1670 Missing Audio Segments - DMR Cap+
2023-10-15 08:56:48 -04:00
Dennis Sheirer
b391ebbb5f #1670 Resolves missing audio segments for some DMR Cap+ call events. 2023-10-15 08:55:35 -04:00
Denny Sheirer
bad5a1f9f0 Merge pull request #1669 from DSheirer/1668-message-viewer-p25-support
#1668 Message Recording Viewer - P25P1 Support
2023-10-15 07:17:06 -04:00
Dennis Sheirer
a7470b463f #1668 Updates message recording (.bits) viewer to support P25 Phase 1. 2023-10-15 07:14:53 -04:00
Denny Sheirer
d6c706cb35 Merge pull request #1667 from DSheirer/1666-p25-moto-unit-gps
#1666 P25 Motorola Unit GPS Parsing & Plot to Map
2023-10-14 08:16:48 -04:00
Dennis Sheirer
a0d6950c75 #1666 P25 Motorola unit self-reported GPS location parsing and plotting to map. 2023-10-14 08:15:35 -04:00
Denny Sheirer
2495200b36 Merge pull request #1663 from DSheirer/1648-dmr-lrrp-not-plotting-map
#1648 DMR Embedded GPS & LRRP Plotting
2023-10-05 03:32:08 -04:00
Dennis Sheirer
bfe7ae0b1b #1648 Resolves issue with plotting DMR embedded GPS and LRRP positions. 2023-10-05 03:31:17 -04:00
Denny Sheirer
4912abbf07 Merge pull request #1662 from DSheirer/1661-dmr-gps-location-parsing-error
#1661 DMR Talker GPS Lat/Long Values Incorrect
2023-10-05 03:21:24 -04:00
Dennis Sheirer
2f2998d02e #1661 Resolves issue with parsing of DMR embedded GPS latitude and longitude values. 2023-10-05 03:20:17 -04:00
59 changed files with 4394 additions and 894 deletions

View File

@@ -53,7 +53,8 @@ repositories {
maven { url "https://jitpack.io" }
}
version = '0.6.0-beta2'
//Note: this should match the GitLab release tag.
version = '0.6.0-beta-3'
//Java 20 is required for this version of the Project Panama preview/incubator feature
java {

View File

@@ -388,6 +388,8 @@ public class AliasList
}
break;
case PATCH_GROUP:
List<Alias> aliases = new ArrayList<>();
PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier;
PatchGroup patchGroup = patchGroupIdentifier.getValue();
@@ -395,8 +397,6 @@ public class AliasList
if(patchGroupAliasList != null)
{
List<Alias> aliases = new ArrayList<>();
Alias alias = patchGroupAliasList.getAlias(patchGroup.getPatchGroup());
if(alias != null)
@@ -404,19 +404,36 @@ public class AliasList
aliases.add(alias);
}
for(TalkgroupIdentifier patchedGroup: patchGroup.getPatchedGroupIdentifiers())
for(TalkgroupIdentifier patchedTalkgroup: patchGroup.getPatchedTalkgroupIdentifiers())
{
Alias patchedAlias = patchGroupAliasList.getAlias(patchedGroup);
Alias patchedTalkgroupAlias = patchGroupAliasList.getAlias(patchedTalkgroup);
if(patchedAlias != null && !aliases.contains(patchedAlias))
if(patchedTalkgroupAlias != null && !aliases.contains(patchedTalkgroupAlias))
{
aliases.add(patchedAlias);
aliases.add(patchedTalkgroupAlias);
}
}
return aliases;
}
break;
if(patchGroup.hasPatchedRadios())
{
RadioAliasList radioAliasList = mRadioProtocolMap.get(patchGroupIdentifier.getProtocol());
if(radioAliasList != null)
{
for(RadioIdentifier patchedRadio: patchGroup.getPatchedRadioIdentifiers())
{
Alias patchedRadioAlias = radioAliasList.getAlias(patchedRadio);
if(patchedRadioAlias != null && !aliases.contains(patchedRadioAlias))
{
aliases.add(patchedRadioAlias);
}
}
}
}
return aliases;
case RADIO:
RadioIdentifier radio = (RadioIdentifier)identifier;
@@ -483,9 +500,7 @@ public class AliasList
{
if(alias != null)
{
List<Alias> aliases = new ArrayList<>();
aliases.add(alias);
return aliases;
return Collections.singletonList(alias);
}
return Collections.emptyList();

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -38,9 +38,6 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
@@ -48,12 +45,13 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Audio broadcaster to push completed audio recordings to the Broadcastify call push API.
@@ -475,7 +473,11 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
StringBuilder sb = new StringBuilder();
sb.append(patchGroup.getPatchGroup().getValue().toString());
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
{
sb.append(",").append(patched.getValue());
}
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
{
sb.append(",").append(patched.getValue());
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,7 +20,6 @@
package io.github.dsheirer.audio.broadcast.rdioscanner;
import com.google.common.net.HttpHeaders;
import com.google.common.base.Joiner;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
@@ -34,7 +33,6 @@ import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
@@ -42,24 +40,12 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.nio.file.Files;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.time.Duration;
import java.util.List;
import java.util.Queue;
@@ -67,6 +53,8 @@ import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
@@ -525,7 +513,12 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
sb.append(patchGroup.getPatchGroup().getValue().toString());
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
{
sb.append(",").append(patched.getValue());
}
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
{
sb.append(",").append(patched.getValue());
}

View File

@@ -844,13 +844,18 @@ public class BinaryMessage extends BitSet
*/
public int getTwosComplement(int start, int end)
{
BinaryMessage sub = getSubMessage(start, end);
boolean negative = sub.get(start);
sub.flip(0, sub.size());
int value = sub.getInt(0, sub.size()) + 1;
value *= (negative ? -1 : 1);
return value;
if(get(start))
{
//Negative value - flip and add one
BinaryMessage fragment = getSubMessage(start, end);
fragment.flip(0, fragment.size());
return fragment.getInt(1, fragment.size()) + 1;
}
else
{
//Positive value - return the contents.
return getInt(start + 1, end);
}
}

View File

@@ -24,8 +24,6 @@ import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.controller.channel.map.ChannelMap;
import io.github.dsheirer.controller.channel.map.ChannelRange;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.dmr.DMRRecordingViewer;
import io.github.dsheirer.gui.dmr.ViewDmrRecordingViewerRequest;
import io.github.dsheirer.gui.icon.IconManager;
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
import io.github.dsheirer.gui.playlist.PlaylistEditor;
@@ -37,6 +35,8 @@ import io.github.dsheirer.gui.preference.PreferenceEditorType;
import io.github.dsheirer.gui.preference.UserPreferencesEditor;
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
import io.github.dsheirer.gui.preference.calibration.CalibrationDialog;
import io.github.dsheirer.gui.viewer.RecordingViewer;
import io.github.dsheirer.gui.viewer.ViewRecordingViewerRequest;
import io.github.dsheirer.icon.IconModel;
import io.github.dsheirer.jmbe.JmbeEditor;
import io.github.dsheirer.jmbe.JmbeEditorRequest;
@@ -67,7 +67,7 @@ public class JavaFxWindowManager extends Application
public static final String USER_PREFERENCES_EDITOR = "preferences";
public static final String STAGE_MONITOR_KEY_CALIBRATION_DIALOG = "calibration.dialog";
public static final String STAGE_MONITOR_KEY_CHANNEL_MAP_EDITOR = "channel.map";
public static final String STAGE_MONITOR_KEY_DMR_MESSAGE_VIEWER = "dmr.message.viewer";
public static final String STAGE_MONITOR_KEY_RECORDING_VIEWER = "recording.viewer";
public static final String STAGE_MONITOR_KEY_ICON_MANAGER_EDITOR = "icon.manager";
public static final String STAGE_MONITOR_KEY_JMBE_EDITOR = "jmbe.editor";
public static final String STAGE_MONITOR_KEY_PLAYLIST_EDITOR = "playlist";
@@ -82,14 +82,14 @@ public class JavaFxWindowManager extends Application
private TunerManager mTunerManager;
private UserPreferences mUserPreferences;
private UserPreferencesEditor mUserPreferencesEditor;
private DMRRecordingViewer mDmrRecordingViewer;
private RecordingViewer mRecordingViewer;
private Stage mChannelMapStage;
private Stage mIconManagerStage;
private Stage mJmbeEditorStage;
private Stage mPlaylistStage;
private Stage mUserPreferencesStage;
private Stage mDmrRecordingViewerStage;
private Stage mRecordingViewerStage;
/**
* Constructs an instance. Note: this constructor is used for Swing applications.
@@ -195,31 +195,31 @@ public class JavaFxWindowManager extends Application
}
/**
* Stage for the DMR Message Viewer
* Stage for the recording viewer
*/
public Stage getDmrRecordingViewerStage()
public Stage getRecordingViewerStage()
{
if(mDmrRecordingViewerStage == null)
if(mRecordingViewerStage == null)
{
createJFXPanel();
Scene scene = new Scene(getDmrRecordingViewer(), 1100, 800);
mDmrRecordingViewerStage = new Stage();
mDmrRecordingViewerStage.setTitle("sdrtrunk - DMR Recording Viewer");
mDmrRecordingViewerStage.setScene(scene);
mUserPreferences.getJavaFxPreferences().monitor(mDmrRecordingViewerStage, STAGE_MONITOR_KEY_DMR_MESSAGE_VIEWER);
Scene scene = new Scene(getRecordingViewer(), 1100, 800);
mRecordingViewerStage = new Stage();
mRecordingViewerStage.setTitle("sdrtrunk - Message Recording Viewer (.bits)");
mRecordingViewerStage.setScene(scene);
mUserPreferences.getJavaFxPreferences().monitor(mRecordingViewerStage, STAGE_MONITOR_KEY_RECORDING_VIEWER);
}
return mDmrRecordingViewerStage;
return mRecordingViewerStage;
}
public DMRRecordingViewer getDmrRecordingViewer()
public RecordingViewer getRecordingViewer()
{
if(mDmrRecordingViewer == null)
if(mRecordingViewer == null)
{
mDmrRecordingViewer = new DMRRecordingViewer();
mRecordingViewer = new RecordingViewer();
}
return mDmrRecordingViewer;
return mRecordingViewer;
}
public Stage getIconManagerStage()
@@ -453,12 +453,12 @@ public class JavaFxWindowManager extends Application
* Process a channel map editor request
*/
@Subscribe
public void process(final ViewDmrRecordingViewerRequest request)
public void process(final ViewRecordingViewerRequest request)
{
execute(() -> {
getDmrRecordingViewerStage().show();
getDmrRecordingViewerStage().requestFocus();
getDmrRecordingViewerStage().toFront();
getRecordingViewerStage().show();
getRecordingViewerStage().requestFocus();
getRecordingViewerStage().toFront();
});
}

View File

@@ -32,13 +32,13 @@ import io.github.dsheirer.controller.channel.ChannelAutoStartFrame;
import io.github.dsheirer.controller.channel.ChannelException;
import io.github.dsheirer.controller.channel.ChannelSelectionManager;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.dmr.ViewDmrRecordingViewerRequest;
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
import io.github.dsheirer.gui.preference.CalibrateRequest;
import io.github.dsheirer.gui.preference.PreferenceEditorType;
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
import io.github.dsheirer.gui.preference.calibration.CalibrationDialog;
import io.github.dsheirer.gui.viewer.ViewRecordingViewerRequest;
import io.github.dsheirer.icon.IconModel;
import io.github.dsheirer.log.ApplicationLog;
import io.github.dsheirer.map.MapService;
@@ -420,9 +420,9 @@ public class SDRTrunk implements Listener<TunerEvent>
viewMenu.add(new JSeparator());
JMenuItem dmrMessageViewerMenu = new JMenuItem("DMR Recording Viewer");
dmrMessageViewerMenu.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewDmrRecordingViewerRequest()));
viewMenu.add(dmrMessageViewerMenu);
JMenuItem recordingViewerMenu = new JMenuItem("Message Recording Viewer (.bits)");
recordingViewerMenu.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewRecordingViewerRequest()));
viewMenu.add(recordingViewerMenu);
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
settingsMenu.addActionListener(arg0 -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));

View File

@@ -17,20 +17,22 @@
* ****************************************************************************
*/
package io.github.dsheirer.gui.dmr;
package io.github.dsheirer.gui.viewer;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.DMRMessageFramer;
import io.github.dsheirer.module.decode.dmr.DMRMessageProcessor;
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
import io.github.dsheirer.record.binary.BinaryReader;
import io.github.dsheirer.util.ThreadPool;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.prefs.Preferences;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
@@ -40,10 +42,10 @@ import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
@@ -58,21 +60,19 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility application to load and view a DMR .bits recording file with the messages fully parsed.
* DMR Viewer panel
*/
public class DMRRecordingViewer extends VBox
public class DmrViewer extends VBox
{
private static final Logger mLog = LoggerFactory.getLogger(DMRRecordingViewer.class);
private static final Logger mLog = LoggerFactory.getLogger(DmrViewer.class);
private static final KeyCodeCombination KEY_CODE_COPY = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory";
private Preferences mPreferences = Preferences.userNodeForPackage(DMRRecordingViewer.class);
private Scene mScene;
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory.dmr";
private Preferences mPreferences = Preferences.userNodeForPackage(DmrViewer.class);
private Button mSelectFileButton;
private Label mSelectedFileLabel;
private TableView<IMessage> mMessageTableView;
@@ -86,8 +86,9 @@ public class DMRRecordingViewer extends VBox
private TextField mFindText;
private Button mFindButton;
private Button mFindNextButton;
private ProgressIndicator mLoadingIndicator;
public DMRRecordingViewer()
public DmrViewer()
{
setPadding(new Insets(5));
setSpacing(5);
@@ -132,6 +133,21 @@ public class DMRRecordingViewer extends VBox
getChildren().addAll(fileBox, filterBox, getMessageTableView());
}
/**
* Spinny loading icon to show over the message table view
*/
private ProgressIndicator getLoadingIndicator()
{
if(mLoadingIndicator == null)
{
mLoadingIndicator = new ProgressIndicator();
mLoadingIndicator.setProgress(-1);
mLoadingIndicator.setVisible(false);
}
return mLoadingIndicator;
}
/**
* Processes the recording file and loads the content into the viewer
* @param file containing a .bits recording of decoded DMR data.
@@ -140,33 +156,45 @@ public class DMRRecordingViewer extends VBox
{
if(file != null && file.exists())
{
getSelectedFileLabel().setText(file.getName());
mMessages.clear();
getLoadingIndicator().setVisible(true);
getSelectedFileLabel().setText("Loading ...");
final boolean useCompressed = getUseCompressedTalkgroups().isSelected();
DMRMessageFramer messageFramer = new DMRMessageFramer(null);
DecodeConfigDMR config = new DecodeConfigDMR();
if(getUseCompressedTalkgroups().isSelected())
ThreadPool.CACHED.submit(new Runnable()
{
config.setUseCompressedTalkgroups(true);
}
DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config);
messageFramer.setListener(messageProcessor);
messageProcessor.setMessageListener(message -> mMessages.add(message));
try(BinaryReader reader = new BinaryReader(file.toPath(), 200))
{
while(reader.hasNext())
@Override
public void run()
{
ByteBuffer buffer = reader.next();
messageFramer.receive(buffer);
}
}
catch(Exception ioe)
{
ioe.printStackTrace();
}
List<IMessage> messages = new ArrayList<>();
DMRMessageFramer messageFramer = new DMRMessageFramer(null);
DecodeConfigDMR config = new DecodeConfigDMR();
config.setUseCompressedTalkgroups(useCompressed);
DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config);
messageFramer.setListener(messageProcessor);
messageProcessor.setMessageListener(message -> messages.add(message));
getMessageTableView().scrollTo(0);
try(BinaryReader reader = new BinaryReader(file.toPath(), 200))
{
while(reader.hasNext())
{
ByteBuffer buffer = reader.next();
messageFramer.receive(buffer);
}
}
catch(Exception ioe)
{
ioe.printStackTrace();
}
Platform.runLater(() -> {
getLoadingIndicator().setVisible(false);
getSelectedFileLabel().setText(file.getName());
mMessages.addAll(messages);
getMessageTableView().scrollTo(0);
});
}
});
}
}
@@ -176,9 +204,9 @@ public class DMRRecordingViewer extends VBox
private void updateFilters()
{
Predicate<IMessage> timeslotPredicate = message ->
(getShowTS0().isSelected() && (message.getTimeslot() == 0)) ||
(getShowTS1().isSelected() && (message.getTimeslot() == 1)) ||
(getShowTS2().isSelected() && (message.getTimeslot() == 2));
(getShowTS0().isSelected() && (message.getTimeslot() == 0)) ||
(getShowTS1().isSelected() && (message.getTimeslot() == 1)) ||
(getShowTS2().isSelected() && (message.getTimeslot() == 2));
String filterText = getSearchText().getText();
@@ -256,6 +284,7 @@ public class DMRRecordingViewer extends VBox
if(mMessageTableView == null)
{
mMessageTableView = new TableView<>();
mMessageTableView.setPlaceholder(getLoadingIndicator());
SortedList<IMessage> sortedList = new SortedList<>(mFilteredMessages);
sortedList.comparatorProperty().bind(mMessageTableView.comparatorProperty());
mMessageTableView.setItems(sortedList);
@@ -509,37 +538,9 @@ public class DMRRecordingViewer extends VBox
{
if(mUseCompressedTalkgroups == null)
{
mUseCompressedTalkgroups = new CheckBox("Use Compressed Talkgroups");
mUseCompressedTalkgroups = new CheckBox("Use Hytera Tier III Compressed Talkgroups");
}
return mUseCompressedTalkgroups;
}
public static void main(String[] args)
{
Application viewer = new Application()
{
@Override
public void start(Stage primaryStage) throws Exception
{
Scene scene = new Scene(new DMRRecordingViewer(), 1100, 800);
primaryStage.setTitle("DMR Recording Viewer");
primaryStage.setScene(scene);
primaryStage.show();
}
};
Runnable r = () -> {
try
{
viewer.start(new Stage());
}
catch(Exception e)
{
mLog.error("Error starting DMR recording viewer application", e);
}
};
Platform.startup(r);
}
}

View File

@@ -0,0 +1,462 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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.viewer;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.StuffBitsMessage;
import io.github.dsheirer.module.decode.p25.phase1.P25P1MessageFramer;
import io.github.dsheirer.module.decode.p25.phase1.P25P1MessageProcessor;
import io.github.dsheirer.record.binary.BinaryReader;
import io.github.dsheirer.util.ThreadPool;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.prefs.Preferences;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* APCO25 Phase 1 viewer panel
*/
public class P25P1Viewer extends VBox
{
private static final Logger mLog = LoggerFactory.getLogger(P25P1Viewer.class);
private static final KeyCodeCombination KEY_CODE_COPY = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory.p25p1";
private Preferences mPreferences = Preferences.userNodeForPackage(P25P1Viewer.class);
private Button mSelectFileButton;
private Label mSelectedFileLabel;
private TableView<IMessage> mMessageTableView;
private ObservableList<IMessage> mMessages = FXCollections.observableArrayList();
private FilteredList<IMessage> mFilteredMessages = new FilteredList<>(mMessages);
private TextField mSearchText;
private TextField mFindText;
private Button mFindButton;
private Button mFindNextButton;
private ProgressIndicator mLoadingIndicator;
public P25P1Viewer()
{
setPadding(new Insets(5));
setSpacing(5);
HBox fileBox = new HBox();
fileBox.setMaxWidth(Double.MAX_VALUE);
fileBox.setAlignment(Pos.CENTER_LEFT);
fileBox.setSpacing(5);
HBox.setHgrow(getSelectFileButton(), Priority.NEVER);
HBox.setHgrow(getSelectedFileLabel(), Priority.ALWAYS);
getSelectedFileLabel().setAlignment(Pos.BASELINE_CENTER);
fileBox.getChildren().addAll(getSelectFileButton(), getSelectedFileLabel());
HBox filterBox = new HBox();
filterBox.setMaxWidth(Double.MAX_VALUE);
filterBox.setAlignment(Pos.BASELINE_CENTER);
filterBox.setSpacing(5);
Label searchLabel = new Label("Message Filter:");
HBox.setMargin(searchLabel, new Insets(0,0,0,15));
Label findLabel = new Label("Find:");
HBox.setHgrow(getFindText(), Priority.ALWAYS);
HBox.setHgrow(getSearchText(), Priority.ALWAYS);
filterBox.getChildren().addAll(findLabel, getFindText(), getFindButton(), getFindNextButton(), searchLabel,
getSearchText());
VBox.setVgrow(fileBox, Priority.NEVER);
VBox.setVgrow(filterBox, Priority.NEVER);
VBox.setVgrow(getMessageTableView(), Priority.ALWAYS);
getChildren().addAll(fileBox, filterBox, getMessageTableView());
}
/**
* Processes the recording file and loads the content into the viewer
*
* Note: invoke this method off of the UI thread in a thread pool executor and the results will be loaded into the
* message table back on the JavaFX UI thread.
*
* @param file containing a .bits recording of decoded DMR data.
*/
private void load(File file)
{
if(file != null && file.exists())
{
mMessages.clear();
getLoadingIndicator().setVisible(true);
getSelectedFileLabel().setText("Loading ...");
ThreadPool.CACHED.submit(() -> {
List<IMessage> messages = new ArrayList<>();
P25P1MessageFramer messageFramer = new P25P1MessageFramer(null, 9600);
P25P1MessageProcessor messageProcessor = new P25P1MessageProcessor();
messageFramer.setListener(messageProcessor);
messageProcessor.setMessageListener(message -> {
if(!(message instanceof StuffBitsMessage))
{
messages.add(message);
}
});
try(BinaryReader reader = new BinaryReader(file.toPath(), 200))
{
while(reader.hasNext())
{
ByteBuffer buffer = reader.next();
messageFramer.receive(buffer);
}
}
catch(Exception ioe)
{
ioe.printStackTrace();
}
Platform.runLater(() -> {
getLoadingIndicator().setVisible(false);
getSelectedFileLabel().setText(file.getName());
mMessages.addAll(messages);
getMessageTableView().scrollTo(0);
});
});
}
}
/**
* Updates the filter(s) applies to the list of messages
*/
private void updateFilters()
{
String filterText = getSearchText().getText();
if(filterText != null && !filterText.isEmpty())
{
Predicate<IMessage> textPredicate = message -> message.toString().toLowerCase().contains(filterText.toLowerCase());
mFilteredMessages.setPredicate(textPredicate);
}
else
{
mFilteredMessages.setPredicate(null);
}
}
/**
* Finds and selects the first row containing the text argument.
* @param text to search for.
*/
private void find(String text)
{
if(text != null && !text.isEmpty())
{
for(IMessage message: mFilteredMessages)
{
if(message.toString().toLowerCase().contains(text.toLowerCase()))
{
getMessageTableView().getSelectionModel().select(message);
getMessageTableView().scrollTo(message);
return;
}
}
}
}
/**
* Finds and selects the first row containing the text argument, after the currently selected row.
* @param text to search for.
*/
private void findNext(String text)
{
if(text != null && !text.isEmpty())
{
IMessage selected = getMessageTableView().getSelectionModel().getSelectedItem();
if(selected == null)
{
find(text);
return;
}
int row = mFilteredMessages.indexOf(selected);
for(int x = row + 1; x < mFilteredMessages.size(); x++)
{
if(x < mFilteredMessages.size())
{
IMessage message = mFilteredMessages.get(x);
if(message.toString().toLowerCase().contains(text.toLowerCase()))
{
getMessageTableView().getSelectionModel().select(message);
getMessageTableView().scrollTo(message);
return;
}
}
}
}
}
/**
* List view control with DMR messages
*/
private TableView<IMessage> getMessageTableView()
{
if(mMessageTableView == null)
{
mMessageTableView = new TableView<>();
mMessageTableView.setPlaceholder(getLoadingIndicator());
SortedList<IMessage> sortedList = new SortedList<>(mFilteredMessages);
sortedList.comparatorProperty().bind(mMessageTableView.comparatorProperty());
mMessageTableView.setItems(sortedList);
mMessageTableView.setOnKeyPressed(event ->
{
if(KEY_CODE_COPY.match(event))
{
final Set<Integer> rows = new TreeSet<>();
for (final TablePosition tablePosition : mMessageTableView.getSelectionModel().getSelectedCells())
{
rows.add(tablePosition.getRow());
}
final StringBuilder sb = new StringBuilder();
boolean firstRow = true;
for (final Integer row : rows)
{
if(firstRow)
{
firstRow = false;
}
else
{
sb.append('\n');
}
boolean firstCol = true;
for (final TableColumn<?, ?> column : mMessageTableView.getColumns())
{
if(firstCol)
{
firstCol = false;
}
else
{
sb.append('\t');
}
final Object cellData = column.getCellData(row);
sb.append(cellData == null ? "" : cellData.toString());
}
}
final ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.putString(sb.toString());
Clipboard.getSystemClipboard().setContent(clipboardContent);
}
});
TableColumn timestampColumn = new TableColumn();
timestampColumn.setPrefWidth(110);
timestampColumn.setText("Time");
timestampColumn.setCellValueFactory(new PropertyValueFactory<>("timestamp"));
TableColumn validColumn = new TableColumn();
validColumn.setPrefWidth(50);
validColumn.setText("Valid");
validColumn.setCellValueFactory(new PropertyValueFactory<>("valid"));
TableColumn messageColumn = new TableColumn();
messageColumn.setPrefWidth(1000);
messageColumn.setText("Message");
messageColumn.setCellValueFactory((Callback<TableColumn.CellDataFeatures, ObservableValue>) param -> {
SimpleStringProperty property = new SimpleStringProperty();
if(param.getValue() instanceof IMessage message)
{
property.set(message.toString());
}
return property;
});
mMessageTableView.getColumns().addAll(timestampColumn, validColumn, messageColumn);
}
return mMessageTableView;
}
/**
* File selection button
* @return button
*/
private Button getSelectFileButton()
{
if(mSelectFileButton == null)
{
mSelectFileButton = new Button("Select ...");
mSelectFileButton.onActionProperty().set(event -> {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select DMR .bits Recording");
String lastDirectory = mPreferences.get(LAST_SELECTED_DIRECTORY, null);
if(lastDirectory != null)
{
File file = new File(lastDirectory);
if(file.exists() && file.isDirectory())
{
fileChooser.setInitialDirectory(file);
}
}
fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("sdrtrunk bits recording", "*.bits"));
final File selected = fileChooser.showOpenDialog(getScene().getWindow());
if(selected != null)
{
mPreferences.put(LAST_SELECTED_DIRECTORY, selected.getParent());
load(selected);
}
});
}
return mSelectFileButton;
}
/**
* Spinny loading icon to show over the message table view
*/
private ProgressIndicator getLoadingIndicator()
{
if(mLoadingIndicator == null)
{
mLoadingIndicator = new ProgressIndicator();
mLoadingIndicator.setProgress(-1);
mLoadingIndicator.setVisible(false);
}
return mLoadingIndicator;
}
/**
* Selected file path label.
*/
private Label getSelectedFileLabel()
{
if(mSelectedFileLabel == null)
{
mSelectedFileLabel = new Label(" ");
}
return mSelectedFileLabel;
}
/**
* Search text filter box
* @return text control for entering search text
*/
private TextField getSearchText()
{
if(mSearchText == null)
{
mSearchText = new TextField();
mSearchText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
}
return mSearchText;
}
/**
* Text box for find text
*/
private TextField getFindText()
{
if(mFindText == null)
{
mFindText = new TextField();
mFindText.setOnKeyPressed(event -> {
if(event.getCode().equals(KeyCode.ENTER))
{
getFindButton().fire();
}
});
mFindText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
}
return mFindText;
}
/**
* Find button to search for the text in the find text box.
* @return button
*/
private Button getFindButton()
{
if(mFindButton == null)
{
mFindButton = new Button("Find");
mFindButton.setOnAction(event -> find(getFindText().getText()));
}
return mFindButton;
}
/**
* Find next button to search for the text in the find text box.
* @return button
*/
private Button getFindNextButton()
{
if(mFindNextButton == null)
{
mFindNextButton = new Button("Next");
mFindNextButton.setOnAction(event -> findNext(getFindText().getText()));
}
return mFindNextButton;
}
}

View File

@@ -0,0 +1,180 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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.viewer;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility application to load and view .bits recording file with the messages fully parsed.
*
* Supported Protocols: DMR and APCO25 Phase 1.
*/
public class RecordingViewer extends VBox
{
private static final Logger mLog = LoggerFactory.getLogger(RecordingViewer.class);
private MenuBar mMenuBar;
private TabPane mTabPane;
private int mTabCounterDmr = 1;
private int mTabCounterP25P1 = 1;
/**
* Constructs an instance
*/
public RecordingViewer()
{
VBox.setVgrow(getTabPane(), Priority.ALWAYS);
getChildren().addAll(getMenuBar(), getTabPane());
}
public MenuBar getMenuBar()
{
if(mMenuBar == null)
{
mMenuBar = new MenuBar();
Menu fileMenu = new Menu("File");
Menu createNewViewerMenu = new Menu("New Viewer ...");
MenuItem dmrMenuItem = new MenuItem("DMR");
dmrMenuItem.onActionProperty().set(event -> getTabPane().getTabs().add(new LabeledTab("DMR-" + mTabCounterDmr++, new DmrViewer())));
MenuItem p25p1MenuItem = new MenuItem("P25P1");
p25p1MenuItem.onActionProperty().set(event -> getTabPane().getTabs().add(new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer())));
createNewViewerMenu.getItems().addAll(dmrMenuItem, p25p1MenuItem);
MenuItem exitMenu = new MenuItem("Exit");
exitMenu.onActionProperty().set(event -> ((Stage)getScene().getWindow()).close());
fileMenu.getItems().addAll(createNewViewerMenu, new SeparatorMenuItem(), exitMenu);
mMenuBar.getMenus().add(fileMenu);
}
return mMenuBar;
}
/**
* Tab pane for each viewer instance
*/
public TabPane getTabPane()
{
if(mTabPane == null)
{
mTabPane = new TabPane();
mTabPane.setMaxHeight(Double.MAX_VALUE);
mTabPane.getTabs().add(new LabeledTab("DMR-" + mTabCounterDmr++, new DmrViewer()));
mTabPane.getTabs().add(new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer()));
}
return mTabPane;
}
/**
* Decorates the tab with label renaming feature using double-click or right-click to change text
*/
public class LabeledTab extends Tab
{
private Label mLabel = new Label();
private TextField mTextField = new TextField();
/**
* Constructs an instance
* @param label to use initially
* @param node for content
*/
public LabeledTab(String label, Node node)
{
super(null, node);
mLabel.setText(label);
setGraphic(mLabel);
mLabel.setOnMouseClicked(event -> {
if((event.getButton().equals(MouseButton.PRIMARY) && event.getClickCount() > 1) ||
event.getButton().equals(MouseButton.SECONDARY))
{
setGraphic(mTextField);
mTextField.setText(mLabel.getText());
mTextField.selectAll();
mTextField.requestFocus();
}
});
mTextField.setOnAction(event -> {
mLabel.setText(mTextField.getText());
LabeledTab.this.setGraphic(mLabel);
});
mTextField.focusedProperty().addListener((observable, oldValue, newValue) -> {
if(!newValue)
{
mLabel.setText(mTextField.getText());
LabeledTab.this.setGraphic(mLabel);
}
});
}
public LabeledTab(String label)
{
this(label, null);
}
}
public static void main(String[] args)
{
Application viewer = new Application()
{
@Override
public void start(Stage primaryStage) throws Exception
{
Scene scene = new Scene(new RecordingViewer(), 1100, 800);
primaryStage.setTitle("Message Recording Viewer (.bits)");
primaryStage.setScene(scene);
primaryStage.show();
}
};
Runnable r = () -> {
try
{
viewer.start(new Stage());
}
catch(Exception e)
{
mLog.error("Error starting message recording viewer application", e);
}
};
Platform.startup(r);
}
}

View File

@@ -17,17 +17,17 @@
* ****************************************************************************
*/
package io.github.dsheirer.gui.dmr;
package io.github.dsheirer.gui.viewer;
import io.github.dsheirer.gui.JavaFxWindowRequest;
/**
* Request to show the DMR recording viewer
* Request to show the recording viewer
*/
public class ViewDmrRecordingViewerRequest extends JavaFxWindowRequest
public class ViewRecordingViewerRequest extends JavaFxWindowRequest
{
/**
* Constructs an instance
*/
public ViewDmrRecordingViewerRequest() {}
public ViewRecordingViewerRequest() {}
}

View File

@@ -1,69 +1,161 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * 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/>
* * *****************************************************************************
* 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.identifier.patch;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Temporary grouping or regrouping of talkgroups or individual radios.
*/
public class PatchGroup
{
// private final static Logger mLog = LoggerFactory.getLogger(PatchGroup.class);
private TalkgroupIdentifier mPatchGroupIdentifier;
private List<TalkgroupIdentifier> mPatchedGroupIdentifiers = new ArrayList<>();
private List<TalkgroupIdentifier> mPatchedTalkgroupIdentifiers = new ArrayList<>();
private List<RadioIdentifier> mPatchedRadioIdentifiers = new ArrayList<>();
private int mVersion;
public PatchGroup(TalkgroupIdentifier patchGroupIdentifier)
/**
* Constructs an instance
* @param patchGroupIdentifier for the patch group
* @param version for this patch group identifier. Note: if the patch group identifier is frequently reused, a
* new version number will indicate a new instance of the patch group.
*/
public PatchGroup(TalkgroupIdentifier patchGroupIdentifier, int version)
{
mPatchGroupIdentifier = patchGroupIdentifier;
mVersion = version;
}
/**
* Constructs an instance
* @param patchGroupIdentifier that is the overall identifier for this temporary grouping.
*/
public PatchGroup(TalkgroupIdentifier patchGroupIdentifier)
{
this(patchGroupIdentifier, 0);
}
/**
* Version number for this patch group used to differentiate frequently reused patch group identifiers.
* @return version number (aka super group sequence number).
*/
public int getVersion()
{
return mVersion;
}
/**
* Patch group identifier.
* @return identifier
*/
public TalkgroupIdentifier getPatchGroup()
{
return mPatchGroupIdentifier;
}
public void addPatchedGroup(TalkgroupIdentifier patchedGroupIdentifier)
/**
* Indicates if this patch group contains any patched talkgroups.
* @return true if there are patched talkgroups.
*/
public boolean hasPatchedTalkgroups()
{
if(!mPatchedGroupIdentifiers.contains(patchedGroupIdentifier))
return !getPatchedTalkgroupIdentifiers().isEmpty();
}
/**
* Indicates if this patch group contains any patched radio identifiers.
* @return true if there are patched radios.
*/
public boolean hasPatchedRadios()
{
return !getPatchedRadioIdentifiers().isEmpty();
}
/**
* Adds the talkgroup identifier to this patched group.
* @param patchedGroupIdentifier to add
*/
public void addPatchedTalkgroup(TalkgroupIdentifier patchedGroupIdentifier)
{
if(!mPatchedTalkgroupIdentifiers.contains(patchedGroupIdentifier))
{
mPatchedGroupIdentifiers.add(patchedGroupIdentifier);
mPatchedTalkgroupIdentifiers.add(patchedGroupIdentifier);
}
}
public void addPatchedGroups(List<TalkgroupIdentifier> patchedGroupIdentifiers)
/**
* Adds a list of talkgroup identifiers to this patched group.
* @param patchedGroupIdentifiers to add
*/
public void addPatchedTalkgroups(List<TalkgroupIdentifier> patchedGroupIdentifiers)
{
for(TalkgroupIdentifier identifier : patchedGroupIdentifiers)
{
addPatchedGroup(identifier);
addPatchedTalkgroup(identifier);
}
}
public List<TalkgroupIdentifier> getPatchedGroupIdentifiers()
/**
* List of patched group identifiers that are part of this patch group.
* @return patched talkgroups.
*/
public List<TalkgroupIdentifier> getPatchedTalkgroupIdentifiers()
{
return mPatchedGroupIdentifiers;
return mPatchedTalkgroupIdentifiers;
}
/**
* Adds the radio identifier to this patched group.
* @param patchedRadioIdentifier to add
*/
public void addPatchedRadio(RadioIdentifier patchedRadioIdentifier)
{
if(!mPatchedRadioIdentifiers.contains(patchedRadioIdentifier))
{
mPatchedRadioIdentifiers.add(patchedRadioIdentifier);
}
}
/**
* Adds a list of radio identifiers to this patched group.
* @param patchedRadioIdentifiers to add
*/
public void addPatchedRadios(List<RadioIdentifier> patchedRadioIdentifiers)
{
for(RadioIdentifier identifier : patchedRadioIdentifiers)
{
addPatchedRadio(identifier);
}
}
/**
* List of patched radio identifiers that are part of this patch group.
* @return patched radios.
*/
public List<RadioIdentifier> getPatchedRadioIdentifiers()
{
return mPatchedRadioIdentifiers;
}
@Override
@@ -72,9 +164,14 @@ public class PatchGroup
StringBuilder sb = new StringBuilder();
sb.append("P:").append(getPatchGroup());
if(!getPatchedGroupIdentifiers().isEmpty())
if(hasPatchedTalkgroups())
{
sb.append(" ").append(getPatchedGroupIdentifiers());
sb.append(getPatchedTalkgroupIdentifiers());
}
if(hasPatchedRadios())
{
sb.append(getPatchedRadioIdentifiers());
}
return sb.toString();

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,27 +22,23 @@ package io.github.dsheirer.identifier.patch;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Manager for (temporary) patch groups and related identifiers.
* Manager for (temporary) patch groups aka super groups. This manager monitors patch group additions and deletions and
* maintains a map of the current state of each patch group active in the system.
*
* Patch groups are primarily used on Motorola P25 systems. This manager monitors patch group additions and deletions
* and maintains a map of the current patch groups.
*
* Traffic channel calls will sometimes reference a patch group by simply using the patch group identifier. This
* manager will replace that reference with the full patch group so that the call event has the full patch group
* including all of the patched groups, which may not have been included in the patch group reference on the
* traffic channel.
* Traffic channel calls will reference a patch group by simply using the patch group identifier. This
* manager will replace that reference with the current state of the full patch group so that the call event has the
* full patch group including all patched talkgroups or individual radios, which may not have been included in the
* patch group reference on the control or traffic channel.
*/
public class PatchGroupManager
{
// private final static Logger mLog = LoggerFactory.getLogger(PatchGroupManager.class);
private Map<Integer,Long> mPatchGroupLastUpdatedMap = new HashMap<>();
private Map<Integer,PatchGroupIdentifier> mPatchGroupMap = new HashMap<>();
/**
@@ -58,13 +54,13 @@ public class PatchGroupManager
public void clear()
{
mPatchGroupMap.clear();
mPatchGroupLastUpdatedMap.clear();
}
/**
* Adds the patch group identifier to the set of managed patch groups. If the patch group is already being
* managed, the new identifier is checked for additional patched talkgroups and they are added to the existing
* managed patch group.
* managed, the new patch group argument is checked for additional patched talkgroups and they are added to the
* existing managed patch group. Where the patch group version (super group sequence number) is different, the
* existing patch group is discarded and replaced with the updated version of the patch group.
*
* @param patchGroupIdentifier to add or update
*/
@@ -76,8 +72,19 @@ public class PatchGroupManager
{
if(mPatchGroupMap.containsKey(update.getPatchGroup().getValue()))
{
PatchGroup current = mPatchGroupMap.get(update.getPatchGroup().getValue()).getValue();
current.addPatchedGroups(update.getPatchedGroupIdentifiers());
PatchGroup existingPatchGroup = mPatchGroupMap.get(update.getPatchGroup().getValue()).getValue();
//If the patch group version number is the same, this is an update
if(existingPatchGroup.getVersion() == update.getVersion())
{
existingPatchGroup.addPatchedTalkgroups(update.getPatchedTalkgroupIdentifiers());
existingPatchGroup.addPatchedRadios(update.getPatchedRadioIdentifiers());
}
//Otherwise, this is a replace operation.
else
{
mPatchGroupMap.put(update.getPatchGroup().getValue(), patchGroupIdentifier);
}
}
else
{
@@ -109,7 +116,6 @@ public class PatchGroupManager
{
int id = patchGroupIdentifier.getValue().getPatchGroup().getValue();
mPatchGroupMap.remove(id);
mPatchGroupLastUpdatedMap.remove(id);
}
/**
@@ -153,23 +159,28 @@ public class PatchGroupManager
*/
public Identifier update(Identifier identifier)
{
if(identifier == null)
{
return identifier;
}
if(identifier.getIdentifierClass() == IdentifierClass.USER && identifier.getRole() == Role.TO)
if(identifier != null && identifier.getIdentifierClass() == IdentifierClass.USER && identifier.getRole() == Role.TO)
{
switch(identifier.getForm())
{
case PATCH_GROUP:
if(identifier instanceof PatchGroupIdentifier)
case TALKGROUP:
if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
{
Identifier mapValue = mPatchGroupMap.get(talkgroupIdentifier.getValue());
if (mapValue != null)
{
return mapValue;
}
}
break;
case PATCH_GROUP:
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier;
int patchGroupId = patchGroupIdentifier.getValue().getPatchGroup().getValue();
Identifier mapValue = mPatchGroupMap.get(patchGroupId);
if (mapValue != null) {
if (mapValue != null)
{
return mapValue;
}
}

View File

@@ -145,6 +145,7 @@ public abstract class DecodeConfigAnalog extends DecodeConfiguration implements
{
BW_3_0("3.0 kHz", 3000.0),
BW_5_0("5.0 kHz", 5000.0),
BW_7_5("7.5 kHz", 7500.0),
BW_8_33("8.33 kHz", 8333.0),
BW_12_5("12.5 kHz", 12500.0),
BW_15_0("15.0 kHz", 15000.0),
@@ -168,7 +169,7 @@ public abstract class DecodeConfigAnalog extends DecodeConfiguration implements
public static EnumSet<Bandwidth> AM_BANDWIDTHS = EnumSet.of(BW_3_0, BW_5_0, BW_8_33, BW_15_0, BW_25_0);
//FM demodulator channel bandwidth options
public static EnumSet<Bandwidth> FM_BANDWIDTHS = EnumSet.of(BW_12_5, BW_25_0);
public static EnumSet<Bandwidth> FM_BANDWIDTHS = EnumSet.of(BW_7_5, BW_12_5, BW_25_0);
/**
* Indicates if this entry is valid for the AM decoder.

View File

@@ -424,18 +424,19 @@ public class DMRDecoderState extends TimeslotDecoderState
broadcast(packetEvent);
GeoPosition geoPosition = PacketUtil.extractGeoPosition(packet.getPacket());
}
if (geoPosition != null) {
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, packet.getTimestamp())
.channel(getCurrentChannel())
.identifiers(new IdentifierCollection(packet.getIdentifiers()))
.protocol(Protocol.LRRP)
.location(geoPosition)
.build();
GeoPosition geoPosition = PacketUtil.extractGeoPosition(packet.getPacket());
broadcast(plottableDecodeEvent);
}
if (geoPosition != null) {
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, packet.getTimestamp())
.channel(getCurrentChannel())
.identifiers(new IdentifierCollection(packet.getIdentifiers()))
.protocol(Protocol.LRRP)
.location(geoPosition)
.build();
broadcast(plottableDecodeEvent);
}
}
@@ -1209,19 +1210,17 @@ public class DMRDecoderState extends TimeslotDecoderState
}
break;
case FULL_STANDARD_GPS_INFO:
if(message instanceof GPSInformation)
if(message instanceof GPSInformation gpsInformation)
{
GPSInformation gpsInformation = (GPSInformation)message;
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
ic.update(gpsInformation.getGPSLocation());
PlottableDecodeEvent plottableGPS = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, message.getTimestamp())
.channel(getCurrentChannel())
.details("LOCATION:" + gpsInformation.getGPSLocation())
.identifiers(new IdentifierCollection(getIdentifierCollection().getIdentifiers()))
.protocol(Protocol.DMR)
.location(gpsInformation.getPosition())
.build();
DecodeEvent gpsEvent = DMRDecodeEvent.builder(DecodeEventType.GPS, message.getTimestamp())
.identifiers(ic)
.timeslot(getTimeslot())
.details("LOCATION:" + gpsInformation.getGPSLocation())
.build();
broadcast(gpsEvent);
broadcast(plottableGPS);
}
break;
case FULL_STANDARD_TALKER_ALIAS_COMPLETE:

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -38,7 +38,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.AbstractVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
import io.github.dsheirer.preference.UserPreferences;
@@ -105,63 +104,54 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
{
if(hasAudioCodec() && message.getTimeslot() == getTimeslot())
{
//DCDM doesn't provide FLCs or EMBs ... assume that the call is unencrypted.
if(!mEncryptedCallStateEstablished && message instanceof VoiceMessage vm &&
vm.getSyncPattern().isDirectMode())
//Attempt to set the audio encryption state from certain types of messages
if(!mEncryptedCallStateEstablished)
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = false;
List<byte[]> frames = vm.getAMBEFrames();
for(byte[] frame: frames)
//DCDM doesn't provide FLCs or EMBs ... assume that the call is unencrypted.
if(message instanceof VoiceMessage vm && vm.getSyncPattern().isDirectMode())
{
processAudio(frame, message.getTimestamp());
mEncryptedCallStateEstablished = true;
mEncryptedCall = false;
}
}
//Both Motorola and Hytera signal their Basic Privacy (BP) scrambling in some of the Voice B-F frames
//in the EMB field.
else if(!mEncryptedCallStateEstablished && message instanceof VoiceEMBMessage voiceMessage)
{
if(voiceMessage.getEMB().isValid())
//Both Motorola and Hytera signal their Basic Privacy (BP) scrambling in some of the Voice B-F frames
//in the EMB field.
else if(message instanceof VoiceEMBMessage voiceMessage && voiceMessage.getEMB().isValid())
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = voiceMessage.getEMB().isEncrypted();
}
else if(message instanceof VoiceHeader voiceHeader)
{
LCMessage lc = voiceHeader.getLCMessage();
List<byte[]> frames = voiceMessage.getAMBEFrames();
for(byte[] frame: frames)
{
processAudio(frame, message.getTimestamp());
}
}
else if(message instanceof VoiceMessage voiceMessage)
{
List<byte[]> frames = voiceMessage.getAMBEFrames();
for(byte[] frame: frames)
{
processAudio(frame, message.getTimestamp());
}
}
else if(!mEncryptedCallStateEstablished && message instanceof VoiceHeader voiceHeader)
{
LCMessage lc = voiceHeader.getLCMessage();
if(lc instanceof FullLCMessage flc) //We know it is, but this null checks as well
{
if(flc.isValid())
if(lc instanceof FullLCMessage flc) //We know it is, but this null checks as well
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = flc.isEncrypted();
if(flc.isValid())
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = flc.isEncrypted();
}
}
}
//Note: the DMRMessageProcessor extracts Full Link Control messages from Voice Frames B-C and sends them
// independent of any DMR Burst messaging. When encountered, it can be assumed that they are part of
// an ongoing call and can be used to establish encryption state when the FLC is a voice channel user.
else if(message instanceof AbstractVoiceChannelUser avcu)
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = avcu.getServiceOptions().isEncrypted();
}
}
//Note: the DMRMessageProcess extracts Full Link Control messages from Voice Frames B-C and sends them
// independent of any DMR Burst messaging. When encountered, it can be assumed that they are part of
// an ongoing call and can be used to establish encryption state when the FLC is a voice channel user.
else if(!mEncryptedCallStateEstablished && message instanceof AbstractVoiceChannelUser avcu)
//Queue or process audio frames. Note: audio frames are held/queued until encrypted state is established
//before any audio is generated for the audio segment.
if(message instanceof VoiceMessage voiceMessage)
{
ServiceOptions serviceOptions = avcu.getServiceOptions();
mEncryptedCallStateEstablished = true;
mEncryptedCall = serviceOptions.isEncrypted();
List<byte[]> frames = voiceMessage.getAMBEFrames();
for(byte[] frame: frames)
{
processAudio(frame, message.getTimestamp());
}
}
else if(message instanceof Terminator)
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,10 +24,10 @@ import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.location.LocationIdentifier;
import io.github.dsheirer.module.decode.dmr.identifier.DMRLocation;
import io.github.dsheirer.module.decode.dmr.message.type.PositionError;
import org.apache.commons.math3.util.FastMath;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.math3.util.FastMath;
import org.jdesktop.swingx.mapviewer.GeoPosition;
/**
* GPS Information
@@ -81,16 +81,30 @@ public class GPSInformation extends FullLCMessage
return PositionError.fromValue(getMessage().getInt(POSITION_ERROR));
}
/**
* Latitude in decimal degrees
*/
public double getLatitude()
{
return getMessage().getTwosComplement(LATITUDE_START, LATITUDE_END) * LATITUDE_UNITS;
}
/**
* Longitude in decimal degrees
*/
public double getLongitude()
{
return getMessage().getTwosComplement(LONGITUDE_START, LONGITUDE_END) * LONGITUDE_UNITS;
}
/**
* Geo-position for the lat/lon
*/
public GeoPosition getPosition()
{
return new GeoPosition(getLatitude(), getLongitude());
}
/**
* GPS location as an identifier
*/

View File

@@ -21,13 +21,13 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.AbstractVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.type.CapacityPlusServiceOptions;
/**
* Any Capacity Plus Voice Channel User link control message that contains vendor-specific service options.
*/
public abstract class CapacityPlusVoiceChannelUser extends FullLCMessage implements IServiceOptionsProvider
public abstract class CapacityPlusVoiceChannelUser extends AbstractVoiceChannelUser implements IServiceOptionsProvider
{
private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
//Reed Solomon FEC: 72-95

View File

@@ -59,6 +59,8 @@ public class DecodeConfigNBFM extends DecodeConfigAnalog
{
switch(getBandwidth())
{
case BW_7_5:
return new ChannelSpecification(25000.0, 7500, 3500.0, 3750.0);
case BW_12_5:
return new ChannelSpecification(25000.0, 12500, 6000.0, 7000.0);
case BW_25_0:

View File

@@ -57,6 +57,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.P25Message;
import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HDUMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HeaderData;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaUnitGPS;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCCallTermination;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCExtendedFunctionCommand;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCMessageUpdate;
@@ -94,6 +95,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.umbtc.isp.UMBTCTe
import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.HarrisGroupRegroupExplicitEncryptionCommand;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaDenyResponse;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.PatchGroupVoiceChannelGrant;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.PatchGroupVoiceChannelGrantUpdate;
@@ -1221,6 +1223,21 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
mPatchGroupManager.removePatchGroups(tsbk.getIdentifiers());
break;
//L3HARRIS PATCH GROUP OPCODES
case HARRIS_OSP_GRG_EXENC_CMD:
if(tsbk instanceof HarrisGroupRegroupExplicitEncryptionCommand regroup)
{
if(regroup.getRegroupOptions().isActivate())
{
mPatchGroupManager.addPatchGroup(regroup.getPatchGroup());
}
else
{
mPatchGroupManager.removePatchGroup(regroup.getPatchGroup());
}
}
break;
//STANDARD - INBOUND OPCODES
case ISP_GROUP_VOICE_SERVICE_REQUEST:
if(tsbk instanceof GroupVoiceServiceRequest)
@@ -1885,6 +1902,22 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
case UNIT_TO_UNIT_ANSWER_REQUEST:
processBroadcast(lcw.getIdentifiers(), timestamp, DecodeEventType.PAGE, "Unit-to-Unit Answer Request");
break;
case MOTOROLA_UNIT_GPS:
if(lcw instanceof LCMotorolaUnitGPS moto)
{
String gpsDetails = "LOCATION: " + moto.getLatitude() + " " + moto.getLongitude();
PlottableDecodeEvent event = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, timestamp)
.location(new GeoPosition(moto.getLatitude(), moto.getLongitude()))
.channel(getCurrentChannel())
.details(gpsDetails)
.end(timestamp)
.protocol(Protocol.APCO25)
.identifiers(new IdentifierCollection(getIdentifierCollection().getIdentifiers()))
.build();
broadcast(event);
}
break;
default:
// mLog.debug("Unrecognized LCW Opcode: " + lcw.getOpcode().name() + " VENDOR:" + lcw.getVendor() +
// " OPCODE:" + lcw.getOpcodeNumber());

View File

@@ -42,6 +42,7 @@ public class TerminatorMessageFilterSet extends FilterSet<IMessage>
addFilter(new TerminatorMessageFilter("Command/Status", LinkControlOpcode.COMMAND_STATUS_OPCODES));
addFilter(new TerminatorMessageFilter("Network/Channel", LinkControlOpcode.NETWORK_OPCODES));
addFilter(new TerminatorMessageFilter("Voice", LinkControlOpcode.VOICE_OPCODES));
addFilter(new TerminatorMessageFilter("L3Harris", LinkControlOpcode.L3HARRIS_OPCODES));
addFilter(new TerminatorMessageFilter("Motorola", LinkControlOpcode.MOTOROLA_OPCODES));
List<LinkControlOpcode> others = new ArrayList<>();

View File

@@ -96,8 +96,14 @@ public enum LinkControlOpcode
MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_UPDATE("PATCH GROUP VOICE CHANNEL UPDATE", 1),
MOTOROLA_PATCH_GROUP_ADD("PATCH GROUP ADD", 3),
MOTOROLA_PATCH_GROUP_DELETE("PATCH GROUP DELETE", 4),
MOTOROLA_UNIT_GPS("UNIT GPS", 6),
MOTOROLA_TALK_COMPLETE("TALK_COMPLETE", 15),
MOTOROLA_UNKNOWN("UNKNOWN", -1),
MOTOROLA_UNKNOWN("MOTOROLA UNKNOWN", -1),
L3HARRIS_UNKNOWN_0A("UNKNOWN OPCODE 10", 10),
L3HARRIS_UNKNOWN_2A("UNKNOWN OPCODE 42", 42),
L3HARRIS_UNKNOWN_2B("UNKNOWN OPCODE 43", 43),
L3HARRIS_UNKNOWN("L3HARRIS UNKNOWN", -1),
UNKNOWN("UNKNOWN", -1);
@@ -129,6 +135,11 @@ public enum LinkControlOpcode
public static final EnumSet<LinkControlOpcode> MOTOROLA_OPCODES =
EnumSet.range(MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER, MOTOROLA_UNKNOWN);
/**
* L3Harris Opcodes
*/
public static final EnumSet<LinkControlOpcode> L3HARRIS_OPCODES = EnumSet.of(L3HARRIS_UNKNOWN_0A,
L3HARRIS_UNKNOWN_2A, L3HARRIS_UNKNOWN_2B, L3HARRIS_UNKNOWN);
/**
* Network/channel related opcodes
@@ -190,6 +201,18 @@ public enum LinkControlOpcode
return values()[value];
}
break;
case HARRIS:
switch(value)
{
case 10:
return L3HARRIS_UNKNOWN_0A;
case 42:
return L3HARRIS_UNKNOWN_2A;
case 43:
return L3HARRIS_UNKNOWN_2B;
default:
return L3HARRIS_UNKNOWN;
}
case MOTOROLA:
switch(value)
{
@@ -201,16 +224,18 @@ public enum LinkControlOpcode
return MOTOROLA_PATCH_GROUP_ADD;
case 4:
return MOTOROLA_PATCH_GROUP_DELETE;
case 6:
return MOTOROLA_UNIT_GPS;
case 15:
return MOTOROLA_TALK_COMPLETE;
default:
return MOTOROLA_UNKNOWN;
}
default:
if(0 <= value && value <= 63)
{
return values()[value];
}
}
if(0 <= value && value <= 63)
{
return values()[value];
}
return UNKNOWN;

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -15,17 +14,21 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.lc;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode10;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode42;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode43;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupAdd;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupDelete;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupVoiceChannelUpdate;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaTalkComplete;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaUnitGPS;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaUnknownOpcode;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCAdjacentSiteStatusBroadcast;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCAdjacentSiteStatusBroadcastExplicit;
@@ -128,6 +131,15 @@ public class LinkControlWordFactory
case UNIT_TO_UNIT_VOICE_CHANNEL_USER:
return new LCUnitToUnitVoiceChannelUser(binaryMessage);
case L3HARRIS_UNKNOWN_0A:
return new LCHarrisUnknownOpcode10(binaryMessage);
case L3HARRIS_UNKNOWN_2A:
return new LCHarrisUnknownOpcode42(binaryMessage);
case L3HARRIS_UNKNOWN_2B:
return new LCHarrisUnknownOpcode43(binaryMessage);
case L3HARRIS_UNKNOWN:
return new UnknownLinkControlWord(binaryMessage);
case MOTOROLA_PATCH_GROUP_ADD:
return new LCMotorolaPatchGroupAdd(binaryMessage);
case MOTOROLA_PATCH_GROUP_DELETE:
@@ -138,6 +150,8 @@ public class LinkControlWordFactory
return new LCMotorolaTalkComplete(binaryMessage);
case MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_UPDATE:
return new LCMotorolaPatchGroupVoiceChannelUpdate(binaryMessage);
case MOTOROLA_UNIT_GPS:
return new LCMotorolaUnitGPS(binaryMessage);
case MOTOROLA_UNKNOWN:
return new LCMotorolaUnknownOpcode(binaryMessage);

View File

@@ -0,0 +1,128 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
import java.util.ArrayList;
import java.util.List;
/**
* L3Harris Unknown Opcode 10 (0x0A)
* <p>
* Observed in a traffic channel carried by a TDU during an SNDCP packet data session. The controller send an 'All
* blocks received' PDU to the radio in the middle of a stream of these TDU/LC messages and the traffic channel ended
* with Call Termination TDULC messages. So, this may be some form of current data session user information update.
*/
public class LCHarrisUnknownOpcode10 extends LinkControlWord
{
private static final int[] UNKNOWN_1 = {16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] TARGET_RADIO = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
42, 43, 44, 45, 46, 47};
private static final int[] SOURCE_RADIO = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
66, 67, 68, 69, 70, 71};
private RadioIdentifier mSourceRadio;
private RadioIdentifier mTargetRadio;
private List<Identifier> mIdentifiers;
/**
* Constructs a Link Control Word from the binary message sequence.
*
* @param message
*/
public LCHarrisUnknownOpcode10(BinaryMessage message)
{
super(message);
}
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("**CRC-FAILED** ");
}
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
else
{
sb.append("L3HARRIS UNKNOWN OPCODE 10 ");
sb.append(" FM:").append(getSourceRadio());
sb.append(" TO:").append(getTargetRadio());
sb.append(" UNK:").append(getUnknown());
sb.append(" MSG:").append(getMessage().toHexString());
}
return sb.toString();
}
public String getUnknown()
{
return getMessage().getHex(UNKNOWN_1, 2);
}
/**
* Source radio identifier
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_RADIO));
}
return mSourceRadio;
}
/**
* Target radio identifier
*/
public RadioIdentifier getTargetRadio()
{
if(mTargetRadio == null)
{
mTargetRadio = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_RADIO));
}
return mTargetRadio;
}
/**
* List of identifiers contained in this message
*/
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getSourceRadio());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,74 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
import java.util.Collections;
import java.util.List;
/**
* L3Harris Unknown Opcode 42 (0x2A)
* <p>
* Observed in a voice traffic channel carried by an LDU1. This was transmitted once followed by an Opcode 43 (0x2B)
* which is also unknown.
*/
public class LCHarrisUnknownOpcode42 extends LinkControlWord
{
/**
* Constructs a Link Control Word from the binary message sequence.
*
* @param message
*/
public LCHarrisUnknownOpcode42(BinaryMessage message)
{
super(message);
}
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("**CRC-FAILED** ");
}
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
else
{
sb.append("L3HARRIS UNKNOWN OPCODE 42 MSG:").append(getMessage().toHexString());
}
return sb.toString();
}
/**
* List of identifiers contained in this message
*/
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,74 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
import java.util.Collections;
import java.util.List;
/**
* L3Harris Unknown Opcode 43 (0x2B)
* <p>
* Observed in a voice traffic channel carried by an LDU1. This was transmitted once following an Opcode 42 (0x2A)
* which is also unknown.
*/
public class LCHarrisUnknownOpcode43 extends LinkControlWord
{
/**
* Constructs a Link Control Word from the binary message sequence.
*
* @param message
*/
public LCHarrisUnknownOpcode43(BinaryMessage message)
{
super(message);
}
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("**CRC-FAILED** ");
}
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
else
{
sb.append("L3HARRIS UNKNOWN OPCODE 43 MSG:").append(getMessage().toHexString());
}
return sb.toString();
}
/**
* List of identifiers contained in this message
*/
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -66,7 +66,7 @@ public class LCMotorolaPatchGroupAdd extends MotorolaLinkControlWord
if(mPatchGroup == null)
{
PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getMessage().getInt(PATCH_GROUP)));
patchGroup.addPatchedGroups(getPatchedGroups());
patchGroup.addPatchedTalkgroups(getPatchedGroups());
mPatchGroup = APCO25PatchGroup.create(patchGroup);
}

View File

@@ -66,7 +66,7 @@ public class LCMotorolaPatchGroupDelete extends MotorolaLinkControlWord
if(mPatchGroup == null)
{
PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getMessage().getInt(PATCH_GROUP)));
patchGroup.addPatchedGroups(getPatchedGroups());
patchGroup.addPatchedTalkgroups(getPatchedGroups());
mPatchGroup = APCO25PatchGroup.create(patchGroup);
}

View File

@@ -0,0 +1,84 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import java.util.Collections;
import java.util.List;
/**
* Motorola Unit Self-Reported GPS Location
*/
public class LCMotorolaUnitGPS extends MotorolaLinkControlWord
{
private static final double LATITUDE_MULTIPLIER = 90.0 / 0x7FFFFF;
private static final double LONGITUDE_MULTIPLIER = 180.0 / 0x7FFFFF;
private static final int[] UNKNOWN = {16, 17, 18, 19, 20, 21, 22, 23};
private static final int LATITUDE_SIGN = 24;
private static final int[] LATITUDE = {25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
private static final int LONGITUDE_SIGN = 48;
private static final int[] LONGITUDE = {49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
/**
* Constructs an instance
* @param message binary
*/
public LCMotorolaUnitGPS(BinaryMessage message)
{
super(message);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("MOTOROLA UNIT GPS LOCATION: ").append(getLatitude()).append(" ").append(getLongitude());
return sb.toString();
}
/**
* GPS Latitude value.
* @return value in decimal degrees
*/
public double getLatitude()
{
return getMessage().getInt(LATITUDE) * LATITUDE_MULTIPLIER * (getMessage().get(LATITUDE_SIGN) ? -1 : 1);
}
/**
* GPS Longitude value.
* @return value in decimal degrees
*/
public double getLongitude()
{
return getMessage().getInt(LONGITUDE) * LONGITUDE_MULTIPLIER * (getMessage().get(LONGITUDE_SIGN) ? -1 : 1);
}
/**
* List of identifiers contained in this message
*/
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp;
@@ -29,7 +28,6 @@ import io.github.dsheirer.module.decode.p25.reference.NetworkAddressType;
import io.github.dsheirer.module.decode.p25.reference.PDUPriorityMaximumType;
import io.github.dsheirer.module.decode.p25.reference.ReadyTimer;
import io.github.dsheirer.module.decode.p25.reference.StandbyTimer;
import java.util.ArrayList;
import java.util.List;
@@ -81,7 +79,7 @@ public class ActivateTdsContextAccept extends SNDCPMessage
}
sb.append(" READY TIMER:").append(getReadyTimer().getSeconds()).append("s");
sb.append(" STANDBY TIMER:").append(getStandbyTimer().getSeconds()).append("s");
sb.append(" MTU:").append(getMaximumTransmitUnit().getByteCount()).append("B");
sb.append(" MTU:").append(getMaximumTransmitUnit().getByteCount());
sb.append(" AUTHORIZED GROUPS:").append(getDataAccessGroups());
return sb.toString();
}

View File

@@ -176,11 +176,11 @@ public enum Opcode
MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN(14, "CCH PLND SHUTDWN", "CONTROL CHANNEL PLANNED SHUTDOWN"),
MOTOROLA_OSP_UNKNOWN(-1, "MOTOROLA OSP UNKNOWN OPCODE", "MOTOROLA OSP UNKNOWN OPCODE"),
//Vendor: motorola, Inbound Service Packet (ISP)
//Vendor: L3Harris, Inbound Service Packet (ISP)
HARRIS_ISP_UNKNOWN(-1, "HARRIS ISP UNKNOWN OPCODE", "HARRIS ISP UNKNOWN OPCODE"),
//Vendor: harris, Outbound Service Packet (OSP)
HARRIS_OSP_TDMA_SYNC(48, "HARRIS TDMA SYNC", "HARRIS TDMA SYNC BROADCAST"),
//Vendor: L3Harris, Outbound Service Packet (OSP)
HARRIS_OSP_GRG_EXENC_CMD(48, "HARRIS REGROUP", "HARRIS GROUP REGROUP COMMAND WITH EXPLICIT ENCRYPTION"),
HARRIS_OSP_UNKNOWN(-1, "HARRIS OSP UNKNOWN OPCODE", "HARRIS OSP UNKNOWN OPCODE"),
//Vendor: unknown, Inbound Service Packet (ISP)
@@ -294,7 +294,7 @@ public enum Opcode
/**
* Harris opcodes
*/
public static final EnumSet<Opcode> HARRIS = EnumSet.of(HARRIS_ISP_UNKNOWN, HARRIS_OSP_TDMA_SYNC,
public static final EnumSet<Opcode> HARRIS = EnumSet.of(HARRIS_ISP_UNKNOWN, HARRIS_OSP_GRG_EXENC_CMD,
HARRIS_OSP_UNKNOWN);
/**
@@ -425,7 +425,7 @@ public enum Opcode
switch(value)
{
case 48:
return HARRIS_OSP_TDMA_SYNC;
return HARRIS_OSP_GRG_EXENC_CMD;
default:
return HARRIS_OSP_UNKNOWN;
}

View File

@@ -27,7 +27,7 @@ import io.github.dsheirer.edac.trellis.ViterbiDecoder_1_2_P25;
import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID;
import io.github.dsheirer.module.decode.p25.phase1.P25P1Interleave;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.isp.UnknownHarrisISPMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.HarrisTDMASyncBroadcast;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.HarrisGroupRegroupExplicitEncryptionCommand;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.UnknownHarrisOSPMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.isp.UnknownMotorolaISPMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.ChannelLoading;
@@ -298,8 +298,8 @@ public class TSBKMessageFactory
case HARRIS_ISP_UNKNOWN:
return new UnknownHarrisISPMessage(dataUnitID, message, nac, timestamp);
case HARRIS_OSP_TDMA_SYNC:
return new HarrisTDMASyncBroadcast(dataUnitID, message, nac, timestamp);
case HARRIS_OSP_GRG_EXENC_CMD:
return new HarrisGroupRegroupExplicitEncryptionCommand(dataUnitID, message, nac, timestamp);
case HARRIS_OSP_UNKNOWN:
return new UnknownHarrisOSPMessage(dataUnitID, message, nac, timestamp);

View File

@@ -0,0 +1,200 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup;
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage;
import io.github.dsheirer.module.decode.p25.reference.RegroupOptions;
import java.util.ArrayList;
import java.util.List;
/**
* Harris Group Regroup Action and Explicit Encryption Command.
* <p>
* This OSP is used to create, remove or update a Supergroup address associated with one member WGID or WUID with
* explicit encryption parameters.
*
* See: https://forums.radioreference.com/threads/duke-energy-p25-system.411183/page-28#post-3908078
*/
public class HarrisGroupRegroupExplicitEncryptionCommand extends OSPMessage
{
private static final int[] REGROUP_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] SUPERGROUP_ADDRESS = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] ENCRYPTION_KEY_ID = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] TARGET_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
63, 64, 65, 66, 67, 68, 69, 70, 71};
private TalkgroupIdentifier mSuperGroupIdentifier;
private APCO25PatchGroup mPatchGroup;
private Identifier mPatchedIdentifier;
private List<Identifier> mIdentifiers;
private RegroupOptions mRegroupOptions;
/**
* Constructs a TSBK from the binary message sequence.
*/
public HarrisGroupRegroupExplicitEncryptionCommand(P25P1DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp)
{
super(dataUnitId, message, nac, timestamp);
}
/**
* Options for this regrouping that indicate the regrouping actions and targets.
* @return regroup options
*/
public RegroupOptions getRegroupOptions()
{
if(mRegroupOptions == null)
{
mRegroupOptions = new RegroupOptions(getMessage().getInt(REGROUP_OPTIONS));
}
return mRegroupOptions;
}
/**
* Encryption key ID to use for the patch group. Note: this only applies for patched talkgroups and the value is
* zero if the target address is a radio identifier.
* @return encryption key identifier
*/
public int getEncryptionKeyId()
{
return getMessage().getInt(ENCRYPTION_KEY_ID);
}
/**
* Indicates if there is a specified encryption key to use for the patch or simulselect communications.
* @return true if key is specified.
*/
public boolean hasEncryptionKeyId()
{
return getEncryptionKeyId() > 0;
}
/**
* APCO25 Patch Group or Simul-Select (e.g. Harris) loaded with the target address specified in this message.
* @return patch group
*/
public APCO25PatchGroup getPatchGroup()
{
if(mPatchGroup == null)
{
PatchGroup patchGroup = new PatchGroup(getSuperGroup(), getRegroupOptions().getSupergroupSequenceNumber());
if(getTargetAddress() instanceof APCO25Talkgroup talkgroup)
{
patchGroup.addPatchedTalkgroup(talkgroup);
}
else if(getTargetAddress() instanceof APCO25RadioIdentifier radio)
{
patchGroup.addPatchedRadio(radio);
}
mPatchGroup = APCO25PatchGroup.create(patchGroup);
}
return mPatchGroup;
}
/**
* Target address for the regrouping action.
* @return target talkgroup or radio address.
*/
public Identifier getTargetAddress()
{
if(mPatchedIdentifier == null)
{
if(getRegroupOptions().isTalkgroupAddress())
{
mPatchedIdentifier = APCO25Talkgroup.create(getMessage().getInt(TARGET_ADDRESS));
}
else
{
mPatchedIdentifier = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS));
}
}
return mPatchedIdentifier;
}
/**
* Talkgroup identifier for the super group aka patch group
*
* @return patch group identifier
*/
public TalkgroupIdentifier getSuperGroup()
{
if(mSuperGroupIdentifier == null)
{
mSuperGroupIdentifier = APCO25Talkgroup.create(getMessage().getInt(SUPERGROUP_ADDRESS));
}
return mSuperGroupIdentifier;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(getMessageStub());
if(getRegroupOptions().isActivate())
{
sb.append(" ACTIVATE");
sb.append(getRegroupOptions().isPatch() ? " TWO-WAY PATCH" : " ONE-WAY SIMUL-SELECT");
sb.append(" SUPERGROUP:").append(getSuperGroup());
sb.append(" V").append(getRegroupOptions().getSupergroupSequenceNumber());
sb.append(" INCLUDE");
sb.append(getRegroupOptions().isTalkgroupAddress() ? " TALKGROUP:" : " RADIO:");
sb.append(getTargetAddress());
if(hasEncryptionKeyId())
{
sb.append(" USE ENCRYPTION KEY:").append(getEncryptionKeyId());
}
}
else
{
sb.append(" DEACTIVATE SUPERGROUP:").append(getSuperGroup());
sb.append(" V").append(getRegroupOptions().getSupergroupSequenceNumber());
}
return sb.toString();
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getPatchGroup());
}
return mIdentifiers;
}
}

View File

@@ -1,58 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage;
import java.util.Collections;
import java.util.List;
/**
* Harris TDMA Sync Broadcast message.
*
* Note: Standard Opcode 48 is a TDMA Sync message, so this likely has something
* to do with TDMA Phase II
*/
public class HarrisTDMASyncBroadcast extends OSPMessage
{
/**
* Constructs a TSBK from the binary message sequence.
*/
public HarrisTDMASyncBroadcast(P25P1DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp)
{
super(dataUnitId, message, nac, timestamp);
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(getMessageStub());
sb.append(" HARRIS TDMA SYNC BROADCAST");
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.EMPTY_LIST;
}
}

View File

@@ -66,7 +66,7 @@ public class PatchGroupAdd extends OSPMessage
if(mPatchGroup == null)
{
PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getPatchAddress()));
patchGroup.addPatchedGroups(getPatchedTalkgroups());
patchGroup.addPatchedTalkgroups(getPatchedTalkgroups());
mPatchGroup = APCO25PatchGroup.create(patchGroup);
}

View File

@@ -66,7 +66,7 @@ public class PatchGroupDelete extends OSPMessage
if(mPatchGroup == null)
{
PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getPatchAddress()));
patchGroup.addPatchedGroups(getPatchedTalkgroups());
patchGroup.addPatchedTalkgroups(getPatchedTalkgroups());
mPatchGroup = APCO25PatchGroup.create(patchGroup);
}

View File

@@ -87,6 +87,7 @@ import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToU
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelGrantUpdateExtended;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserAbbreviated;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserExtended;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisRegroupCommand;
import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -391,6 +392,19 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
case PHASE1_109_UNIT_REGISTRATION_COMMAND_ABBREVIATED:
broadcast(message, mac, getCurrentChannel(), DecodeEventType.COMMAND, "UNIT REGISTRATION");
break;
case PHASE1_176_L3HARRIS_GROUP_REGROUP:
if(mac instanceof L3HarrisRegroupCommand regroup)
{
if(regroup.getRegroupOptions().isActivate())
{
mPatchGroupManager.addPatchGroup(regroup.getPatchGroup());
}
else
{
mPatchGroupManager.removePatchGroup(regroup.getPatchGroup());
}
}
break;
case PHASE1_197_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED:
if(mac instanceof UnitToUnitAnswerRequestExtended uuare)
{

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * 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/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase2.message.mac;
@@ -87,11 +84,11 @@ import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToU
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserExtended;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnknownStructure;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnknownVendorMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisRegroupCommand;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Message factory for creating Encoded MAC Information (EMI) message parser instances.
@@ -293,6 +290,8 @@ public class MacMessageFactory
return new AdjacentStatusBroadcastAbbreviated(message, offset);
case PHASE1_125_IDENTIFIER_UPDATE:
return new FrequencyBandUpdate(message, offset);
case PHASE1_176_L3HARRIS_GROUP_REGROUP:
return new L3HarrisRegroupCommand(message, offset);
case PHASE1_192_GROUP_VOICE_CHANNEL_GRANT_EXTENDED:
return new GroupVoiceChannelGrantExtended(message, offset);
case PHASE1_195_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT:

View File

@@ -76,6 +76,7 @@ public enum MacOpcode
PHASE1_123_NETWORK_STATUS_BROADCAST_ABBREVIATED(123, "NETWORK STATUS BROADCAST ABBREVIATED", 11),
PHASE1_124_ADJACENT_STATUS_BROADCAST_ABBREVIATED(124, "ADJACENT STATUS BROADCAST ABBREVIATED", 9),
PHASE1_125_IDENTIFIER_UPDATE(125, "IDENTIFIER UPDATE", 9),
PHASE1_176_L3HARRIS_GROUP_REGROUP(176, "REGROUP COMMAND", 17),
PHASE1_PARTITION_1_UNKNOWN_OPCODE(-1, "UNKNOWN PHASE 1 OPCODE", -1),
VENDOR_PARTITION_2_UNKNOWN_OPCODE(-1, "UNKNOWN VENDOR OPCODE", -1),
@@ -138,7 +139,7 @@ public enum MacOpcode
TDMA_1_GROUP_VOICE_CHANNEL_USER_ABBREVIATED, TDMA_2_UNIT_TO_UNIT_VOICE_CHANNEL_USER,
TDMA_3_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER, TDMA_5_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE,
TDMA_33_GROUP_VOICE_CHANNEL_USER_EXTENDED, TDMA_34_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED,
TDMA_37_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT);
TDMA_37_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT, PHASE1_176_L3HARRIS_GROUP_REGROUP);
/**
* Data channel grants

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * 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/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure;
@@ -26,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure;
import java.util.Collections;
import java.util.List;
@@ -72,7 +68,7 @@ public class FrequencyBandUpdate extends MacStructure implements IFrequencyBand
@Override
public int getIdentifier()
{
return getMessage().getInt(FREQUENCY_BAND_IDENTIFIER);
return getMessage().getInt(FREQUENCY_BAND_IDENTIFIER, getOffset());
}
@Override

View File

@@ -0,0 +1,272 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup;
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure;
import io.github.dsheirer.module.decode.p25.reference.Encryption;
import io.github.dsheirer.module.decode.p25.reference.RegroupOptions;
import io.github.dsheirer.module.decode.p25.reference.Vendor;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* L3Harris Group Regroup Explicit Encryption Command. Provides super group activate/deactivate for dynamic regrouping
* of talkgroups or groups of individual radios and includes optional encryption algorithm and key parameters when the
* regrouping targets talkgroups (not radios).
*
* See: https://forums.radioreference.com/threads/duke-energy-p25-system.411183/page-28#post-3908078
*/
public class L3HarrisRegroupCommand extends MacStructure
{
private static final int[] VENDOR = {8, 9, 10, 11, 12, 13, 14, 15};
private static final int[] LENGTH = {18, 19, 20, 21, 22, 23};
private static final int[] REGROUP_OPTIONS = {24, 25, 26, 27, 28, 29, 30, 31};
private static final int[] SUPERGROUP = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] KEY_ID = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63};
//Variation 1: Radio identifiers only.
private static final int[] RADIO_1 = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
83, 84, 85, 86, 87};
private static final int[] RADIO_2 = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105,
106, 107, 108, 109, 110, 111};
private static final int[] RADIO_3 = {112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126,
127, 128, 129, 130, 131, 132, 133, 134, 135};
//Variation 2: Talkgroups and Algorithm ID
private static final int[] ALGORITHM_ID = {64, 65, 66, 67, 68, 69, 70, 71};
private static final int[] TALKGROUP_1 = {72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87};
private static final int[] TALKGROUP_2 = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103};
private static final int[] TALKGROUP_3 = {104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119};
private static final int[] TALKGROUP_4 = {120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135};
private RegroupOptions mRegroupOptions;
private PatchGroupIdentifier mPatchGroupIdentifier;
private List<Identifier> mIdentifiers;
/**
* Constructs the message
*
* @param message containing the message bits
* @param offset into the message for this structure
*/
public L3HarrisRegroupCommand(CorrectedBinaryMessage message, int offset)
{
super(message, offset);
}
/**
* Textual representation of this message
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
if(getVendor() == Vendor.HARRIS)
{
sb.append("L3HARRIS ");
}
else
{
sb.append("WARNING: UNKNOWN VENDOR:").append(getVendor());
}
if(getRegroupOptions().isActivate())
{
sb.append(" ACTIVATE");
sb.append(getRegroupOptions().isPatch() ? " TWO-WAY PATCH" : " ONE-WAY SIMUL-SELECT");
sb.append(" SUPERGROUP:").append(getPatchGroup().getValue().getPatchGroup().getValue());
sb.append(" V").append(getRegroupOptions().getSupergroupSequenceNumber());
sb.append(" V").append(getRegroupOptions().getSupergroupSequenceNumber());
if(getRegroupOptions().isActivate())
{
if(getRegroupOptions().isTalkgroupAddress())
{
sb.append(" INCLUDE TALKGROUPS:");
sb.append(getPatchGroup().getValue().getPatchedTalkgroupIdentifiers().stream()
.map(tg -> tg.getValue().toString()).collect(Collectors.joining(",")));
Encryption encryption = getEncryptionAlgorithm();
if(encryption != Encryption.UNENCRYPTED)
{
sb.append(" USE ENCRYPTION:").append(encryption.name());
sb.append(" KEY:").append(getKeyId());
}
}
else
{
sb.append(" INCLUDE RADIOS:");
sb.append(getPatchGroup().getValue().getPatchedRadioIdentifiers().stream()
.map(radio -> radio.getValue().toString()).collect(Collectors.joining(",")));
}
}
}
else
{
sb.append(" DEACTIVATE SUPERGROUP:").append(getPatchGroup().getValue().getPatchGroup().getValue());
}
sb.append(" MSG LENGTH:").append(getLength());
return sb.toString();
}
/**
* Vendor ID. This should be L3Harris unless another vendor is also using this Opcode.
*/
public Vendor getVendor()
{
return Vendor.fromValue(getMessage().getInt(VENDOR, getOffset()));
}
/**
* Message length. Note: I'm unsure if this is the count of identifiers or if this is the quantity of bytes.
* @return length.
*/
public int getLength()
{
return getMessage().getInt(LENGTH, getOffset());
}
/**
* Regrouping options for this patch group (ie activate, deactivate, patch group version, etc.
* @return patch group options.
*/
public RegroupOptions getRegroupOptions()
{
if(mRegroupOptions == null)
{
mRegroupOptions = new RegroupOptions(getMessage().getInt(REGROUP_OPTIONS, getOffset()));
}
return mRegroupOptions;
}
/**
* Encryption key ID. Note: only available when the regroup options indicates this is a talkgroup supergroup.
* @return key ID.
*/
public int getKeyId()
{
return getMessage().getInt(KEY_ID, getOffset());
}
/**
* Encryption algorithm for talkgroup super-group.
*/
public Encryption getEncryptionAlgorithm()
{
return Encryption.fromValue(getMessage().getInt(ALGORITHM_ID, getOffset()));
}
/**
* Supergroup/Patch group referenced by this message including any patched talkgroups or individual radio identifiers.
* @return patch group.
*/
public PatchGroupIdentifier getPatchGroup()
{
if(mPatchGroupIdentifier == null)
{
TalkgroupIdentifier patchGroupId = APCO25Talkgroup.create(getMessage().getInt(SUPERGROUP, getOffset()));
PatchGroup patchGroup = new PatchGroup(patchGroupId, getRegroupOptions().getSupergroupSequenceNumber());
if(getRegroupOptions().isTalkgroupAddress())
{
int talkgroup1 = getMessage().getInt(TALKGROUP_1, getOffset());
if(talkgroup1 > 0)
{
patchGroup.addPatchedTalkgroup(APCO25Talkgroup.create(talkgroup1));
int talkgroup2 = getMessage().getInt(TALKGROUP_2, getOffset());
if(talkgroup2 > 0)
{
patchGroup.addPatchedTalkgroup(APCO25Talkgroup.create(talkgroup2));
int talkgroup3 = getMessage().getInt(TALKGROUP_3, getOffset());
if(talkgroup3 > 0)
{
patchGroup.addPatchedTalkgroup(APCO25Talkgroup.create(talkgroup3));
int talkgroup4 = getMessage().getInt(TALKGROUP_4, getOffset());
if(talkgroup4 > 0)
{
patchGroup.addPatchedTalkgroup(APCO25Talkgroup.create(talkgroup4));
}
}
}
}
}
else
{
int radio1 = getMessage().getInt(RADIO_1, getOffset());
if(radio1 > 0)
{
patchGroup.addPatchedRadio(APCO25RadioIdentifier.createTo(radio1));
int radio2 = getMessage().getInt(RADIO_2, getOffset());
if(radio2 > 0)
{
patchGroup.addPatchedRadio(APCO25RadioIdentifier.createTo(radio2));
int radio3 = getMessage().getInt(RADIO_3, getOffset());
if(radio3 > 0)
{
patchGroup.addPatchedRadio(APCO25RadioIdentifier.createTo(radio3));
}
}
}
}
mPatchGroupIdentifier = APCO25PatchGroup.create(patchGroup);
}
return mPatchGroupIdentifier;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getPatchGroup());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,81 @@
package io.github.dsheirer.module.decode.p25.reference;
/**
* Harris group regroup options
*/
public class RegroupOptions
{
private static final int MASK_REGROUP_TYPE = 0x80;
private static final int MASK_GROUP_INDIVIDUAL = 0x40;
private static final int MASK_ACTIVATE = 0x20;
private static final int MASK_SUPERGROUP_SEQUENCE_NUMBER = 0x1F;
private int mValue;
/**
* Constructs an instance
* @param value
*/
public RegroupOptions(int value)
{
mValue = value;
}
/**
* Type of regrouping
* @return regroup type
*/
public RegroupType getRegroupType()
{
return (mValue & MASK_REGROUP_TYPE) == MASK_REGROUP_TYPE ?
RegroupType.SIMULSELECT_ONE_WAY_REGROUP : RegroupType.PATCH_TWO_WAY_REGROUP;
}
/**
* Indicates if this is a two-way patch (true) or one-way simul-select (false) regrouping
* @return patch (true) or simulselect (false)
*/
public boolean isPatch()
{
return getRegroupType() == RegroupType.PATCH_TWO_WAY_REGROUP;
}
/**
* Indicates if the regroup target address is a group address (true) or individual address (false).
* @return true for group or false for individual address.
*/
public boolean isTalkgroupAddress()
{
return (mValue & MASK_GROUP_INDIVIDUAL) == MASK_GROUP_INDIVIDUAL;
}
/**
* Indicates if this is an activation (true) or a deactivation (false) of the supergroup.
* @return activation (true) or deactivation (false)
*/
public boolean isActivate()
{
return (mValue & MASK_ACTIVATE) == MASK_ACTIVATE;
}
/**
* Unique identifier for the regrouping
* @return unique id SSN
*/
public int getSupergroupSequenceNumber()
{
return (mValue & MASK_SUPERGROUP_SEQUENCE_NUMBER);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(isActivate() ? "ACTIVATE" : "DEACTIVATE");
sb.append(isTalkgroupAddress() ? " TALKGROUP" : " INDIVIDUAL");
sb.append(isPatch() ? " PATCH" : " SIMUL-SELECT");
return sb.toString();
}
public enum RegroupType {PATCH_TWO_WAY_REGROUP, SIMULSELECT_ONE_WAY_REGROUP};
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * 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/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.preference.identifier.talkgroup;
@@ -83,10 +80,20 @@ public class APCO25TalkgroupFormatter extends AbstractIntegerFormatter
sb.append("[");
int counter = 0;
for(TalkgroupIdentifier patchedGroup: patchGroup.getPatchedGroupIdentifiers())
for(TalkgroupIdentifier patchedGroup: patchGroup.getPatchedTalkgroupIdentifiers())
{
sb.append(format(patchedGroup, format, fixedWidth));
if(counter++ < patchGroup.getPatchedGroupIdentifiers().size() - 1)
if(counter++ < patchGroup.getPatchedTalkgroupIdentifiers().size() - 1)
{
sb.append(",");
}
}
counter = 0;
for(RadioIdentifier patchedRadio: patchGroup.getPatchedRadioIdentifiers())
{
sb.append(format(patchedRadio, format, fixedWidth));
if(counter++ < patchGroup.getPatchedRadioIdentifiers().size() - 1)
{
sb.append(",");
}

View File

@@ -55,9 +55,14 @@ import io.github.dsheirer.source.tuner.rtl.RTL2832UnknownTunerEditor;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerEditor;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TEmbeddedTuner;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerEditor;
import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013EmbeddedTuner;
import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerEditor;
import io.github.dsheirer.source.tuner.rtl.r8x.R8xTunerEditor;
import io.github.dsheirer.source.tuner.rtl.r8x.r820t.R820TEmbeddedTuner;
import io.github.dsheirer.source.tuner.rtl.r8x.r820t.R820TTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.r8x.r828d.R828DEmbeddedTuner;
import io.github.dsheirer.source.tuner.rtl.r8x.r828d.R828DTunerConfiguration;
import io.github.dsheirer.source.tuner.sdrplay.DiscoveredRspTuner;
import io.github.dsheirer.source.tuner.sdrplay.RspTuner;
import io.github.dsheirer.source.tuner.sdrplay.api.DeviceSelectionMode;
@@ -366,8 +371,10 @@ public class TunerFactory
return switch(tunerType)
{
case ELONICS_E4000 -> new E4KEmbeddedTuner(adapter);
case FITIPOWER_FC0013 -> new FC0013EmbeddedTuner(adapter);
case RAFAELMICRO_R820T -> new R820TEmbeddedTuner(adapter);
default -> throw new SourceException("Unsupported/Unrecognized Tuner Type");
case RAFAELMICRO_R828D -> new R828DEmbeddedTuner(adapter);
default -> throw new SourceException("Unsupported/Unrecognized Tuner Type: " + tunerType);
};
}
@@ -384,6 +391,8 @@ public class TunerFactory
return new AirspyTunerConfiguration(uniqueID);
case ELONICS_E4000:
return new E4KTunerConfiguration(uniqueID);
case FITIPOWER_FC0013:
return new FC0013TunerConfiguration(uniqueID);
case FUNCUBE_DONGLE_PRO:
return new FCD1TunerConfiguration(uniqueID);
case FUNCUBE_DONGLE_PRO_PLUS:
@@ -394,6 +403,8 @@ public class TunerFactory
return new HackRFTunerConfiguration(uniqueID);
case RAFAELMICRO_R820T:
return new R820TTunerConfiguration(uniqueID);
case RAFAELMICRO_R828D:
return new R828DTunerConfiguration(uniqueID);
case RECORDING:
return RecordingTunerConfiguration.create();
case RSP_1:
@@ -474,8 +485,11 @@ public class TunerFactory
{
case ELONICS_E4000:
return new E4KTunerEditor(userPreferences, tunerManager, discoveredTuner);
case FITIPOWER_FC0013:
return new FC0013TunerEditor(userPreferences, tunerManager, discoveredTuner);
case RAFAELMICRO_R820T:
return new R820TTunerEditor(userPreferences, tunerManager, discoveredTuner);
case RAFAELMICRO_R828D:
return new R8xTunerEditor(userPreferences, tunerManager, discoveredTuner);
}
}
return new RTL2832UnknownTunerEditor(userPreferences, tunerManager, discoveredTuner);

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -31,7 +31,9 @@ import io.github.dsheirer.source.tuner.fcd.proplusV2.FCD2TunerConfiguration;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerConfiguration;
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.r8x.r820t.R820TTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.r8x.r828d.R828DTunerConfiguration;
import io.github.dsheirer.source.tuner.sdrplay.RspTunerConfiguration;
/**
@@ -42,11 +44,13 @@ import io.github.dsheirer.source.tuner.sdrplay.RspTunerConfiguration;
@JsonSubTypes.Type(value = AirspyTunerConfiguration.class, name = "airspyTunerConfiguration"),
@JsonSubTypes.Type(value = AirspyHfTunerConfiguration.class, name = "airspyHfTunerConfiguration"),
@JsonSubTypes.Type(value = E4KTunerConfiguration.class, name = "e4KTunerConfiguration"),
@JsonSubTypes.Type(value = FC0013TunerConfiguration.class, name = "fc0013TunerConfiguration"),
@JsonSubTypes.Type(value = FCD1TunerConfiguration.class, name = "fcd1TunerConfiguration"),
@JsonSubTypes.Type(value = FCD2TunerConfiguration.class, name = "fcd2TunerConfiguration"),
@JsonSubTypes.Type(value = HackRFTunerConfiguration.class, name = "hackRFTunerConfiguration"),
@JsonSubTypes.Type(value = RecordingTunerConfiguration.class, name = "recordingTunerConfiguration"),
@JsonSubTypes.Type(value = R820TTunerConfiguration.class, name = "r820TTunerConfiguration"),
@JsonSubTypes.Type(value = R828DTunerConfiguration.class, name = "r828DTunerConfiguration"),
@JsonSubTypes.Type(value = RspTunerConfiguration.class, name = "rspTunerConfiguration"),
})
@JacksonXmlRootElement(localName = "tuner_configuration")

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,7 +24,9 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.r8x.r820t.R820TTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.r8x.r828d.R828DTunerConfiguration;
/**
* RTL2832 tuner configuration
@@ -32,12 +34,15 @@ import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerConfiguration;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = E4KTunerConfiguration.class, name = "e4KTunerConfiguration"),
@JsonSubTypes.Type(value = FC0013TunerConfiguration.class, name = "fc0013TunerConfiguration"),
@JsonSubTypes.Type(value = R820TTunerConfiguration.class, name = "r820TTunerConfiguration"),
@JsonSubTypes.Type(value = R828DTunerConfiguration.class, name = "r828DTunerConfiguration"),
})
@JacksonXmlRootElement(localName = "tuner_configuration")
public abstract class RTL2832TunerConfiguration extends TunerConfiguration
{
private RTL2832TunerController.SampleRate mSampleRate = RTL2832TunerController.SampleRate.RATE_2_400MHZ;
private boolean mBiasTEnabled = false;
/**
* Default constructor to support Jackson
@@ -61,4 +66,23 @@ public abstract class RTL2832TunerConfiguration extends TunerConfiguration
{
mSampleRate = sampleRate;
}
/**
* Sets the enabled state of the Bias-T
* @param enabled true to turn-on the bias-T
*/
public void setBiasT(boolean enabled)
{
mBiasTEnabled = enabled;
}
/**
* Indicates if the Bias-T is enabled.
* @return true if enabled.
*/
@JacksonXmlProperty(isAttribute = true, localName = "bias_t")
public boolean isBiasT()
{
return mBiasTEnabled;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -66,6 +66,7 @@ public class RTL2832TunerController extends USBTunerController
private Descriptor mDescriptor;
private EmbeddedTuner mEmbeddedTuner;
private long mTunedFrequency = 0;
private boolean mBiasTEnabled = false;
private ReentrantLock mLock = new ReentrantLock();
/**
@@ -174,7 +175,7 @@ public class RTL2832TunerController extends USBTunerController
try
{
writeRegister(Block.USB, Address.USB_SYSCTL.getAddress(), 0x09, 1);
writeRegister(Block.USB, Address.USB_SYSCTL, 0x09, 1);
}
catch(LibUsbException lue)
{
@@ -195,7 +196,7 @@ public class RTL2832TunerController extends USBTunerController
try
{
writeRegister(Block.USB, Address.USB_SYSCTL.getAddress(), 0x09, 1);
writeRegister(Block.USB, Address.USB_SYSCTL, 0x09, 1);
}
catch(LibUsbException lue)
{
@@ -238,7 +239,7 @@ public class RTL2832TunerController extends USBTunerController
if(tunerType == TunerType.UNKNOWN)
{
throw new SourceException("Unrecognized embedded tuner type for RTL-2832");
throw new SourceException("Unrecognized RTL-2832 embedded tuner type: " + tunerType);
}
mEmbeddedTuner = TunerFactory.getRtlEmbeddedTuner(tunerType, new ControllerAdapter(this));
@@ -299,6 +300,26 @@ public class RTL2832TunerController extends USBTunerController
return null;
}
/**
* Sets the enabled state of the bias-t
* @param enabled true to turn-on the bias-t or false to turn-off the bias-t.
*/
public void setBiasT(boolean enabled)
{
setGPIOOutput((byte)0x01);
setGPIOBit((byte)0x01, enabled);
mBiasTEnabled = enabled;
}
/**
* Indicates if the bias-t is enabled.
* @return true if enabled.
*/
public boolean isBiasT()
{
return mBiasTEnabled;
}
/**
* Overrides updates for measured frequency error so that the updates can also be applied to the
* frequency error correction manager for automatic PPM updating.
@@ -333,21 +354,28 @@ public class RTL2832TunerController extends USBTunerController
*/
public String getUniqueID()
{
if(mDescriptor != null && mDescriptor.hasSerial())
if(mDescriptor != null)
{
if(hasEmbeddedTuner())
if(mDescriptor.hasSerial())
{
return TunerClass.RTL2832 + "/" + getTunerType().getLabel() + " " + mDescriptor.getSerial();
if(hasEmbeddedTuner())
{
return TunerClass.RTL2832 + "/" + getTunerType().getLabel() + " " + mDescriptor.getSerial();
}
else
{
return TunerClass.RTL2832 + " " + mDescriptor.getSerial();
}
}
else
{
return TunerClass.RTL2832 + " " + mDescriptor.getSerial();
return "RTL-2832 USB Bus:" + mBus + " Port:" + mPortAddress;
}
}
else
{
int serial = (0xFF & getDeviceDescriptor().iSerialNumber());
return TunerClass.RTL2832.toString() + " " + serial;
return TunerClass.RTL2832 + " " + serial;
}
}
@@ -437,8 +465,8 @@ public class RTL2832TunerController extends USBTunerController
*/
public void resetUSBBuffer() throws LibUsbException
{
writeRegister(Block.USB, Address.USB_EPA_CTL.getAddress(), 0x1002, 2);
writeRegister(Block.USB, Address.USB_EPA_CTL.getAddress(), 0x0000, 2);
writeRegister(Block.USB, Address.USB_EPA_CTL, 0x1002, 2);
writeRegister(Block.USB, Address.USB_EPA_CTL, 0x0000, 2);
}
/**
@@ -448,13 +476,13 @@ public class RTL2832TunerController extends USBTunerController
public void initBaseband() throws LibUsbException
{
/* Initialize USB */
writeRegister(Block.USB, Address.USB_SYSCTL.getAddress(), 0x09, 1);
writeRegister(Block.USB, Address.USB_EPA_MAXPKT.getAddress(), 0x0002, 2);
writeRegister(Block.USB, Address.USB_EPA_CTL.getAddress(), 0x1002, 2);
writeRegister(Block.USB, Address.USB_SYSCTL, 0x09, 1);
writeRegister(Block.USB, Address.USB_EPA_MAXPKT, 0x0002, 2);
writeRegister(Block.USB, Address.USB_EPA_CTL, 0x1002, 2);
/* Power on demod */
writeRegister(Block.SYS, Address.DEMOD_CTL_1.getAddress(), 0x22, 1);
writeRegister(Block.SYS, Address.DEMOD_CTL.getAddress(), 0xE8, 1);
writeRegister(Block.SYS, Address.DEMOD_CTL_1, 0x22, 1);
writeRegister(Block.SYS, Address.DEMOD_CTL, 0xE8, 1);
/* Reset demod */
writeDemodRegister(Page.ONE, (short) 0x01, 0x14, 1); //Bit 3 = soft reset
@@ -513,7 +541,7 @@ public class RTL2832TunerController extends USBTunerController
*/
private void deinitBaseband() throws IllegalArgumentException, UsbDisconnectedException
{
writeRegister(Block.SYS, Address.DEMOD_CTL.getAddress(), 0x20, 1);
writeRegister(Block.SYS, Address.DEMOD_CTL, 0x20, 1);
}
/**
@@ -527,7 +555,7 @@ public class RTL2832TunerController extends USBTunerController
private void setGPIOBit(byte bitMask, boolean enabled) throws LibUsbException
{
//Get current register value
int value = readRegister(Block.SYS, Address.GPO.getAddress(), 1);
int value = readRegister(Block.SYS, Address.GPO, 1);
//Update the masked bits
if(enabled)
@@ -540,7 +568,7 @@ public class RTL2832TunerController extends USBTunerController
}
//Write the change back to the device
writeRegister(Block.SYS, Address.GPO.getAddress(), value, 1);
writeRegister(Block.SYS, Address.GPO, value, 1);
}
/**
@@ -553,16 +581,16 @@ public class RTL2832TunerController extends USBTunerController
private void setGPIOOutput(byte bitMask) throws LibUsbException
{
//Get current register value
int value = readRegister(Block.SYS, Address.GPD.getAddress(), 1);
int value = readRegister(Block.SYS, Address.GPD, 1);
//Mask the value and rewrite it
writeRegister(Block.SYS, Address.GPO.getAddress(), value & ~bitMask, 1);
writeRegister(Block.SYS, Address.GPO, value & ~bitMask, 1);
//Get current register value
value = readRegister(Block.SYS, Address.GPOE.getAddress(), 1);
value = readRegister(Block.SYS, Address.GPOE, 1);
//Mask the value and rewrite it
writeRegister(Block.SYS, Address.GPOE.getAddress(), value | bitMask, 1);
writeRegister(Block.SYS, Address.GPOE, value | bitMask, 1);
}
/**
@@ -572,10 +600,9 @@ public class RTL2832TunerController extends USBTunerController
*/
private void enableI2CRepeater(boolean enabled) throws LibUsbException
{
Page page = Page.ONE;
short address = 1;
int value = (enabled ? 0x18 : 0x10);
writeDemodRegister(page, address, value, 1);
writeDemodRegister(Page.ONE, address, value, 1);
}
/**
@@ -720,7 +747,7 @@ public class RTL2832TunerController extends USBTunerController
* @param length of value in bytes
* @throws LibUsbException on error
*/
private void writeRegister(Block block, short address, int value, int length) throws LibUsbException
private void writeRegister(Block block, Address address, int value, int length) throws LibUsbException
{
ByteBuffer buffer = ByteBuffer.allocateDirect(length);
buffer.order(ByteOrder.BIG_ENDIAN);
@@ -740,7 +767,7 @@ public class RTL2832TunerController extends USBTunerController
}
buffer.rewind();
write(address, block, buffer);
write(address.getAddress(), block, buffer);
}
/**
@@ -751,10 +778,10 @@ public class RTL2832TunerController extends USBTunerController
* @return value read
* @throws LibUsbException on error
*/
private int readRegister(Block block, short address, int length) throws LibUsbException
private int readRegister(Block block, Address address, int length) throws LibUsbException
{
ByteBuffer buffer = ByteBuffer.allocateDirect(2);
read(address, block, buffer);
read(address.getAddress(), block, buffer);
buffer.order(ByteOrder.LITTLE_ENDIAN);
if(length == 2)
@@ -869,11 +896,11 @@ public class RTL2832TunerController extends USBTunerController
if(type == TunerTypeCheck.FC2580)
{
return ((value & 0x7F) == type.getCheckValue());
return ((value & 0x7F) == (type.getCheckValue() & 0xFF));
}
else
{
return (value == type.getCheckValue());
return (value == (type.getCheckValue() & 0xFF));
}
}
catch(LibUsbException e)
@@ -973,7 +1000,7 @@ public class RTL2832TunerController extends USBTunerController
public void setSampleRateFrequencyCorrection(int ppm) throws SourceException
{
int offset = -ppm * TWO_TO_22_POWER / 1000000;
int offset = -ppm * TWO_TO_22_POWER / 1_000_000;
writeDemodRegister(Page.ONE, (short) 0x3F, (offset & 0xFF), 1);
writeDemodRegister(Page.ONE, (short) 0x3E, (Integer.rotateRight(offset, 8) & 0xFF), 1);
/* Test to retune controller to apply frequency correction */
@@ -983,7 +1010,7 @@ public class RTL2832TunerController extends USBTunerController
}
catch(Exception e)
{
throw new SourceException("couldn't set sample rate frequency correction", e);
throw new SourceException("Couldn't set sample rate frequency correction", e);
}
}
@@ -1027,7 +1054,7 @@ public class RTL2832TunerController extends USBTunerController
try
{
/* Tell the RTL-2832 to address the EEPROM */
writeRegister(Block.I2C, EEPROM_ADDRESS, (byte) offset, 1);
writeRegister(Block.I2C, Address.EEPROM, (byte) offset, 1);
}
catch(LibUsbException e)
{
@@ -1068,7 +1095,7 @@ public class RTL2832TunerController extends USBTunerController
}
int offsetAndValue = Integer.rotateLeft((0xFF & offset), 8) | (0xFF & value);
writeRegister(Block.I2C, EEPROM_ADDRESS, offsetAndValue, 2);
writeRegister(Block.I2C, Address.EEPROM, offsetAndValue, 2);
}
public enum Address
@@ -1093,7 +1120,8 @@ public class RTL2832TunerController extends USBTunerController
SYSINTE_1(0x3009),
SYSINTS_1(0x300A),
DEMOD_CTL_1(0x300B),
IR_SUSPEND(0x300C);
IR_SUSPEND(0x300C),
EEPROM(0xA0);
private int mAddress;
@@ -1314,6 +1342,14 @@ public class RTL2832TunerController extends USBTunerController
getLabels();
}
/**
* Indicates if this is a rtl-sdr.com V4 R828D dongle with notch filtering.
*/
public boolean isRtlSdrV4()
{
return "RTLSDRBlog".equals(getVendorLabel()) && "Blog V4".equals(getProductLabel());
}
public boolean isValid()
{
return mData[0] != (byte)0x0 && mData[1] != (byte)0x0;
@@ -1479,6 +1515,15 @@ public class RTL2832TunerController extends USBTunerController
return mController.isRunning();
}
/**
* Indicates if this is a rtl-sdr.com V4 dongle with support for notch filtering.
* @return true if this is a V4 R828D tuner.
*/
public boolean isV4Dongle()
{
return mController.getDescriptor().isRtlSdrV4();
}
/**
* Device handle for the tuner controller.
* @return

View File

@@ -41,6 +41,7 @@ import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JSeparator;
import javax.swing.JToggleButton;
import javax.swing.SpinnerNumberModel;
import javax.usb.UsbException;
@@ -52,6 +53,7 @@ public class E4KTunerEditor extends TunerEditor<RTL2832Tuner, E4KTunerConfigurat
private final static Logger mLog = LoggerFactory.getLogger(E4KTunerEditor.class);
private static final long serialVersionUID = 1L;
private JButton mTunerInfoButton;
private JToggleButton mBiasTButton;
private JComboBox<SampleRate> mSampleRateCombo;
private JComboBox<E4KGain> mMasterGainCombo;
private JComboBox<E4KMixerGain> mMixerGainCombo;
@@ -95,7 +97,8 @@ public class E4KTunerEditor extends TunerEditor<RTL2832Tuner, E4KTunerConfigurat
add(getTunerInfoButton());
add(new JLabel("Status:"));
add(getTunerStatusLabel(), "wrap");
add(getTunerStatusLabel());
add(getBiasTButton(), "wrap");
add(getButtonPanel(), "span,align left");
@@ -144,6 +147,8 @@ public class E4KTunerEditor extends TunerEditor<RTL2832Tuner, E4KTunerConfigurat
if(hasTuner())
{
getBiasTButton().setEnabled(true);
getBiasTButton().setSelected(getConfiguration().isBiasT());
getTunerInfoButton().setEnabled(true);
getSampleRateCombo().setEnabled(true);
getSampleRateCombo().setSelectedItem(getConfiguration().getSampleRate());
@@ -174,6 +179,8 @@ public class E4KTunerEditor extends TunerEditor<RTL2832Tuner, E4KTunerConfigurat
}
else
{
getBiasTButton().setEnabled(false);
getBiasTButton().setSelected(false);
getTunerInfoButton().setEnabled(false);
getSampleRateCombo().setEnabled(false);
getMasterGainCombo().setEnabled(false);
@@ -187,6 +194,28 @@ public class E4KTunerEditor extends TunerEditor<RTL2832Tuner, E4KTunerConfigurat
setLoading(false);
}
/**
* Bias-T toggle button
* @return
*/
private JToggleButton getBiasTButton()
{
if(mBiasTButton == null)
{
mBiasTButton = new JToggleButton("Bias-T");
mBiasTButton.setEnabled(false);
mBiasTButton.addActionListener(e -> {
if(!isLoading())
{
getTuner().getController().setBiasT(mBiasTButton.isSelected());
save();
}
});
}
return mBiasTButton;
}
private JComboBox getIfGainCombo()
{
if(mIfGainCombo == null)
@@ -459,7 +488,7 @@ public class E4KTunerEditor extends TunerEditor<RTL2832Tuner, E4KTunerConfigurat
double value = ((SpinnerNumberModel)getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue();
config.setFrequencyCorrection(value);
config.setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected());
config.setBiasT(getTuner().getController().isBiasT());
config.setSampleRate((SampleRate)getSampleRateCombo().getSelectedItem());
config.setMasterGain((E4KGain)getMasterGainCombo().getSelectedItem());
config.setMixerGain((E4KMixerGain)getMixerGainCombo().getSelectedItem());

View File

@@ -0,0 +1,842 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.rtl.fc0013;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.EmbeddedTuner;
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
import java.text.DecimalFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.LibUsbException;
/**
* Fitipower FC0013 Tuner
* <p>
* Ported from: https://github.com/osmocom/rtl-sdr/blob/master/src/tuner_fc0013.c
*
* Register: Original Contents / Shadow Contents - Comments
* 0x00: .. / 00 - Dummy register for 0-index
* 0x01: 05 / 09
* 0x02: 10 / 16
* 0x03: 00 / 00
* 0x04: 00 / 00
* 0x05: 0A / 17
* 0x06: 0A / 02 - Low Pass Filter Bandwidth
* 0x07: 00 / 2A - 0x2x setup for 28.8MHz crystal
* 0x08: B0 / FF - AGC clock divide by 256, AGC gain 1/256, Loop bw 1/8
* 0x09: 6E / 6E - Disable loop through, enable loop through: 0x6F
* 0x0A: B8 / B8 - Disable LO Test Buffer
* 0x0B: 82 / 82
* 0x0C: 7C / FC - Depending on AGC Up-Down mode, may need 0xF8
* 0x0D: 00 / 01 - AGC not forcing & LNA forcing, we may need 0x02
* 0x0E: 00 / 00
* 0x0F: 00 / 00
* 0x10: 00 / 00
* 0x11: 08 / 00
* 0x12: 00 / 00
* 0x13: 00 / 00
* 0x14: C0 / 50 - DVB-t=High Gain, UHF=middle gain (0x48), or low gain (0x40)
* 0x15: 01 / 01
*/
public class FC0013EmbeddedTuner extends EmbeddedTuner
{
private final static Logger mLog = LoggerFactory.getLogger(FC0013EmbeddedTuner.class);
private DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.000000");
private static final long MINIMUM_SUPPORTED_FREQUENCY = 13_500_000;
private static final long MAXIMUM_SUPPORTED_FREQUENCY = 1_907_999_890l;
private static final double USABLE_BANDWIDTH_PERCENT = 0.95;
private static final int DC_SPIKE_AVOID_BUFFER = 15000;
//Hardware I2C address
private static final byte I2C_ADDRESS = (byte) 0xC6;
//Hardware crystal oscillator frequency - used by the frequency divider
private static final int XTAL_FREQUENCY = 28_800_000;
private static final int XTAL_FREQUENCY_DIVIDED_BY_2 = XTAL_FREQUENCY / 2;
//Initial register settings to apply to the tuner
private static byte[] REGISTERS = {(byte) 0x00, (byte) 0x09, (byte) 0x16, (byte) 0x00, (byte) 0x00, (byte) 0x17,
(byte) 0x02, (byte) 0x2A, (byte) 0xFF, (byte) 0x6E, (byte) 0xB8, (byte) 0x82, (byte) 0xFE, (byte) 0x01,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x50, (byte) 0x01};
private long mTunedFrequency = MINIMUM_SUPPORTED_FREQUENCY;
/**
* Constructs an instance
*
* @param adapter for controlling the RTL2832
*/
public FC0013EmbeddedTuner(RTL2832TunerController.ControllerAdapter adapter)
{
super(adapter);
}
@Override
public TunerType getTunerType()
{
return TunerType.FITIPOWER_FC0013;
}
@Override
public long getMinimumFrequencySupported()
{
return MINIMUM_SUPPORTED_FREQUENCY;
}
@Override
public long getMaximumFrequencySupported()
{
return MAXIMUM_SUPPORTED_FREQUENCY;
}
@Override
public int getDcSpikeHalfBandwidth()
{
return DC_SPIKE_AVOID_BUFFER;
}
@Override
public double getUsableBandwidthPercent()
{
return USABLE_BANDWIDTH_PERCENT;
}
/**
* Applies the tuner configuration settings.
* @param tunerConfig containing settings to apply
* @throws SourceException if there is an error.
*/
@Override
public void apply(TunerConfiguration tunerConfig) throws SourceException
{
if(tunerConfig instanceof FC0013TunerConfiguration config)
{
try
{
setGain(config.getAGC(), config.getLnaGain());
}
catch(Exception e)
{
throw new SourceException("Error while applying tuner config", e);
}
}
else
{
throw new IllegalArgumentException("Unrecognized tuner config [" + tunerConfig.getClass() + "]");
}
}
/**
* Writes the value to the FC0013 register and optionally controls the I2C repeater
*
* @param register to write
* @param value to write
* @param controlI2CRepeater true to turn on the I2C repeater before and after the write operation. If this is
* set to false, you need to have previously enabled the I2C repeater before invoking this method call.
*/
private void write(Register register, byte value, boolean controlI2CRepeater)
{
getAdapter().writeI2CRegister(I2C_ADDRESS, register.byteValue(), value, controlI2CRepeater);
}
/**
* Writes the value to the FC0013 register and optionally controls the I2C repeater
*
* @param register to write
* @param value to write
* @param mask to use when reading the full register byte that has ones in the bits that should be untouched and
* zeros in the bits that should be cleared prior to applying (or'ing) the value into the register.
* @param controlI2CRepeater true to turn on the I2C repeater before and after the write operation. If this is
* set to false, you need to have previously enabled the I2C repeater before invoking this method call.
*/
public void writeMaskedRegister(Register register, byte value, byte mask, boolean controlI2CRepeater)
{
byte content = (byte) (readRegister(register, controlI2CRepeater) & 0xFF);
content &= mask;
content |= value;
write(register, content, controlI2CRepeater);
}
/**
* Writes the value to a field within an FC0013 register and optionally controls the I2C repeater
*
* @param field to write
* @param value to write
* @param controlI2CRepeater true to turn on the I2C repeater before and after the write operation. If this is
* set to false, you need to have previously enabled the I2C repeater before invoking this method call.
*/
private void write(Field field, byte value, boolean controlI2CRepeater)
{
writeMaskedRegister(field.getRegister(), value, field.getMask(), controlI2CRepeater);
}
/**
* Reads the FC0013 tuner register value.
*
* @param register to read
* @param controlI2CRepeater true to turn on the I2C repeater before and after the write operation. If this is
* set to false, you need to have previously enabled the I2C repeater before invoking this method call.
* @return register value.
*/
private int readRegister(Register register, boolean controlI2CRepeater)
{
return getAdapter().readI2CRegister(I2C_ADDRESS, register.byteValue(), controlI2CRepeater);
}
@Override
public void setSamplingMode(RTL2832TunerController.SampleMode mode) throws LibUsbException
{
//No-op
}
@Override
public void setSampleRateFilters(int sampleRate) throws SourceException
{
//No-op
}
/**
* Currently tuned frequency.
* @return frequency
* @throws SourceException never
*/
public long getTunedFrequency() throws SourceException
{
return mTunedFrequency;
}
/**
* Sets the frequency value to the tuner. Note: this is managed by the frequency controller along with PPM adjust.
* @param frequency in Hertz to apply to the tuner
* @throws SourceException if there is an error.
*/
@Override
public synchronized void setTunedFrequency(long frequency) throws SourceException
{
getAdapter().getLock().lock();
try
{
//Capture current I2C repeater state and enable if necessary.
boolean i2CEnabledState = getAdapter().isI2CRepeaterEnabled();
if(!i2CEnabledState)
{
getAdapter().enableI2CRepeater();
}
setVhfTracking(frequency);
setFilters(frequency);
FrequencyDivider divider = FrequencyDivider.fromFrequency(frequency);
boolean vcoSelect = ((double)frequency / (double)divider.getIntegralFrequencyMultiplier()) >= 212.5;
int integral = (int)(frequency / divider.getIntegralFrequencyMultiplier());
int pm = integral / 8; //pm is units of 8x integrals
pm = Math.max(pm, 11);
pm = Math.min(pm, 31);
int am = integral - (pm * 8); //am is units of 1x integrals
if(am < 2)
{
am += 8;
pm--;
}
am = Math.min(am, 15);
integral = pm * 8 + am;
int residual = (int)(frequency - (integral * divider.getIntegralFrequencyMultiplier()));
//fractional is units of 1/65536th of 2x integrals
int fractional = (int)Math.round(residual / divider.getFractionalFrequencyMultiplier());
if(pm < 11 || pm > 31 || am < 2 || am > 15 || fractional < 0 || fractional > 65535)
{
String message = "FC0013 - no valid PLL combination for frequency [" + frequency + "] using divider [" +
divider + "] pm [" + pm + "] am [" + am + "] fractional [" + fractional + "]";
mLog.error(message);
throw new SourceException(message);
}
setFrequencyValues(divider, pm, am, fractional, vcoSelect);
//Restore the I2C repeater to previous state
if(!i2CEnabledState)
{
getAdapter().disableI2CRepeater();
}
}
catch(LibUsbException e)
{
mLog.error("FC0013 tuner error while setting tuned frequency [" + frequency + "]", e);
}
finally
{
getAdapter().getLock().unlock();
}
mTunedFrequency = frequency;
}
/**
* Apply initial settings to the tuner
*/
protected void initTuner()
{
getAdapter().enableI2CRepeater();
boolean i2CRepeaterControl = false;
//Write initial register settings to the tuner
for(int x = 1; x < REGISTERS.length; x++)
{
write(Register.values()[x], REGISTERS[x], i2CRepeaterControl);
}
write(Field.R0D_AGC, (byte)0x08, false);
write(Register.R13, (byte)0x0A, false);
getAdapter().disableI2CRepeater();
}
/**
* Applies the frequency settings and calibrates the PLL. Note: I2C repeater must be enabled before invoking.
* @param divider to use
* @param pm units of 8x integrals
* @param am units of 1x integrals
* @param fractional value.
* @param vcoSelect to enable (true) or disable (false)
* @throws SourceException if the settings are invalid or the PLL can't be calibrated with the settings.
* @return true if the frequency settings were applied successfully.
*/
private boolean setFrequencyValues(FrequencyDivider divider, int pm, int am, int fractional, boolean vcoSelect)
throws SourceException
{
if(pm < 11 || pm > 31)
{
throw new IllegalArgumentException("PM value [" + pm + "] must be in range 11-31");
}
if(am < 2 || am > 15)
{
throw new IllegalArgumentException("AM value [" + am + "] must be in range 2-15");
}
if(fractional < 0 || fractional > 65535)
{
throw new IllegalArgumentException("Fractional value [" + fractional + "] must be in range 0-65,535");
}
double exactFrequency = divider.calculate(pm, am, fractional);
long frequency = (long)exactFrequency;
byte register5 = divider.getRegister5();
register5 |= 0x07; //modified for Realtek demod
byte register6 = divider.getRegister6();
if(vcoSelect)
{
register6 |= 0x08;
}
//Write the integral units to the PLL
write(Register.R01, (byte)(am & 0xFF), false);
write(Register.R02, (byte)(pm & 0xFF), false);
//Write the fractional units to the PLL
write(Register.R03, (byte)((fractional >> 8) & 0xFF), false);
write(Register.R04, (byte)(fractional & 0xFF), false);
//Set the frequency divider, bandwidth, and enable the clock output
write(Register.R05, register5, false);
write(Register.R06, register6, false);
//Whatever register 11 does ... seems like it's a gain thing.
int tmp = readRegister(Register.R11, false);
if(divider == FrequencyDivider.D64)
{
write(Register.R11, (byte)(tmp | 0x04), false);
}
else
{
write(Register.R11, (byte)(tmp & 0xFB), false);
}
//Perform VCO Calibration
write(Register.R0E, (byte)0x80, false);
write(Register.R0E, (byte)0x00, false);
write(Register.R0E, (byte)0x00, false);
int calibration = readRegister(Register.R0E, false) & 0x3F;
boolean recalibrateRequired = false;
if(vcoSelect && calibration > 0x3C)
{
register6 &= ~0x08;
recalibrateRequired = true;
}
else if(!vcoSelect && calibration < 0x02)
{
register6 |= 0x08;
recalibrateRequired = true;
}
if(recalibrateRequired)
{
write(Register.R06, register6, false);
write(Register.R0E, (byte)0x80, false);
write(Register.R0E, (byte)0x00, false);
write(Register.R0E, (byte)0x00, false);
calibration = readRegister(Register.R0E, false) & 0x3F;
if((!vcoSelect & calibration < 0x02) || (vcoSelect & calibration > 0x3C))
{
String msg = "Unable to tune frequency [" + fractional + "] PLL calibration [" + Integer.toHexString(calibration).toUpperCase() + "] out of limits [02-3C]";
mLog.error(msg);
throw new SourceException(msg);
}
}
return true;
}
/**
* Sets the tuner filters according to the tuned frequency. Note: I2C repeater must be enabled before invoking.
* @param frequency of the tuner
*/
private void setFilters(long frequency)
{
byte vhf = (byte)0x00; //default to disabled
byte uhf = (byte)0x00; //default to disabled
if(frequency < 300_000_000)
{
vhf = (byte)0x10; //enable
}
else if(frequency < 862_000_000)
{
uhf = (byte)0x40;
}
else
{
//The original source code seems to target a GPS filter, but it's using the same uhf filter setting.
uhf = (byte)0x40;
}
write(Field.R07_VHF_FILTER, vhf, false);
write(Field.R14_UHF_FILTER, uhf, false);
}
/**
* Sets VHF tracking according to the tuned frequency. Note: I2C repeater must be enabled before invoking.
* @param frequency for the tuner
*/
private void setVhfTracking(long frequency)
{
int tmp = readRegister(Register.R1D, false);
//Clear the middle 3x bits that we're going to set and leave the remaining bits untouched.
tmp &= 0xE3;
if(frequency <= 177_500_000)
{
tmp |= 0x1C; //VHF Track 7
}
else if(frequency <= 184_500_000)
{
tmp |= 0x18; //VHF Track 6
}
else if(frequency <= 191_500_000)
{
tmp |= 0x14; //VHF Track 5
}
else if(frequency <= 198_500_000)
{
tmp |= 0x10; //VHF Track 4
}
else if(frequency <= 205_500_000)
{
tmp |= 0x0C; //VHF Track 3
}
else if(frequency <= 219_500_000)
{
tmp |= 0x08; //VHF Track 2
}
else if(frequency <= 300_000_000)
{
tmp |= 0x04; //VHF Track 1
}
else
{
tmp |= 0x1C; //UHF & GPS
}
write(Register.R1D, (byte) tmp, false);
}
/**
* Sets the gain for the tuner. Note: if agc=true, the manual gain setting is ignored.
* @param agc automatic gain control.
* @param manualGain setting.
*/
public void setGain(boolean agc, LNAGain manualGain)
{
getAdapter().getLock().lock();
try
{
boolean repeaterState = getAdapter().isI2CRepeaterEnabled();
if(!repeaterState)
{
getAdapter().enableI2CRepeater();
}
if(agc)
{
write(Field.R0D_AGC, (byte)0x00, false);
write(Register.R13, (byte)0x0A, false);
}
else
{
write(Field.R14_LNA_GAIN, manualGain.byteValue(), false);
write(Field.R0D_AGC, (byte)0x08, false);
write(Register.R13, (byte)0x0A, false);
}
if(!repeaterState)
{
getAdapter().disableI2CRepeater();
}
}
finally
{
getAdapter().getLock().unlock();
}
}
/**
* Enumeration of FC0013 registers. Provides convenience method for accessing the entry's ordinal position as a
* byte value for reading/writing.
*/
private enum Register
{
R00, R01, R02, R03, R04, R05, R06, R07, R08, R09, R0A, R0B, R0C, R0D, R0E, R0F, R10, R11, R12, R13, R14, R15,
R16, R17, R18, R19, R1A, R1B, R1C, R1D;
/**
* Returns the ordinal position of the entry within the enumeration as a byte value.
*/
public byte byteValue()
{
return (byte) ordinal();
}
}
/**
* Masked tuner register fields enumeration
*/
private enum Field
{
R06_BANDWIDTH(Register.R06, 0xC0),
R07_VHF_FILTER(Register.R07, 0xEF),
R0D_AGC(Register.R0D, 0xF7),
R14_LNA_GAIN(Register.R14, 0xE0),
R14_UHF_FILTER(Register.R14, 0x1F);
private Register mRegister;
private byte mMask;
/**
* Constructs an instance
* @param register for the field
* @param mask for the field
*/
Field(Register register, int mask)
{
mRegister = register;
mMask = (byte) mask;
}
/**
* Register for the field.
* @return register
*/
public Register getRegister()
{
return mRegister;
}
/**
* Bit field mask for the register that masks out the target field bits prior to applying a value.
* @return mask value.
*/
public byte getMask()
{
return mMask;
}
}
/**
* LNA gain values enumeration
*/
public enum LNAGain
{
G00("0 LOW", 0x02),
G01("1", 0x03),
G02("2", 0x05),
G03("3", 0x04),
G04("4", 0x00),
G05("5", 0x07),
G06("6", 0x01),
G07("7", 0x06),
G08("8", 0x0F),
G09("9", 0x0E),
G10("10", 0x0D),
G11("11", 0x0C),
G12("12", 0x0B),
G13("13", 0x0A),
G14("14", 0x09),
G15("15", 0x08),
G16("16", 0x17),
G17("17", 0x16),
G18("18", 0x15),
G19("19", 0x14),
G20("20", 0x13),
G21("21", 0x12),
G22("22", 0x11),
G23("23 HIGH", 0x10);
private String mLabel;
private byte mValue;
/**
* Constructs an instance
*
* @param label to show to the user
* @param value to apply to the register
*/
LNAGain(String label, int value)
{
mLabel = label;
mValue = (byte) value;
}
/**
* Byte value for the gain setting.
*
* @return byte value.
*/
public byte byteValue()
{
return mValue;
}
@Override
public String toString()
{
return mLabel;
}
}
/**
* SDM PLL Frequency Divider and Supported Frequency Ranges
*
* Note: each divider frequency range is overlapping with other divider frequency range(s) and also within each
* frequency divider there are overlapping frequency range across each PM value. Each unit of PM is 8x the integral
* and each unit of AM is 1x the integral. You can specify a range of 2-15 units of AM which causes overlap when
* each unit of PM represents 8x units of AM.
*
* So, for a requested frequency, there are likely multiple combinations of Divider/PM/AM/Fractional that will
* tune the frequency, each with varying degrees of correctness.
*
* For each divider range, minimum and maximum frequency is calculated as:
* Min: PM = 11, AM = 2, FRACTIONAL = 0
* Max: PM = 31, AM = 15, FRACTIONAL = 65,535, or the highest setting that can be calibrated by the VCO.
*
* integral = XTAL_FREQ / 2 / divider
*
* Where: (integral * ((PM * 8) + AM)) + (2 * integral / 65,536 * fractional)
*
* Minimum tunable frequency step size is (2 * integral / 65,536).
*/
public enum FrequencyDivider
{
D96(96, true, 0x82, 13_500_000, 39_749_997),
D64(64, false, 0x02, 20_250_000, 59_624_996), //Special one, why?
D48(48, true, 0x42, 27_000_000, 79_499_995),
D32(32, false, 0x82, 40_500_000, 119_249_993),
D24(24, true, 0x22, 54_000_000, 158_999_990),
D16(16, false, 0x42, 81_000_000, 238_499_986),
D12(12, true, 0x12, 108_000_000, 317_999_981),
D08(8, false, 0x22, 162_000_000, 476_999_972),
D06(6, true, 0x0A, 235_200_000, 635_999_963),
D04(4, false, 0x12, 514_900_000, 953_999_945),
D02(2, false, 0x0A, 648_000_000, 1_907_999_890);
private int mDivider;
private boolean mIs3xMode;
private int mRegister5;
private long mMinimumFrequency;
private long mMaximumFrequency;
/**
* Constructs an instance
* @param divider quantity
* @param is3xMode indicates 3x divider (true) or 2x divider (false)
* @param register5 setting
* @param minimumFrequency supported by the divider
* @param maximumFrequency supported by the divider
*/
FrequencyDivider(int divider, boolean is3xMode, int register5, long minimumFrequency, long maximumFrequency)
{
mDivider = divider;
mIs3xMode = is3xMode;
mRegister5 = register5;
mMinimumFrequency = minimumFrequency;
mMaximumFrequency = maximumFrequency;
}
/**
* Register 5 frequency multiplier setting
*/
public byte getRegister5()
{
return (byte)(mRegister5 & 0xFF);
}
/**
* Register 6 multiplier mode setting (2x or 3x)
*/
public byte getRegister6()
{
//0xAx OR Enable clock (0x20) OR bandwidth set to 6 MHz (0x80).
return is3xMode() ? (byte)0xA0 : (byte)0xA2;
}
/**
* Calculates the frequency for this frequency divider.
* @param pm - 8x integral units
* @param am - 1x integral units
* @param fractional integral units
* @param vcoSelect to indicate if the frequency requires VCO select (ie AM + 2)
* @return frequency
*/
public double getFrequency(int pm, int am, int fractional, boolean vcoSelect)
{
if(vcoSelect)
{
return calculate(pm, am, fractional);
}
else
{
return calculate(pm, am - 2, fractional);
}
}
/**
* Indicates if the frequency requires VCO select for this divider.
* @param frequency to evaluate
* @return true if vco select
*/
public boolean isVcoSelect(double frequency)
{
return (frequency * getDivider()) > 3_060_000_000l;
}
private double calculate(int pm, int am, int fractional)
{
return (((8l * pm) + am) * getIntegralFrequencyMultiplier()) + (getFractionalFrequencyMultiplier() * fractional);
}
/**
* Integral unit of frequency multiplier supported by the PLL for the specified frequency divider.
* @return integral frequency multiplier.
*/
public int getIntegralFrequencyMultiplier()
{
return XTAL_FREQUENCY_DIVIDED_BY_2 / mDivider;
}
/**
* Fractional (1/65536th) unit of frequency of 2x integrals that is supported by the 16-bit fractional PLL.
* @return fractional frequency multiplier
*/
public double getFractionalFrequencyMultiplier()
{
return XTAL_FREQUENCY / mDivider / 65_536d;
}
/**
* Indicates if this divider is mode 2x (false) or mode 3x (true) as a divider of the base crystal frequency.
* @return true if 3x mode.
*/
public boolean is3xMode()
{
return mIs3xMode;
}
/**
* Divider value (of the base crystal frequency).
* @return divider
*/
public int getDivider()
{
return mDivider;
}
/**
* Minimum frequency supported by this divider.
* @return minimum frequency (Hz).
*/
public long getMinimumFrequency()
{
return mMinimumFrequency;
}
/**
* Maximum frequency supported by this divider.
* @return maximum frequency (Hz).
*/
public long getMaximumFrequency()
{
return mMaximumFrequency;
}
/**
* Indicates if the frequency is contained by the min/max values of the current frequency divider entry.
* @param frequency to test
* @return true if contained.
*/
public boolean contains(long frequency)
{
return mMinimumFrequency <= frequency && frequency <= mMaximumFrequency;
}
/**
* Returns the matching frequency divider for the specified frequency, or returns divider 16x, if the frequency
* is outside any supported frequency ranges.
*
* @param frequency - desired frequency
* @return - FrequencyDivider to use for the specified frequency
*/
public static FrequencyDivider fromFrequency(long frequency)
{
for(FrequencyDivider divider : FrequencyDivider.values())
{
if(divider.contains(frequency))
{
return divider;
}
}
return FrequencyDivider.D16;
}
}
}

View File

@@ -0,0 +1,95 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.rtl.fc0013;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerConfiguration;
/**
* RTL-2832 with embedded FC0013 tuner configuration
*/
public class FC0013TunerConfiguration extends RTL2832TunerConfiguration
{
private FC0013EmbeddedTuner.LNAGain mLnaGain = FC0013EmbeddedTuner.LNAGain.G10;
private boolean mAgc = false;
/**
* Default constructor for JAXB
*/
public FC0013TunerConfiguration()
{
}
@JsonIgnore
@Override
public TunerType getTunerType()
{
return TunerType.FITIPOWER_FC0013;
}
/**
* Constructs an instance
*
* @param uniqueID for the tuner
*/
public FC0013TunerConfiguration(String uniqueID)
{
super(uniqueID);
}
/**
* LNA gain value.
* @return value
*/
@JacksonXmlProperty(isAttribute = true, localName = "lna_gain")
public FC0013EmbeddedTuner.LNAGain getLnaGain()
{
return mLnaGain;
}
/**
* Sets the LNA gain value.
* @param lnaGain value.
*/
public void setLnaGain(FC0013EmbeddedTuner.LNAGain lnaGain)
{
mLnaGain = lnaGain;
}
/**
* Automatic Gain Control (AGC) state
* @return AGC enabled state
*/
@JacksonXmlProperty(isAttribute = true, localName = "agc")
public boolean getAGC()
{
return mAgc;
}
/**
* Sets the AGC
* @param enabled state of the AGC
*/
public void setAGC(boolean enabled)
{
mAgc = enabled;
}
}

View File

@@ -0,0 +1,404 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.rtl.fc0013;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.source.tuner.rtl.RTL2832Tuner;
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController.SampleRate;
import io.github.dsheirer.source.tuner.ui.TunerEditor;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.LibUsbException;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JToggleButton;
import javax.swing.SpinnerNumberModel;
/**
* FC0013 Tuner Editor
*/
public class FC0013TunerEditor extends TunerEditor<RTL2832Tuner, FC0013TunerConfiguration>
{
private final static Logger mLog = LoggerFactory.getLogger(FC0013TunerEditor.class);
private static final long serialVersionUID = 1L;
private static final FC0013EmbeddedTuner.LNAGain DEFAULT_LNA_GAIN = FC0013EmbeddedTuner.LNAGain.G14;
private JButton mTunerInfoButton;
private JToggleButton mBiasTButton;
private JComboBox<SampleRate> mSampleRateCombo;
private JToggleButton mAgcToggleButton;
private JComboBox<FC0013EmbeddedTuner.LNAGain> mLNAGainCombo;
/**
* Constructs an instance
* @param userPreferences for wide-band recordings
* @param tunerManager for saving configurations
* @param discoveredTuner to edit
*/
public FC0013TunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
{
super(userPreferences, tunerManager, discoveredTuner);
init();
tunerStatusUpdated();
}
/**
* Access the FC0013 embedded tuner
* @return tuner if there is a tuner, or null otherwise
*/
private FC0013EmbeddedTuner getEmbeddedTuner()
{
if(hasTuner())
{
return (FC0013EmbeddedTuner) getTuner().getController().getEmbeddedTuner();
}
return null;
}
private String getLogPrefix()
{
return getEmbeddedTuner().getTunerType().getLabel() + " Tuner Controller - ";
}
private void init()
{
setLayout(new MigLayout("fill,wrap 3", "[right][grow,fill][fill]",
"[][][][][][][][][][][][][][][][grow]"));
add(new JLabel("Tuner:"));
add(getTunerIdLabel());
add(getTunerInfoButton());
add(new JLabel("Status:"));
add(getTunerStatusLabel());
add(getBiasTButton(), "wrap");
add(getButtonPanel(), "span,align left");
add(new JSeparator(), "span,growx,push");
add(new JLabel("Frequency (MHz):"));
add(getFrequencyPanel(), "wrap");
add(new JLabel("Sample Rate:"));
add(getSampleRateCombo(), "wrap");
add(new JSeparator(), "span,growx,push");
JPanel gainPanel = new JPanel();
gainPanel.add(new JLabel("Gain"));
gainPanel.add(getAgcToggleButton());
gainPanel.add(new JLabel("LNA:"));
add(gainPanel);
add(getLNAGainCombo(), "wrap");
}
@Override
protected void tunerStatusUpdated()
{
setLoading(true);
if(hasTuner())
{
getTunerIdLabel().setText(getTuner().getPreferredName());
}
else
{
getTunerIdLabel().setText(getDiscoveredTuner().getId());
}
String status = getDiscoveredTuner().getTunerStatus().toString();
if(getDiscoveredTuner().hasErrorMessage())
{
status += " - " + getDiscoveredTuner().getErrorMessage();
}
getTunerStatusLabel().setText(status);
getButtonPanel().updateControls();
getFrequencyPanel().updateControls();
if(hasTuner())
{
getBiasTButton().setEnabled(true);
getBiasTButton().setSelected(getConfiguration().isBiasT());
getTunerInfoButton().setEnabled(true);
getSampleRateCombo().setEnabled(true);
getSampleRateCombo().setSelectedItem(getConfiguration().getSampleRate());
getAgcToggleButton().setEnabled(true);
getAgcToggleButton().setSelected(getConfiguration().getAGC());
getLNAGainCombo().setEnabled(!getConfiguration().getAGC());
getLNAGainCombo().setSelectedItem(getConfiguration().getLnaGain());
}
else
{
getBiasTButton().setEnabled(false);
getBiasTButton().setSelected(false);
getTunerInfoButton().setEnabled(false);
getSampleRateCombo().setEnabled(false);
getAgcToggleButton().setEnabled(false);
getLNAGainCombo().setEnabled(false);
}
updateSampleRateToolTip();
setLoading(false);
}
/**
* Bias-T toggle button
* @return bias-t button
*/
private JToggleButton getBiasTButton()
{
if(mBiasTButton == null)
{
mBiasTButton = new JToggleButton("Bias-T");
mBiasTButton.setEnabled(false);
mBiasTButton.addActionListener(e -> {
if(!isLoading())
{
getTuner().getController().setBiasT(mBiasTButton.isSelected());
save();
}
});
}
return mBiasTButton;
}
/**
* Hyperlink button that provides tuner information
*/
private JButton getTunerInfoButton()
{
if(mTunerInfoButton == null)
{
mTunerInfoButton = new JButton("Info");
mTunerInfoButton.setEnabled(false);
mTunerInfoButton.addActionListener(e -> JOptionPane.showMessageDialog(FC0013TunerEditor.this,
getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE));
}
return mTunerInfoButton;
}
private JComboBox getLNAGainCombo()
{
if(mLNAGainCombo == null)
{
mLNAGainCombo = new JComboBox<>(FC0013EmbeddedTuner.LNAGain.values());
mLNAGainCombo.setEnabled(false);
mLNAGainCombo.addActionListener(arg0 ->
{
if(!isLoading())
{
try
{
FC0013EmbeddedTuner.LNAGain lnaGain = (FC0013EmbeddedTuner.LNAGain) mLNAGainCombo.getSelectedItem();
if(lnaGain == null)
{
lnaGain = DEFAULT_LNA_GAIN;
}
if(mLNAGainCombo.isEnabled())
{
getEmbeddedTuner().setGain(getAgcToggleButton().isSelected(), lnaGain);
}
save();
}
catch(Exception e)
{
JOptionPane.showMessageDialog(FC0013TunerEditor.this, getLogPrefix() +
"couldn't apply the LNA gain setting - " + e.getLocalizedMessage());
mLog.error(getLogPrefix() + "couldn't apply LNA gain setting - ", e);
}
}
});
mLNAGainCombo.setToolTipText("<html>LNA Gain. Set master gain to <b>MANUAL</b> to enable adjustment</html>");
}
return mLNAGainCombo;
}
private JComboBox getSampleRateCombo()
{
if(mSampleRateCombo == null)
{
mSampleRateCombo = new JComboBox<>(SampleRate.values());
mSampleRateCombo.setEnabled(false);
mSampleRateCombo.addActionListener(e ->
{
if(!isLoading())
{
SampleRate sampleRate = (SampleRate) mSampleRateCombo.getSelectedItem();
try
{
getTuner().getController().setSampleRate(sampleRate);
save();
}
catch(SourceException | LibUsbException eSampleRate)
{
JOptionPane.showMessageDialog(FC0013TunerEditor.this,
getLogPrefix() + "couldn't apply the sample rate setting [" +
sampleRate.getLabel() + "] " + eSampleRate.getLocalizedMessage());
mLog.error(getLogPrefix() + "couldn't apply sample rate setting [" + sampleRate.getLabel() +
"]", eSampleRate);
}
}
});
}
return mSampleRateCombo;
}
private JToggleButton getAgcToggleButton()
{
if(mAgcToggleButton == null)
{
mAgcToggleButton = new JToggleButton("AGC");
mAgcToggleButton.setEnabled(false);
mAgcToggleButton.addActionListener(arg0 ->
{
if(!isLoading())
{
try
{
boolean agc = getAgcToggleButton().isSelected();
FC0013EmbeddedTuner.LNAGain lnaGain = (FC0013EmbeddedTuner.LNAGain)getLNAGainCombo().getSelectedItem();
getEmbeddedTuner().setGain(agc, lnaGain);
getLNAGainCombo().setEnabled(!agc);
save();
}
catch(Exception e)
{
JOptionPane.showMessageDialog(FC0013TunerEditor.this, getLogPrefix() +
"couldn't set AGC" + e.getLocalizedMessage());
mLog.error(getLogPrefix() + "couldn't set AGC", e);
}
}
});
mAgcToggleButton.setToolTipText("<html>Automatic Gain Control (AGC). </html>");
}
return mAgcToggleButton;
}
/**
* Updates the sample rate tooltip according to the tuner controller's lock state.
*/
private void updateSampleRateToolTip()
{
if(hasTuner() && getTuner().getTunerController().isLockedSampleRate())
{
getSampleRateCombo().setToolTipText("Sample Rate is locked. Disable decoding channels to unlock.");
}
else if(hasTuner())
{
getSampleRateCombo().setToolTipText("Select a sample rate for the tuner");
}
else
{
getSampleRateCombo().setToolTipText("No tuner available");
}
}
@Override
public void setTunerLockState(boolean locked)
{
getFrequencyPanel().updateControls();
getSampleRateCombo().setEnabled(!locked);
updateSampleRateToolTip();
}
private String getTunerInfo()
{
StringBuilder sb = new StringBuilder();
RTL2832TunerController.Descriptor descriptor = getTuner().getController().getDescriptor();
sb.append("<html><h3>RTL-2832 with " + getEmbeddedTuner().getTunerType().getLabel() + " Tuner</h3>");
if(descriptor == null)
{
sb.append("No EEPROM Descriptor Available");
}
else
{
sb.append("<b>USB ID: </b>");
sb.append(descriptor.getVendorID());
sb.append(":");
sb.append(descriptor.getProductID());
sb.append("<br>");
sb.append("<b>Vendor: </b>");
sb.append(descriptor.getVendorLabel());
sb.append("<br>");
sb.append("<b>Product: </b>");
sb.append(descriptor.getProductLabel());
sb.append("<br>");
sb.append("<b>Serial: </b>");
sb.append(descriptor.getSerial());
sb.append("<br>");
sb.append("<b>IR Enabled: </b>");
sb.append(descriptor.irEnabled());
sb.append("<br>");
sb.append("<b>Remote Wake: </b>");
sb.append(descriptor.remoteWakeupEnabled());
sb.append("<br>");
}
return sb.toString();
}
@Override
public void save()
{
if(hasConfiguration() && !isLoading())
{
FC0013TunerConfiguration config = getConfiguration();
config.setBiasT(getTuner().getController().isBiasT());
config.setFrequency(getFrequencyControl().getFrequency());
double value = ((SpinnerNumberModel)getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue();
config.setFrequencyCorrection(value);
config.setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected());
config.setSampleRate((SampleRate)getSampleRateCombo().getSelectedItem());
config.setAGC(getAgcToggleButton().isSelected());
FC0013EmbeddedTuner.LNAGain lnaGain = (FC0013EmbeddedTuner.LNAGain)getLNAGainCombo().getSelectedItem();
config.setLnaGain(lnaGain);
saveConfiguration();
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -16,87 +16,78 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.rtl.r820t;
package io.github.dsheirer.source.tuner.rtl.r8x;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerConfiguration;
/**
* RTL-2832 with embedded R820T tuner configuration
* RTL-2832 with embedded R8xxx tuner configuration
*/
public class R820TTunerConfiguration extends RTL2832TunerConfiguration
public abstract class R8xTunerConfiguration extends RTL2832TunerConfiguration
{
private R820TEmbeddedTuner.R820TGain mMasterGain = R820TEmbeddedTuner.R820TGain.GAIN_327;
private R820TEmbeddedTuner.R820TMixerGain mMixerGain = R820TEmbeddedTuner.R820TMixerGain.GAIN_105;
private R820TEmbeddedTuner.R820TLNAGain mLNAGain = R820TEmbeddedTuner.R820TLNAGain.GAIN_222;
private R820TEmbeddedTuner.R820TVGAGain mVGAGain = R820TEmbeddedTuner.R820TVGAGain.GAIN_210;
private R8xEmbeddedTuner.MasterGain mMasterMasterGain = R8xEmbeddedTuner.MasterGain.GAIN_327;
private R8xEmbeddedTuner.MixerGain mMixerGain = R8xEmbeddedTuner.MixerGain.GAIN_105;
private R8xEmbeddedTuner.LNAGain mLNAGain = R8xEmbeddedTuner.LNAGain.GAIN_222;
private R8xEmbeddedTuner.VGAGain mVGAGain = R8xEmbeddedTuner.VGAGain.GAIN_210;
/**
* Default constructor for JAXB
*/
public R820TTunerConfiguration()
public R8xTunerConfiguration()
{
}
@JsonIgnore
@Override
public TunerType getTunerType()
{
return TunerType.RAFAELMICRO_R820T;
}
/**
* Constructs an instance
*
* @param uniqueID for the tuner
*/
public R820TTunerConfiguration(String uniqueID)
public R8xTunerConfiguration(String uniqueID)
{
super(uniqueID);
}
@JacksonXmlProperty(isAttribute = true, localName = "master_gain")
public R820TEmbeddedTuner.R820TGain getMasterGain()
public R8xEmbeddedTuner.MasterGain getMasterGain()
{
return mMasterGain;
return mMasterMasterGain;
}
public void setMasterGain(R820TEmbeddedTuner.R820TGain gain)
public void setMasterGain(R8xEmbeddedTuner.MasterGain masterGain)
{
mMasterGain = gain;
mMasterMasterGain = masterGain;
}
@JacksonXmlProperty(isAttribute = true, localName = "mixer_gain")
public R820TEmbeddedTuner.R820TMixerGain getMixerGain()
public R8xEmbeddedTuner.MixerGain getMixerGain()
{
return mMixerGain;
}
public void setMixerGain(R820TEmbeddedTuner.R820TMixerGain mixerGain)
public void setMixerGain(R8xEmbeddedTuner.MixerGain mixerGain)
{
mMixerGain = mixerGain;
}
@JacksonXmlProperty(isAttribute = true, localName = "lna_gain")
public R820TEmbeddedTuner.R820TLNAGain getLNAGain()
public R8xEmbeddedTuner.LNAGain getLNAGain()
{
return mLNAGain;
}
public void setLNAGain(R820TEmbeddedTuner.R820TLNAGain lnaGain)
public void setLNAGain(R8xEmbeddedTuner.LNAGain lnaGain)
{
mLNAGain = lnaGain;
}
@JacksonXmlProperty(isAttribute = true, localName = "vga_gain")
public R820TEmbeddedTuner.R820TVGAGain getVGAGain()
public R8xEmbeddedTuner.VGAGain getVGAGain()
{
return mVGAGain;
}
public void setVGAGain(R820TEmbeddedTuner.R820TVGAGain vgaGain)
public void setVGAGain(R8xEmbeddedTuner.VGAGain vgaGain)
{
mVGAGain = vgaGain;
}

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.rtl.r820t;
package io.github.dsheirer.source.tuner.rtl.r8x;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.source.SourceException;
@@ -25,10 +25,6 @@ import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.source.tuner.rtl.RTL2832Tuner;
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController.SampleRate;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TEmbeddedTuner.R820TGain;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TEmbeddedTuner.R820TLNAGain;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TEmbeddedTuner.R820TMixerGain;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TEmbeddedTuner.R820TVGAGain;
import io.github.dsheirer.source.tuner.ui.TunerEditor;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
@@ -40,20 +36,25 @@ import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JSeparator;
import javax.swing.JToggleButton;
import javax.swing.SpinnerNumberModel;
import javax.usb.UsbException;
public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfiguration>
/**
* R8xxx Tuner Editor
*/
public class R8xTunerEditor extends TunerEditor<RTL2832Tuner, R8xTunerConfiguration>
{
private final static Logger mLog = LoggerFactory.getLogger(R820TTunerEditor.class);
private final static Logger mLog = LoggerFactory.getLogger(R8xTunerEditor.class);
private static final long serialVersionUID = 1L;
private static final R820TGain DEFAULT_GAIN = R820TGain.GAIN_279;
private static final R8xEmbeddedTuner.MasterGain DEFAULT_GAIN = R8xEmbeddedTuner.MasterGain.GAIN_279;
private JButton mTunerInfoButton;
private JToggleButton mBiasTButton;
private JComboBox<SampleRate> mSampleRateCombo;
private JComboBox<R820TGain> mMasterGainCombo;
private JComboBox<R820TMixerGain> mMixerGainCombo;
private JComboBox<R820TLNAGain> mLNAGainCombo;
private JComboBox<R820TVGAGain> mVGAGainCombo;
private JComboBox<R8xEmbeddedTuner.MasterGain> mMasterGainCombo;
private JComboBox<R8xEmbeddedTuner.MixerGain> mMixerGainCombo;
private JComboBox<R8xEmbeddedTuner.LNAGain> mLNAGainCombo;
private JComboBox<R8xEmbeddedTuner.VGAGain> mVGAGainCombo;
/**
* Constructs an instance
@@ -61,7 +62,7 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
* @param tunerManager for saving configurations
* @param discoveredTuner to edit
*/
public R820TTunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
public R8xTunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
{
super(userPreferences, tunerManager, discoveredTuner);
init();
@@ -69,19 +70,24 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
}
/**
* Access the R820T embedded tuner
* @return R820T tuner if there is a tuner, or null otherwise
* Access the R8xxx embedded tuner
* @return R8xxx tuner if there is a tuner, or null otherwise
*/
private R820TEmbeddedTuner getEmbeddedTuner()
private R8xEmbeddedTuner getEmbeddedTuner()
{
if(hasTuner())
{
return (R820TEmbeddedTuner)getTuner().getController().getEmbeddedTuner();
return (R8xEmbeddedTuner) getTuner().getController().getEmbeddedTuner();
}
return null;
}
private String getLogPrefix()
{
return getEmbeddedTuner().getTunerType().getLabel() + " Tuner Controller - ";
}
private void init()
{
setLayout(new MigLayout("fill,wrap 3", "[right][grow,fill][fill]",
@@ -92,7 +98,8 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
add(getTunerInfoButton());
add(new JLabel("Status:"));
add(getTunerStatusLabel(), "wrap");
add(getTunerStatusLabel());
add(getBiasTButton(), "wrap");
add(getButtonPanel(), "span,align left");
@@ -145,15 +152,17 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
if(hasTuner())
{
getBiasTButton().setEnabled(true);
getBiasTButton().setSelected(getConfiguration().isBiasT());
getTunerInfoButton().setEnabled(true);
getSampleRateCombo().setEnabled(true);
getSampleRateCombo().setSelectedItem(getConfiguration().getSampleRate());
getMasterGainCombo().setEnabled(true);
R820TGain gain = getConfiguration().getMasterGain();
R8xEmbeddedTuner.MasterGain gain = getConfiguration().getMasterGain();
getMasterGainCombo().setEnabled(true);
getMasterGainCombo().setSelectedItem(gain);
if(gain == R820TGain.MANUAL)
if(gain == R8xEmbeddedTuner.MasterGain.MANUAL)
{
getMixerGainCombo().setSelectedItem(getConfiguration().getMixerGain());
getMixerGainCombo().setEnabled(true);
@@ -178,6 +187,8 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
}
else
{
getBiasTButton().setEnabled(false);
getBiasTButton().setSelected(false);
getTunerInfoButton().setEnabled(false);
getSampleRateCombo().setEnabled(false);
getMasterGainCombo().setEnabled(false);
@@ -191,6 +202,28 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
setLoading(false);
}
/**
* Bias-T toggle button
* @return
*/
private JToggleButton getBiasTButton()
{
if(mBiasTButton == null)
{
mBiasTButton = new JToggleButton("Bias-T");
mBiasTButton.setEnabled(false);
mBiasTButton.addActionListener(e -> {
if(!isLoading())
{
getTuner().getController().setBiasT(mBiasTButton.isSelected());
save();
}
});
}
return mBiasTButton;
}
/**
* Hyperlink button that provides tuner information
*/
@@ -200,7 +233,7 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
{
mTunerInfoButton = new JButton("Info");
mTunerInfoButton.setEnabled(false);
mTunerInfoButton.addActionListener(e -> JOptionPane.showMessageDialog(R820TTunerEditor.this,
mTunerInfoButton.addActionListener(e -> JOptionPane.showMessageDialog(R8xTunerEditor.this,
getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE));
}
@@ -211,13 +244,13 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
{
if(mVGAGainCombo == null)
{
mVGAGainCombo = new JComboBox<>(R820TVGAGain.values());
mVGAGainCombo = new JComboBox<>(R8xEmbeddedTuner.VGAGain.values());
mVGAGainCombo.setEnabled(false);
mVGAGainCombo.addActionListener(arg0 ->
{
try
{
R820TVGAGain vgaGain = (R820TVGAGain) mVGAGainCombo.getSelectedItem();
R8xEmbeddedTuner.VGAGain vgaGain = (R8xEmbeddedTuner.VGAGain) mVGAGainCombo.getSelectedItem();
if(vgaGain == null)
{
@@ -233,9 +266,9 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
}
catch(UsbException e)
{
JOptionPane.showMessageDialog(R820TTunerEditor.this, "R820T Tuner Controller - " +
JOptionPane.showMessageDialog(R8xTunerEditor.this, getLogPrefix() +
"couldn't apply the VGA gain setting - " + e.getLocalizedMessage());
mLog.error("R820T Tuner Controller - couldn't apply VGA gain setting", e);
mLog.error(getLogPrefix() + "couldn't apply VGA gain setting", e);
}
});
mVGAGainCombo.setToolTipText("<html>VGA Gain. Set master gain to <b>MANUAL</b> to enable adjustment</html>");
@@ -248,13 +281,13 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
{
if(mLNAGainCombo == null)
{
mLNAGainCombo = new JComboBox<>(R820TLNAGain.values());
mLNAGainCombo = new JComboBox<>(R8xEmbeddedTuner.LNAGain.values());
mLNAGainCombo.setEnabled(false);
mLNAGainCombo.addActionListener(arg0 ->
{
try
{
R820TLNAGain lnaGain = (R820TLNAGain) mLNAGainCombo.getSelectedItem();
R8xEmbeddedTuner.LNAGain lnaGain = (R8xEmbeddedTuner.LNAGain) mLNAGainCombo.getSelectedItem();
if(lnaGain == null)
{
@@ -270,9 +303,9 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
}
catch(UsbException e)
{
JOptionPane.showMessageDialog(R820TTunerEditor.this, "R820T Tuner Controller - " +
JOptionPane.showMessageDialog(R8xTunerEditor.this, getLogPrefix() +
"couldn't apply the LNA gain setting - " + e.getLocalizedMessage());
mLog.error("R820T Tuner Controller - couldn't apply LNA " + "gain setting - ", e);
mLog.error(getLogPrefix() + "couldn't apply LNA " + "gain setting - ", e);
}
});
mLNAGainCombo.setToolTipText("<html>LNA Gain. Set master gain to <b>MANUAL</b> to enable adjustment</html>");
@@ -300,11 +333,11 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
}
catch(SourceException | LibUsbException eSampleRate)
{
JOptionPane.showMessageDialog(R820TTunerEditor.this,
"R820T Tuner Controller - couldn't apply the sample rate setting [" +
JOptionPane.showMessageDialog(R8xTunerEditor.this,
getLogPrefix() + "couldn't apply the sample rate setting [" +
sampleRate.getLabel() + "] " + eSampleRate.getLocalizedMessage());
mLog.error("R820T Tuner Controller - couldn't apply sample rate setting [" + sampleRate.getLabel() +
mLog.error(getLogPrefix() + "couldn't apply sample rate setting [" + sampleRate.getLabel() +
"]", eSampleRate);
}
}
@@ -318,7 +351,7 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
{
if(mMixerGainCombo == null)
{
mMixerGainCombo = new JComboBox<>(R820TMixerGain.values());
mMixerGainCombo = new JComboBox<>(R8xEmbeddedTuner.MixerGain.values());
mMixerGainCombo.setEnabled(false);
mMixerGainCombo.addActionListener(arg0 ->
{
@@ -326,7 +359,7 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
{
try
{
R820TMixerGain mixerGain = (R820TMixerGain) mMixerGainCombo.getSelectedItem();
R8xEmbeddedTuner.MixerGain mixerGain = (R8xEmbeddedTuner.MixerGain) mMixerGainCombo.getSelectedItem();
if(mixerGain == null)
{
@@ -342,10 +375,10 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
}
catch(UsbException e)
{
JOptionPane.showMessageDialog(R820TTunerEditor.this, "R820T Tuner Controller - " +
JOptionPane.showMessageDialog(R8xTunerEditor.this, getLogPrefix() +
"couldn't apply the mixer gain setting - " + e.getLocalizedMessage());
mLog.error("R820T Tuner Controller - couldn't apply mixer gain setting - ", e);
mLog.error(getLogPrefix() + "couldn't apply mixer gain setting - ", e);
}
}
});
@@ -359,7 +392,7 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
{
if(mMasterGainCombo == null)
{
mMasterGainCombo = new JComboBox<>(R820TGain.values());
mMasterGainCombo = new JComboBox<>(R8xEmbeddedTuner.MasterGain.values());
mMasterGainCombo.setEnabled(false);
mMasterGainCombo.addActionListener(arg0 ->
{
@@ -367,10 +400,10 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
{
try
{
R820TGain gain = (R820TGain)getMasterGainCombo().getSelectedItem();
getEmbeddedTuner().setGain((R820TGain)getMasterGainCombo().getSelectedItem(), true);
R8xEmbeddedTuner.MasterGain gain = (R8xEmbeddedTuner.MasterGain)getMasterGainCombo().getSelectedItem();
getEmbeddedTuner().setGain((R8xEmbeddedTuner.MasterGain)getMasterGainCombo().getSelectedItem(), true);
if(gain == R820TGain.MANUAL)
if(gain == R8xEmbeddedTuner.MasterGain.MANUAL)
{
getMixerGainCombo().setSelectedItem(gain.getMixerGain());
getMixerGainCombo().setEnabled(true);
@@ -397,9 +430,9 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
}
catch(UsbException e)
{
JOptionPane.showMessageDialog(R820TTunerEditor.this, "R820T Tuner Controller - " +
JOptionPane.showMessageDialog(R8xTunerEditor.this, getLogPrefix() +
"couldn't apply the gain setting - " + e.getLocalizedMessage());
mLog.error("R820T Tuner Controller - couldn't apply gain setting - ", e);
mLog.error(getLogPrefix() + "couldn't apply gain setting - ", e);
}
}
});
@@ -442,7 +475,7 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
{
StringBuilder sb = new StringBuilder();
RTL2832TunerController.Descriptor descriptor = getTuner().getController().getDescriptor();
sb.append("<html><h3>RTL-2832 with R820T Tuner</h3>");
sb.append("<html><h3>RTL-2832 with " + getEmbeddedTuner().getTunerType().getLabel() + " Tuner</h3>");
if(descriptor == null)
{
@@ -485,21 +518,21 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
{
if(hasConfiguration() && !isLoading())
{
R820TTunerConfiguration config = getConfiguration();
R8xTunerConfiguration config = getConfiguration();
config.setBiasT(getTuner().getController().isBiasT());
config.setFrequency(getFrequencyControl().getFrequency());
double value = ((SpinnerNumberModel)getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue();
config.setFrequencyCorrection(value);
config.setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected());
config.setSampleRate((SampleRate)getSampleRateCombo().getSelectedItem());
R820TGain gain = (R820TGain)getMasterGainCombo().getSelectedItem();
R8xEmbeddedTuner.MasterGain gain = (R8xEmbeddedTuner.MasterGain)getMasterGainCombo().getSelectedItem();
config.setMasterGain(gain);
R820TMixerGain mixerGain = (R820TMixerGain)getMixerGainCombo().getSelectedItem();
R8xEmbeddedTuner.MixerGain mixerGain = (R8xEmbeddedTuner.MixerGain)getMixerGainCombo().getSelectedItem();
config.setMixerGain(mixerGain);
R820TLNAGain lnaGain = (R820TLNAGain)getLNAGainCombo().getSelectedItem();
R8xEmbeddedTuner.LNAGain lnaGain = (R8xEmbeddedTuner.LNAGain)getLNAGainCombo().getSelectedItem();
config.setLNAGain(lnaGain);
R820TVGAGain vgaGain = (R820TVGAGain)getVGAGainCombo().getSelectedItem();
R8xEmbeddedTuner.VGAGain vgaGain = (R8xEmbeddedTuner.VGAGain)getVGAGainCombo().getSelectedItem();
config.setVGAGain(vgaGain);
saveConfiguration();
}

View File

@@ -0,0 +1,92 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.rtl.r8x.r820t;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
import io.github.dsheirer.source.tuner.rtl.r8x.R8xEmbeddedTuner;
import javax.usb.UsbException;
/**
* Rafael Micro R820T and R820T2 Embedded Tuner implementation
*/
public class R820TEmbeddedTuner extends R8xEmbeddedTuner
{
private static final byte I2C_WRITE_ADDRESS = (byte) 0x34;
private static final byte I2C_READ_ADDRESS = (byte) 0x35;
private static final int VCO_POWER_REF = 2;
/**
* Constructs an instance
* @param adapter for accessing RTL2832USBController interfaces
*/
public R820TEmbeddedTuner(RTL2832TunerController.ControllerAdapter adapter)
{
super(adapter, VCO_POWER_REF);
}
@Override
public TunerType getTunerType()
{
return TunerType.RAFAELMICRO_R820T;
}
@Override
public byte getI2CWriteAddress()
{
return I2C_WRITE_ADDRESS;
}
@Override
public byte getI2CReadAddress()
{
return I2C_READ_ADDRESS;
}
/**
* Sets the center frequency. Setting the frequency is a two-part process
* of setting the multiplexer and then setting the Oscillator (PLL).
*/
@Override
public synchronized void setTunedFrequency(long frequency) throws SourceException
{
getAdapter().getLock().lock();
try
{
getAdapter().enableI2CRepeater();
boolean controlI2C = false;
long offsetFrequency = frequency + IF_FREQUENCY;
setMux(offsetFrequency, controlI2C);
setPLL(offsetFrequency, controlI2C);
getAdapter().disableI2CRepeater();
}
catch(UsbException e)
{
throw new SourceException("R820TTunerController - exception while setting frequency [" + frequency + "] - " +
e.getLocalizedMessage());
}
finally
{
getAdapter().getLock().unlock();
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.rtl.r8x.r820t;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.rtl.r8x.R8xTunerConfiguration;
/**
* RTL-2832 with embedded R820T tuner configuration
*/
public class R820TTunerConfiguration extends R8xTunerConfiguration
{
/**
* Empty constructor for Jackson serialization
*/
public R820TTunerConfiguration()
{
}
/**
* Constructs an instance
* @param uniqueId for the tuner
*/
public R820TTunerConfiguration(String uniqueId)
{
super(uniqueId);
}
@JsonIgnore
@Override
public TunerType getTunerType()
{
return TunerType.RAFAELMICRO_R820T;
}
}

View File

@@ -0,0 +1,134 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.rtl.r8x.r828d;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
import io.github.dsheirer.source.tuner.rtl.r8x.R8xEmbeddedTuner;
import javax.usb.UsbException;
/**
* Rafael Micro R828D Embedded Tuner implementation
*/
public class R828DEmbeddedTuner extends R8xEmbeddedTuner
{
private static final byte I2C_WRITE_ADDRESS = (byte) 0x74;
private static final byte I2C_READ_ADDRESS = (byte) 0x75;
private static final int VCO_POWER_REF = 1;
/**
* RTL-SDR.com blog V4 dongle indicator.
*/
private final boolean mIsV4Dongle;
/**
* Constructs an instance
* @param adapter for accessing RTL2832USBController interfaces
*/
public R828DEmbeddedTuner(RTL2832TunerController.ControllerAdapter adapter)
{
super(adapter, VCO_POWER_REF);
//Set flag to indicate if this is a V4 dongle that supports notch filtering.
mIsV4Dongle = adapter.isV4Dongle();
}
@Override
public TunerType getTunerType()
{
return TunerType.RAFAELMICRO_R828D;
}
@Override
public byte getI2CWriteAddress()
{
return I2C_WRITE_ADDRESS;
}
@Override
public byte getI2CReadAddress()
{
return I2C_READ_ADDRESS;
}
/**
* Sets the center frequency. Setting the frequency is a two-part process
* of setting the multiplexer and then setting the Oscillator (PLL).
*/
@Override
public synchronized void setTunedFrequency(long frequency) throws SourceException
{
getAdapter().getLock().lock();
try
{
getAdapter().enableI2CRepeater();
boolean controlI2C = false;
long offsetFrequency = frequency + IF_FREQUENCY;
setMux(offsetFrequency, controlI2C);
setPLL(offsetFrequency, controlI2C);
//Select the RF input
if(mIsV4Dongle)
{
//Disable notch filtering if frequency falls into a notched band, otherwise enable it.
boolean isNotchFilterFrequency = (frequency < 2_200_000) ||
(85_000_000 < frequency && frequency < 112_000_000) ||
(172_000_000 < frequency && frequency < 242_000_000);
writeRegister(Register.DRAIN, isNotchFilterFrequency ? (byte)0x00 : (byte)0x08, controlI2C);
if(frequency <= 28_800_000) //Use cable 2 input
{
writeRegister(Register.INPUT_SELECTOR_AIR, (byte)0x20, controlI2C); //disabled
writeRegister(Register.INPUT_SELECTOR_CABLE_1, (byte)0x00, controlI2C); //disabled
writeRegister(Register.INPUT_SELECTOR_CABLE_2, (byte)0x08, controlI2C); //enabled
}
else if(frequency < 250_000_000)
{
writeRegister(Register.INPUT_SELECTOR_AIR, (byte)0x20, controlI2C); //disabled
writeRegister(Register.INPUT_SELECTOR_CABLE_1, (byte)0x40, controlI2C); //enabled
writeRegister(Register.INPUT_SELECTOR_CABLE_2, (byte)0x00, controlI2C); //disabled
}
else
{
writeRegister(Register.INPUT_SELECTOR_AIR, (byte)0x00, controlI2C); //enabled
writeRegister(Register.INPUT_SELECTOR_CABLE_1, (byte)0x00, controlI2C); //disabled
writeRegister(Register.INPUT_SELECTOR_CABLE_2, (byte)0x00, controlI2C); //disabled
}
}
else
{
byte air_cable_in = (frequency > 345_000_000) ? (byte)0x00 : (byte)0x60;
writeRegister(Register.INPUT_SELECTOR_AIR_AND_CABLE_1, air_cable_in, controlI2C);
}
getAdapter().disableI2CRepeater();
}
catch(UsbException e)
{
throw new SourceException("R820TTunerController - exception while setting frequency [" + frequency + "] - " +
e.getLocalizedMessage());
}
finally
{
getAdapter().getLock().unlock();
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.rtl.r8x.r828d;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.rtl.r8x.R8xTunerConfiguration;
/**
* RTL-2832 with embedded R828D tuner configuration
*/
public class R828DTunerConfiguration extends R8xTunerConfiguration
{
/**
* Empty constructor for Jackson serialization
*/
public R828DTunerConfiguration()
{
}
/**
* Constructs an instance
* @param uniqueId for the tuner
*/
public R828DTunerConfiguration(String uniqueId)
{
super(uniqueId);
}
@JsonIgnore
@Override
public TunerType getTunerType()
{
return TunerType.RAFAELMICRO_R828D;
}
}

View File

@@ -56,8 +56,8 @@ public abstract class USBTunerController extends TunerController
protected static final byte USB_BULK_TRANSFER_ENDPOINT = (byte) 0x81;
private static final long USB_BULK_TRANSFER_TIMEOUT_MS = 2000l;
private int mBus;
private String mPortAddress;
protected int mBus;
protected String mPortAddress;
private Context mDeviceContext = new Context();
private Device mDevice;
private DeviceHandle mDeviceHandle;