Compare commits

..

60 Commits

Author SHA1 Message Date
Denny Sheirer
16aed8741d Merge pull request #1721 from DSheirer/1720-version-0.6.0-beta-5
#1720 Updates version to 0.6.0 Beta 5
2023-11-10 05:43:31 -05:00
Dennis Sheirer
e878ca0c7f #1720 Updates version to 0.6.0 Beta 5 2023-11-10 05:42:41 -05:00
Denny Sheirer
ca61dd2543 Merge pull request #1719 from DSheirer/1619-java-fx-windows-open-minimized
#1619 JavaFX Windows Show Minimized
2023-11-10 05:34:09 -05:00
Dennis Sheirer
6187253589 #1619 Resolves issue where JavaFX window size and placement might show minimized after application is started and an initial JavaFX window is opened and then closed, where it impacted subsequent JavaFX window openings. Corrects issue where JavaFX stage monitors were not being registered correctly, so that they could be deregistered prior to application shutdown. 2023-11-10 05:31:05 -05:00
Denny Sheirer
b51c80742f Merge pull request #1717 from DSheirer/1681-audio-recordings-incorrect-system-and-site
#1681 Audio Recorder Mixing Up System and Site When Decoding Multiple Systems
2023-11-10 04:51:54 -05:00
Dennis Sheirer
15352b9216 #1681 Resolves issue where audio recordings have incorrect system and site labels in metadata. Adds synchronized to channel processing manager's start channel request method to ensure that each channel is configured and started sequentially to avoid chance that simultaneous traffic channel grants occurring across two separate systems get inter-mingled on configuration and startup. 2023-11-10 04:34:45 -05:00
Denny Sheirer
a848b21ae8 Merge pull request #1716 from DSheirer/1715-rtl-tuner-v3-v4-mixed-tuning-issue
#1715 RTL2832 Tuner Issue When Using V4 Dongle With Other R820T Tuners
2023-11-10 03:49:53 -05:00
Dennis Sheirer
279aca7287 #1715 Resolves issue of using both legacy and new V4 RTL2832 dongles at same time causes errant tuning behavior on the first tuner that gets initialized. Removed static qualifier from VCO reference value so that each tuner is allowed to initialize correctly. 2023-11-10 03:47:01 -05:00
Denny Sheirer
88879c956a Merge pull request #1714 from DSheirer/732-p25p2-missing-from-identifier-call-event
#732 P25 Phase 2 - FROM ID Updated For Call Event Logging
2023-11-09 04:51:01 -05:00
Dennis Sheirer
729b7e27a8 #732 Resolves P25P2 call events sometimes missing FROM radio ID even though the ID was decoded. 2023-11-09 04:48:42 -05:00
Denny Sheirer
feaed168ae Merge pull request #1713 from DSheirer/967-bulk-alias-editor-set-recordable
#967 Multi-Alias Editor Supports Changing Record Audio Status
2023-11-09 04:34:28 -05:00
Dennis Sheirer
50edb18db3 967 Updates bulk alias editor to support editing alias recordable status. 2023-11-09 04:30:25 -05:00
Denny Sheirer
7302e2806d Merge pull request #1712 from DSheirer/968-alias-editor-clear-overlap-state
#968 Playlist Alias Editor - Clear Overlap Error Warning Once Resolved
2023-11-09 04:15:17 -05:00
Dennis Sheirer
8845306b4a #968 Resolves issue with clearing alias ID overlap state when the user edits the alias to correct the overlap. 2023-11-09 04:12:46 -05:00
Denny Sheirer
09e19e1486 Merge pull request #1708 from tadscottsmith/rdio-scanner-filename
Explicitly send filename to Rdio-Scanner.
2023-11-07 04:12:14 -05:00
Denny Sheirer
0f5478925c Update README.md 2023-11-07 04:01:40 -05:00
Denny Sheirer
09dce0b0d1 Merge pull request #1710 from DSheirer/1709-nightly-build-timestamp-info
#1709 Nightly Release Build Timestamp Info
2023-11-07 03:32:42 -05:00
Dennis Sheirer
ad98b1cb5e #1709 Adds build timestamp info to nightly releases. 2023-11-07 03:31:31 -05:00
tadscottsmith
8effd97749 Explicitly send filename to Rdio-Scanner. 2023-11-06 10:54:41 -06:00
Denny Sheirer
9dbed72c32 Merge pull request #1707 from DSheirer/1706-nightly-release
#1706 Github Action For Nightly Releases
2023-11-06 05:06:19 -05:00
Dennis Sheirer
b4dd3692b8 #1706 Github action to create nightly application releases. 2023-11-06 05:05:40 -05:00
Denny Sheirer
030cbb856c Update README.md 2023-11-06 04:36:59 -05:00
Denny Sheirer
511101e9b7 Merge pull request #1705 from DSheirer/1704-harris-patch-group-formatting
#1704 Patch Group Formatting
2023-11-06 02:52:49 -05:00
Dennis Sheirer
60dd2b2df7 #1704 Updates patch group format for compatibility. 2023-11-06 02:50:46 -05:00
Denny Sheirer
218dc94e02 Merge pull request #1701 from DSheirer/1644-sdrplay-api-3-12
#1644 SDRPlay API Version 3.12 For Mac OSX
2023-11-05 07:22:04 -05:00
Dennis Sheirer
2440f578b1 #1644 Update SDRPlay API for version 3.12 compatability 2023-11-05 07:20:29 -05:00
Denny Sheirer
09c87237cb Merge pull request #1700 from DSheirer/1651-gradle-build-version-naming
#1651 Version 0.6.0 Beta 4 and Version Naming Conventions
2023-11-05 07:10:00 -05:00
Dennis Sheirer
3d974ddd91 #1651 Increment version to 0.6.0 Beta 4 and add version naming conventions comments. 2023-11-05 07:08:18 -05:00
Denny Sheirer
1489c970ba Merge pull request #1699 from DSheirer/1653-channel-edit-with-filtering
#1653 Playlist Channel Edits With Applied Filter Produces Error
2023-11-05 06:58:36 -05:00
Dennis Sheirer
9e0afca3d1 #1653 Playlist channel edits with applied filter doesn't produce error 2023-11-05 06:56:49 -05:00
Denny Sheirer
99eda4013e Merge pull request #1698 from DSheirer/1654-playlist-editor-channel-frequency-sort
#1654 Playlist Channel Editor Frequency Column Sort
2023-11-05 06:32:23 -05:00
Dennis Sheirer
a62f81f890 #1654 Playlist channel editor sorts by frequency numerically (vs lexicographically) 2023-11-05 06:29:23 -05:00
Denny Sheirer
66f0731731 Merge pull request #1697 from DSheirer/1665-refresh-broadcastify-streams
#1665 Refresh Broadcastify Streams In Playlist Editor
2023-11-05 06:00:48 -05:00
Dennis Sheirer
dc239801c6 #1665 Add option to refresh available streams from broadcastify in playlist editor's streaming tab. 2023-11-05 05:58:32 -05:00
Denny Sheirer
d4ba0988bc Merge pull request #1696 from DSheirer/1673-playlist-editor-broadcastify-system-id
#1673 Broadcastify Call Streaming Config Editor's default value of 0 …
2023-11-05 05:24:03 -05:00
Dennis Sheirer
ad0f953b2a #1673 Broadcastify Call Streaming Config Editor's default value of 0 causes field to be uneditable. 2023-11-05 05:20:15 -05:00
Denny Sheirer
e1bc9eabd6 Merge pull request #1695 from DSheirer/1694-sdrplay-rsp-tuner-memory-leaks
#1694 SDRPlay RSP Memory Leak & Audio Playback Delays
2023-11-05 04:26:48 -05:00
Dennis Sheirer
d3dc1a8667 #1694 Resolves memory leak in SDRPlay RSP tuner implementation. Sample stream and event callback functions were using a global arena for memory allocation where memory was not being de-allocated. Changed to on-demand arena creation and deterministic memory de-allocation. Also, when using a reduced sample rate from the RSP tuner, the dispatcher was not keeping up with the inbound sample buffer rate, resulting in a buildup of RSP native sample buffers over time and causing growing audio delays with broken/stilted audio during playback. 2023-11-05 04:24:19 -05:00
Denny Sheirer
bf278120de Merge pull request #1692 from DSheirer/DSheirer-patch-1
Update README.md
2023-11-04 07:41:29 -04:00
Denny Sheirer
2f52f6e009 Update README.md 2023-11-04 07:41:12 -04:00
Denny Sheirer
02363c3113 Merge pull request #1690 from DSheirer/1689-harris-patch-group-prefixes
#1689 Increment Build 0.6.0 Beta 3 and Remove TG/RA prefixes for Harris Patch Groups
2023-11-04 07:13:10 -04:00
Dennis Sheirer
cfaac0fcbc #1689 Increment Build 0.6.0 Beta 3 and Remove TG/RA prefixes for Harris patch groups. 2023-11-04 07:12:29 -04:00
Denny Sheirer
bee0405e00 Merge pull request #1688 from DSheirer/1687-rtl2832-fc0013
Support For RTL2832 With Embedded FC0013 Tuner
2023-11-04 06:54:55 -04:00
Dennis Sheirer
50bdf0ee81 #1687 Adds support for RTL2832 with embedded FC0013 tuner. 2023-11-04 06:52:37 -04:00
Denny Sheirer
a27ddbe0d0 Merge pull request #1679 from DSheirer/1643-r828d-v4-tuner
#1643 RTL2832/R828D aka RTL-SDR V4 Dongle & Bias-T Support
2023-10-22 08:40:33 -04:00
Dennis Sheirer
0d652b9153 #1643 Adds support for RTL2832 with embedded R828D tuner and RTL-SDR.com V4 dongles. Introduces Bias-T support for all RTL-2832 dongles. 2023-10-22 08:38:16 -04:00
Denny Sheirer
9468fd189b Merge pull request #1677 from DSheirer/1607-harris-p25-patch-groups
#1607 P25 P1/P2 L3Harris Patch Group Support
2023-10-21 07:00:41 -04:00
sheirerd
215737ad9b #1607 Add support for L3Harris dynamic regroup/patch messages and events. Includes parsing for L3Harris unknown opcodes 10, 42 and 43. 2023-10-21 06:58:11 -04:00
Denny Sheirer
123c25b142 Merge pull request #1672 from DSheirer/1650-fm-decoder-7_5-bandwidth
#1650 Adds 7.5 kHz channel bandwidth option for FM channels.
2023-10-15 09:22:48 -04:00
Dennis Sheirer
af56198f0e #1650 Adds 7.5 kHz channel bandwidth option for FM channels. 2023-10-15 09:21:38 -04:00
Denny Sheirer
3c73fdfd6e Merge pull request #1671 from DSheirer/1670-dmr-cap-plus-missing-call-audio
#1670 Missing Audio Segments - DMR Cap+
2023-10-15 08:56:48 -04:00
Dennis Sheirer
b391ebbb5f #1670 Resolves missing audio segments for some DMR Cap+ call events. 2023-10-15 08:55:35 -04:00
Denny Sheirer
bad5a1f9f0 Merge pull request #1669 from DSheirer/1668-message-viewer-p25-support
#1668 Message Recording Viewer - P25P1 Support
2023-10-15 07:17:06 -04:00
Dennis Sheirer
a7470b463f #1668 Updates message recording (.bits) viewer to support P25 Phase 1. 2023-10-15 07:14:53 -04:00
Denny Sheirer
d6c706cb35 Merge pull request #1667 from DSheirer/1666-p25-moto-unit-gps
#1666 P25 Motorola Unit GPS Parsing & Plot to Map
2023-10-14 08:16:48 -04:00
Dennis Sheirer
a0d6950c75 #1666 P25 Motorola unit self-reported GPS location parsing and plotting to map. 2023-10-14 08:15:35 -04:00
Denny Sheirer
2495200b36 Merge pull request #1663 from DSheirer/1648-dmr-lrrp-not-plotting-map
#1648 DMR Embedded GPS & LRRP Plotting
2023-10-05 03:32:08 -04:00
Dennis Sheirer
bfe7ae0b1b #1648 Resolves issue with plotting DMR embedded GPS and LRRP positions. 2023-10-05 03:31:17 -04:00
Denny Sheirer
4912abbf07 Merge pull request #1662 from DSheirer/1661-dmr-gps-location-parsing-error
#1661 DMR Talker GPS Lat/Long Values Incorrect
2023-10-05 03:21:24 -04:00
Dennis Sheirer
2f2998d02e #1661 Resolves issue with parsing of DMR embedded GPS latitude and longitude values. 2023-10-05 03:20:17 -04:00
101 changed files with 4893 additions and 1181 deletions

