mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
Compare commits
60 Commits
v0.6.0-bet
...
v0.6.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16aed8741d | ||
|
|
e878ca0c7f | ||
|
|
ca61dd2543 | ||
|
|
6187253589 | ||
|
|
b51c80742f | ||
|
|
15352b9216 | ||
|
|
a848b21ae8 | ||
|
|
279aca7287 | ||
|
|
88879c956a | ||
|
|
729b7e27a8 | ||
|
|
feaed168ae | ||
|
|
50edb18db3 | ||
|
|
7302e2806d | ||
|
|
8845306b4a | ||
|
|
09e19e1486 | ||
|
|
0f5478925c | ||
|
|
09dce0b0d1 | ||
|
|
ad98b1cb5e | ||
|
|
8effd97749 | ||
|
|
9dbed72c32 | ||
|
|
b4dd3692b8 | ||
|
|
030cbb856c | ||
|
|
511101e9b7 | ||
|
|
60dd2b2df7 | ||
|
|
218dc94e02 | ||
|
|
2440f578b1 | ||
|
|
09c87237cb | ||
|
|
3d974ddd91 | ||
|
|
1489c970ba | ||
|
|
9e0afca3d1 | ||
|
|
99eda4013e | ||
|
|
a62f81f890 | ||
|
|
66f0731731 | ||
|
|
dc239801c6 | ||
|
|
d4ba0988bc | ||
|
|
ad0f953b2a | ||
|
|
e1bc9eabd6 | ||
|
|
d3dc1a8667 | ||
|
|
bf278120de | ||
|
|
2f52f6e009 | ||
|
|
02363c3113 | ||
|
|
cfaac0fcbc | ||
|
|
bee0405e00 | ||
|
|
50bdf0ee81 | ||
|
|
a27ddbe0d0 | ||
|
|
0d652b9153 | ||
|
|
9468fd189b | ||
|
|
215737ad9b | ||
|
|
123c25b142 | ||
|
|
af56198f0e | ||
|
|
3c73fdfd6e | ||
|
|
b391ebbb5f | ||
|
|
bad5a1f9f0 | ||
|
|
a7470b463f | ||
|
|
d6c706cb35 | ||
|
|
a0d6950c75 | ||
|
|
2495200b36 | ||
|
|
bfe7ae0b1b | ||
|
|
4912abbf07 | ||
|
|
2f2998d02e |
2
.github/workflows/gradle.yml
vendored
2
.github/workflows/gradle.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 20
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
|
||||
47
.github/workflows/nightly.yml
vendored
Normal file
47
.github/workflows/nightly.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
# Create nightly release for Linux, Mac and Windows using the Linux runner.
|
||||
name: Nightly Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
# Don't run nightly build for pull requests - uncomment to test changes to this action within scope of the PR review
|
||||
# pull_request:
|
||||
# branches: [ master ]
|
||||
|
||||
jobs:
|
||||
nightly:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 20
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '20'
|
||||
distribution: 'liberica'
|
||||
java-package: 'jdk+fx'
|
||||
|
||||
- name: Build Linux and Mac releases with Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
arguments: runtimeZipOthers -PprojectVersion=nightly
|
||||
|
||||
- name: Build Windows release with Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
arguments: runtimeZipWindows -PprojectVersion=nightly
|
||||
|
||||
- name: Create Build Info file
|
||||
run: |
|
||||
echo -e "ref: $GITHUB_REF \ncommit: $GITHUB_SHA\nbuild: $(date +"%Y-%m-%dT%H:%M:%SZ")" > build/image/build_info.txt
|
||||
|
||||
- name: Add build artifacts to nightly release
|
||||
uses: pyTooling/Actions/releaser@main
|
||||
with:
|
||||
tag: nightly
|
||||
rm: false
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: |
|
||||
build/image/*.zip
|
||||
build/image/build_info.txt
|
||||
@@ -1,8 +1,10 @@
|
||||

|
||||

|
||||
|
||||
# sdrtrunk
|
||||
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
||||
|
||||
* [Help/Wiki Home Page](https://github.com/DSheirer/sdrtrunk/wiki)
|
||||
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/Getting-Started)
|
||||
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/User-Manual)
|
||||
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
|
||||
@@ -18,6 +20,13 @@ All release versions of sdrtrunk are available from the [releases](https://githu
|
||||
* **(beta)** These versions are currently being tested for bugs and functionality prior to final release.
|
||||
* **(final)** These versions have been tested and are the current release version.
|
||||
|
||||
## Download Nightly Software Build
|
||||
The [nightly](https://github.com/DSheirer/sdrtrunk/releases/tag/nightly) release contains current builds of the software
|
||||
for all supported operating systems. This version of the software may contain bugs and may not run correctly. However,
|
||||
it let's you preview the most recent changes and fixes before the next software release. **Always backup your
|
||||
playlist(s) before you use the nightly builds.** Note: the nightly release is updated each time code changes are
|
||||
committed to the code base, so it's not really 'nightly' as much as it is 'current'.
|
||||
|
||||
## Minimum System Requirements
|
||||
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
|
||||
* **CPU:** 4-core
|
||||
|
||||
@@ -53,7 +53,8 @@ repositories {
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
version = '0.6.0-beta2'
|
||||
//Version is defined in gradle.properties so that we can CLI override it for nightly builds.
|
||||
version = project.findProperty('projectVersion')
|
||||
|
||||
//Java 20 is required for this version of the Project Panama preview/incubator feature
|
||||
java {
|
||||
|
||||
31
gradle.properties
Normal file
31
gradle.properties
Normal file
@@ -0,0 +1,31 @@
|
||||
#
|
||||
# ******************************************************************************
|
||||
# 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/>
|
||||
# *****************************************************************************
|
||||
#
|
||||
|
||||
# Version naming conventions:
|
||||
# Alpha: maj.min.patch-alpha-build
|
||||
# Beta: maj.min.patch-beta-build
|
||||
# Release: maj.min.patch
|
||||
#
|
||||
# Example: 0.6.0-beta-5
|
||||
#
|
||||
# Note: this is so that the file names for the resultant release build products match the GitLab release asset
|
||||
# tag name, specifically the dashes inserted before and after the alpha/beta labels.
|
||||
# See: https://github.com/DSheirer/sdrtrunk/issues/1651
|
||||
|
||||
projectVersion=0.6.0-beta-5
|
||||
@@ -184,6 +184,7 @@ public class AliasFactory
|
||||
copy.setGroup(original.getGroup());
|
||||
copy.setColor(original.getColor());
|
||||
copy.setIconName(original.getIconName());
|
||||
copy.setRecordable(original.isRecordable());
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
/**
|
||||
@@ -244,6 +232,9 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
|
||||
String talkgroupLabel = getTalkgroupLabel(audioRecording);
|
||||
String talkgroupGroup = getTalkgroupGroup(audioRecording);
|
||||
String systemLabel = getSystemLabel(audioRecording);
|
||||
String path = audioRecording.getPath().toString();
|
||||
// Remove TEMPORARY_STREAM_FILE_SUFFIX
|
||||
String audioName = path.substring(path.substring(0, path.lastIndexOf("_")).lastIndexOf("_") + 1);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -264,6 +255,7 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
|
||||
RdioScannerBuilder bodyBuilder = new RdioScannerBuilder();
|
||||
bodyBuilder.addPart(FormField.KEY, getBroadcastConfiguration().getApiKey())
|
||||
.addPart(FormField.SYSTEM, getBroadcastConfiguration().getSystemID())
|
||||
.addAudioName(audioName)
|
||||
.addFile(audioBytes)
|
||||
.addPart(FormField.DATE_TIME, timestampSeconds)
|
||||
.addPart(FormField.TALKGROUP_ID, talkgroup)
|
||||
@@ -525,7 +517,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());
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ public class RdioScannerBuilder
|
||||
private static final String DASH_DASH = "--";
|
||||
private static final String BOUNDARY = "--sdrtrunk-sdrtrunk-sdrtrunk";
|
||||
private List<Part> mParts = new ArrayList<>();
|
||||
private byte[] audioBytes = null;
|
||||
private byte[] audioBytes = null;
|
||||
private String audioName = null;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -51,6 +52,15 @@ public class RdioScannerBuilder
|
||||
return BOUNDARY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Audio file name part to the call
|
||||
*/
|
||||
public RdioScannerBuilder addAudioName(String value)
|
||||
{
|
||||
audioName = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Audio bytes part to the call
|
||||
*/
|
||||
@@ -110,7 +120,8 @@ public class RdioScannerBuilder
|
||||
}
|
||||
StringBuilder sb= new StringBuilder();
|
||||
sb.append(DASH_DASH).append(boundary).append("\r\n");
|
||||
sb.append("Content-Disposition: form-data; name=\"").append("audio").append("\"\r\n\r\n");
|
||||
sb.append("Content-Disposition: form-data; filename =\"").append(audioName);
|
||||
sb.append("\"; name=\"").append("audio").append("\"\r\n\r\n");
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -365,7 +365,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
* @param request containing channel and other details
|
||||
* @throws ChannelException if a source is not available for the channel
|
||||
*/
|
||||
private void startProcessing(ChannelStartProcessingRequest request) throws ChannelException
|
||||
private synchronized void startProcessing(ChannelStartProcessingRequest request) throws ChannelException
|
||||
{
|
||||
Channel channel = request.getChannel();
|
||||
|
||||
|
||||
@@ -68,8 +68,8 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
*/
|
||||
private static final int PROCESSED_CHANNEL_RESULTS_THRESHOLD = 1024;
|
||||
|
||||
//Sized to process 1/20th batch of 152 buffers a second, at 40 times per second
|
||||
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(152 / 20, 25);
|
||||
//Sized to process 40 times per second
|
||||
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(25);
|
||||
private FloatFFT_1D mFFT;
|
||||
private float[] mInlineSamples;
|
||||
private float[] mInlineFilter;
|
||||
@@ -406,9 +406,9 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
*/
|
||||
public class IFFTProcessorDispatcher extends Dispatcher<List<float[]>>
|
||||
{
|
||||
public IFFTProcessorDispatcher(int batchSize, long interval)
|
||||
public IFFTProcessorDispatcher(long interval)
|
||||
{
|
||||
super("sdrtrunk polyphase ifft processor", batchSize, interval);
|
||||
super("sdrtrunk polyphase ifft processor", interval);
|
||||
|
||||
//We create a listener interface to receive the batched channel results arrays from the scheduled thread pool
|
||||
//dispatcher thread that is part of this continuous buffer processor. We perform an IFFT on each
|
||||
|
||||
@@ -110,7 +110,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
}
|
||||
|
||||
mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING);
|
||||
mBufferDispatcher = new Dispatcher("sdrtrunk polyphase buffer processor", 50, 10);
|
||||
mBufferDispatcher = new Dispatcher("sdrtrunk polyphase buffer processor", 10);
|
||||
mBufferDispatcher.setListener(mNativeBufferReceiver);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +50,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
||||
mInputChannelCount = inputChannelCount;
|
||||
//Process 1/10th of the sample rate per second at a rate of 20 times a second (200% of anticipated rate)
|
||||
mHeartbeatManager = heartbeatManager;
|
||||
mChannelResultsDispatcher = new Dispatcher("sdrtrunk polyphase channel", (int)(sampleRate / 10),
|
||||
50, mHeartbeatManager);
|
||||
mChannelResultsDispatcher = new Dispatcher("sdrtrunk polyphase channel",50, mHeartbeatManager);
|
||||
mChannelResultsDispatcher.setListener(floats -> {
|
||||
try
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
@@ -164,27 +164,7 @@ public class JavaFxWindowManager extends Application
|
||||
public void shutdown()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
|
||||
if(mChannelMapStage != null)
|
||||
{
|
||||
mUserPreferences.getJavaFxPreferences().unmonitor(mChannelMapStage);
|
||||
}
|
||||
|
||||
if(mIconManagerStage != null)
|
||||
{
|
||||
mUserPreferences.getJavaFxPreferences().unmonitor(mIconManagerStage);
|
||||
}
|
||||
|
||||
if(mPlaylistStage != null)
|
||||
{
|
||||
mUserPreferences.getJavaFxPreferences().unmonitor(mPlaylistStage);
|
||||
}
|
||||
|
||||
if(mUserPreferencesStage != null)
|
||||
{
|
||||
mUserPreferences.getJavaFxPreferences().unmonitor(mUserPreferencesStage);
|
||||
}
|
||||
|
||||
mUserPreferences.getJavaFxPreferences().clearStageMonitors();
|
||||
Platform.exit();
|
||||
}
|
||||
|
||||
@@ -195,31 +175,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 +433,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();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -1,33 +1,29 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.control;
|
||||
|
||||
import java.util.function.UnaryOperator;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.TextFormatter;
|
||||
import javafx.util.converter.IntegerStringConverter;
|
||||
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* Text field control for integer values.
|
||||
*/
|
||||
@@ -52,7 +48,7 @@ public class IntegerTextField extends TextField
|
||||
return null;
|
||||
};
|
||||
|
||||
setTextFormatter(new TextFormatter<Integer>(new IntegerStringConverter(), 0, filter));
|
||||
setTextFormatter(new TextFormatter<Integer>(new IntegerStringConverter(), null, filter));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,7 +67,7 @@ public class IntegerTextField extends TextField
|
||||
//Do nothing ... we couldn't parse the value
|
||||
}
|
||||
|
||||
return null;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2021 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
|
||||
@@ -19,16 +19,12 @@
|
||||
|
||||
package io.github.dsheirer.gui.playlist.alias;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.controlsfx.control.ToggleSwitch;
|
||||
|
||||
import com.google.common.collect.Ordering;
|
||||
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.gui.playlist.Editor;
|
||||
import io.github.dsheirer.icon.Icon;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import java.util.List;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
@@ -49,6 +45,7 @@ import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.util.Callback;
|
||||
import org.controlsfx.control.ToggleSwitch;
|
||||
|
||||
/**
|
||||
* Editor for multiple selected aliases providing limited options for changing attributes of multiple aliases
|
||||
@@ -65,6 +62,8 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
private ToggleSwitch mMonitorAudioToggleSwitch;
|
||||
private ComboBox<Integer> mMonitorPriorityComboBox;
|
||||
private Button mApplyMonitorButton;
|
||||
private ToggleSwitch mRecordToggleSwitch;
|
||||
private Button mApplyRecordButton;
|
||||
|
||||
private BooleanProperty mChangeInProgressProperty;
|
||||
private ReadOnlyBooleanProperty mChangeInProgressROProperty;
|
||||
@@ -144,6 +143,17 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
GridPane.setConstraints(getApplyMonitorButton(), 4, row);
|
||||
gridPane.getChildren().add(getApplyMonitorButton());
|
||||
|
||||
Label recordLabel = new Label("Record");
|
||||
GridPane.setHalignment(recordLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(recordLabel, 0, ++row);
|
||||
gridPane.getChildren().add(recordLabel);
|
||||
|
||||
GridPane.setConstraints(getRecordToggleSwitch(), 1, row);
|
||||
gridPane.getChildren().add(getRecordToggleSwitch());
|
||||
|
||||
GridPane.setConstraints(getApplyRecordButton(), 4, row);
|
||||
gridPane.getChildren().add(getApplyRecordButton());
|
||||
|
||||
getChildren().add(gridPane);
|
||||
}
|
||||
|
||||
@@ -326,6 +336,19 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
return mMonitorAudioToggleSwitch;
|
||||
}
|
||||
|
||||
private ToggleSwitch getRecordToggleSwitch()
|
||||
{
|
||||
if(mRecordToggleSwitch == null)
|
||||
{
|
||||
mRecordToggleSwitch = new ToggleSwitch();
|
||||
mRecordToggleSwitch.setSelected(false);
|
||||
mRecordToggleSwitch.selectedProperty()
|
||||
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
}
|
||||
|
||||
return mRecordToggleSwitch;
|
||||
}
|
||||
|
||||
private ComboBox<Integer> getMonitorPriorityComboBox()
|
||||
{
|
||||
if(mMonitorPriorityComboBox == null)
|
||||
@@ -346,8 +369,6 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
return mMonitorPriorityComboBox;
|
||||
}
|
||||
|
||||
private static int setCount = 0;
|
||||
|
||||
private Button getApplyMonitorButton()
|
||||
{
|
||||
if(mApplyMonitorButton == null)
|
||||
@@ -378,7 +399,6 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
for(Alias alias : getItem())
|
||||
{
|
||||
alias.setCallPriority(pri);
|
||||
//System.out.println(++setCount);
|
||||
}
|
||||
|
||||
endChange();
|
||||
@@ -389,6 +409,25 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
return mApplyMonitorButton;
|
||||
}
|
||||
|
||||
private Button getApplyRecordButton()
|
||||
{
|
||||
if(mApplyRecordButton == null)
|
||||
{
|
||||
mApplyRecordButton = new Button("Apply");
|
||||
mApplyRecordButton.setOnAction(event -> {
|
||||
startChange();
|
||||
boolean record = getRecordToggleSwitch().isSelected();
|
||||
for(Alias alias : getItem())
|
||||
{
|
||||
alias.setRecordable(record);
|
||||
}
|
||||
endChange();
|
||||
});
|
||||
}
|
||||
|
||||
return mApplyRecordButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cell factory for combo box for dislaying icon name and graphic
|
||||
*/
|
||||
|
||||
@@ -387,7 +387,7 @@ public class AliasConfigurationEditor extends SplitPane
|
||||
idsColumn.setCellValueFactory(new IdentifierCountCell());
|
||||
|
||||
TableColumn<Alias, Boolean> errorsColumn = new TableColumn<>("Error");
|
||||
errorsColumn.setPrefWidth(80);
|
||||
errorsColumn.setPrefWidth(120);
|
||||
errorsColumn.setCellValueFactory(new PropertyValueFactory<>("overlap"));
|
||||
errorsColumn.setCellFactory(param ->
|
||||
{
|
||||
|
||||
@@ -362,7 +362,10 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
for(AliasID aliasID: getIdentifiersList().getItems())
|
||||
{
|
||||
//Create a copy of the identifier so that the alias and the editor don't have the same instance
|
||||
alias.addAliasID(AliasFactory.copyOf(aliasID));
|
||||
//and reset the overlap flag (if set) so that the alias list can reevaluate the overlap state.
|
||||
AliasID copy = AliasFactory.copyOf(aliasID);
|
||||
copy.setOverlap(false);
|
||||
alias.addAliasID(copy);
|
||||
}
|
||||
|
||||
//Remove and replace alias actions
|
||||
|
||||
@@ -92,9 +92,9 @@ public class AMConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public AMConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getEventLogPane());
|
||||
|
||||
@@ -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,6 +38,8 @@ import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
@@ -69,9 +71,6 @@ import org.controlsfx.control.ToggleSwitch;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Channel configuration editor
|
||||
*/
|
||||
@@ -100,6 +99,7 @@ public abstract class ChannelConfigurationEditor extends Editor<Channel>
|
||||
private IconNode mPlayGraphicNode;
|
||||
private IconNode mStopGraphicNode;
|
||||
private ChannelProcessingMonitor mChannelProcessingMonitor = new ChannelProcessingMonitor();
|
||||
private IFilterProcessor mFilterProcessor;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -108,11 +108,12 @@ public abstract class ChannelConfigurationEditor extends Editor<Channel>
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public ChannelConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
mPlaylistManager = playlistManager;
|
||||
mTunerManager = tunerManager;
|
||||
mUserPreferences = userPreferences;
|
||||
mFilterProcessor = filterProcessor;
|
||||
|
||||
setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
@@ -689,7 +690,16 @@ public abstract class ChannelConfigurationEditor extends Editor<Channel>
|
||||
mSaveButton.setMaxWidth(Double.MAX_VALUE);
|
||||
mSaveButton.disableProperty().bind(modifiedProperty().not());
|
||||
mSaveButton.setOnAction(event -> {
|
||||
save();
|
||||
if(mFilterProcessor != null)
|
||||
{
|
||||
mFilterProcessor.clearFilter();
|
||||
save();
|
||||
mFilterProcessor.restoreFilter();
|
||||
}
|
||||
else
|
||||
{
|
||||
save();
|
||||
}
|
||||
|
||||
if(getItem().isProcessing())
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -23,11 +23,10 @@ import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Provides access to channel configuration editors for various decoder types.
|
||||
@@ -41,31 +40,35 @@ public class ChannelConfigurationEditorFactory
|
||||
* Constructs an editor for the specified decoder type
|
||||
* @param decoderType to create
|
||||
* @param playlistManager for the editor
|
||||
* @param tunerManager for tuners
|
||||
* @param userPreferences for preferences
|
||||
* @param filterProcessor to be notified to clear and restore any applied filters.
|
||||
* @return constructed editor
|
||||
*/
|
||||
public static ChannelConfigurationEditor getEditor(DecoderType decoderType, PlaylistManager playlistManager,
|
||||
TunerManager tunerManager, UserPreferences userPreferences)
|
||||
TunerManager tunerManager, UserPreferences userPreferences,
|
||||
IFilterProcessor filterProcessor)
|
||||
{
|
||||
switch(decoderType)
|
||||
{
|
||||
case AM:
|
||||
return new AMConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new AMConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case DMR:
|
||||
return new DMRConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new DMRConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case NBFM:
|
||||
return new NBFMConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new NBFMConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case LTR_NET:
|
||||
return new LTRNetConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new LTRNetConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case LTR:
|
||||
return new LTRConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new LTRConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case MPT1327:
|
||||
return new MPT1327ConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new MPT1327ConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case PASSPORT:
|
||||
return new PassportConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new PassportConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case P25_PHASE1:
|
||||
return new P25P1ConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new P25P1ConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case P25_PHASE2:
|
||||
return new P25P2ConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new P25P2ConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
default:
|
||||
if(decoderType != null && !mLoggedUnrecognizedTypes.contains(decoderType))
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -26,6 +26,12 @@ import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
@@ -63,17 +69,10 @@ import org.controlsfx.control.textfield.TextFields;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* JavaFX editor for managing channel configurations.
|
||||
*/
|
||||
public class ChannelEditor extends SplitPane
|
||||
public class ChannelEditor extends SplitPane implements IFilterProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelEditor.class);
|
||||
private PlaylistManager mPlaylistManager;
|
||||
@@ -106,7 +105,8 @@ public class ChannelEditor extends SplitPane
|
||||
mPlaylistManager = playlistManager;
|
||||
mTunerManager = tunerManager;
|
||||
mUserPreferences = userPreferences;
|
||||
mUnknownConfigurationEditor = new UnknownConfigurationEditor(mPlaylistManager, mTunerManager, userPreferences);
|
||||
mUnknownConfigurationEditor = new UnknownConfigurationEditor(mPlaylistManager, mTunerManager,
|
||||
userPreferences, this);
|
||||
|
||||
HBox channelsBox = new HBox();
|
||||
channelsBox.setSpacing(10.0);
|
||||
@@ -203,7 +203,7 @@ public class ChannelEditor extends SplitPane
|
||||
if(editor == null)
|
||||
{
|
||||
editor = ChannelConfigurationEditorFactory.getEditor(channelDecoderType, mPlaylistManager,
|
||||
mTunerManager, mUserPreferences);
|
||||
mTunerManager, mUserPreferences, this);
|
||||
|
||||
if(editor != null)
|
||||
{
|
||||
@@ -374,6 +374,24 @@ public class ChannelEditor extends SplitPane
|
||||
mChannelFilteredList.setPredicate(mChannelListFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily clear the filter when the channel configuration editor is applying changes.
|
||||
*/
|
||||
@Override
|
||||
public void clearFilter()
|
||||
{
|
||||
mChannelFilteredList.setPredicate(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the filter once the channel configuration editor has completed applying changes.
|
||||
*/
|
||||
@Override
|
||||
public void restoreFilter()
|
||||
{
|
||||
mChannelFilteredList.setPredicate(mChannelListFilter);
|
||||
}
|
||||
|
||||
private TableView<Channel> getChannelTableView()
|
||||
{
|
||||
if(mChannelTableView == null)
|
||||
@@ -449,6 +467,25 @@ public class ChannelEditor extends SplitPane
|
||||
nameColumn.setPrefWidth(200);
|
||||
|
||||
TableColumn frequencyColumn = new TableColumn("Frequency");
|
||||
frequencyColumn.setComparator((o1, o2) -> {
|
||||
if(o1 instanceof Channel c1 && o2 instanceof Channel c2)
|
||||
{
|
||||
if(!c1.getFrequencyList().isEmpty() && !c2.getFrequencyList().isEmpty())
|
||||
{
|
||||
return Long.compare(c1.getFrequencyList().get(0), c2.getFrequencyList().get(0));
|
||||
}
|
||||
else if(!c1.getFrequencyList().isEmpty())
|
||||
{
|
||||
return Long.compare(c1.getFrequencyList().get(0), 0l);
|
||||
}
|
||||
else if(!c2.getFrequencyList().isEmpty())
|
||||
{
|
||||
return Long.compare(0l, c2.getFrequencyList().get(0));
|
||||
}
|
||||
}
|
||||
|
||||
return Integer.compare(o1.hashCode(), o2.hashCode());
|
||||
});
|
||||
frequencyColumn.setCellValueFactory(new FrequencyCellValueFactory());
|
||||
frequencyColumn.setPrefWidth(100);
|
||||
|
||||
|
||||
@@ -99,9 +99,9 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public DMRConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getEventLogPane());
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.playlist.channel;
|
||||
|
||||
/**
|
||||
* Interface for a processor that implements a filtering mechanism and can be commanded by an external controller to
|
||||
* temporarily clear a filter and then restore the filter.
|
||||
*/
|
||||
public interface IFilterProcessor
|
||||
{
|
||||
void clearFilter();
|
||||
void restoreFilter();
|
||||
}
|
||||
@@ -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
|
||||
@@ -37,6 +37,8 @@ import io.github.dsheirer.record.RecorderType;
|
||||
import io.github.dsheirer.record.config.RecordConfiguration;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -50,9 +52,6 @@ import org.controlsfx.control.SegmentedButton;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* LTR (standard) channel configuration editor
|
||||
*/
|
||||
@@ -77,9 +76,9 @@ public class LTRConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public LTRConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getAuxDecoderPane());
|
||||
|
||||
@@ -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
|
||||
@@ -37,6 +37,8 @@ import io.github.dsheirer.record.RecorderType;
|
||||
import io.github.dsheirer.record.config.RecordConfiguration;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -50,9 +52,6 @@ import org.controlsfx.control.SegmentedButton;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* LTR-Net channel configuration editor
|
||||
*/
|
||||
@@ -76,9 +75,10 @@ public class LTRNetConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param tunerManager for tuners
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public LTRNetConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager, UserPreferences userPreferences)
|
||||
public LTRNetConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getAuxDecoderPane());
|
||||
|
||||
@@ -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,6 +38,8 @@ import io.github.dsheirer.record.RecorderType;
|
||||
import io.github.dsheirer.record.config.RecordConfiguration;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.HPos;
|
||||
@@ -56,9 +58,6 @@ import javafx.scene.layout.VBox;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static io.github.dsheirer.module.decode.config.DecodeConfiguration.CALL_TIMEOUT_MAXIMUM;
|
||||
import static io.github.dsheirer.module.decode.config.DecodeConfiguration.CALL_TIMEOUT_MINIMUM;
|
||||
|
||||
@@ -87,9 +86,9 @@ public class MPT1327ConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public MPT1327ConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getEventLogPane());
|
||||
|
||||
@@ -27,7 +27,6 @@ import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor;
|
||||
import io.github.dsheirer.gui.playlist.source.FrequencyEditor;
|
||||
import io.github.dsheirer.gui.playlist.source.SourceConfigurationEditor;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.analog.DecodeConfigAnalog;
|
||||
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
|
||||
@@ -41,6 +40,8 @@ import io.github.dsheirer.record.RecorderType;
|
||||
import io.github.dsheirer.record.config.RecordConfiguration;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
@@ -58,9 +59,6 @@ import javafx.scene.text.TextAlignment;
|
||||
import org.controlsfx.control.SegmentedButton;
|
||||
import org.controlsfx.control.ToggleSwitch;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Narrow-Band FM channel configuration editor
|
||||
*/
|
||||
@@ -95,9 +93,9 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public NBFMConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getAuxDecoderPane());
|
||||
|
||||
@@ -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
|
||||
@@ -36,6 +36,8 @@ import io.github.dsheirer.record.RecorderType;
|
||||
import io.github.dsheirer.record.config.RecordConfiguration;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.HPos;
|
||||
@@ -54,9 +56,6 @@ import org.controlsfx.control.ToggleSwitch;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* P25 Phase 1 channel configuration editor
|
||||
*/
|
||||
@@ -83,9 +82,9 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public P25P1ConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getEventLogPane());
|
||||
|
||||
@@ -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
|
||||
@@ -37,6 +37,8 @@ import io.github.dsheirer.record.RecorderType;
|
||||
import io.github.dsheirer.record.config.RecordConfiguration;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -46,9 +48,6 @@ import javafx.scene.layout.VBox;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* P25 Phase 2 channel configuration editor
|
||||
*/
|
||||
@@ -72,9 +71,9 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences
|
||||
*/
|
||||
public P25P2ConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getEventLogPane());
|
||||
|
||||
@@ -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
|
||||
@@ -36,6 +36,8 @@ import io.github.dsheirer.record.RecorderType;
|
||||
import io.github.dsheirer.record.config.RecordConfiguration;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TitledPane;
|
||||
@@ -43,9 +45,6 @@ import javafx.scene.layout.VBox;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Passport channel configuration editor
|
||||
*/
|
||||
@@ -68,9 +67,9 @@ public class PassportConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences
|
||||
*/
|
||||
public PassportConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getAuxDecoderPane());
|
||||
|
||||
@@ -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
|
||||
@@ -45,9 +45,9 @@ public class UnknownConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param aliasModel
|
||||
*/
|
||||
public UnknownConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -28,6 +28,11 @@ import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfigura
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
@@ -48,6 +53,7 @@ import javafx.scene.control.TabPane;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
@@ -58,12 +64,6 @@ import jiconfont.javafx.IconNode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Editor for broadcast audio stream configurations
|
||||
*/
|
||||
@@ -75,6 +75,7 @@ public class StreamingEditor extends SplitPane
|
||||
private TableView<ConfiguredBroadcast> mConfiguredBroadcastTableView;
|
||||
private MenuButton mNewButton;
|
||||
private Button mDeleteButton;
|
||||
private Button mRefreshButton;
|
||||
private TabPane mTabPane;
|
||||
private Tab mConfigurationTab;
|
||||
private Tab mAliasTab;
|
||||
@@ -101,7 +102,7 @@ public class StreamingEditor extends SplitPane
|
||||
refreshBroadcastifyStreams();
|
||||
|
||||
VBox buttonsBox = new VBox();
|
||||
buttonsBox.getChildren().addAll(getNewButton(), getDeleteButton());
|
||||
buttonsBox.getChildren().addAll(getNewButton(), getDeleteButton(), getRefreshButton());
|
||||
buttonsBox.setPadding(new Insets(0, 0, 0, 10));
|
||||
buttonsBox.setSpacing(10);
|
||||
|
||||
@@ -231,21 +232,16 @@ public class StreamingEditor extends SplitPane
|
||||
{
|
||||
if(mPlaylistManager.getRadioReference().availableProperty().get())
|
||||
{
|
||||
ThreadPool.CACHED.submit(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
ThreadPool.CACHED.submit(() -> {
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
List<UserFeedBroadcast> feeds = mPlaylistManager.getRadioReference().getService().getUserFeeds();
|
||||
mBroadcastifyFeeds.clear();
|
||||
mBroadcastifyFeeds.addAll(feeds);
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Unable to refresh broadcastify stream configuration(s)");
|
||||
}
|
||||
List<UserFeedBroadcast> feeds = mPlaylistManager.getRadioReference().getService().getUserFeeds();
|
||||
mBroadcastifyFeeds.clear();
|
||||
mBroadcastifyFeeds.addAll(feeds);
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Unable to refresh broadcastify stream configuration(s)");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -348,6 +344,22 @@ public class StreamingEditor extends SplitPane
|
||||
return mNewButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh broadcastify feeds.
|
||||
* @return button to refresh.
|
||||
*/
|
||||
private Button getRefreshButton()
|
||||
{
|
||||
if(mRefreshButton == null)
|
||||
{
|
||||
mRefreshButton = new Button("Refresh");
|
||||
mRefreshButton.setTooltip(new Tooltip("Refresh streams available from Broadcastify"));
|
||||
mRefreshButton.setOnAction(event -> refreshBroadcastifyStreams());
|
||||
}
|
||||
|
||||
return mRefreshButton;
|
||||
}
|
||||
|
||||
private Button getDeleteButton()
|
||||
{
|
||||
if(mDeleteButton == null)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
462
src/main/java/io/github/dsheirer/gui/viewer/P25P1Viewer.java
Normal file
462
src/main/java/io/github/dsheirer/gui/viewer/P25P1Viewer.java
Normal 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;
|
||||
}
|
||||
}
|
||||
180
src/main/java/io/github/dsheirer/gui/viewer/RecordingViewer.java
Normal file
180
src/main/java/io/github/dsheirer/gui/viewer/RecordingViewer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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(" ").append(getPatchedTalkgroupIdentifiers());
|
||||
}
|
||||
|
||||
if(hasPatchedRadios())
|
||||
{
|
||||
sb.append(" ").append(getPatchedRadioIdentifiers());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -1045,6 +1059,8 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
||||
{
|
||||
if(mCurrentCallEvent != null)
|
||||
{
|
||||
//Refresh the identifier collection before we close out the event
|
||||
mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf());
|
||||
mCurrentCallEvent.end(timestamp);
|
||||
broadcast(mCurrentCallEvent);
|
||||
mCurrentCallEvent = null;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
@@ -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(",");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -20,6 +20,10 @@
|
||||
package io.github.dsheirer.preference.javafx;
|
||||
|
||||
import io.github.dsheirer.preference.decoder.JmbeLibraryPreference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.prefs.Preferences;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
@@ -28,11 +32,6 @@ import javafx.stage.Stage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* Manages user preferences for JavaFX elements (e.g. stages).
|
||||
*/
|
||||
@@ -59,7 +58,7 @@ public class JavaFxPreferences
|
||||
*/
|
||||
public void monitor(Stage stage, String key)
|
||||
{
|
||||
mStageMonitors.remove(new StageMonitor(stage, key));
|
||||
mStageMonitors.add(new StageMonitor(stage, key));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,6 +80,21 @@ public class JavaFxPreferences
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all stage monitors and prepares for shutdown.
|
||||
*/
|
||||
public void clearStageMonitors()
|
||||
{
|
||||
Iterator<StageMonitor> it = mStageMonitors.iterator();
|
||||
|
||||
while(it.hasNext())
|
||||
{
|
||||
StageMonitor next = it.next();
|
||||
it.remove();
|
||||
next.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors a stage's coordinates and stores location. On construction, applies stored coordinates to the stage.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 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
|
||||
@@ -14,16 +14,13 @@
|
||||
*
|
||||
* 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.properties;
|
||||
|
||||
import io.github.dsheirer.gui.SDRTrunk;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -37,6 +34,8 @@ import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.jar.Manifest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* SystemProperties - provides an isolated instance of properties for the application
|
||||
@@ -48,6 +47,7 @@ public class SystemProperties
|
||||
private static String DEFAULT_APP_ROOT = "SDRTrunk";
|
||||
private static String PROPERTIES_FILENAME = "SDRTrunk.properties";
|
||||
private static String MANIFEST_VERSION = "Implementation-Version";
|
||||
private static String BUILD_TIMESTAMP = "Build-Timestamp";
|
||||
|
||||
private static SystemProperties INSTANCE;
|
||||
private static Properties mProperties;
|
||||
@@ -208,11 +208,19 @@ public class SystemProperties
|
||||
if(manifest != null)
|
||||
{
|
||||
String version = manifest.getMainAttributes().getValue(MANIFEST_VERSION);
|
||||
String timestamp = manifest.getMainAttributes().getValue(BUILD_TIMESTAMP);
|
||||
|
||||
if(version != null)
|
||||
{
|
||||
sb.append(" v");
|
||||
sb.append(version);
|
||||
if(version.contains("nightly") && timestamp != null)
|
||||
{
|
||||
sb.append( " nightly - ").append(timestamp);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" v");
|
||||
sb.append(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ public class BinaryRecorder extends Module implements IByteBufferListener
|
||||
private final static Logger mLog = LoggerFactory.getLogger(BinaryRecorder.class);
|
||||
private static final int MAX_RECORDING_BYTE_SIZE = 524288; //500 kB
|
||||
|
||||
private Dispatcher<ByteBuffer> mBufferProcessor = new Dispatcher<>("sdrtrunk binary recorder", 200, 250);
|
||||
private Dispatcher<ByteBuffer> mBufferProcessor = new Dispatcher<>("sdrtrunk binary recorder", 250);
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private Path mBaseRecordingPath;
|
||||
private String mRecordingIdentifier;
|
||||
|
||||
@@ -44,7 +44,7 @@ public class ComplexSamplesWaveRecorder extends Module implements IComplexSample
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ComplexSamplesWaveRecorder.class);
|
||||
|
||||
private Dispatcher<ComplexSamples> mBufferProcessor = new Dispatcher<>("sdrtrunk complex wave recorder", 100, 250);
|
||||
private Dispatcher<ComplexSamples> mBufferProcessor = new Dispatcher<>("sdrtrunk complex wave recorder", 250);
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private BufferWaveWriter mWriter;
|
||||
private String mFilePrefix;
|
||||
|
||||
@@ -47,7 +47,7 @@ public class NativeBufferWaveRecorder extends Module implements Listener<INative
|
||||
private static final Logger mLog = LoggerFactory.getLogger(ComplexSamplesWaveRecorder.class);
|
||||
private static final long STATUS_UPDATE_BYTE_INTERVAL = 1_048_576;
|
||||
private static final long MAX_RECORDING_SIZE = (long)Integer.MAX_VALUE * 2l;
|
||||
private Dispatcher<INativeBuffer> mBufferProcessor = new Dispatcher<>("sdrtrunk native buffer wave recorder", 100, 250);
|
||||
private Dispatcher<INativeBuffer> mBufferProcessor = new Dispatcher<>("sdrtrunk native buffer wave recorder", 250);
|
||||
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private NativeBufferWaveWriter mWriter;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -74,8 +74,7 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
||||
mQDecimationFilter = DecimationFilterFactory.getRealDecimationFilter(decimation);
|
||||
|
||||
//Set dispatcher to process 1/10 of estimated sample arrival rate, 20 times per second (up to 200% per interval)
|
||||
mBufferDispatcher = new Dispatcher("sdrtrunk heterodyne channel " + tunerChannel.getFrequency(),
|
||||
(int)(sampleRate / 10), 50, getHeartbeatManager());
|
||||
mBufferDispatcher = new Dispatcher("sdrtrunk heterodyne channel " + tunerChannel.getFrequency(), 50, getHeartbeatManager());
|
||||
mBufferDispatcher.setListener(new NativeBufferProcessor());
|
||||
|
||||
//Setup the frequency mixer to the current source frequency
|
||||
|
||||
@@ -55,7 +55,7 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou
|
||||
super(listener, tunerChannel);
|
||||
mTunerController = tunerController;
|
||||
mBufferDispatcher = new Dispatcher<>("sdrtrunk pass-through channel " + tunerChannel.getFrequency(),
|
||||
250, 50, getHeartbeatManager());
|
||||
50, getHeartbeatManager());
|
||||
mBufferDispatcher.setListener(new BufferProcessor());
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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("R828DTunerController - exception while setting frequency [" + frequency + "] - " +
|
||||
e.getLocalizedMessage());
|
||||
}
|
||||
finally
|
||||
{
|
||||
getAdapter().getLock().unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ public class SDRplay
|
||||
private static final Logger mLog = LoggerFactory.getLogger(SDRplay.class);
|
||||
|
||||
/**
|
||||
* Foreign memory arenaallocation resource scope
|
||||
* Foreign memory arena allocation resource scope
|
||||
*/
|
||||
private final Arena mArena = Arena.openShared();
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ public enum Version
|
||||
V3_08(3.08f, true),
|
||||
V3_09(3.09f, true),
|
||||
V3_10(3.10f, true),
|
||||
V3_11(3.11f, true);
|
||||
V3_11(3.11f, true),
|
||||
V3_12(3.12f, true);
|
||||
|
||||
private float mValue;
|
||||
private boolean mSupported;
|
||||
|
||||
@@ -37,24 +37,24 @@ public class CallbackFunctions
|
||||
|
||||
/**
|
||||
* Constructs a callback functions for single-tuner use.
|
||||
* @param arena for native memory allocation
|
||||
* @param longLifecycleArena for native memory allocation for long lifecycle objects.
|
||||
* @param deviceEventListener for device events
|
||||
* @param streamListener for streaming samples
|
||||
* @param streamCallbackListener for streaming events
|
||||
*/
|
||||
public CallbackFunctions(Arena arena, IDeviceEventListener deviceEventListener,
|
||||
public CallbackFunctions(Arena longLifecycleArena, IDeviceEventListener deviceEventListener,
|
||||
IStreamListener streamListener, IStreamCallbackListener streamCallbackListener)
|
||||
{
|
||||
//Create the event callback function
|
||||
mDeviceEventAdapter = new DeviceEventAdapter(arena, deviceEventListener);
|
||||
MemorySegment eventFunction = sdrplay_api_EventCallback_t.allocate(mDeviceEventAdapter, arena.scope());
|
||||
mDeviceEventAdapter = new DeviceEventAdapter(deviceEventListener);
|
||||
MemorySegment eventFunction = sdrplay_api_EventCallback_t.allocate(mDeviceEventAdapter, longLifecycleArena.scope());
|
||||
|
||||
//Create the stream A callback function
|
||||
mStreamACallbackAdapter = new StreamCallbackAdapter(arena, streamListener, streamCallbackListener);
|
||||
MemorySegment streamAFunction = sdrplay_api_StreamCallback_t.allocate(mStreamACallbackAdapter, arena.scope());
|
||||
mStreamACallbackAdapter = new StreamCallbackAdapter(streamListener, streamCallbackListener);
|
||||
MemorySegment streamAFunction = sdrplay_api_StreamCallback_t.allocate(mStreamACallbackAdapter, longLifecycleArena.scope());
|
||||
|
||||
//Create the callback functions union and populate the callback functions
|
||||
mCallbackFunctionsMemorySegment = sdrplay_api_CallbackFnsT.allocate(arena);
|
||||
mCallbackFunctionsMemorySegment = sdrplay_api_CallbackFnsT.allocate(longLifecycleArena);
|
||||
sdrplay_api_CallbackFnsT.EventCbFn$set(mCallbackFunctionsMemorySegment, eventFunction);
|
||||
sdrplay_api_CallbackFnsT.StreamACbFn$set(mCallbackFunctionsMemorySegment, streamAFunction);
|
||||
}
|
||||
@@ -72,15 +72,15 @@ public class CallbackFunctions
|
||||
IStreamCallbackListener streamCallbackListener)
|
||||
{
|
||||
//Create the event callback function
|
||||
mDeviceEventAdapter = new DeviceEventAdapter(arena, deviceEventListener);
|
||||
mDeviceEventAdapter = new DeviceEventAdapter(deviceEventListener);
|
||||
MemorySegment eventFunction = sdrplay_api_EventCallback_t.allocate(mDeviceEventAdapter, arena.scope());
|
||||
|
||||
//Create the stream A callback function
|
||||
mStreamACallbackAdapter = new StreamCallbackAdapter(arena, streamAListener, streamCallbackListener);
|
||||
mStreamACallbackAdapter = new StreamCallbackAdapter(streamAListener, streamCallbackListener);
|
||||
MemorySegment streamAFunction = sdrplay_api_StreamCallback_t.allocate(mStreamACallbackAdapter, arena.scope());
|
||||
|
||||
//Create the stream B callback function
|
||||
mStreamBCallbackAdapter = new StreamCallbackAdapter(arena, streamBListener, streamCallbackListener);
|
||||
mStreamBCallbackAdapter = new StreamCallbackAdapter(streamBListener, streamCallbackListener);
|
||||
MemorySegment streamBFunction = sdrplay_api_StreamCallback_t.allocate(mStreamBCallbackAdapter, arena.scope());
|
||||
|
||||
//Create the callback functions union and populate the callback functions
|
||||
|
||||
@@ -36,24 +36,14 @@ import org.slf4j.LoggerFactory;
|
||||
public class DeviceEventAdapter implements sdrplay_api_EventCallback_t
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(DeviceEventAdapter.class);
|
||||
|
||||
private Arena mArena;
|
||||
private IDeviceEventListener mDeviceEventListener;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
* @param arena to use in creating foreign memory segments to access foreign structures.
|
||||
* @param listener to receive translated device events.
|
||||
*/
|
||||
public DeviceEventAdapter(Arena arena, IDeviceEventListener listener)
|
||||
public DeviceEventAdapter(IDeviceEventListener listener)
|
||||
{
|
||||
if(arena == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Resource scope must be non-null");
|
||||
}
|
||||
|
||||
mArena = arena;
|
||||
|
||||
setListener(listener);
|
||||
}
|
||||
|
||||
@@ -75,35 +65,44 @@ public class DeviceEventAdapter implements sdrplay_api_EventCallback_t
|
||||
public void apply(int eventTypeId, int tunerSelectId, MemorySegment eventParametersPointer,
|
||||
MemorySegment callbackContext)
|
||||
{
|
||||
MemorySegment memorySegment = sdrplay_api_EventParamsT.ofAddress(eventParametersPointer, mArena.scope());
|
||||
EventType eventType = EventType.fromValue(eventTypeId);
|
||||
TunerSelect tunerSelect = TunerSelect.fromValue(tunerSelectId);
|
||||
|
||||
switch(eventType)
|
||||
try(Arena arena = Arena.openConfined())
|
||||
{
|
||||
case GAIN_CHANGE -> {
|
||||
mDeviceEventListener.processGainChange(tunerSelect,
|
||||
EventParametersFactory.createGainCallbackParameters(memorySegment));
|
||||
}
|
||||
case POWER_OVERLOAD_CHANGE -> {
|
||||
mDeviceEventListener.processPowerOverload(tunerSelect,
|
||||
EventParametersFactory.createPowerOverloadCallbackParameters(memorySegment));
|
||||
}
|
||||
case DEVICE_REMOVED -> {
|
||||
mDeviceEventListener.processDeviceRemoval(tunerSelect);
|
||||
}
|
||||
case RSP_DUO_MODE_CHANGE -> {
|
||||
mDeviceEventListener.processRspDuoModeChange(tunerSelect,
|
||||
EventParametersFactory.createRspDuoModeCallbackParameters(memorySegment));
|
||||
}
|
||||
case UNKNOWN -> {
|
||||
mLog.warn("Unknown device event callback ignored. Please contact the library developer as this may " +
|
||||
"indicate a change to the SDRPlay API change. Tuner:" + tunerSelect + " Event Type ID:" +
|
||||
eventTypeId);
|
||||
mDeviceEventListener.processEvent(eventType, tunerSelect);
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalStateException("DeviceEventAdapter must be updated handle EventType." + eventType);
|
||||
MemorySegment memorySegment = sdrplay_api_EventParamsT.ofAddress(eventParametersPointer, arena.scope());
|
||||
EventType eventType = EventType.fromValue(eventTypeId);
|
||||
TunerSelect tunerSelect = TunerSelect.fromValue(tunerSelectId);
|
||||
|
||||
switch(eventType)
|
||||
{
|
||||
case GAIN_CHANGE ->
|
||||
{
|
||||
mDeviceEventListener.processGainChange(tunerSelect,
|
||||
EventParametersFactory.createGainCallbackParameters(memorySegment));
|
||||
}
|
||||
case POWER_OVERLOAD_CHANGE ->
|
||||
{
|
||||
mDeviceEventListener.processPowerOverload(tunerSelect,
|
||||
EventParametersFactory.createPowerOverloadCallbackParameters(memorySegment));
|
||||
}
|
||||
case DEVICE_REMOVED ->
|
||||
{
|
||||
mDeviceEventListener.processDeviceRemoval(tunerSelect);
|
||||
}
|
||||
case RSP_DUO_MODE_CHANGE ->
|
||||
{
|
||||
mDeviceEventListener.processRspDuoModeChange(tunerSelect,
|
||||
EventParametersFactory.createRspDuoModeCallbackParameters(memorySegment));
|
||||
}
|
||||
case UNKNOWN ->
|
||||
{
|
||||
mLog.warn("Unknown device event callback ignored. Please contact the library developer as this may " +
|
||||
"indicate a change to the SDRPlay API change. Tuner:" + tunerSelect + " Event Type ID:" +
|
||||
eventTypeId);
|
||||
mDeviceEventListener.processEvent(eventType, tunerSelect);
|
||||
}
|
||||
default ->
|
||||
{
|
||||
throw new IllegalStateException("DeviceEventAdapter must be updated handle EventType." + eventType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,24 +35,16 @@ import org.slf4j.LoggerFactory;
|
||||
public class StreamCallbackAdapter implements sdrplay_api_StreamCallback_t
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(StreamCallbackAdapter.class);
|
||||
private Arena mArena;
|
||||
private IStreamListener mStreamListener;
|
||||
private IStreamCallbackListener mStreamCallbackListener;
|
||||
|
||||
/**
|
||||
* Constructs an instance of the callback implementation
|
||||
* @param arena for defining new foreign memory segments
|
||||
* @param streamListener to receive transferred I/Q samples and event details
|
||||
* @param listener to receive callback parameters
|
||||
*/
|
||||
public StreamCallbackAdapter(Arena arena, IStreamListener streamListener, IStreamCallbackListener listener)
|
||||
public StreamCallbackAdapter(IStreamListener streamListener, IStreamCallbackListener listener)
|
||||
{
|
||||
if(arena == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Arena must be non-null");
|
||||
}
|
||||
|
||||
mArena = arena;
|
||||
mStreamCallbackListener = listener;
|
||||
setListener(streamListener);
|
||||
}
|
||||
@@ -81,24 +73,27 @@ public class StreamCallbackAdapter implements sdrplay_api_StreamCallback_t
|
||||
{
|
||||
if(mStreamListener != null || mStreamCallbackListener != null)
|
||||
{
|
||||
//Translate the callback parameters pointer to a memory segment and re-construct the parameters as a Java object
|
||||
StreamCallbackParameters parameters = new StreamCallbackParameters(sdrplay_api_StreamCbParamsT
|
||||
.ofAddress(parametersPointer, mArena.scope()));
|
||||
|
||||
if(mStreamCallbackListener != null)
|
||||
try(Arena arena = Arena.openConfined())
|
||||
{
|
||||
mStreamCallbackListener.process(mStreamListener.getTunerSelect(), parameters, reset);
|
||||
}
|
||||
//Translate the callback parameters pointer to a memory segment and re-construct the parameters as a Java object
|
||||
StreamCallbackParameters parameters = new StreamCallbackParameters(sdrplay_api_StreamCbParamsT
|
||||
.ofAddress(parametersPointer, arena.scope()));
|
||||
|
||||
if(mStreamListener != null)
|
||||
{
|
||||
//Allocate memory segments from I/Q pointers, transfer from native to JVM array, and send to listener
|
||||
long arrayByteSize = ValueLayout.JAVA_SHORT.byteSize() * sampleCount;
|
||||
MemorySegment iSamples = MemorySegment.ofAddress(iSamplesPointer.address(), arrayByteSize, mArena.scope());
|
||||
MemorySegment qSamples = MemorySegment.ofAddress(qSamplesPointer.address(), arrayByteSize, mArena.scope());
|
||||
short[] i = iSamples.toArray(ValueLayout.JAVA_SHORT);
|
||||
short[] q = qSamples.toArray(ValueLayout.JAVA_SHORT);
|
||||
mStreamListener.processStream(i, q, parameters, Flag.evaluate(reset));
|
||||
if(mStreamCallbackListener != null)
|
||||
{
|
||||
mStreamCallbackListener.process(mStreamListener.getTunerSelect(), parameters, reset);
|
||||
}
|
||||
|
||||
if(mStreamListener != null)
|
||||
{
|
||||
//Allocate memory segments from I/Q pointers, transfer from native to JVM array, and send to listener
|
||||
long arrayByteSize = ValueLayout.JAVA_SHORT.byteSize() * sampleCount;
|
||||
MemorySegment iSamples = MemorySegment.ofAddress(iSamplesPointer.address(), arrayByteSize, arena.scope());
|
||||
MemorySegment qSamples = MemorySegment.ofAddress(qSamplesPointer.address(), arrayByteSize, arena.scope());
|
||||
short[] i = iSamples.toArray(ValueLayout.JAVA_SHORT);
|
||||
short[] q = qSamples.toArray(ValueLayout.JAVA_SHORT);
|
||||
mStreamListener.processStream(i, q, parameters, Flag.evaluate(reset));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user