View File

@@ -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
View 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

View File

@@ -1,8 +1,10 @@
![Gradle Build](https://github.com/dsheirer/sdrtrunk/actions/workflows/gradle.yml/badge.svg)
![Nightly Release](https://github.com/dsheirer/sdrtrunk/actions/workflows/nightly.yml/badge.svg)
# 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

View File

@@ -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
View 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

View File

@@ -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;
}

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -38,9 +38,6 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
@@ -48,12 +45,13 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Audio broadcaster to push completed audio recordings to the Broadcastify call push API.
@@ -475,7 +473,11 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
StringBuilder sb = new StringBuilder();
sb.append(patchGroup.getPatchGroup().getValue().toString());
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
{
sb.append(",").append(patched.getValue());
}
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
{
sb.append(",").append(patched.getValue());
}

View File

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

View File

@@ -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();
}

View File

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

View File

@@ -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();

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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
{

View File

@@ -24,8 +24,6 @@ import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.controller.channel.map.ChannelMap;
import io.github.dsheirer.controller.channel.map.ChannelRange;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.dmr.DMRRecordingViewer;
import io.github.dsheirer.gui.dmr.ViewDmrRecordingViewerRequest;
import io.github.dsheirer.gui.icon.IconManager;
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
import io.github.dsheirer.gui.playlist.PlaylistEditor;
@@ -37,6 +35,8 @@ import io.github.dsheirer.gui.preference.PreferenceEditorType;
import io.github.dsheirer.gui.preference.UserPreferencesEditor;
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
import io.github.dsheirer.gui.preference.calibration.CalibrationDialog;
import io.github.dsheirer.gui.viewer.RecordingViewer;
import io.github.dsheirer.gui.viewer.ViewRecordingViewerRequest;
import io.github.dsheirer.icon.IconModel;
import io.github.dsheirer.jmbe.JmbeEditor;
import io.github.dsheirer.jmbe.JmbeEditorRequest;
@@ -67,7 +67,7 @@ public class JavaFxWindowManager extends Application
public static final String USER_PREFERENCES_EDITOR = "preferences";
public static final String STAGE_MONITOR_KEY_CALIBRATION_DIALOG = "calibration.dialog";
public static final String STAGE_MONITOR_KEY_CHANNEL_MAP_EDITOR = "channel.map";
public static final String STAGE_MONITOR_KEY_DMR_MESSAGE_VIEWER = "dmr.message.viewer";
public static final String STAGE_MONITOR_KEY_RECORDING_VIEWER = "recording.viewer";
public static final String STAGE_MONITOR_KEY_ICON_MANAGER_EDITOR = "icon.manager";
public static final String STAGE_MONITOR_KEY_JMBE_EDITOR = "jmbe.editor";
public static final String STAGE_MONITOR_KEY_PLAYLIST_EDITOR = "playlist";
@@ -82,14 +82,14 @@ public class JavaFxWindowManager extends Application
private TunerManager mTunerManager;
private UserPreferences mUserPreferences;
private UserPreferencesEditor mUserPreferencesEditor;
private DMRRecordingViewer mDmrRecordingViewer;
private RecordingViewer mRecordingViewer;
private Stage mChannelMapStage;
private Stage mIconManagerStage;
private Stage mJmbeEditorStage;
private Stage mPlaylistStage;
private Stage mUserPreferencesStage;
private Stage mDmrRecordingViewerStage;
private Stage mRecordingViewerStage;
/**
* Constructs an instance. Note: this constructor is used for Swing applications.
@@ -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();
});
}

View File

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

View File

@@ -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;
}
/**

View File

@@ -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
*/

View File

@@ -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 ->
{

View File

@@ -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

View File

@@ -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());

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -38,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())
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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))
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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);

View File

@@ -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());

View File

@@ -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();
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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());

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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());

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -38,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());

View File

@@ -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());

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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());

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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());

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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());

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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());
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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)

View File

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

View File

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

View File

@@ -0,0 +1,180 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.viewer;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility application to load and view .bits recording file with the messages fully parsed.
*
* Supported Protocols: DMR and APCO25 Phase 1.
*/
public class RecordingViewer extends VBox
{
private static final Logger mLog = LoggerFactory.getLogger(RecordingViewer.class);
private MenuBar mMenuBar;
private TabPane mTabPane;
private int mTabCounterDmr = 1;
private int mTabCounterP25P1 = 1;
/**
* Constructs an instance
*/
public RecordingViewer()
{
VBox.setVgrow(getTabPane(), Priority.ALWAYS);
getChildren().addAll(getMenuBar(), getTabPane());
}
public MenuBar getMenuBar()
{
if(mMenuBar == null)
{
mMenuBar = new MenuBar();
Menu fileMenu = new Menu("File");
Menu createNewViewerMenu = new Menu("New Viewer ...");
MenuItem dmrMenuItem = new MenuItem("DMR");
dmrMenuItem.onActionProperty().set(event -> getTabPane().getTabs().add(new LabeledTab("DMR-" + mTabCounterDmr++, new DmrViewer())));
MenuItem p25p1MenuItem = new MenuItem("P25P1");
p25p1MenuItem.onActionProperty().set(event -> getTabPane().getTabs().add(new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer())));
createNewViewerMenu.getItems().addAll(dmrMenuItem, p25p1MenuItem);
MenuItem exitMenu = new MenuItem("Exit");
exitMenu.onActionProperty().set(event -> ((Stage)getScene().getWindow()).close());
fileMenu.getItems().addAll(createNewViewerMenu, new SeparatorMenuItem(), exitMenu);
mMenuBar.getMenus().add(fileMenu);
}
return mMenuBar;
}
/**
* Tab pane for each viewer instance
*/
public TabPane getTabPane()
{
if(mTabPane == null)
{
mTabPane = new TabPane();
mTabPane.setMaxHeight(Double.MAX_VALUE);
mTabPane.getTabs().add(new LabeledTab("DMR-" + mTabCounterDmr++, new DmrViewer()));
mTabPane.getTabs().add(new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer()));
}
return mTabPane;
}
/**
* Decorates the tab with label renaming feature using double-click or right-click to change text
*/
public class LabeledTab extends Tab
{
private Label mLabel = new Label();
private TextField mTextField = new TextField();
/**
* Constructs an instance
* @param label to use initially
* @param node for content
*/
public LabeledTab(String label, Node node)
{
super(null, node);
mLabel.setText(label);
setGraphic(mLabel);
mLabel.setOnMouseClicked(event -> {
if((event.getButton().equals(MouseButton.PRIMARY) && event.getClickCount() > 1) ||
event.getButton().equals(MouseButton.SECONDARY))
{
setGraphic(mTextField);
mTextField.setText(mLabel.getText());
mTextField.selectAll();
mTextField.requestFocus();
}
});
mTextField.setOnAction(event -> {
mLabel.setText(mTextField.getText());
LabeledTab.this.setGraphic(mLabel);
});
mTextField.focusedProperty().addListener((observable, oldValue, newValue) -> {
if(!newValue)
{
mLabel.setText(mTextField.getText());
LabeledTab.this.setGraphic(mLabel);
}
});
}
public LabeledTab(String label)
{
this(label, null);
}
}
public static void main(String[] args)
{
Application viewer = new Application()
{
@Override
public void start(Stage primaryStage) throws Exception
{
Scene scene = new Scene(new RecordingViewer(), 1100, 800);
primaryStage.setTitle("Message Recording Viewer (.bits)");
primaryStage.setScene(scene);
primaryStage.show();
}
};
Runnable r = () -> {
try
{
viewer.start(new Stage());
}
catch(Exception e)
{
mLog.error("Error starting message recording viewer application", e);
}
};
Platform.startup(r);
}
}

View File

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

View File

@@ -1,69 +1,161 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.identifier.patch;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Temporary grouping or regrouping of talkgroups or individual radios.
*/
public class PatchGroup
{
// private final static Logger mLog = LoggerFactory.getLogger(PatchGroup.class);
private TalkgroupIdentifier mPatchGroupIdentifier;
private List<TalkgroupIdentifier> mPatchedGroupIdentifiers = new ArrayList<>();
private List<TalkgroupIdentifier> mPatchedTalkgroupIdentifiers = new ArrayList<>();
private List<RadioIdentifier> mPatchedRadioIdentifiers = new ArrayList<>();
private int mVersion;
public PatchGroup(TalkgroupIdentifier patchGroupIdentifier)
/**
* Constructs an instance
* @param patchGroupIdentifier for the patch group
* @param version for this patch group identifier. Note: if the patch group identifier is frequently reused, a
* new version number will indicate a new instance of the patch group.
*/
public PatchGroup(TalkgroupIdentifier patchGroupIdentifier, int version)
{
mPatchGroupIdentifier = patchGroupIdentifier;
mVersion = version;
}
/**
* Constructs an instance
* @param patchGroupIdentifier that is the overall identifier for this temporary grouping.
*/
public PatchGroup(TalkgroupIdentifier patchGroupIdentifier)
{
this(patchGroupIdentifier, 0);
}
/**
* Version number for this patch group used to differentiate frequently reused patch group identifiers.
* @return version number (aka super group sequence number).
*/
public int getVersion()
{
return mVersion;
}
/**
* Patch group identifier.
* @return identifier
*/
public TalkgroupIdentifier getPatchGroup()
{
return mPatchGroupIdentifier;
}
public void addPatchedGroup(TalkgroupIdentifier patchedGroupIdentifier)
/**
* Indicates if this patch group contains any patched talkgroups.
* @return true if there are patched talkgroups.
*/
public boolean hasPatchedTalkgroups()
{
if(!mPatchedGroupIdentifiers.contains(patchedGroupIdentifier))
return !getPatchedTalkgroupIdentifiers().isEmpty();
}
/**
* Indicates if this patch group contains any patched radio identifiers.
* @return true if there are patched radios.
*/
public boolean hasPatchedRadios()
{
return !getPatchedRadioIdentifiers().isEmpty();
}
/**
* Adds the talkgroup identifier to this patched group.
* @param patchedGroupIdentifier to add
*/
public void addPatchedTalkgroup(TalkgroupIdentifier patchedGroupIdentifier)
{
if(!mPatchedTalkgroupIdentifiers.contains(patchedGroupIdentifier))
{
mPatchedGroupIdentifiers.add(patchedGroupIdentifier);
mPatchedTalkgroupIdentifiers.add(patchedGroupIdentifier);
}
}
public void addPatchedGroups(List<TalkgroupIdentifier> patchedGroupIdentifiers)
/**
* Adds a list of talkgroup identifiers to this patched group.
* @param patchedGroupIdentifiers to add
*/
public void addPatchedTalkgroups(List<TalkgroupIdentifier> patchedGroupIdentifiers)
{
for(TalkgroupIdentifier identifier : patchedGroupIdentifiers)
{
addPatchedGroup(identifier);
addPatchedTalkgroup(identifier);
}
}
public List<TalkgroupIdentifier> getPatchedGroupIdentifiers()
/**
* List of patched group identifiers that are part of this patch group.
* @return patched talkgroups.
*/
public List<TalkgroupIdentifier> getPatchedTalkgroupIdentifiers()
{
return mPatchedGroupIdentifiers;
return mPatchedTalkgroupIdentifiers;
}
/**
* Adds the radio identifier to this patched group.
* @param patchedRadioIdentifier to add
*/
public void addPatchedRadio(RadioIdentifier patchedRadioIdentifier)
{
if(!mPatchedRadioIdentifiers.contains(patchedRadioIdentifier))
{
mPatchedRadioIdentifiers.add(patchedRadioIdentifier);
}
}
/**
* Adds a list of radio identifiers to this patched group.
* @param patchedRadioIdentifiers to add
*/
public void addPatchedRadios(List<RadioIdentifier> patchedRadioIdentifiers)
{
for(RadioIdentifier identifier : patchedRadioIdentifiers)
{
addPatchedRadio(identifier);
}
}
/**
* List of patched radio identifiers that are part of this patch group.
* @return patched radios.
*/
public List<RadioIdentifier> getPatchedRadioIdentifiers()
{
return mPatchedRadioIdentifiers;
}
@Override
@@ -72,9 +164,14 @@ public class PatchGroup
StringBuilder sb = new StringBuilder();
sb.append("P:").append(getPatchGroup());
if(!getPatchedGroupIdentifiers().isEmpty())
if(hasPatchedTalkgroups())
{
sb.append(" ").append(getPatchedGroupIdentifiers());
sb.append(" ").append(getPatchedTalkgroupIdentifiers());
}
if(hasPatchedRadios())
{
sb.append(" ").append(getPatchedRadioIdentifiers());
}
return sb.toString();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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.
*/

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

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

View File

@@ -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

View File

@@ -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());
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -31,7 +31,9 @@ import io.github.dsheirer.source.tuner.fcd.proplusV2.FCD2TunerConfiguration;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerConfiguration;
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.r8x.r820t.R820TTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.r8x.r828d.R828DTunerConfiguration;
import io.github.dsheirer.source.tuner.sdrplay.RspTunerConfiguration;
/**
@@ -42,11 +44,13 @@ import io.github.dsheirer.source.tuner.sdrplay.RspTunerConfiguration;
@JsonSubTypes.Type(value = AirspyTunerConfiguration.class, name = "airspyTunerConfiguration"),
@JsonSubTypes.Type(value = AirspyHfTunerConfiguration.class, name = "airspyHfTunerConfiguration"),
@JsonSubTypes.Type(value = E4KTunerConfiguration.class, name = "e4KTunerConfiguration"),
@JsonSubTypes.Type(value = FC0013TunerConfiguration.class, name = "fc0013TunerConfiguration"),
@JsonSubTypes.Type(value = FCD1TunerConfiguration.class, name = "fcd1TunerConfiguration"),
@JsonSubTypes.Type(value = FCD2TunerConfiguration.class, name = "fcd2TunerConfiguration"),
@JsonSubTypes.Type(value = HackRFTunerConfiguration.class, name = "hackRFTunerConfiguration"),
@JsonSubTypes.Type(value = RecordingTunerConfiguration.class, name = "recordingTunerConfiguration"),
@JsonSubTypes.Type(value = R820TTunerConfiguration.class, name = "r820TTunerConfiguration"),
@JsonSubTypes.Type(value = R828DTunerConfiguration.class, name = "r828DTunerConfiguration"),
@JsonSubTypes.Type(value = RspTunerConfiguration.class, name = "rspTunerConfiguration"),
})
@JacksonXmlRootElement(localName = "tuner_configuration")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();

View File

@@ -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;

View File

@@ -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

View File

@@ -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);
}
}
}
}

View File

@@ -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));
}
}
}
}

View File

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

Some files were not shown because too many files have changed in this diff Show More